Browse Source

first commit

lem 3 năm trước cách đây
commit
ef93978435
100 tập tin đã thay đổi với 30141 bổ sung0 xóa
  1. 9 0
      .editorconfig
  2. 8 0
      .env.development
  3. 5 0
      .env.production
  4. 6 0
      .eslintignore
  5. 25 0
      .eslintrc.js
  6. 22 0
      .gitignore
  7. 10 0
      .postcssrc.js
  8. 24 0
      README.md
  9. 6 0
      babel.config.js
  10. 18889 0
      package-lock.json
  11. 158 0
      package.json
  12. 5 0
      postcss.config.js
  13. 261 0
      public/index.html
  14. 0 0
      public/static/.gitkeep
  15. BIN
      public/static/img/avatar.png
  16. BIN
      public/static/img/bg.png
  17. BIN
      public/static/img/favicon.ico
  18. BIN
      public/static/img/login.jpg
  19. BIN
      public/static/img/login.png
  20. 40 0
      src/assets/img/1.svg
  21. 40 0
      src/assets/img/2.svg
  22. 41 0
      src/assets/img/3.svg
  23. 41 0
      src/assets/img/4.svg
  24. 40 0
      src/assets/img/5.svg
  25. 40 0
      src/assets/img/6.svg
  26. 40 0
      src/assets/img/7.svg
  27. 41 0
      src/assets/img/8.svg
  28. 41 0
      src/assets/img/9.svg
  29. BIN
      src/assets/img/Scheme.png
  30. BIN
      src/assets/img/avatar.png
  31. BIN
      src/assets/img/bg.png
  32. 40 0
      src/assets/img/left_layout.svg
  33. BIN
      src/assets/img/logo.png
  34. 40 0
      src/assets/img/top_layout.svg
  35. BIN
      src/assets/img/wall-bky.png
  36. BIN
      src/assets/img/wallbg.png
  37. BIN
      src/assets/img/xwh.png
  38. BIN
      src/assets/img/zjs.png
  39. 964 0
      src/assets/scss/index.scss
  40. 179 0
      src/assets/scss/login.scss
  41. 441 0
      src/assets/scss/theme.scss
  42. 148 0
      src/components/Exception/Exception.vue
  43. 19 0
      src/components/Exception/typeConfig.js
  44. 45 0
      src/components/List/ListItem.vue
  45. 53 0
      src/components/List/ListItemMeta.vue
  46. 20 0
      src/components/List/index.vue
  47. 101 0
      src/components/List/style/index.scss
  48. 118 0
      src/components/NoticeIcon/NoticeList.vue
  49. 130 0
      src/components/NoticeIcon/index.vue
  50. 20 0
      src/components/antIcon/AntIcon.vue
  51. 105 0
      src/components/avatar/index.vue
  52. 114 0
      src/components/cityPicker/index.vue
  53. 30 0
      src/components/colors/ColorPicker.vue
  54. 26 0
      src/components/colors/mixins/mainPageModel.js
  55. 188 0
      src/components/colors/mixins/theme.js
  56. 101 0
      src/components/countDown/index.vue
  57. 119 0
      src/components/editor/WangEditor.vue
  58. 347 0
      src/components/gridSelect/index.vue
  59. 96 0
      src/components/icon/index.vue
  60. 131 0
      src/components/numberInfo/NumberInfo.vue
  61. 90 0
      src/components/table-tree-column/index.vue
  62. 232 0
      src/components/treeSelect/treeSelect.vue
  63. 408 0
      src/components/userSelect/UserSelectDialog.vue
  64. 86 0
      src/components/userSelect/index.vue
  65. 13 0
      src/directive/index.js
  66. 78 0
      src/main.js
  67. 1 0
      src/router/import-development.js
  68. 1 0
      src/router/import-production.js
  69. 159 0
      src/router/index.js
  70. 25 0
      src/store/index.js
  71. 61 0
      src/store/modules/common.js
  72. 52 0
      src/store/modules/config.js
  73. 29 0
      src/store/modules/user.js
  74. 5 0
      src/utils/bus.js
  75. 179 0
      src/utils/common.js
  76. 59 0
      src/utils/dictUtils.js
  77. 11 0
      src/utils/filter.js
  78. 137 0
      src/utils/httpRequest.js
  79. 281 0
      src/utils/icons.js
  80. 773 0
      src/utils/icons2.js
  81. 266 0
      src/utils/index.js
  82. 425 0
      src/utils/validate.js
  83. 300 0
      src/utils/validator.js
  84. 68 0
      src/views/common/404.vue
  85. 116 0
      src/views/layout/UpdatePassword.vue
  86. 278 0
      src/views/layout/_common_center.vue
  87. 147 0
      src/views/layout/_common_left.vue
  88. 62 0
      src/views/layout/_common_left_submenu.vue
  89. 237 0
      src/views/layout/_common_right.vue
  90. 315 0
      src/views/layout/_common_top.vue
  91. 96 0
      src/views/main.vue
  92. 132 0
      src/views/modules/calendar/MyCalendar.vue
  93. 138 0
      src/views/modules/calendar/MyCalendarForm.vue
  94. 177 0
      src/views/modules/car/CarForm.vue
  95. 337 0
      src/views/modules/car/CarList.vue
  96. 11 0
      src/views/modules/car/CarProject.vue
  97. 406 0
      src/views/modules/code/Guide.vue
  98. 339 0
      src/views/modules/database/datamodel/DataSetForm.vue
  99. 244 0
      src/views/modules/database/datamodel/DataSetList.vue
  100. 0 0
      src/views/modules/database/link/DataSourceForm.vue

+ 9 - 0
.editorconfig

@@ -0,0 +1,9 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true

+ 8 - 0
.env.development

@@ -0,0 +1,8 @@
+# 开发环境配置
+ENV = 'development'
+
+# Jeeplus快速开发平台/开发环境
+VUE_APP_BASE_API = '/api'
+
+# Jeeplus快速开发平台/后台地址
+VUE_APP_SERVER_URL = 'http://localhost:8082'

+ 5 - 0
.env.production

@@ -0,0 +1,5 @@
+# 生产环境配置
+ENV = 'production'
+
+#Jeeplus快速开发平台/后台地址
+VUE_APP_SERVER_URL = 'http://xxxx.jeeplus.org/jeeplus-vue'

+ 6 - 0
.eslintignore

@@ -0,0 +1,6 @@
+/build/
+/config/
+/dist/
+/*.js
+/static/display/
+/src/components/colors/

+ 25 - 0
.eslintrc.js

@@ -0,0 +1,25 @@
+// https://eslint.org/docs/user-guide/configuring
+
+module.exports = {
+  root: true,
+  parser: 'babel-eslint',
+  parserOptions: {
+    sourceType: 'module'
+  },
+  env: {
+    browser: true,
+  },
+  // https://github.com/standard/standard/blob/master/docs/RULES-en.md
+  extends: 'standard',
+  // required to lint *.vue files
+  plugins: [
+    'html'
+  ],
+  // add your custom rules here
+  rules: {
+    // allow async-await
+    'generator-star-spacing': 'off',
+    // allow debugger during development
+    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
+  }
+}

+ 22 - 0
.gitignore

@@ -0,0 +1,22 @@
+.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?
+APP.vue

+ 10 - 0
.postcssrc.js

@@ -0,0 +1,10 @@
+// https://github.com/michael-ciniawsky/postcss-load-config
+
+module.exports = {
+  "plugins": {
+    "postcss-import": {},
+    "postcss-url": {},
+    // to edit target browsers: use "browserslist" field in package.json
+    "autoprefixer": {}
+  }
+}

+ 24 - 0
README.md

@@ -0,0 +1,24 @@
+# jeeplus-ui
+
+## Project setup
+```
+npm install
+```
+
+### Compiles and hot-reloads for development
+```
+npm run serve
+```
+
+### Compiles and minifies for production
+```
+npm run build
+```
+
+### Lints and fixes files
+```
+npm run lint
+```
+
+### Customize configuration
+See [Configuration Reference](https://cli.vuejs.org/config/).

+ 6 - 0
babel.config.js

@@ -0,0 +1,6 @@
+module.exports = {
+  presets: [
+    '@vue/cli-plugin-babel/preset'
+  ],
+  sourceType: 'unambiguous'
+}

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 18889 - 0
package-lock.json


+ 158 - 0
package.json

@@ -0,0 +1,158 @@
+{
+  "name": "jeeplus-ui",
+  "version": "1.0.0",
+  "description": "jeeplus快速开发平台",
+  "author": "www.jeeplus.org",
+  "private": true,
+  "scripts": {
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "lint": "vue-cli-service lint"
+  },
+  "dependencies": {
+    "@fullcalendar/core": "^4.3.1",
+    "@fullcalendar/daygrid": "^4.3.0",
+    "@fullcalendar/interaction": "^4.3.0",
+    "@fullcalendar/resource-timeline": "^4.3.0",
+    "@fullcalendar/timegrid": "^4.3.0",
+    "@fullcalendar/vue": "^4.3.1",
+    "axios": "^0.19.0",
+    "bpmn-js-cli": "^1.1.0",
+    "bpmn-js-task-priorities": "^0.2.0",
+    "core-js": "^3.4.2",
+    "css-loader": "^3.2.0",
+    "dayjs": "^1.8.18",
+    "echarts": "^4.5.0",
+    "element-ui": "^2.13.0",
+    "font-awesome": "^4.7.0",
+    "jeeplus-flowable": "^1.0.18",
+    "jeeplus-filemanager": "^2.0.0",
+    "jeeplus-form-make": "^1.3.1",
+    "jeeplus-gencode": "^1.0.21",
+    "js-cookie": "^2.2.1",
+    "locale": "^0.1.0",
+    "lodash": "^4.17.15",
+    "lodash.omit": "^4.5.0",
+    "lodash.pick": "^4.4.0",
+    "moment": "^2.24.0",
+    "normalize.css": "^8.0.0",
+    "numeral": "^2.0.6",
+    "qiniu": "^7.2.1",
+    "qiniu-js": "^2.5.1",
+    "scroll-tabs": "^1.0.1",
+    "selection-update": "^0.1.2",
+    "sortable.js": "^0.3.0",
+    "sortablejs": "^1.10.1",
+    "style-loader": "^1.0.0",
+    "svg-sprite-loader": "^4.1.6",
+    "tiny-svg": "^2.2.2",
+    "v-charts": "^1.19.0",
+    "v-jsoneditor": "^1.2.3",
+    "viewerjs": "^1.2.0",
+    "vue": "^2.6.10",
+    "vue-clipboard2": "^0.3.1",
+    "vue-cookie": "^1.1.4",
+    "vue-count-to": "^1.0.13",
+    "vue-echarts": "^4.1.0",
+    "vue-grid-layout": "^2.3.7",
+    "vue-i18n": "5.0.3",
+    "vue-particles": "^1.0.9",
+    "vue-router": "^3.1.3",
+    "vue2-editor": "^2.10.2",
+    "vue2-ace-editor": "^0.0.15",
+    "vuedraggable": "^2.23.2",
+    "vuex": "^3.1.2",
+    "wangeditor": "^3.1.1"
+  },
+  "devDependencies": {
+    "@babel/runtime": "^7.7.4",
+    "@vue/cli-plugin-babel": "^4.0.5",
+    "@vue/cli-plugin-eslint": "^4.0.5",
+    "@vue/cli-service": "^4.0.5",
+    "autoprefixer": "^9.7.2",
+    "babel-core": "^6.26.3",
+    "babel-eslint": "^7.2.3",
+    "babel-loader": "^8.0.6",
+    "babel": "^6.23.0",
+    "babel-plugin-component": "^1.1.1",
+    "babel-polyfill": "^6.26.0",
+    "babel-plugin-dynamic-import-node": "^2.3.0",
+    "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
+    "babel-plugin-transform-runtime": "^6.23.0",
+    "babel-preset-env": "^1.7.0",
+    "babel-preset-stage-2": "^6.24.1",
+    "babel-register": "^6.26.0",
+    "chalk": "^3.0.0",
+    "chromedriver": "^78.0.1",
+    "copy-webpack-plugin": "^5.0.5",
+    "cross-spawn": "^7.0.1",
+    "css-loader": "^3.2.0",
+    "eslint": "^3.19.0",
+    "eslint-config-standard": "^10.2.1",
+    "eslint-friendly-formatter": "^3.0.0",
+    "eslint-loader": "^1.7.1",
+    "eslint-plugin-html": "^3.0.0",
+    "eslint-plugin-import": "^2.7.0",
+    "eslint-plugin-node": "^5.2.0",
+    "eslint-plugin-promise": "^3.5.0",
+    "eslint-plugin-standard": "^3.0.1",
+    "eventsource-polyfill": "^0.9.6",
+    "extract-text-webpack-plugin": "^3.0.2",
+    "file-loader": "^4.3.0",
+    "friendly-errors-webpack-plugin": "^1.7.0",
+    "html-webpack-plugin": "^3.2.0",
+    "less": "^3.10.3",
+    "less-loader": "^5.0.0",
+    "nightwatch": "^1.3.0",
+    "node-notifier": "^6.0.0",
+    "node-sass": "^4.13.0",
+    "optimize-css-assets-webpack-plugin": "^5.0.3",
+    "ora": "^4.0.3",
+    "portfinder": "^1.0.25",
+    "postcss-import": "^12.0.1",
+    "postcss-loader": "^3.0.0",
+    "postcss-url": "^8.0.0",
+    "resolve-url-loader": "^3.1.1",
+    "rimraf": "^3.0.0",
+    "saas": "^1.0.0",
+    "sass": "^1.23.7",
+    "sass-loader": "^8.0.0",
+    "selenium-server": "^3.141.59",
+    "semver": "^6.3.0",
+    "shelljs": "^0.8.3",
+    "style-loader": "^1.0.0",
+    "stylus-loader": "^3.0.2",
+    "uglifyjs-webpack-plugin": "^2.2.0",
+    "url-loader": "^2.3.0",
+    "vue-loader": "^15.7.2",
+    "vue-style-loader": "^4.1.2",
+    "vue-template-compiler": "^2.6.10",
+    "webpack": "^4.41.2",
+    "webpack-bundle-analyzer": "^3.6.0",
+    "webpack-dev-server": "^3.9.0",
+    "webpack-merge": "^4.2.2"
+  },
+  "eslintConfig": {
+    "root": true,
+    "env": {
+      "node": true
+    },
+    "extends": [
+      "plugin:vue/essential",
+      "eslint:recommended"
+    ],
+    "rules": {},
+    "parserOptions": {
+      "parser": "babel-eslint"
+    }
+  },
+  "postcss": {
+    "plugins": {
+      "autoprefixer": {}
+    }
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions"
+  ]
+}

+ 5 - 0
postcss.config.js

@@ -0,0 +1,5 @@
+module.exports = { 
+  plugins: { 
+    'autoprefixer': {browsers: 'last 5 version'} 
+  } 
+}

+ 261 - 0
public/index.html

@@ -0,0 +1,261 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>Jeeplus快速开发平台</title>
+  <meta name="renderer" content="webkit">
+  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
+  <link rel="icon" href="/static/img/favicon.ico">
+
+  <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: 150px;
+      height: 150px;
+      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: #3452ff;
+      /* 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: 19px;
+      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: 13px;
+      color: #FFF;
+      opacity: 0.5;
+    }
+  </style>
+</head>
+
+<body>
+
+
+  <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">正在加载 jeeplus vue,请耐心等待...
+        <br>
+        <span>V5.2</span>
+      </div>
+    </div>
+  </div>
+</body>
+</html>

+ 0 - 0
public/static/.gitkeep


BIN
public/static/img/avatar.png


BIN
public/static/img/bg.png


BIN
public/static/img/favicon.ico


BIN
public/static/img/login.jpg


BIN
public/static/img/login.png


+ 40 - 0
src/assets/img/1.svg

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
+    <title>Group 5 Copy 5</title>
+    <desc>Created with Sketch.</desc>
+    <defs>
+        <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
+            <feMerge>
+                <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
+                <feMergeNode in="SourceGraphic"></feMergeNode>
+            </feMerge>
+        </filter>
+        <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
+        <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="setting-copy-2" transform="translate(-1190.000000, -136.000000)">
+            <g id="Group-8" transform="translate(1167.000000, 0.000000)">
+                <g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
+                    <mask id="mask-3" fill="white">
+                        <use xlink:href="#path-2"></use>
+                    </mask>
+                    <g id="Rectangle-18">
+                        <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
+                        <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
+                    </g>
+                    <rect id="Rectangle-11" fill="#408df7" mask="url(#mask-3)" x="-1" y="0" width="49" height="10"></rect>
+                    <rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="10" width="16" height="34"></rect>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 40 - 0
src/assets/img/2.svg

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
+    <title>Group 5 Copy 5</title>
+    <desc>Created with Sketch.</desc>
+    <defs>
+        <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
+            <feMerge>
+                <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
+                <feMergeNode in="SourceGraphic"></feMergeNode>
+            </feMerge>
+        </filter>
+        <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
+        <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="setting-copy-2" transform="translate(-1190.000000, -136.000000)">
+            <g id="Group-8" transform="translate(1167.000000, 0.000000)">
+                <g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
+                    <mask id="mask-3" fill="white">
+                        <use xlink:href="#path-2"></use>
+                    </mask>
+                    <g id="Rectangle-18">
+                        <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
+                        <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
+                    </g>
+                    <rect id="Rectangle-11" fill="#408df7" mask="url(#mask-3)" x="-1" y="0" width="49" height="10"></rect>
+                    <rect id="Rectangle-18" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="10" width="16" height="34"></rect>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 41 - 0
src/assets/img/3.svg

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
+    <title>Group 5 Copy 5</title>
+    <desc>Created with Sketch.</desc>
+    <defs>
+        <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
+            <feMerge>
+                <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
+                <feMergeNode in="SourceGraphic"></feMergeNode>
+            </feMerge>
+        </filter>
+        <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
+        <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="setting-copy-2" transform="translate(-1190.000000, -136.000000)">
+            <g id="Group-8" transform="translate(1167.000000, 0.000000)">
+                <g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
+                    <mask id="mask-3" fill="white">
+                        <use xlink:href="#path-2"></use>
+                    </mask>
+                    <g id="Rectangle-18">
+                        <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
+                        <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
+                    </g>
+                    <rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="10"></rect>
+                    <rect id="Rectangle-11" fill="#408df7" mask="url(#mask-3)" x="16" y="0" width="49" height="10"></rect>
+                    <rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="10" width="16" height="34"></rect>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 41 - 0
src/assets/img/4.svg

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
+    <title>Group 5 Copy 5</title>
+    <desc>Created with Sketch.</desc>
+    <defs>
+        <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
+            <feMerge>
+                <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
+                <feMergeNode in="SourceGraphic"></feMergeNode>
+            </feMerge>
+        </filter>
+        <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
+        <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="setting-copy-2" transform="translate(-1190.000000, -136.000000)">
+            <g id="Group-8" transform="translate(1167.000000, 0.000000)">
+                <g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
+                    <mask id="mask-3" fill="white">
+                        <use xlink:href="#path-2"></use>
+                    </mask>
+                    <g id="Rectangle-18">
+                        <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
+                        <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
+                    </g>
+                    <rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="10"></rect>
+                    <rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="16" y="0" width="49" height="10"></rect>
+                    <rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="10" width="16" height="34"></rect>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 40 - 0
src/assets/img/5.svg

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
+    <title>Group 5 Copy 5</title>
+    <desc>Created with Sketch.</desc>
+    <defs>
+        <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
+            <feMerge>
+                <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
+                <feMergeNode in="SourceGraphic"></feMergeNode>
+            </feMerge>
+        </filter>
+        <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
+        <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="setting-copy-2" transform="translate(-1190.000000, -136.000000)">
+            <g id="Group-8" transform="translate(1167.000000, 0.000000)">
+                <g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
+                    <mask id="mask-3" fill="white">
+                        <use xlink:href="#path-2"></use>
+                    </mask>
+                    <g id="Rectangle-18">
+                        <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
+                        <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
+                    </g>
+                    <rect id="Rectangle-11" fill="#408df7" mask="url(#mask-3)" x="-1" y="0" width="49" height="10"></rect>
+                    <rect id="Rectangle-18" fill="#408df7" mask="url(#mask-3)" x="0" y="10" width="16" height="34"></rect>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 40 - 0
src/assets/img/6.svg

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
+    <title>Group 5 Copy 5</title>
+    <desc>Created with Sketch.</desc>
+    <defs>
+        <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
+            <feMerge>
+                <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
+                <feMergeNode in="SourceGraphic"></feMergeNode>
+            </feMerge>
+        </filter>
+        <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
+        <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="setting-copy-2" transform="translate(-1190.000000, -136.000000)">
+            <g id="Group-8" transform="translate(1167.000000, 0.000000)">
+                <g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
+                    <mask id="mask-3" fill="white">
+                        <use xlink:href="#path-2"></use>
+                    </mask>
+                    <g id="Rectangle-18">
+                        <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
+                        <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
+                    </g>
+                    <rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="-1" y="0" width="49" height="10"></rect>
+                    <rect id="Rectangle-18" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="16" height="44"></rect>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 40 - 0
src/assets/img/7.svg

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
+    <title>Group 5 Copy 5</title>
+    <desc>Created with Sketch.</desc>
+    <defs>
+        <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
+            <feMerge>
+                <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
+                <feMergeNode in="SourceGraphic"></feMergeNode>
+            </feMerge>
+        </filter>
+        <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
+        <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="setting-copy-2" transform="translate(-1190.000000, -136.000000)">
+            <g id="Group-8" transform="translate(1167.000000, 0.000000)">
+                <g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
+                    <mask id="mask-3" fill="white">
+                        <use xlink:href="#path-2"></use>
+                    </mask>
+                    <g id="Rectangle-18">
+                        <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
+                        <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
+                    </g>
+                    <rect id="Rectangle-11" fill="#303648" mask="url(#mask-3)" x="-1" y="0" width="49" height="10"></rect>
+                    <rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="44"></rect>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 41 - 0
src/assets/img/8.svg

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
+    <title>Group 5 Copy 5</title>
+    <desc>Created with Sketch.</desc>
+    <defs>
+        <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
+            <feMerge>
+                <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
+                <feMergeNode in="SourceGraphic"></feMergeNode>
+            </feMerge>
+        </filter>
+        <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
+        <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="setting-copy-2" transform="translate(-1190.000000, -136.000000)">
+            <g id="Group-8" transform="translate(1167.000000, 0.000000)">
+                <g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
+                    <mask id="mask-3" fill="white">
+                        <use xlink:href="#path-2"></use>
+                    </mask>
+                    <g id="Rectangle-18">
+                        <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
+                        <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
+                    </g>
+                    <rect id="Rectangle-18" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="16" height="10"></rect>
+                    <rect id="Rectangle-11" fill="#408df7" mask="url(#mask-3)" x="16" y="0" width="49" height="10"></rect>
+                    <rect id="Rectangle-18" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="10" width="16" height="34"></rect>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 41 - 0
src/assets/img/9.svg

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
+    <title>Group 5 Copy 5</title>
+    <desc>Created with Sketch.</desc>
+    <defs>
+        <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
+            <feMerge>
+                <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
+                <feMergeNode in="SourceGraphic"></feMergeNode>
+            </feMerge>
+        </filter>
+        <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
+        <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="setting-copy-2" transform="translate(-1190.000000, -136.000000)">
+            <g id="Group-8" transform="translate(1167.000000, 0.000000)">
+                <g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
+                    <mask id="mask-3" fill="white">
+                        <use xlink:href="#path-2"></use>
+                    </mask>
+                    <g id="Rectangle-18">
+                        <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
+                        <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
+                    </g>
+                    <rect id="Rectangle-18" fill="#408df7" mask="url(#mask-3)" x="0" y="0" width="16" height="10"></rect>
+                    <rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="16" y="0" width="49" height="10"></rect>
+                    <rect id="Rectangle-18" fill="#408df7" mask="url(#mask-3)" x="0" y="10" width="16" height="34"></rect>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

BIN
src/assets/img/Scheme.png


BIN
src/assets/img/avatar.png


BIN
src/assets/img/bg.png


+ 40 - 0
src/assets/img/left_layout.svg

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
+    <title>Group 5 Copy 5</title>
+    <desc>Created with Sketch.</desc>
+    <defs>
+        <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
+            <feMerge>
+                <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
+                <feMergeNode in="SourceGraphic"></feMergeNode>
+            </feMerge>
+        </filter>
+        <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
+        <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="setting-copy-2" transform="translate(-1190.000000, -136.000000)">
+            <g id="Group-8" transform="translate(1167.000000, 0.000000)">
+                <g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
+                    <mask id="mask-3" fill="white">
+                        <use xlink:href="#path-2"></use>
+                    </mask>
+                    <g id="Rectangle-18">
+                        <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
+                        <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
+                    </g>
+                    <rect id="Rectangle-11" fill="#F0F2F5" mask="url(#mask-3)" x="-1" y="0" width="49" height="10"></rect>
+                    <rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="44"></rect>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

BIN
src/assets/img/logo.png


+ 40 - 0
src/assets/img/top_layout.svg

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
+    <title>Group 5 Copy 5</title>
+    <desc>Created with Sketch.</desc>
+    <defs>
+        <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
+            <feMerge>
+                <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
+                <feMergeNode in="SourceGraphic"></feMergeNode>
+            </feMerge>
+        </filter>
+        <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
+        <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
+            <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
+            <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
+            <feColorMatrix values="0 0 0 0 0   0 0 0 0 0   0 0 0 0 0  0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
+        </filter>
+    </defs>
+    <g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="setting-copy-2" transform="translate(-1190.000000, -136.000000)">
+            <g id="Group-8" transform="translate(1167.000000, 0.000000)">
+                <g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
+                    <mask id="mask-3" fill="white">
+                        <use xlink:href="#path-2"></use>
+                    </mask>
+                    <g id="Rectangle-18">
+                        <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
+                        <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
+                    </g>
+                    <rect id="Rectangle-11" fill="#303648" mask="url(#mask-3)" x="-1" y="0" width="49" height="10"></rect>
+                    <rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="44"></rect>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

BIN
src/assets/img/wall-bky.png


BIN
src/assets/img/wallbg.png


BIN
src/assets/img/xwh.png


BIN
src/assets/img/zjs.png


+ 964 - 0
src/assets/scss/index.scss

@@ -0,0 +1,964 @@
+// 站点主色
+$--color-primary: #3f8ef7;
+$navbar--background-color: $--color-primary;
+$sidebar--background-color-dark:#181922; // #263238;
+$sidebar--color-text-dark: rgba(163,175,183,.9);
+$content--background-color: #fff;
+*,
+*:before,
+*:after {
+  box-sizing: border-box;
+}
+
+a {
+  color: mix(#fff, $--color-primary, 20%);
+  text-decoration: none;
+  &:focus,
+  &:hover {
+    color: $--color-primary;
+    text-decoration: underline;
+  }
+}
+img {
+  vertical-align: middle;
+}
+
+/* Utils
+------------------------------ */
+.clearfix:before,
+.clearfix:after {
+  content: " ";
+  display: table;
+}
+.clearfix:after {
+  clear: both;
+}
+
+
+/* Animation
+------------------------------ */
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity .5s;
+}
+.fade-enter,
+.fade-leave-to {
+  opacity: 0;
+}
+
+
+/* Reset element-ui
+------------------------------ */
+.jp-wrapper {
+  .el-pagination {
+    margin-top: 15px;
+    text-align: right;
+  }
+}
+
+
+/* Layout
+------------------------------ */
+.jp-wrapper {
+  position: relative;
+  min-width: 736px;
+  background: #f1f2f5
+}
+.min300 {
+  min-width: 300px;
+}
+
+/* Sidebar fold
+------------------------------ */
+.jp-sidebar--fold {
+  .jp-navbar__header,
+  .jp-navbar__brand,
+  .jp-sidebar,
+  .jp-sidebar__inner,
+  .el-menu.jp-sidebar__menu {
+    width: 64px;
+  }
+  .jp-navbar__body,
+  .jp-content__wrapper {
+    margin-left: 64px;
+  }
+  .jp-navbar__brand {
+    &-lg {
+      display: none;
+    }
+    &-mini {
+      display: inline-block;
+    }
+  }
+  .jp-sidebar,
+  .jp-sidebar__inner {
+    overflow: initial;
+  }
+  .jp-content--tabs > .el-tabs > .el-tabs__header {
+    left: 64px;
+  }
+}
+// animation
+.jp-navbar__header,
+.jp-navbar__brand,
+.jp-navbar__body,
+.jp-sidebar,
+.jp-sidebar__inner,
+.jp-sidebar__menu.el-menu,
+.jp-sidebar__menu-icon,
+.jp-content__wrapper,
+.jp-content--tabs > .el-tabs .el-tabs__header {
+  transition: inline-block .3s, left .3s, width .3s, margin-left .3s, font-size .3s;
+}
+
+
+/* Navbar
+------------------------------ */
+.jp-navbar {
+  position: fixed;
+  top: 0;
+  right: 0;
+  left: 0;
+  z-index: 1030;
+  height: 60px;
+  // box-shadow: 0 2px 4px rgba(0,0,0,.08);
+  background-color: $navbar--background-color;
+    .el-dropdown-link {
+      color: #8f9398;
+    }
+  &--1 , &--2, &--3, &--5, &--7, &--8{
+    .jp-navbar__body {
+      background-color: transparent;
+    }
+    .el-menu {
+      > .el-menu-item,
+      > .el-submenu > .el-submenu__title {
+        color: #fff;
+        &:focus,
+        &:hover {
+          color: #fff !important;
+          background-color: rgba(0,0,0,.1) !important;
+        }
+      }
+      > .el-menu-item.is-active,
+      > .el-submenu.is-active > .el-submenu__title {
+        border-bottom-color: transparent;
+        color: #ffffff !important;
+        background-color: rgba(0,0,0,.1) !important;
+      }
+      .el-menu-item i,
+      .el-submenu__title i,
+      .el-dropdown .el-dropdown-link{
+        color: #fff !important;
+      }
+    }
+    .el-menu--popup-bottom-start {
+      background-color: rgba(0,0,0,.1) !important;
+    }
+  }
+  &--3, &--4, &--7{
+    .jp-navbar__header {
+      background: $sidebar--background-color-dark;
+      border-bottom: 1px solid #101117;
+    }
+  }
+  &--7{
+      .jp-navbar__body {
+        background-color:  $sidebar--background-color-dark;
+      }
+      .el-menu {
+        > .el-menu-item,
+        > .el-submenu > .el-submenu__title {
+          color: rgba(163, 175, 183, 0.9);
+        }
+      }
+  }
+  &--6, &--8{
+    background-color: white;
+    .jp-navbar__header {
+      background: white;
+      .jp-navbar__brand{
+        .jp-navbar__brand-lg, .jp-navbar__brand-mini{
+          color: #515a6e;
+        }
+      }
+      
+    }
+  }
+  &__header {
+    position: relative;
+    float: left;
+    width: 230px;
+    height: 60px;
+    overflow: hidden;
+    // -webkit-box-shadow: 2px 0 8px 0 rgba(29,35,41,.05);
+    // box-shadow: 2px 0 8px 0 rgba(29,35,41,.05);
+    // border-right: 1px solid #f8f8f9;
+  }
+  &__brand {
+    display: table-cell;
+    vertical-align: middle;
+    width: 230px;
+    height: 60px;
+    margin: 0;
+    line-height: 60px;
+    font-size: 20px;
+    text-align: center;
+    text-transform: uppercase;
+    white-space: nowrap;
+    color: #fff;
+
+    &-lg,
+    &-mini {
+      margin: 0 5px;
+      color: #fff;
+      &:focus,
+      &:hover {
+        color: #fff;
+        text-decoration: none;
+      }
+    }
+    &-mini {
+      display: none;
+    }
+  }
+  &__switch {
+    font-size: 18px;
+    border-bottom: none !important;
+  }
+  &__avatar {
+    border-bottom: none !important;
+    * {
+      vertical-align: inherit;
+    }
+    .el-dropdown-link {
+      > img {
+        width: 36px;
+        height: auto;
+        margin-right: 5px;
+        border-radius: 100%;
+        vertical-align: middle;
+      }
+    }
+  }
+  &__body {
+    position: relative;
+    margin-left: 230px;
+    padding-right: 15px;
+    background-color: #fff;
+    .el-menu--horizontal>.el-menu-item {
+      border-bottom: 0px;
+    }
+  }
+  &__menu {
+    float: left;
+    background-color: transparent !important;
+    border-bottom: 0;
+
+    &--right {
+      float: right;
+      .el-menu-item {
+        border-bottom: 0;
+        padding: 0 10px;
+       }
+      .el-menu-item.is-active {
+        border-bottom: 0;
+       }
+    }
+    a:focus,
+    a:hover {
+      text-decoration: none;
+    }
+    .el-menu-item,
+    .el-submenu > .el-submenu__title {
+      height: 60px;
+      line-height: 60px;
+    }
+    .el-menu-item [class^=el-icon-] {
+      margin-right: 0px;
+    }
+    .el-submenu > .el-menu {
+      top: 55px;
+    }
+    .el-badge {
+      display: inline;
+      z-index: 2;
+      &__content {
+        line-height: 16px;
+      }
+    }
+  }
+}
+/* Sidebar
+------------------------------ */
+.jp-sidebar {
+  position: fixed;
+  top: 60px;
+  left: 0;
+  bottom: 0;
+  z-index: 1020;
+  width: 230px;
+  overflow: hidden;
+  box-shadow: 0 2px 4px rgba(0,0,0,.08);
+  .jp-sidebar__menu-icon{
+    margin-right: 5px;
+  }
+
+  &--2, &--6, &--8{
+    background-color: white;
+    .jp-sidebar__menu.el-menu {
+      border-top: 1px solid #E4E7ED;
+    }
+  }
+  &--3, &--4, &--7{
+    .jp-sidebar__menu.el-menu {
+      border-top: 1px solid #101117;
+    }
+  }
+
+  &--1,
+  &--1-popper,
+  &--3,
+  &--3-popper,
+  &--4,
+  &--4-popper ,
+  &--7,
+  &--7-popper  {
+    background-color: $sidebar--background-color-dark;
+    .jp-sidebar__menu.el-menu,
+    > .el-menu--popup {
+      background-color: $sidebar--background-color-dark;
+      .el-menu-item,
+      .el-submenu > .el-submenu__title {
+        height: 40px;
+        line-height: 40px;
+        white-space: nowrap;
+        list-style: none;
+        color: $sidebar--color-text-dark;
+        &:focus,
+        &:hover {
+          color: mix(#fff, $sidebar--color-text-dark, 50%);
+          background-color: mix(#fff, $sidebar--background-color-dark, 2.5%);
+        }
+      }
+      .el-menu,
+      .el-submenu.is-opened {
+        background-color: mix(#000, $sidebar--background-color-dark, 15%);
+        > .el-submenu__title {
+          color: #fff;
+        }
+      }
+      .el-menu-item.is-active,
+      .el-submenu.is-active > .el-submenu__title {
+        color: mix(#fff, $sidebar--color-text-dark, 80%);
+      }
+    }
+  }
+  &--5,
+  &--5-popper,
+  &--9,
+  &--9-popper   {
+    background-color: $sidebar--background-color-dark;
+    .jp-menu-category {
+      color: #ffffff;
+    }
+    .jp-sidebar__menu.el-menu,
+    > .el-menu--popup {
+      background-color: transparent;
+      .el-menu-item,
+      .el-submenu > .el-submenu__title {
+        height: 40px;
+        line-height: 40px;
+        white-space: nowrap;
+        list-style: none;
+        color: #fff;
+        i {
+          color: #fff;
+        }
+        &:focus,
+        &:hover {
+          color: #fff;
+          background-color: rgba(0, 0, 0, 0.1) !important;
+        }
+      }
+      .el-menu,
+      .el-submenu.is-opened {
+        background-color: transparent;
+        > .el-submenu__title {
+          color: #fff;
+          i {
+            color: #fff;
+          }
+        }
+      }
+      .el-menu-item.is-active,
+      .el-submenu.is-active > .el-submenu__title {
+        color: #fff;
+        i {
+          color: #fff;
+        }
+      }
+    }
+  }
+  &__inner {
+    position: relative;
+    z-index: 1;
+    width: 250px;
+    height: 100%;
+    padding-bottom: 15px;
+    overflow-y: scroll;
+  }
+  &__menu.el-menu {
+    width: 230px;
+    border-top: 1px solid #31344826;
+    border-right: 0;
+  }
+  &__menu-icon {
+    width: 24px;
+    // margin-right: 5px;
+    text-align: center;
+    font-size: 16px;
+    color: inherit !important;
+  }
+}
+
+.width100 {
+  width: 100%;
+}
+
+.el-submenu [class^=el-icon-] {
+  vertical-align: middle;
+  width: 24px;
+  text-align: center;
+  font-size: 14px;
+}
+.el-menu-item [class^=el-icon-] {
+  width: 24px;
+  text-align: center;
+  font-size: 14px;
+  vertical-align: middle;
+}
+.el-menu--collapse>.el-menu-item [class^=el-icon-],
+.el-menu--collapse>.el-menu-item [class^=fa],
+.el-menu--collapse>.el-submenu>.el-submenu__title [class^=el-icon-],
+.el-menu--collapse>.el-submenu>.el-submenu__title [class^=fa]{
+  font-size: 20px
+}
+
+/* Content
+------------------------------ */
+.jp-content {
+  position: relative;
+  padding: 10px;
+
+  &__wrapper {
+    position: relative;
+    padding-top: 60px;
+    margin-left: 230px;
+    min-height: 100%;
+    background:transparent;
+  }
+  &--tabs {
+    padding: 40px 0 0;
+  }
+  > .el-tabs {
+    > .el-tabs__header {
+      position: fixed;
+      top: 60px;
+      left: 230px;
+      right: 0;
+      // z-index: 930;
+      padding: 0 95px 0 15px;
+      background: #fff;
+      // border-bottom: solid 1px #e4eaec;
+      display: block;
+      z-index: 100;
+      -webkit-user-select: none;
+      -moz-user-select: none;
+      -ms-user-select: none;
+      user-select: none;
+      // position: relative;
+      // padding: 0 10px;
+      // margin-bottom: 10px;
+      border-top: 1px solid #E4E7ED;
+      background-color: #fff;
+      -webkit-box-shadow: 0 1px 2px 0 rgba(0,0,0,.05);
+      box-shadow: 0 1px 2px 0 rgba(0,0,0,.05);
+
+      .el-tabs__item {
+        margin: 0 3px;
+        height: 40px;
+        line-height: 40px;
+        font-size: 13px;
+        font-weight: 400;
+        color: #b1afaf;
+        border: none;
+      }
+      .el-tabs__item.is-active {
+        color: #409eff;
+        border-bottom: 2px solid #409eff;
+      }
+      .el-tabs__nav {
+        margin-bottom: 0;
+        border: 0;
+        &:after {
+          display: none;
+        }
+      }
+    }
+    > .el-tabs__content {
+      padding: 10px;
+      > .jp-tabs__tools {
+        position: fixed;
+        top: 60px;
+        right: 0;
+        z-index: 931;
+        height: 36px;
+        padding: 0 12px;
+        font-size: 16px;
+        line-height: 36px;
+        // background-color: $content--background-color;
+        cursor: pointer;
+        .el-icon--right {
+          margin-left: 0;
+        }
+      }
+    }
+  }
+}
+
+.help-block {
+  color: #9ca0a7;
+  font-size: 12px;
+  line-height: 1;
+  padding-top: 4px;
+  position: absolute;
+  top: 100%;
+  left: 0;
+}
+
+.jp-navbar .el-menu > .el-menu-item.is-active, .jp-navbar .el-menu > .el-submenu.is-active > .el-submenu__title {
+  border-bottom-color: transparent;
+  color: #526069;
+  background-color: rgba(243,247,249,.6);
+}
+
+
+// 设置左侧菜单栏 end
+
+.condition-container{
+  .datetime {
+      .el-input__inner{
+          width:180px;
+      }
+  }
+  .el-input__inner{
+      width:141px;
+  }
+}
+.irate{
+  .el-icon-star-on{
+    color:#67c23a !important;
+  }
+} 
+.operation-container {
+.cell {
+  padding: 10px !important;
+}
+.el-button {
+  &:nth-child(3) {
+    margin-top: 10px;
+    margin-left: 0px;
+  }
+  &:nth-child(4) {
+    margin-top: 10px;
+  }
+}
+}
+
+.el-upload {
+input[type="file"] {
+  display: none !important;
+}
+}
+
+.el-upload__input {
+display: none;
+}
+
+.cell {
+.el-tag {
+  margin-right: 8px;
+}
+}
+
+.small-padding {
+.cell {
+  padding-left: 8px;
+  padding-right: 8px;
+}
+}
+
+.status-col {
+.cell {
+  padding: 0 10px;
+  text-align: center;
+  .el-tag {
+    margin-right: 0px;
+  }
+}
+}
+
+//暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461
+.el-dialog {
+transform: none;
+left: 0;
+position: relative;
+margin: 0 auto;
+}
+
+
+//textarea修改样式
+.article-textarea {
+textarea {
+  padding-right: 40px;
+  resize: none;
+  border: none;
+  border-radius: 0px;
+  border-bottom: 1px solid #bfcbd9;
+}
+}
+
+//element ui upload
+.upload-container {
+.el-upload {
+  width: 100%;
+  .el-upload-dragger {
+    width: 100%;
+    height: 200px;
+  }
+}
+}
+
+.top-menu{
+.el-badge__content{
+  top: 12px !important;
+}
+}
+.li-badge{
+  .one{
+      .el-badge__content{
+          background-color: #a9d86e;
+      }
+  }
+  .three{
+     .el-badge__content{
+          background-color: #FCB322;
+      }
+  }
+}
+
+// 搜索区域
+.search-form{
+width:100%;
+min-width:750px;
+.el-form-item{
+    margin-bottom: 10px;
+     .el-date-editor{
+        width:166px;
+        .el-input__inner{
+            padding-right: 0;
+        }
+    }
+}
+}
+.fillcontain{
+.el-input__inner{
+  height:30px !important;
+  line-height: 30px !important;
+}
+}
+.el-table th {
+    word-break: break-word;
+    color: rgba(0,0,0,.85);
+    background-color: #fafafa !important;
+}
+.el-table.el-table--medium td{
+  padding: 7px 0 !important;
+}
+.el-table.el-table--medium td.el-table__expanded-cell[class*=cell] {
+  padding: 20px 50px !important;
+}
+.el-table td, .el-table th.is-leaf {
+  border-bottom: 1px solid #EBEEF5;
+}
+
+.el-tabs--border-card>.el-tabs__header .el-tabs__item.is-active{
+color:#a9d86e !important;
+}
+
+.leftShareList{
+width:200px;
+}
+.shareOther a{
+color:#000000;
+}
+.moreShareList{
+padding: 0;
+ul{
+  width:100%;
+  a{
+    color:#000000;
+    padding: 5px 10px;
+    box-sizing: border-box;
+  }
+  a:hover{
+    background: #e8e8e8;
+  }
+  li{
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    .svg-icon{
+      margin-right: 10px;
+    }
+    span{
+      line-height: 0;
+    }
+  }
+}
+}
+
+.qcodepopper{
+.wechat-area{
+    align-items: center;
+    .titles{
+      font-size: 14px;
+      background: #a9d86e;
+      color:#fff;
+      height: 30px;
+      line-height: 30px;
+      width:100px;
+      text-align: center;
+      border-radius: 20px;
+    }
+    img{
+      width:100px;
+      height:100px;
+    }
+}
+}
+.yanshare{
+.shareUl{
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  li{
+    display: flex;
+    flex:1;
+    justify-content: center;
+    cursor: pointer;
+  }
+}
+}
+.yanSharewx{
+padding: 10px 0;
+width:100px !important;
+.qrcodeArea{
+  border-radius: 4px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  width:100px;
+  margin: 0 auto;
+  .saoTitle{
+    font-size: 14px;
+    color:#a9d86e;
+    margin-bottom: 5px;
+  }
+}
+}
+
+
+
+.salesPrice{
+span{
+  font-weight: bold;
+}
+}
+.salesStatus{
+span{
+   font-size: 12px;
+   padding: 5px 10px;
+   color:#fff;
+   border-radius: 5px;
+}
+}
+.salesUsername{
+ .cell{
+   display: flex;
+   align-items: center;
+    .userImg{
+      width: 40px;
+      border-radius: 50%;
+      margin-right: 10px;
+    }
+ }
+}
+
+// .cardBody{
+//  .el-card__body{
+//    display: flex;
+//    flex-direction: column;
+//  }
+// }
+.logContainer{
+.box-card{
+  height: 100%;
+  .el-card__body{
+    padding: 10px 20px;
+    height: 80%;
+  }
+  .item{
+    border-bottom: 1px solid #e8e8e8;
+    padding: 10px 0;
+  }
+}
+}
+.red {
+  color: red;
+}
+.query-form {
+  padding-top: 25px;
+  background-color: #f2f2f2;
+  padding-left: 25px;
+  margin-bottom: 30px;
+}
+.table {
+  margin-top: 10px
+}
+.jp-navbar__menu.el-menu--horizontal.el-menu{
+  border-bottom: 0px;
+}
+.jp-navbar__menu >.el_menu_item{
+  width:124px;
+  padding-left: 10px;
+  padding-right: 10px;
+  white-space:nowrap;
+  text-overflow:ellipsis;
+  overflow:hidden;
+}
+.el-dialog {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  margin: 0 !important;
+  transform: translate(-50%, -50%);
+  max-height: calc(100% - 30px);
+  max-width: calc(100% - 30px);
+  display: flex;
+  flex-direction: column;
+    > .el-dialog__body {
+      overflow: auto;
+      color: #666666;
+  }
+}
+.zZindex {
+  z-index:6000 !important;
+}
+.act-form {
+  padding-left: 10px;
+  padding-right: 10px;
+}
+.pull-right {
+  text-align: right;
+}
+.showPic {
+  width: 80% !important;
+}
+.el-tree-node__content .tree-item-button{
+  display: none;
+}
+.el-tree-node__content:hover .tree-item-button{
+  display: unset;
+}
+.custom-tree-node {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  font-size: 14px;
+  padding-right: 8px;
+}
+.filter-tree{
+  margin-top: 15px;
+}
+.bg-white {
+    border: 1px solid #EBEEF5;
+    background-color: #FFF;
+    -webkit-transition: .3s;
+    transition: .3s;
+    border-radius: 4px;
+    overflow: hidden;
+    padding: 20px;
+}
+.rotate-90 {
+  -webkit-transform: rotate(90deg);
+  transform: rotate(90deg);
+  -webkit-transition: all 0.8s;
+  transition: all 0.8s;
+}
+.el-table .cell, .el-table--border td:first-child .cell, .el-table--border th:first-child .cell {
+  padding-left: 10px;
+  padding-right: 10px;
+}
+.avatar-uploader .el-upload {
+  border: 1px dashed #d9d9d9;
+  border-radius: 6px;
+  margin-left: 40px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+}
+.avatar-uploader .el-upload:hover {
+  border-color: #409EFF;
+}
+.avatar-uploader-icon {
+  font-size: 28px;
+  color: #8c939d;
+  width: 150px;
+  height: 150px;
+  line-height: 150px !important;
+  text-align: center;
+}
+.avatar {
+  width: 150x;
+  height: 150px;
+  display: block;
+}
+.el-form-item__content .el-input-group {
+  vertical-align: middle !important;
+}
+.readonly .el-input.is-disabled .el-input__inner ,
+.readonly .el-textarea.is-disabled .el-textarea__inner ,
+.readonly .el-input.is-disabled .el-input__inner {
+    background-color: #f5f7fa8a;
+    border-color: #E4E7ED;
+    color: #606266;
+    cursor: not-allowed;
+}
+.el-dialog__header {
+  border-bottom: 1px solid #e8e8e8;
+  border-radius: 4px 4px 0 0;
+}
+.el-dialog__footer {
+  border-top: 1px solid #e8e8e8;
+  border-radius: 0 0 4px 4px;
+}
+.el-table td img {
+  max-width: 100%;
+}
+.el-table .el-table__row .el-link{
+  display: contents;
+}

+ 179 - 0
src/assets/scss/login.scss

@@ -0,0 +1,179 @@
+html, body{
+    height: 100%;
+    margin: 0;
+    padding: 0;
+}
+.login-container {
+  display: flex;
+  align-items: center;
+  width: 100%;
+  height: 100%;
+  margin: 0 auto;
+  background: url("/static/img/login.png");
+  background-color: #0e6cff;
+  position: relative;
+  background-size: cover;
+  height: 100vh;
+  background-position: center center;
+}
+.login-weaper {
+  margin: 0 auto;
+  width: 1000px;
+  box-shadow: -4px 5px 10px rgba(0, 0, 0, 0.4);
+  .el-input-group__append {
+    border: none;
+  }
+}
+
+.login-left,
+.login-border {
+  position: relative;
+  min-height: 500px;
+  align-items: center;
+  display: flex;
+}
+.login-left {
+  border-top-left-radius: 5px;
+  border-bottom-left-radius: 5px;
+  justify-content: center;
+  flex-direction: column;
+  background-color: rgba(64, 158, 255, 0);
+  color: #fff;
+  float: left;
+  width: 50%;
+  position: relative;
+}
+.login-left .img {
+  width: 400px;
+  animation: bounce-l 2s infinite linear;
+  -webkit-animation: bounce-l 2s infinite linear;
+}
+.login-time {
+  position: absolute;
+  left: 25px;
+  top: 25px;
+  width: 100%;
+  color: #fff;
+  font-weight: 200;
+  opacity: 0.9;
+  font-size: 18px;
+  overflow: hidden;
+  font-weight: 500;
+}
+.login-left .title {
+  text-align: center;
+  color: #fff;
+  font-weight: 300;
+  letter-spacing: 2px;
+  font-size: 25px;
+  font-weight: 600;
+}
+
+.login-border {
+  border-left: none;
+  border-top-right-radius: 5px;
+  border-bottom-right-radius: 5px;
+  color: #fff;
+  background-color: rgba(255, 255, 255, 0.9);;
+  width: 50%;
+  float: left;
+  box-sizing: border-box;
+}
+.login-main {
+  margin: 0 auto;
+  width: 65%;
+  box-sizing: border-box;
+}
+.login-main > h3 {
+  margin-bottom: 20px;
+}
+.login-main > p {
+  color: #76838f;
+}
+.login-title {
+  color: #333;
+  margin-bottom: 40px;
+  font-weight: 500;
+  font-size: 22px;
+  text-align: center;
+  letter-spacing: 4px;
+}
+.login-menu {
+  margin-top: 40px;
+  width: 100%;
+  text-align: center;
+  a {
+    color: #999;
+    font-size: 12px;
+    margin: 0px 8px;
+  }
+}
+.login-submit {
+  width: 100%;
+  height: 45px;
+  border: 1px solid #409EFF;
+  background: none;
+  font-size: 18px;
+  letter-spacing: 2px;
+  font-weight: 300;
+  color: #409EFF;
+  cursor: pointer;
+  margin-top: 30px;
+  // font-family: "neo";
+  transition: 0.25s;
+}
+.login-form {
+  margin: 10px 0;
+  i {
+    color: #333;
+  }
+  .el-form-item__content {
+    width: 100%;
+  }
+  .el-form-item {
+    margin-bottom: 12px;
+  }
+  .el-input {
+    input {
+      padding-bottom: 10px;
+      text-indent: 5px;
+      background: transparent;
+      border: none;
+      border-radius: 0;
+      color: #333;
+      border-bottom: 1px solid rgb(235, 237, 242);
+    }
+    .el-input__prefix {
+      i {
+        padding: 0 5px;
+        font-size: 16px !important;
+      }
+    }
+  }
+}
+.login-code {
+  display: flex;
+  align-items: center;
+  justify-content: space-around;
+  margin: 0 0 0 10px;
+}
+.login-code-img {
+  margin-top: 2px;
+  width: 100px;
+  height: 38px;
+  background-color: #fdfdfd;
+  border: 1px solid #f0f0f0;
+  color: #333;
+  font-size: 14px;
+  font-weight: bold;
+  letter-spacing: 5px;
+  line-height: 38px;
+  text-indent: 5px;
+  text-align: center;
+}
+#particles-js{
+  z-index: 1;
+  width: 100%;
+  height: 100%;
+  position: absolute;
+}

+ 441 - 0
src/assets/scss/theme.scss

@@ -0,0 +1,441 @@
+// $sider-fill: #001529;
+// $color-primary: #108ee9;
+// $color-primary-1: #e6f7ff;
+
+// $border-radius-base     : 4px;
+// $border-radius-sm       : 2px;
+
+// // Avatar
+// $avatar-size-base: 32px;
+// $avatar-size-lg: 40px;
+// $avatar-size-sm: 24px;
+// $avatar-font-size-base: 18px;
+// $avatar-font-size-lg: 24px;
+// $avatar-font-size-sm: 14px;
+// $avatar-bg: #ccc;
+// $avatar-color: #fff;
+// $avatar-border-radius: $border-radius-base;
+
+// $line-height-base: 1.5;
+
+/* stylelint-disable at-rule-empty-line-before,at-rule-name-space-after,at-rule-no-unknown */
+// $import "../color/colors";
+
+// The prefix to use on all css classes from ant.
+$ant-prefix             : ant;
+
+$blue-6: #1890ff;
+$green-6: #52c41a;
+$red-6: #f5222d;
+$gold-6: #faad14;
+$yellow-6: #fadb14;
+
+// -------- Colors -----------
+$primary-color          : $blue-6;
+$info-color             : $blue-6;
+$success-color          : $green-6;
+$processing-color       : $primary-color;
+$error-color            : $red-6;
+$highlight-color        : $red-6;
+$warning-color          : $gold-6;
+$normal-color           : #d9d9d9;
+
+// Color used by default to control hover and active backgrounds and for
+// alert info backgrounds.
+$primary-1: #e6f7ff;  // replace tint($primary-color, 90%)
+$primary-2: #bae7ff;  // replace tint($primary-color, 80%)
+$primary-3: #91d5ff;  // unused
+$primary-4: #69c0ff;  // unused
+$primary-5: #40a9ff;  // color used to control the text color in many active and hover states, replace tint($primary-color, 20%)
+$primary-6: $primary-color;                                 // color used to control the text color of active buttons, don't use, use $primary-color
+$primary-7: #096dd9;  // replace shade($primary-color, 5%)
+$primary-8: #0050b3;  // unused
+$primary-9: #003a8c;  // unused
+$primary-10: #002766;  // unused
+
+// Base Scaffolding Variables
+// ---
+
+// Background color for `<body>`
+$body-background        : #fff;
+// Base background color for most components
+$component-background   : #fff;
+$font-family-no-number  : -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif;
+$font-family            : "Helvetica Neue For Number", $font-family-no-number;
+$code-family            : Consolas, Menlo, Courier, monospace;
+$heading-color          : transparentize(#000, 0.15);
+$text-color             : transparentize(#000, 0.35);
+$text-color-secondary   : transparentize(#000, 0.55);
+$heading-color-dark     : transparentize(#fff, 0);
+$text-color-dark        : transparentize(#fff, 0.15);
+$text-color-secondary-dark: transparentize(#fff, 0.35);
+$font-size-base         : 14px;
+$font-size-lg           : $font-size-base + 2px;
+$font-size-sm           : 12px;
+$line-height-base       : 1.5;
+$border-radius-base     : 4px;
+$border-radius-sm       : 2px;
+
+// vertical paddings
+$padding-lg    : 24px; // containers
+$padding-md    : 16px; // small containers and buttons
+$padding-sm    : 12px; // Form controls and items
+$padding-xs    : 8px;  // small items
+
+// vertical padding for all form controls
+$control-padding-horizontal: $padding-sm;
+$control-padding-horizontal-sm: $padding-xs;
+
+// The background colors for active and hover states for things like
+// list items or table cells.
+$item-active-bg         : $primary-1;
+$item-hover-bg          : $primary-1;
+
+// ICONFONT
+$iconfont-css-prefix    : anticon;
+$icon-url               : "https://at.alicdn.com/t/font_148784_v4ggb6wrjmkotj4i";
+
+// LINK
+$link-color             : $primary-color;
+$link-hover-color       : $primary-5;
+$link-active-color      : $primary-7;
+$link-decoration        : none;
+$link-hover-decoration  : none;
+
+// Animation
+$ease-out            : cubic-bezier(0.215, 0.61, 0.355, 1);
+$ease-in             : cubic-bezier(0.55, 0.055, 0.675, 0.19);
+$ease-in-out         : cubic-bezier(0.645, 0.045, 0.355, 1);
+$ease-out-back       : cubic-bezier(0.12, 0.4, 0.29, 1.46);
+$ease-in-back        : cubic-bezier(0.71, -0.46, 0.88, 0.6);
+$ease-in-out-back    : cubic-bezier(0.71, -0.46, 0.29, 1.46);
+$ease-out-circ       : cubic-bezier(0.08, 0.82, 0.17, 1);
+$ease-in-circ        : cubic-bezier(0.6, 0.04, 0.98, 0.34);
+$ease-in-out-circ    : cubic-bezier(0.78, 0.14, 0.15, 0.86);
+$ease-out-quint      : cubic-bezier(0.23, 1, 0.32, 1);
+$ease-in-quint       : cubic-bezier(0.755, 0.05, 0.855, 0.06);
+$ease-in-out-quint   : cubic-bezier(0.86, 0, 0.07, 1);
+
+// Border color
+$border-color-base      : #d9d9d9;  // base border outline a component
+$border-color-split     : #e8e8e8;  // split border inside a component
+$border-width-base      : 1px;            // width of the border for a component
+$border-style-base      : solid;          // style of a components border
+
+// Outline
+$outline-blur-size      : 0;
+$outline-width          : 2px;
+$outline-color          : $primary-color;
+
+$background-color-light : #fafafa;  // background of header and selected item
+$background-color-base  : #f5f5f5;  // Default grey background color
+
+// Disabled states
+$disabled-color         : transparentize(#000, 0.75);
+$disabled-bg            : $background-color-base;
+$disabled-color-dark    : transparentize(#fff, 0.65);
+
+// Shadow
+$shadow-color           : rgba(0, 0, 0, .15);
+$shadow-1-up            : 0 2px 8px $shadow-color;
+$shadow-1-down          : 0 2px 8px $shadow-color;
+$shadow-1-left          : -2px 0 8px $shadow-color;
+$shadow-1-right         : 2px 0 8px $shadow-color;
+$shadow-2               : 0 4px 12px $shadow-color;
+$box-shadow-base        : $shadow-1-down;
+
+// Buttons
+$btn-font-weight        : 400;
+$btn-border-radius-base : $border-radius-base;
+$btn-border-radius-sm   : $border-radius-base;
+
+$btn-primary-color      : #fff;
+$btn-primary-bg         : $primary-color;
+
+$btn-default-color      : $text-color;
+$btn-default-bg         : #fff;
+$btn-default-border     : $border-color-base;
+
+$btn-danger-color       : $error-color;
+$btn-danger-bg          : $background-color-base;
+$btn-danger-border      : $border-color-base;
+
+$btn-disable-color      : $disabled-color;
+$btn-disable-bg         : $disabled-bg;
+$btn-disable-border     : $border-color-base;
+
+$btn-padding-base       : 0 $padding-md - 1px;
+$btn-font-size-lg       : $font-size-lg;
+$btn-font-size-sm       : $font-size-base;
+$btn-padding-lg         : $btn-padding-base;
+$btn-padding-sm         : 0 $padding-xs - 1px;
+
+$btn-height-base        : 32px;
+$btn-height-lg          : 40px;
+$btn-height-sm          : 24px;
+
+$btn-circle-size        : $btn-height-base;
+$btn-circle-size-lg     : $btn-height-lg;
+$btn-circle-size-sm     : $btn-height-sm;
+
+$btn-group-border       : $primary-5;
+
+// Checkbox
+$checkbox-size          : 16px;
+
+// Radio
+$radio-size             : 16px;
+
+// Radio buttons
+$radio-button-bg        : $btn-default-bg;
+$radio-button-color     : $btn-default-color;
+
+// Media queries breakpoints
+// Extra small screen / phone
+$screen-xs              : 480px;
+$screen-xs-min          : $screen-xs;
+
+// Small screen / tablet
+$screen-sm              : 576px;
+$screen-sm-min          : $screen-sm;
+
+// Medium screen / desktop
+$screen-md              : 768px;
+$screen-md-min          : $screen-md;
+
+// Large screen / wide desktop
+$screen-lg              : 992px;
+$screen-lg-min          : $screen-lg;
+
+// Extra large screen / full hd
+$screen-xl              : 1200px;
+$screen-xl-min          : $screen-xl;
+
+// Extra extra large screen / large descktop
+$screen-xxl              : 1600px;
+$screen-xxl-min          : $screen-xxl;
+
+// provide a maximum
+$screen-xs-max          : ($screen-sm-min - 1px);
+$screen-sm-max          : ($screen-md-min - 1px);
+$screen-md-max          : ($screen-lg-min - 1px);
+$screen-lg-max          : ($screen-xl-min - 1px);
+$screen-xl-max          : ($screen-xxl-min - 1px);
+
+// Grid system
+$grid-columns           : 24;
+$grid-gutter-width      : 0;
+
+// Layout
+$layout-body-background      : #f0f2f5;
+$layout-header-background    : #001529;
+$layout-footer-background    : $layout-body-background;
+$layout-header-height        : 64px;
+$layout-header-padding       : 0 50px;
+$layout-footer-padding       : 24px 50px;
+$layout-sider-background     : $layout-header-background;
+$layout-trigger-height       : 48px;
+$layout-trigger-background   : #002140;
+$layout-trigger-color        : #fff;
+$layout-zero-trigger-width   : 36px;
+$layout-zero-trigger-height  : 42px;
+
+// z-index list
+$zindex-affix           : 10;
+$zindex-back-top        : 10;
+$zindex-modal-mask      : 1000;
+$zindex-modal           : 1000;
+$zindex-notification    : 1010;
+$zindex-message         : 1010;
+$zindex-popover         : 1030;
+$zindex-picker          : 1050;
+$zindex-dropdown        : 1050;
+$zindex-tooltip         : 1060;
+
+// Animation
+$animation-duration-slow: .3s; // Modal
+$animation-duration-base: .2s;
+$animation-duration-fast: .1s; // Tooltip
+
+// Form
+// ---
+$label-required-color        : $highlight-color;
+$label-color                 : $heading-color;
+$form-item-margin-bottom     : 24px;
+$form-item-trailing-colon    : true;
+$form-vertical-label-padding : 0 0 8px;
+$form-vertical-label-margin  : 0;
+
+// Input
+// ---
+$input-height-base           : 32px;
+$input-height-lg             : 40px;
+$input-height-sm             : 24px;
+$input-padding-horizontal    : $control-padding-horizontal - 1px;
+$input-padding-horizontal-base: $input-padding-horizontal;
+$input-padding-horizontal-sm : $control-padding-horizontal-sm - 1px;
+$input-padding-horizontal-lg : $input-padding-horizontal;
+$input-padding-vertical-base : 4px;
+$input-padding-vertical-sm   : 1px;
+$input-padding-vertical-lg   : 6px;
+$input-placeholder-color     : hsv(0, 0, 75%);
+$input-color                 : $text-color;
+$input-border-color          : $border-color-base;
+$input-bg                    : #fff;
+$input-addon-bg              : $background-color-light;
+$input-hover-border-color    : $primary-color;
+$input-disabled-bg           : $disabled-bg;
+
+// Tooltip
+// ---
+//* Tooltip max width
+$tooltip-max-width: 250px;
+//** Tooltip text color
+$tooltip-color: #fff;
+//** Tooltip background color
+$tooltip-bg: rgba(0, 0, 0, .75);
+//** Tooltip arrow width
+$tooltip-arrow-width: 5px;
+//** Tooltip distance with trigger
+$tooltip-distance: $tooltip-arrow-width - 1px + 4px;
+//** Tooltip arrow color
+$tooltip-arrow-color: $tooltip-bg;
+
+// Popover
+// ---
+//** Popover body background color
+$popover-bg: #fff;
+//** Popover text color
+$popover-color: $text-color;
+//** Popover maximum width
+$popover-min-width: 177px;
+//** Popover arrow width
+$popover-arrow-width: 5px;
+//** Popover arrow color
+$popover-arrow-color: $popover-bg;
+//** Popover outer arrow width
+//** Popover outer arrow color
+$popover-arrow-outer-color: $popover-bg;
+//** Popover distance with trigger
+$popover-distance: $popover-arrow-width + 4px;
+
+// Modal
+// --
+$modal-mask-bg: rgba(0, 0, 0, 0.65);
+
+// Progress
+// --
+$progress-default-color: $processing-color;
+$progress-remaining-color: $background-color-base;
+
+// Menu
+// ---
+$menu-collapsed-width: 80px;
+$menu-inline-toplevel-item-height: 40px;
+$menu-item-height: 40px;
+$menu-collapsed-width: 80px;
+$menu-bg: $component-background;
+$menu-item-color: $text-color;
+$menu-highlight-color: $primary-color;
+$menu-item-active-bg: $item-active-bg;
+$menu-item-group-title-color: $text-color-secondary;
+// dark theme
+$menu-dark-color: $text-color-secondary-dark;
+$menu-dark-bg: $layout-header-background;
+$menu-dark-arrow-color: #fff;
+$menu-dark-submenu-bg: #000c17;
+$menu-dark-highlight-color: #fff;
+$menu-dark-item-selected-bg: $primary-color;
+
+
+// Spin
+// ---
+$spin-dot-size-sm: 14px;
+$spin-dot-size: 20px;
+$spin-dot-size-lg: 32px;
+
+// Table
+// --
+$table-header-bg: $background-color-light;
+$table-header-sort-bg: $background-color-base;
+$table-row-hover-bg: $primary-1;
+$table-selected-row-bg: #fafafa;
+$table-padding-vertical: 16px;
+$table-padding-horizontal: 16px;
+
+// Tag
+// --
+$tag-default-bg: $background-color-light;
+$tag-default-color: $text-color;
+$tag-font-size: $font-size-sm;
+
+// TimePicker
+// ---
+$time-picker-panel-column-width: 56px;
+$time-picker-panel-width: $time-picker-panel-column-width * 3;
+$time-picker-selected-bg: $background-color-base;
+
+// Carousel
+// ---
+$carousel-dot-width: 16px;
+$carousel-dot-height: 3px;
+$carousel-dot-active-width: 24px;
+
+// Badge
+// ---
+$badge-height: 20px;
+$badge-dot-size: 6px;
+$badge-font-size: $font-size-sm;
+$badge-status-size: 6px;
+
+// Rate
+// ---
+$rate-star-color: $yellow-6;
+$rate-star-bg: $border-color-split;
+
+// Card
+// ---
+$card-head-color: $heading-color;
+$card-head-background: $component-background;
+$card-head-padding: 16px;
+$card-inner-head-padding: 12px;
+$card-padding-base: 24px;
+$card-padding-wider: 32px;
+$card-actions-background: $background-color-light;
+$card-shadow: 0 2px 8px rgba(0, 0, 0, .09);
+
+// Tabs
+// ---
+$tabs-card-head-background: $background-color-light;
+$tabs-card-height: 40px;
+$tabs-title-font-size: $font-size-base;
+
+// BackTop
+// ---
+$back-top-color: #fff;
+$back-top-bg: $text-color-secondary;
+$back-top-hover-bg: $text-color;
+
+// Avatar
+// ---
+$avatar-size-base: 32px;
+$avatar-size-lg: 40px;
+$avatar-size-sm: 24px;
+$avatar-font-size-base: 18px;
+$avatar-font-size-lg: 24px;
+$avatar-font-size-sm: 14px;
+$avatar-bg: #ccc;
+$avatar-color: #fff;
+$avatar-border-radius: $border-radius-base;
+
+// Switch
+// ---
+$switch-height: 22px;
+$switch-sm-height: 16px;
+$switch-disabled-opacity: 0.4;
+
+// Pagination
+// ---
+$pagination-item-size: 32px;
+$pagination-item-size-sm: 24px;
+

+ 148 - 0
src/components/Exception/Exception.vue

@@ -0,0 +1,148 @@
+<template>
+  <div
+    class="exception"
+    v-bind="$attrs"
+    v-on="$listeners"
+  >
+    <div class="img-block">
+      <div
+        class="img-ele"
+        :style="{backgroundImage: `url(${img || config[pageType].img})`}"
+      />
+    </div>
+    <div class="content">
+      <h1>{{title || config[pageType].title}}</h1>
+      <div class="desc">{{desc || config[pageType].desc}}</div>
+      <div class="actions">
+        <slot
+          v-if="$slots.actions" 
+          name="actions"
+        />
+          <el-button type="primary" @click="goHome">返回首页</el-button>
+      </div>
+    </div>
+  </div>
+</template> 
+
+<script lang="ts">
+import config from './typeConfig'
+
+export default {
+  props: {
+    type: {
+      type: String,
+      default: '404'
+    },
+    title: String,
+    desc: String,
+    img: String
+  },
+  data () {
+    return {
+      config: config
+    }
+  },
+  methods: {
+    goHome () {
+      this.$events.$emit('closeTab', this.$route.fullPath)
+      this.$router.push('/')
+    }
+  },
+  computed: {
+    pageType () {
+      return this.type in this.config ? this.type : '404'
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+
+.exception {
+  display: flex;
+  align-items: center;
+  height: 100%;
+
+  .img-block {
+    flex: 0 0 62.5%;
+    width: 62.5%;
+    padding-right: 152px;
+    zoom: 1;
+    &:before,
+    &:after {
+      content: " ";
+      display: table;
+    }
+    &:after {
+      clear: both;
+      visibility: hidden;
+      font-size: 0;
+      height: 0;
+    }
+  }
+
+  .img-ele {
+    height: 360px;
+    width: 100%;
+    max-width: 430px;
+    float: right;
+    background-repeat: no-repeat;
+    background-position: 50% 50%;
+    background-size: contain;
+  }
+
+  .content {
+    flex: auto;
+
+    h1 {
+      color: #434e59;
+      font-size: 72px;
+      font-weight: 600;
+      line-height: 72px;
+      margin-bottom: 24px;
+    }
+
+    .desc {
+      color: transparentize(#000, 0.55);
+      font-size: 20px;
+      line-height: 28px;
+      margin-bottom: 16px;
+    }
+
+    .actions {
+      button:not(:last-child) {
+        margin-right: 8px;
+      }
+    }
+  }
+}
+
+@media screen and (max-width: 1200px) {
+  .exception {
+    .img-block {
+      padding-right: 88px;
+    }
+  }
+}
+
+@media screen and (max-width: 576px) {
+  .exception {
+    display: block;
+    text-align: center;
+    .img-block {
+      padding-right: 0;
+      margin: 0 auto 24px;
+    }
+  }
+}
+
+@media screen and (max-width: 480px) {
+  .exception {
+    .img-block {
+      margin-bottom: -24px;
+      overflow: hidden;
+    }
+  }
+}
+</style>
+

+ 19 - 0
src/components/Exception/typeConfig.js

@@ -0,0 +1,19 @@
+const config = {
+  403: {
+    img: 'https://gw.alipayobjects.com/zos/rmsportal/wZcnGqRDyhPOEYFcZDnb.svg',
+    title: '403',
+    desc: '抱歉,你无权访问该页面'
+  },
+  404: {
+    img: 'https://gw.alipayobjects.com/zos/rmsportal/KpnpchXsobRgLElEozzI.svg',
+    title: '404',
+    desc: '抱歉,你访问的页面不存在'
+  },
+  500: {
+    img: 'https://gw.alipayobjects.com/zos/rmsportal/RVRUAYdCGeYNBWoKiIwB.svg',
+    title: '500',
+    desc: '抱歉,服务器出错了'
+  }
+}
+
+export default config

+ 45 - 0
src/components/List/ListItem.vue

@@ -0,0 +1,45 @@
+
+<script>
+export default {
+  data () {
+    return {}
+  },
+  methods: {
+    renderActions (h) {
+      const actions = this.$slots.action
+      if (!actions || !(actions.length > 0)) {
+        return null
+      }
+      const children = actions.map((item, i) => {
+        return h('li', { key: `ep-list-item-action-${i}` }, [
+          item,
+          i !== actions.length - 1 &&
+            h('em', { class: 'ep-list-item-action-split' })
+        ])
+      })
+      return h('div', { class: 'ep-list-item-action' }, children)
+    }
+  },
+  render (h) {
+    const content = this.$slots.default ? h(
+      'div',
+      { class: 'ep-list-item-content' },
+      this.$slots.default
+    ) : null
+    const meta = this.$slots.meta
+    const extra = this.$slots.extra
+    const actions = this.renderActions(h)
+
+    const extraContent = h('div', { class: 'ep-list-item-extra-wrapper' }, [
+      h('div', { class: 'ep-list-item-main' }, [meta, content, actions]),
+      h('div', { class: 'ep-list-item-extra' }, extra)
+    ])
+
+    if (extra) {
+      return h('div', { class: 'ep-list-item' }, [extraContent])
+    }
+    return h('div', { class: 'ep-list-item' }, [meta, content, actions])
+  }
+}
+</script>
+

+ 53 - 0
src/components/List/ListItemMeta.vue

@@ -0,0 +1,53 @@
+<template>
+  <div :class="cls">
+    <template v-if="this.$slots.avatar">
+      <div :class="`${cls}-avatar`">
+        <slot name="avatar"></slot>
+      </div>
+    </template>
+    <template v-if="showContent">
+      <div :class="`${cls}-content`">
+        <h4 v-if="title || this.$slots.title" :class="`${cls}-title`">
+          <template v-if="title">
+            {{title}}
+          </template>
+          <template v-else>
+            <slot name="title"></slot>
+          </template>
+        </h4>
+        <div v-if="description || this.$slots.description" :class="`${cls}-description` ">
+          <template v-if="description">
+            {{description}}
+          </template>
+          <template v-else>
+            <slot name="description"></slot>
+          </template>
+        </div>
+      </div>
+    </template>
+  </div>
+</template>
+
+<script>
+export default {
+  props: {
+    title: String,
+    description: String
+  },
+  data () {
+    return {
+      cls: 'ep-list-item-meta'
+    }
+  },
+  computed: {
+    showContent () {
+      return !!(
+        this.title ||
+        this.$slots.title ||
+        this.description ||
+        this.$slots.description
+      )
+    }
+  }
+}
+</script>

+ 20 - 0
src/components/List/index.vue

@@ -0,0 +1,20 @@
+<template>
+  <div class="ep-list">
+    <template v-if="$slots.default">
+      <slot></slot>
+    </template>
+    <template v-else>
+      <div class="ep-list-empty-text">
+        暂无数据
+      </div>
+    </template>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'EpList'
+}
+</script>
+
+<style lang="scss" src="./style/index.scss"></style>

+ 101 - 0
src/components/List/style/index.scss

@@ -0,0 +1,101 @@
+
+$list-cls: 'ep-list';
+
+$text-color: rgba(0, 0, 0, 0.65);
+$text-color-secondary: rgba(0, 0, 0, 0.45);
+$primary-color: #1890ff;
+$font-size-base: 14px;
+$border-color-split: #e8e8e8;
+
+.#{$list-cls} {
+  position: relative;
+  * {
+    outline: none;
+  }
+
+  &-empty-text {
+    padding: 16px;
+    text-align: center;
+  }
+
+  &-item {
+    align-items: center;
+    display: flex;
+    padding-top: 12px;
+    padding-bottom: 12px;
+    border-bottom: 1px solid $border-color-split;
+
+    &-meta {
+      align-items: flex-start;
+      display: flex;
+      flex: 1;
+      font-size: 0;
+      &-avatar {
+        flex: 0;
+        margin-right: 16px;
+      }
+      &-content {
+        flex: 1 0;
+      }
+      &-title {
+        color: $text-color;
+        margin-bottom: 4px;
+        font-size: $font-size-base;
+        line-height: 22px;
+        > a {
+          color: $text-color;
+          transition: all 0.3s;
+          &:hover {
+            color: $primary-color;
+          }
+        }
+      }
+      &-description {
+        color: $text-color-secondary;
+        font-size: $font-size-base;
+        line-height: 22px;
+      }
+    }
+    &-content {
+      display: flex;
+      flex: 1;
+      justify-content: flex-end;
+    }
+    &-content-single {
+      justify-content: flex-start;
+    }
+    &-action {
+      font-size: 0;
+      flex: 0 0 auto;
+      margin-left: 48px;
+      padding: 0;
+      list-style: none;
+      & > li {
+        display: inline-block;
+        color: $text-color-secondary;
+        cursor: pointer;
+        padding: 0 8px;
+        position: relative;
+        font-size: $font-size-base;
+        line-height: 22px;
+        text-align: center;
+      }
+      & > li:first-child {
+        padding-left: 0;
+      }
+      &-split {
+        background-color: $border-color-split;
+        margin-top: -7px;
+        position: absolute;
+        top: 50%;
+        right: 0;
+        width: 1px;
+        height: 14px;
+      }
+    }
+    &-main {
+      display: flex;
+      flex: 1;
+    }
+  }
+}

+ 118 - 0
src/components/NoticeIcon/NoticeList.vue

@@ -0,0 +1,118 @@
+<template>
+  <div>
+    <ep-list class="list">
+      <template v-for="(item, i) in data">
+        <ep-list-item :key="i" class="item">
+          <ep-list-item-meta slot="meta"  class="meta">
+            <avatar slot="avatar" class="avatar" :src="item.avatar"></avatar>
+            <div slot="title" class="title">
+              {{item.title}}
+            </div>
+            <div slot="description">
+               <el-button type="text" style="white-space: inherit;"  v-if="item.type==='通知'" @click=" $refs.oaNotifyForm.init('read', item.id)">
+                <div class="description" style="height:30px" v-html="item.description"></div>
+              </el-button>
+               <el-button type="text" style="white-space: inherit;" v-if="item.type==='站内信'" @click="$refs.receivedMailDetail.init(item.id)">
+                <div class="description" style="height:30px" v-html="item.description"></div>
+              </el-button>
+              <div class="datetime">{{item.datetime}}</div>
+            </div>
+          </ep-list-item-meta>
+        </ep-list-item>
+      </template>
+      <ep-list-item class="item">
+          <ep-list-item-meta>
+            <div slot="description">
+               <router-link :to="url">
+                <div class="description" style="text-align:center">查看更多</div>
+                </router-link>   
+            </div>
+          </ep-list-item-meta>
+      </ep-list-item>
+    </ep-list>
+     <ReceivedMailDetail ref="receivedMailDetail"></ReceivedMailDetail>
+     <OaNotifyForm  ref="oaNotifyForm"></OaNotifyForm>
+  </div>
+</template>
+
+<script>
+import EpList from '@/components/List/index.vue'
+import EpListItem from '@/components/List/ListItem.vue'
+import EpListItemMeta from '@/components/List/ListItemMeta.vue'
+import Avatar from '@/components/avatar/index.vue'
+import ReceivedMailDetail from '@/views/modules/mailbox/ReceivedMailDetail'
+import OaNotifyForm from '@/views/modules/notify/OaNotifyForm'
+
+export default {
+  props: {
+    data: {
+      type: Array,
+      default () {
+        return []
+      }
+    },
+    url: {
+      type: String,
+      default () {
+        return ''
+      }
+    }
+  },
+  components: {
+    EpList,
+    EpListItem,
+    EpListItemMeta,
+    ReceivedMailDetail,
+    OaNotifyForm,
+    Avatar
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+@import '@/assets/scss/theme.scss';
+.list {
+  max-height: 400px;
+  overflow: auto;
+  .item {
+    transition: all 0.3s;
+    overflow: hidden;
+    cursor: pointer;
+    padding-left: 24px;
+    padding-right: 24px;
+
+    &:last-child {
+      border-bottom: 0;
+    }
+    &:hover {
+      background: $primary-1;
+    }
+
+    .meta {
+      width: 100%;
+    }
+    .avatar {
+      background: #fff;
+      margin-top: 4px;
+    }
+    .title {
+      font-weight: normal;
+      margin-bottom: 8px;
+    }
+    .description {
+      font-size: 12px;
+      line-height: $line-height-base;
+      display: -webkit-box;/*作为弹性伸缩盒子模型显示*/
+      -webkit-line-clamp: 1; /*显示的行数;如果要设置2行加...则设置为2*/
+      overflow: hidden; /*超出的文本隐藏*/
+      text-overflow: ellipsis; /* 溢出用省略号*/
+       -webkit-box-orient: vertical;/*伸缩盒子的子元素排列:从上到下*/
+    }
+    .datetime {
+      font-size: 12px;
+      margin-top: 4px;
+      line-height: $line-height-base;
+    }
+  }
+}
+</style>

+ 130 - 0
src/components/NoticeIcon/index.vue

@@ -0,0 +1,130 @@
+<template>
+  <div class="wrapper" :class="{ 'popover-open': popoverOpen }">
+    <el-popover ref="notice-popover" v-model="popoverOpen" popper-class="notice-popover"
+      placement="bottom-end" :offset="-3">
+      <el-tabs class="tabs" v-model="activeTab" @tab-click="onTabChange">
+        <template v-for="tab in tabOptions">
+          <el-tab-pane class="tab-pane" :key="tab.title" :label="tab.titleShow"
+            :name="tab.title">
+            <!-- {{tab.emptyText}} -->
+            <notice-list :url="tab.url" :data="getNoticeData(tab.list)" title="通知"></notice-list>
+          </el-tab-pane>
+        </template>
+      </el-tabs>
+    </el-popover>
+
+    <div class="noticeButton" v-popover:notice-popover>
+      <el-badge :value="unreadSize" class="badge">
+        <i class="fa fa-bell-o"></i>
+      </el-badge>
+    </div>
+  </div>
+
+</template>
+
+<script>
+import moment from 'moment'
+import NoticeList from './NoticeList.vue'
+
+export default {
+  data () {
+    const activeTab =
+      this.tabs && this.tabs.length > 0 ? this.tabs[0].title : ''
+    return {
+      popoverOpen: false,
+      activeTab
+    }
+  },
+  props: {
+    tabs: {
+      type: Array,
+      default () {
+        return []
+      }
+    }
+  },
+  computed: {
+    tabOptions () {
+      return this.tabs.map(tab => {
+        const titleShow =
+          tab.list && tab.list.length > 0
+            ? `${tab.title} (${tab.count})`
+            : tab.title
+        return {
+          ...tab,
+          titleShow
+        }
+      })
+    },
+    unreadSize () {
+      let total = 0
+      this.tabs.forEach((tab) => {
+        total = total + tab.count
+      })
+      return total
+    }
+  },
+  components: {
+    NoticeList
+  },
+  methods: {
+    onTabChange (tab, event) {
+      // console.log('NoticeIcon/index, onTabChange, tab:', tab, 'event:', event)
+    },
+    getNoticeData (noticeList) {
+      if (!noticeList || noticeList.length < 0) {
+        return []
+      }
+      return noticeList.map(item => {
+        return {
+          ...item,
+          datetime: moment(item.datetime).fromNow()
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+.notice-popover {
+  width: 356px;
+  // padding: 4px 0 0 0;
+
+  &[x-placement^='bottom'] {
+    margin-top: -8px;
+  }
+}
+</style>
+
+<style lang="scss" scoped>
+.noticeButton {
+  width: 100%;
+  height: 100%;
+  padding: 0 12px;
+
+  display: flex;
+  align-items: center;
+
+  transition: all 0.3s;
+}
+
+.badge {
+  .el-badge__content {
+    z-index: 1;
+  }
+}
+
+.tabs {
+  .el-tabs__nav-scroll {
+    display: flex;
+    justify-content: center;
+  }
+
+  .el-tabs__header {
+    margin-bottom: 4px;
+  }
+}
+</style>
+
+

+ 20 - 0
src/components/antIcon/AntIcon.vue

@@ -0,0 +1,20 @@
+
+<script>
+import Vue from 'vue'
+
+export default Vue.component('ant-icon', {
+  props: {
+    type: {
+      type: String,
+      required: true
+    }
+  },
+  render (h) {
+    const { type } = this
+    return h('i', {
+      class: ['anticon', `anticon-${type}`],
+      on: this.$listeners
+    })
+  }
+})
+</script>

+ 105 - 0
src/components/avatar/index.vue

@@ -0,0 +1,105 @@
+<template>
+  <span :class="className" v-bind="$attrs">
+    <template v-if="src && isImgExist">
+      <img :src="src" @error="handleImgLoadError" />
+    </template>
+    <template v-else>
+      <span class="avatar-string">
+        <slot></slot>
+      </span>
+    </template>
+  </span>
+</template>
+
+<script>
+export default {
+  data () {
+    const sizeCls = {
+      'avatar-lg': this.size === 'large',
+      'avatar-sm': this.size === 'small'
+    }
+    return {
+      // TODO: set scale auto if image too large
+      // scale: 1,
+      isImgExist: true,
+      className: {
+        'avatar': true,
+        ...sizeCls,
+        'avatar-image': this.src,
+        [`avatar-${this.shape}`]: this.shape
+      }
+    }
+  },
+  props: {
+    shape: {
+      type: String,
+      default: 'circle'
+    },
+    size: {
+      type: String,
+      default: 'default'
+    },
+    src: String
+  },
+  methods: {
+    handleImgLoadError () {
+      this.isImgExist = false
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+@import '@/assets/scss/theme.scss';
+
+@mixin avatar-size($size, $font-size) {
+  width: $size;
+  height: $size;
+  line-height: $size;
+  border-radius: $size / 2;
+
+  & > * {
+    line-height: $size;
+  }
+
+  &.avatar-icon {
+    font-size: $font-size;
+  }
+}
+
+.avatar {
+  display: inline-block;
+  text-align: center;
+  background: $avatar-bg;
+  color: $avatar-color;
+  white-space: nowrap;
+  position: relative;
+  overflow: hidden;
+
+  @include avatar-size($avatar-size-base, $avatar-font-size-base);
+
+  &-lg {
+    @include avatar-size($avatar-size-lg, $avatar-font-size-lg);
+  }
+  &-sm {
+    @include avatar-size($avatar-size-sm, $avatar-font-size-sm);
+  }
+
+  &-square {
+    border-radius: $avatar-border-radius;
+  }
+
+  & > img {
+    width: 100%;
+    height: 100%;
+    display: block;
+  }
+
+  .avatar-string {
+    position: absolute;
+    display: inline-block;
+    left: calc(50% - 4px);
+  }
+}
+</style>
+

+ 114 - 0
src/components/cityPicker/index.vue

@@ -0,0 +1,114 @@
+<template>
+
+   <el-cascader
+   v-model="valueTitle" :disabled="disabled" :clearable="clearable" @clear="clearHandle"
+        :props="props"
+        :size="size"
+         @change="handleChange"
+        :options="valueData">
+  </el-cascader>
+</template>
+
+<script>
+export default {
+  name: 'el-tree-select',
+  props: {
+    /* 配置项 */
+    props: {
+      type: Object,
+      default: () => {
+        return {
+          expandTrigger: 'hover',
+          value: 'name',             // ID字段名
+          label: 'name',         // 显示名称
+          children: 'childNodes'    // 子级字段名
+        }
+      }
+    },
+     /* 选项列表数据(树形结构的对象数组) */
+    list: {
+      type: Array,
+      default: () => { return null }
+    },
+    /* 初始值 */
+    value: {
+      type: String,
+      default: () => { return null }
+    },
+        /* 初始值 */
+    url: {
+      type: String,
+      default: () => { return '/sys/area/treeData' }
+    },
+    disabled: {
+      type: Boolean,
+      dafault: () => { return false }
+    },
+    showCheckbox: {
+      type: Boolean,
+      dafault: () => { return false }
+    },
+    /* 初始值 */
+    label: {
+      type: String,
+      default: () => { return null }
+    },
+    /* 可清空选项 */
+    clearable: {
+      type: Boolean,
+      default: () => { return true }
+    },
+    size: {
+      type: String,
+      default: () => { return 'default' }
+    },
+    /* 自动收起 */
+    accordion: {
+      type: Boolean,
+      default: () => { return false }
+    }
+  },
+  data () {
+    return {
+      valueTitle: [],
+      valueData: []
+    }
+  },
+  created () {
+    this.$http({
+      url: this.url,
+      method: 'get'
+    }).then(({data}) => {
+      this.valueData = data.treeData
+      if (this.value) {
+        this.valueTitle = this.value.split('/')
+      }
+    })
+  },
+  methods: {
+    // 切换选项
+    handleChange (node) {
+      this.$emit('getValue', this.valueTitle.join('/'))
+    },
+    // 清除选中
+    clearHandle () {
+      this.valueTitle = ''
+      this.$emit('getValue', null)
+    },
+    /* 清空选中样式 */
+    clearSelected () {
+      let allNode = document.querySelectorAll('#tree-option .el-tree-node')
+      allNode.forEach((element) => element.classList.remove('is-current'))
+    }
+  },
+  watch: {
+    value () {
+      if (this.value === '') {
+        this.clearHandle()
+      } else if (this.value) {
+        this.valueTitle = this.value.split('/')
+      }
+    }
+  }
+}
+</script>

+ 30 - 0
src/components/colors/ColorPicker.vue

@@ -0,0 +1,30 @@
+<template>
+    <el-color-picker size="small"
+                     class="theme-picker"
+                     popper-class="theme-picker-dropdown"
+                     v-model="theme"></el-color-picker>
+</template>
+
+<script>
+    import theme from "./mixins/theme";
+
+    export default {
+        name: "ColorPicker",
+        mixins: [theme],
+        data () {
+            return {
+                chalk: ""
+            };
+        }
+    };
+</script>
+
+<style>
+    .theme-picker .el-color-picker__trigger {
+        vertical-align: middle;
+    }
+
+    .theme-picker-dropdown .el-color-dropdown__link-btn {
+        display: none;
+    }
+</style>

+ 26 - 0
src/components/colors/mixins/mainPageModel.js

@@ -0,0 +1,26 @@
+export default {
+    data() {
+        return {
+
+        }
+    },
+    methods: {
+        /*
+        * 关闭抽屉
+        * */
+        popoverConfirm() {
+            this.drawer = {
+                ...this.drawer,
+                show: false
+            }
+            this.popover = {
+                ...this.popover,
+                drawerCancel: false
+            }
+        },
+    },
+    created() {
+
+    },
+    mounted() {},
+}

+ 188 - 0
src/components/colors/mixins/theme.js

@@ -0,0 +1,188 @@
+const version = require("element-ui/package.json").version; // element-ui version from node_modules
+const ORIGINAL_THEME = "#409EFF"; // default color
+
+export default {
+    data() {
+        return {
+            theme: ORIGINAL_THEME
+        }
+    },
+    created() {
+        this.theme = this.defaultTheme || ORIGINAL_THEME;
+        this.$events.$on('updateTheme', (val) => {
+            this.theme = val
+            this.updateTheme(val, this.defaultTheme)
+          })
+    },
+    beforeDestroy () {
+        this.$events.$off('updateTheme', {})
+    },
+    watch: {
+        theme(val, oldVal) {
+            this.defaultTheme = val
+            this.updateTheme(val, oldVal);
+        }
+    },
+    computed: {
+        defaultTheme: {
+            get () {
+              return this.$store.state.config.defaultTheme
+            },
+            set (val) {
+              localStorage.setItem('defaultTheme', val)
+              return this.$store.commit('config/updateDefaultTheme', val)
+            }
+        }
+    },
+    methods: {
+        updateTheme(val, oldVal) {
+            if (typeof val !== "string") return;
+            const head = document.getElementsByTagName("head")[0];
+            const themeCluster = this.getThemeCluster(val.replace("#", ""));
+            const originalCluster = this.getThemeCluster(oldVal.replace("#", ""));
+            const getHandler = (variable, id) => {
+                return () => {
+                    const originalCluster = this.getThemeCluster(
+                        ORIGINAL_THEME.replace("#", "")
+                    );
+                    const newStyle = this.updateStyle(
+                        this[variable],
+                        originalCluster,
+                        themeCluster
+                    );
+
+                    let styleTag = document.getElementById(id);
+                    if (!styleTag) {
+                        styleTag = document.createElement("style");
+                        styleTag.setAttribute("id", id);
+                        head.appendChild(styleTag);
+                    }
+                    styleTag.innerText = newStyle;
+                };
+            };
+
+            const chalkHandler = getHandler("chalk", "chalk-style");
+
+            if (!this.chalk) {
+                const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`;
+                this.getCSSString(url, chalkHandler, "chalk");
+            } else {
+                chalkHandler();
+            }
+
+            const link = [].slice.call(
+                document.getElementsByTagName("head")[0].getElementsByTagName("link")
+            );
+            for (let i = link.length - 3; i < link.length; i++) {
+                const style = link[i];
+                if (style.href.indexOf('app') != -1) {
+                    this.getCSSString(style.href, innerText => {
+                        const originalCluster = this.getThemeCluster(
+                            ORIGINAL_THEME.replace("#", "")
+                        );
+                        const newStyle = this.updateStyle(
+                            innerText,
+                            originalCluster,
+                            themeCluster
+                        );
+                        let styleTag = document.getElementById(i);
+                        if (!styleTag) {
+                            styleTag = document.createElement("style");
+                            styleTag.id = i;
+                            styleTag.innerText = newStyle;
+                            head.appendChild(styleTag);
+                        }
+                    });
+                }
+
+            }
+
+            const styles = [].slice
+                .call(document.querySelectorAll("style"))
+                .filter(style => {
+                    const text = style.innerText;
+                    return (
+                        new RegExp(oldVal, "i").test(text) && !/Chalk Variables/.test(text)
+                    );
+                });
+            styles.forEach(style => {
+                const {
+                    innerText
+                } = style;
+                if (typeof innerText !== "string") return;
+                style.innerText = this.updateStyle(
+                    innerText,
+                    originalCluster,
+                    themeCluster
+                );
+            });
+        },
+        updateStyle(style, oldCluster, newCluster) {
+            let newStyle = style;
+            oldCluster.forEach((color, index) => {
+                newStyle = newStyle.replace(new RegExp(color, "ig"), newCluster[index]);
+            });
+            return newStyle;
+        },
+
+        getCSSString(url, callback, variable) {
+            const xhr = new XMLHttpRequest();
+            xhr.onreadystatechange = () => {
+                if (xhr.readyState === 4 && xhr.status === 200) {
+                    if (variable) {
+                        this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, "");
+                    }
+                    callback(xhr.responseText);
+                }
+            };
+            xhr.open("GET", url);
+            xhr.send();
+        },
+
+        getThemeCluster(theme) {
+            const tintColor = (color, tint) => {
+                let red = parseInt(color.slice(0, 2), 16);
+                let green = parseInt(color.slice(2, 4), 16);
+                let blue = parseInt(color.slice(4, 6), 16);
+
+                if (tint === 0) {
+                    // when primary color is in its rgb space
+                    return [red, green, blue].join(",");
+                } else {
+                    red += Math.round(tint * (255 - red));
+                    green += Math.round(tint * (255 - green));
+                    blue += Math.round(tint * (255 - blue));
+
+                    red = red.toString(16);
+                    green = green.toString(16);
+                    blue = blue.toString(16);
+
+                    return `#${red}${green}${blue}`;
+                }
+            };
+
+            const shadeColor = (color, shade) => {
+                let red = parseInt(color.slice(0, 2), 16);
+                let green = parseInt(color.slice(2, 4), 16);
+                let blue = parseInt(color.slice(4, 6), 16);
+
+                red = Math.round((1 - shade) * red);
+                green = Math.round((1 - shade) * green);
+                blue = Math.round((1 - shade) * blue);
+
+                red = red.toString(16);
+                green = green.toString(16);
+                blue = blue.toString(16);
+
+                return `#${red}${green}${blue}`;
+            };
+
+            const clusters = [theme];
+            for (let i = 0; i <= 9; i++) {
+                clusters.push(tintColor(theme, Number((i / 10).toFixed(2))));
+            }
+            clusters.push(shadeColor(theme, 0.1));
+            return clusters;
+        }
+    }
+}

+ 101 - 0
src/components/countDown/index.vue

@@ -0,0 +1,101 @@
+
+<script>
+
+function fixedZero (val) {
+  return val * 1 < 10 ? `0${val}` : val
+}
+
+export default {
+  props: {
+    target: [Date, Number]
+  },
+  data () {
+    return {
+      lastTime: 0,
+      timer: null,
+      interval: 1000
+    }
+  },
+  watch: {
+    target: function (val, oldVal) {
+      clearTimeout(this.timer)
+      const { lastTime } = this.initTime(val)
+      this.lastTime = lastTime
+      this.$nextTick(() => {
+        this.tick()
+      })
+    }
+  },
+  created () {
+    const { lastTime } = this.initTime()
+    this.lastTime = lastTime
+  },
+  mounted () {
+    this.tick()
+  },
+  beforeDestroy () {
+    clearTimeout(this.timer)
+  },
+  methods: {
+    initTime (newTarget) {
+      const target = newTarget || this.target
+      let lastTime = 0
+      let targetTime = 0
+      try {
+        if (Object.prototype.toString.call(target) === '[object Date]') {
+          targetTime = target.getTime()
+        } else {
+          targetTime = new Date(target).getTime()
+        }
+      } catch (e) {
+        throw new Error('invalid target prop')
+      }
+
+      lastTime = targetTime - new Date().getTime()
+      return {
+        lastTime: lastTime < 0 ? 0 : lastTime
+      }
+    },
+    tick () {
+      const { lastTime } = this
+
+      this.timer = setTimeout(() => {
+        if (lastTime < this.interval) {
+          clearTimeout(this.timer)
+          this.lastTime = 0
+          this.$emit('end')
+        } else {
+          this.lastTime = lastTime - this.interval
+          // this.$nextTick(() => {
+          //   this.tick()
+          // })
+          this.tick()
+        }
+      }, this.interval)
+    },
+    renderDefaultFormat (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)
+
+      const content = `${fixedZero(h)}:${fixedZero(m)}:${fixedZero(s)}`
+
+      return this.$createElement('span', content)
+    }
+  },
+  render (h) {
+    const { lastTime } = this
+    return h(
+      'span',
+      {
+        props: this.$attrs,
+        on: this.$listeners
+      },
+      [this.renderDefaultFormat(lastTime)]
+    )
+  }
+}
+</script>

+ 119 - 0
src/components/editor/WangEditor.vue

@@ -0,0 +1,119 @@
+<template lang="html">
+  <div class="editor" style="min-width:700px; padding-right:10px">
+    <div ref="toolbar" class="toolbar">
+    </div>
+    <div ref="editor" class="text">
+    </div>
+  </div>
+</template>
+
+<script>
+import E from 'wangeditor'
+export default {
+  name: 'WangEditor',
+  data () {
+    return {
+      editor: null,
+      info_: null
+    }
+  },
+  model: {
+    prop: 'value',
+    event: 'change'
+  },
+  props: {
+    isClear: {
+      type: Boolean,
+      default: false
+    }
+  },
+  watch: {
+    isClear (val) {
+      // 触发清除文本域内容
+      if (val) {
+        this.editor.txt.clear()
+        this.info_ = null
+      }
+    }
+  },
+  mounted () {
+    this.seteditor()
+  },
+  methods: {
+    init (val) {
+      // 使用 v-model 时,设置初始值
+      this.editor.txt.html(val)
+    },
+    seteditor () {
+      this.editor = new E(this.$refs.toolbar, this.$refs.editor)
+      this.editor.customConfig.uploadImgShowBase64 = true // base 64 存储图片
+      this.editor.customConfig.uploadImgServer = ''// 配置服务器端地址
+      this.editor.customConfig.uploadImgHeaders = { }// 自定义 header
+      this.editor.customConfig.uploadFileName = '' // 后端接受上传文件的参数名
+      this.editor.customConfig.uploadImgMaxSize = 2 * 1024 * 1024 // 将图片大小限制为 2M
+      this.editor.customConfig.uploadImgMaxLength = 6 // 限制一次最多上传 3 张图片
+      this.editor.customConfig.uploadImgTimeout = 3 * 60 * 1000 // 设置超时时间
+      // 配置菜单
+      this.editor.customConfig.menus = [
+        'head', // 标题
+        'bold', // 粗体
+        'fontSize', // 字号
+        'fontName', // 字体
+        'italic', // 斜体
+        'underline', // 下划线
+        'strikeThrough', // 删除线
+        'foreColor', // 文字颜色
+        'backColor', // 背景颜色
+        'link', // 插入链接
+        'list', // 列表
+        'justify', // 对齐方式
+        'quote', // 引用
+        'emoticon', // 表情
+        'image', // 插入图片
+        'table', // 表格
+        'video', // 插入视频
+        'code', // 插入代码
+        'undo', // 撤销
+        'redo' // 重复
+      ]
+      this.editor.customConfig.uploadImgHooks = {
+        fail: (xhr, editor, result) => {
+          // 插入图片失败回调
+        },
+        success: (xhr, editor, result) => {
+          // 图片上传成功回调
+        },
+        timeout: (xhr, editor) => {
+          // 网络超时的回调
+        },
+        error: (xhr, editor) => {
+          // 图片上传错误的回调
+        },
+        customInsert: (insertImg, result, editor) => {
+          // 图片上传成功,插入图片的回调
+        }
+      }
+      this.editor.customConfig.onchange = (html) => {
+        this.info_ = html // 绑定当前逐渐地值
+        this.$emit('change', this.info_) // 将内容同步到父组件中
+      }
+      // 创建富文本编辑器
+      this.editor.create()
+    }
+  }
+}
+</script>
+
+<style lang="css">
+.editor {
+  width: 100%;
+  margin: 0 auto;
+}
+.toolbar {
+  border: 1px solid #ccc;
+}
+.text {
+  border: 1px solid #ccc;
+  height: 500px;
+}
+</style>

+ 347 - 0
src/components/gridSelect/index.vue

@@ -0,0 +1,347 @@
+<template>
+  <div>
+    <el-input placeholder="请选择" :size="size" :readonly="true" style="line-hight:40px" v-model="name" class="input-with-select" >
+      <el-button slot="append" @click="showSelectDialog" icon="el-icon-search"></el-button>
+    </el-input>
+    <el-dialog
+    :title="title"
+    :close-on-click-modal="false"
+    :append-to-body="true"
+    :visible.sync="visible">
+    <el-row :gutter="15">
+    <el-col :span="19">
+    <el-form :inline="true" ref="searchForm" class="query-form"  @keyup.enter.native="refreshList()" @submit.native.prevent>
+         <el-form-item v-for="(search,index) in searchs" :key="index" :prop="search.prop">
+          <el-input size="small" v-model="searchForms[index]" :placeholder="search.label" clearable></el-input>
+         </el-form-item>
+        <el-form-item>
+          <el-button  type="primary" @click="refreshList()" size="small">查询</el-button>
+          <el-button @click="resetSearch()" size="small">重置</el-button>
+        </el-form-item>
+      </el-form>
+    <el-table
+      :data="dataList"
+      v-loading="loading"
+      border
+      size="medium"
+      ref="userTable"
+      @selection-change="selectionChangeHandle"
+      @sort-change="sortChangeHandle"
+      style="width: 100%;">
+      <el-table-column
+        type="selection"
+        header-align="center"
+        align="center"
+        width="50">
+      </el-table-column>
+      <template   v-for="(column, index) in columns">
+      <el-table-column
+        :key="index"
+        :prop="column.prop"
+        header-align="center"
+        align="center"
+        min-width="100px"
+        :label="column.label">
+      </el-table-column>
+      </template>
+    </el-table>
+    <el-pagination
+      @size-change="sizeChangeHandle"
+      @current-change="currentChangeHandle"
+      :current-page="pageNo"
+      :page-sizes="[5, 10, 50, 100]"
+      :page-size="pageSize"
+      :total="total"
+      layout="total, sizes, prev, pager, next, jumper">
+    </el-pagination>
+    </el-col>
+    <el-col :span="5">
+      <el-tag
+        :key="tag.id"
+        v-for="tag in dataListAllSelections"
+        closable
+        :disable-transitions="false"
+        @close="del(tag)">
+        {{tag.name}}
+      </el-tag>
+    </el-col>
+    </el-row>
+     <span slot="footer" class="dialog-footer">
+      <el-button @click="visible = false">关闭</el-button>
+      <el-button type="primary" @click="doSubmit()">确定</el-button>
+    </span>
+    </el-dialog>
+  
+  </div>
+</template>
+
+<script>
+  export default {
+    data () {
+      return {
+        searchForms: [],
+        filterText: '',
+        dataListAllSelections: [],   // 所有选中的数据包含跨页数据
+        dataListSelections: [],
+        idKey: 'id', // 标识列表数据中每一行的唯一键的名称(需要按自己的数据改一下)
+        dataList: [],
+        dynamicTags: [],
+        selectData: [],
+        pageNo: 1,
+        pageSize: 5,
+        total: 0,
+        orderBy: '',
+        loading: false,
+        visible: false,
+        name: ''
+      }
+    },
+    props: {
+      limit: {
+        type: Number,
+        default: 999999
+      },
+      columns: {
+        type: Array,
+        default: () => { return [] }
+      },
+      searchs: {
+        type: Array,
+        default: () => { return [] }
+      },
+      dataListUrl: {
+        type: String,
+        default: () => { return null }
+      },
+      queryEntityUrl: {
+        type: String,
+        default: () => { return null }
+      },
+      entityBeanName: {
+        type: String,
+        default: () => { return null }
+      },
+      value: {
+        type: String,
+        default: () => { return null }
+      },
+      title: {
+        type: String,
+        default: () => { return '' }
+      },
+      labelName: {
+        type: String,
+        default: () => { return '' }
+      },
+      labelValue: {
+        type: String,
+        default: () => { return '' }
+      },
+      size: {
+        type: String,
+        default: () => { return 'default' }
+      }
+    },
+    watch: {
+      value: {
+        handler (newVal) {
+          this.selectData = []
+          if (newVal) {
+            newVal.split(',').forEach((value) => {
+              if (value) {
+                this.$http.get(`${this.queryEntityUrl}?${this.labelValue}=${value}`).then(({data}) => {
+                  if (data[this.entityBeanName][this.labelValue] !== '') {
+                    this.selectData.push(data[this.entityBeanName])
+                  }
+                })
+              }
+            })
+          }
+        },
+        immediate: true,
+        deep: false
+      },
+      selectData: {
+        handler (newVal) {
+          this.dataListAllSelections = JSON.parse(JSON.stringify(this.selectData))
+          this.name = this.dataListAllSelections.map(item => { return item[this.labelName] }).join(',')
+        },
+        immediate: false,
+        deep: false
+      }
+    },
+    methods: {
+      init () {
+        this.visible = true
+        this.$nextTick(() => {
+          this.resetSearch()
+        })
+      },
+           // 设置选中的方法
+      setSelectRow () {
+        if (!this.dataListAllSelections || this.dataListAllSelections.length <= 0) {
+          this.$refs.userTable.clearSelection()
+          return
+        }
+                // 标识当前行的唯一键的名称
+        let idKey = this.idKey
+        let selectAllIds = []
+        this.dataListAllSelections.forEach(row => {
+          selectAllIds.push(row[idKey])
+        })
+        this.$refs.userTable.clearSelection()
+        for (var i = 0; i < this.dataList.length; i++) {
+          if (selectAllIds.indexOf(this.dataList[i][idKey]) >= 0) {
+                        // 设置选中,记住table组件需要使用ref="table"
+            this.$refs.userTable.toggleRowSelection(this.dataList[i], true)
+          }
+        }
+      },
+            // 记忆选择核心方法
+      changePageCoreRecordData () {
+                // 标识当前行的唯一键的名称
+        let idKey = this.idKey
+        let that = this
+              // 如果总记忆中还没有选择的数据,那么就直接取当前页选中的数据,不需要后面一系列计算
+        if (this.dataListAllSelections.length <= 0) {
+          this.dataListSelections.forEach(row => {
+            that.dataListAllSelections.push(row)
+          })
+          return
+        }
+                // 总选择里面的key集合
+        let selectAllIds = []
+        this.dataListAllSelections.forEach(row => {
+          selectAllIds.push(row[idKey])
+        })
+        let selectIds = []
+                // 获取当前页选中的id
+        this.dataListSelections.forEach(row => {
+          selectIds.push(row[idKey])
+                  // 如果总选择里面不包含当前页选中的数据,那么就加入到总选择集合里
+          if (selectAllIds.indexOf(row[idKey]) < 0) {
+            that.dataListAllSelections.push(row)
+          }
+        })
+        let noSelectIds = []
+              // 得到当前页没有选中的id
+        this.dataList.forEach(row => {
+          if (selectIds.indexOf(row[idKey]) < 0) {
+            noSelectIds.push(row[idKey])
+          }
+        })
+        noSelectIds.forEach(id => {
+          if (selectAllIds.indexOf(id) >= 0) {
+            for (let i = 0; i < that.dataListAllSelections.length; i++) {
+              if (that.dataListAllSelections[i][idKey] === id) {
+                                // 如果总选择中有未被选中的,那么就删除这条
+                that.dataListAllSelections.splice(i, 1)
+                break
+              }
+            }
+          }
+        })
+      },
+     // 得到选中的所有数据
+      getAllSelectionData () {
+         // 再执行一次记忆勾选数据匹配,目的是为了在当前页操作勾选后直接获取选中数据
+        this.changePageCoreRecordData()
+      },
+      filterNode (value, data) {
+        if (!value) return true
+        return data.name.indexOf(value) !== -1
+      },
+      del (tag) {
+        this.dataListAllSelections.splice(this.dataListAllSelections.indexOf(tag), 1)
+        this.$nextTick(() => {
+          this.setSelectRow()
+        })
+      },
+      // 获取数据列表
+      refreshList () {
+        this.loading = true
+        let searchForm = {}
+        this.searchs.forEach((search, index) => {
+          searchForm[search.prop] = this.searchForms[index]
+        })
+        this.$http({
+          url: this.dataListUrl,
+          method: 'get',
+          params: {
+            'pageNo': this.pageNo,
+            'pageSize': this.pageSize,
+            'orderBy': this.orderBy,
+            ...searchForm
+          }
+        }).then(({data}) => {
+          if (data && data.success) {
+            this.dataList = data.page.list
+            this.total = data.page.count
+            this.loading = false
+          }
+          this.$nextTick(() => {
+            this.setSelectRow()
+          })
+        })
+      },
+      // 每页数
+      sizeChangeHandle (val) {
+        this.pageSize = val
+        this.pageNo = 1
+        this.refreshList()
+        this.$nextTick(() => {
+          this.changePageCoreRecordData()
+        })
+      },
+      // 当前页
+      currentChangeHandle (val) {
+        this.pageNo = val
+        this.refreshList()
+        this.$nextTick(() => {
+          this.changePageCoreRecordData()
+        })
+      },
+      // 多选
+      selectionChangeHandle (val) {
+        this.dataListSelections = val
+        this.$nextTick(() => {
+          this.changePageCoreRecordData()
+        })
+      },
+       // 排序
+      sortChangeHandle (obj) {
+        if (obj.order === 'ascending') {
+          this.orderBy = obj.prop + ' asc'
+        } else if (obj.order === 'descending') {
+          this.orderBy = obj.prop + ' desc'
+        } else {
+          this.orderBy = ''
+        }
+        this.refreshList()
+      },
+      resetSearch () {
+        this.$refs.searchForm.resetFields()
+        this.searchForms = []
+        this.refreshList()
+      },
+      doSubmit () {
+        if (this.limit < this.dataListAllSelections.length) {
+          this.$message.error(`你最多只能选择${this.limit}条数据`)
+          return
+        }
+        this.visible = false
+        this.name = this.dataListSelections.map((item) => {
+          return item[this.labelName]
+        }).join(',')
+        let value = this.dataListSelections.map((item) => {
+          return item[this.labelValue]
+        }).join(',')
+        this.$emit('getValue', value)
+      },
+      showSelectDialog () {
+        this.visible = true
+        this.init()
+      }
+    }
+  }
+</script>

+ 96 - 0
src/components/icon/index.vue

@@ -0,0 +1,96 @@
+<template>
+  <el-dialog
+    title="图标选择"
+    :close-on-click-modal="false"
+    :visible.sync="visible">
+      <el-tabs value="first" type="card">
+        <el-tab-pane label="Element图标" name="first">
+              <ul class="icon-list">
+                <li v-for="(item, index) in iconList"
+                  :key="index">
+                <el-button
+                  @click="iconActiveHandle(item)"
+                  :class="{ 'is-active': item === icon }">
+                  <i :class="item"></i>
+                </el-button>
+                </li>
+              </ul>
+        </el-tab-pane>
+        <el-tab-pane label="Font Awesome图标" name="second">
+              <ul class="icon-list">
+                <li v-for="(item, index) in iconList2"
+                  :key="index">
+                <el-button
+                  @click="iconActiveHandle(item)"
+                  :class="{ 'is-active': item === icon }">
+                  <i :class="item"></i>
+                </el-button>
+                </li>
+              </ul>
+        </el-tab-pane>
+      </el-tabs>
+    <span slot="footer" class="dialog-footer">
+      <!-- <el-button @click="visible = false">关闭</el-button> -->
+      <!-- <el-button type="primary" @click="doSubmit()">确定</el-button> -->
+      <el-button @click="visible = false">关闭</el-button>
+      <el-button  type="primary" @click="doSubmit()">确定</el-button>
+    </span>
+  </el-dialog>
+</template>
+
+<script>
+  import icons from '@/utils/icons'
+  import icons2 from '@/utils/icons2'
+  export default {
+    name: 'Icon',
+    data () {
+      return {
+        visible: false,
+        iconList: icons,
+        iconList2: icons2,
+        icon: ''
+      }
+    },
+    methods: {
+          // 图标选中
+      iconActiveHandle (iconName) {
+        this.icon = iconName
+      },
+      doSubmit () {
+        this.$emit('getValue', this.icon)
+        this.visible = false
+      }
+    }
+  }
+</script>
+
+<style scoped>
+ul{
+    margin: 10px 0;
+    padding: 0 0 0 20px;
+    font-size: 14px;
+    color: #5e6d82;
+    line-height: 2em;
+}
+
+ul.icon-list {
+    overflow: hidden;
+    list-style: none;
+    padding: 0!important;
+    border: 1px solid #eaeefb;
+    border-radius: 4px;
+}
+.icon-list li {
+    float: left;
+    width: 10%;
+    text-align: center;
+    height:50px;
+    line-height: 50px;
+    color: #666;
+    font-size: 13px;
+    border-right: 1px solid #eee;
+    border-bottom: 1px solid #eee;
+    margin-right: -1px;
+    margin-bottom: -1px;
+}
+</style>

+ 131 - 0
src/components/numberInfo/NumberInfo.vue

@@ -0,0 +1,131 @@
+<template>
+  <div class="number-info" :class="{[`number-info-${theme}`]: theme}">
+    <div v-if="title || this.$slots.title" class="number-info-title">
+      <template v-if="title">
+        {{title}}
+      </template>
+      <template v-else>
+        <slot name="title"></slot>
+      </template>
+    </div>
+    <div v-if="subTitle || this.$slots.subTitle" class="number-info-sub-title">
+      <template v-if="subTitle">
+        {{subTitle}}
+      </template>
+      <template v-else>
+        <slot name="subTitle"></slot>
+      </template>
+    </div>
+    <div class="number-info-value" :style="gapStyle">
+      <span>
+        {{total && total}}
+        <slot v-if="$slots.total" name="total"></slot>
+        <em v-if="suffix || this.$slots.suffix" class="suffix">
+          {{suffix && suffix}}
+          <slot v-if="$slots.suffix" name="suffix"></slot>
+        </em>
+      </span>
+      <span v-if="status || subTotal" class="sub-total">
+        {{subTotal}}
+        <ant-icon v-if="status" :type="`caret-${status}`"></ant-icon>
+      </span>
+    </div>
+  </div>
+</template>
+
+<script>
+import AntIcon from '../antIcon/AntIcon'
+
+export default {
+  props: {
+    title: String,
+    subTitle: String,
+    theme: String,
+    total: [Number, String],
+    status: String,
+    subTotal: Number,
+    gap: Number,
+    suffix: String
+  },
+  components: {
+    AntIcon
+  },
+  data () {
+    const gapStyle = this.gap ? { marginTop: `${this.gap}px` } : null
+    return {
+      gapStyle
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+@import '@/assets/scss/theme.scss';
+
+.number-info {
+  .suffix {
+    color: $text-color;
+    font-size: 16px;
+    font-style: normal;
+    margin-left: 4px;
+  }
+  .number-info-title {
+    color: $text-color;
+    font-size: $font-size-lg;
+    margin-bottom: 16px;
+    transition: all .3s;
+  }
+  .number-info-sub-title {
+    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;
+      }
+      
+        .anticon.icon-caretup {
+          color: $red-6;
+        }
+        .anticon.icon-caretdown {
+          color: $green-6;
+        }
+      
+    }
+  }
+}
+.number-infolight {
+  .number-info-value {
+    & > span {
+      color: $text-color;
+    }
+  }
+}
+</style>

+ 90 - 0
src/components/table-tree-column/index.vue

@@ -0,0 +1,90 @@
+<template>
+  <el-table-column :prop="prop" v-bind="$attrs">
+    <template slot-scope="scope">
+          <span @click.prevent="toggleHandle(scope.$index, scope.row)" :style="childStyles(scope.row)">
+                <i :class="iconClasses(scope.row)" :style="iconStyles(scope.row)"></i> 
+          </span>
+          <span style="padding-left: 10px;">
+            <slot :data="scope.row"  v-bind="scope">
+                      {{ scope.row[prop] }}
+            </slot>
+          </span>
+    </template>
+    
+  </el-table-column>
+</template>
+
+<script>
+  import isArray from 'lodash/isArray'
+
+  export default {
+    name: 'table-tree-column',
+    props: {
+      prop: {
+        type: String
+      },
+      treeKey: {
+        type: String,
+        default: 'id'
+      },
+      parentKey: {
+        type: String,
+        default: 'parentId'
+      },
+      levelKey: {
+        type: String,
+        default: '_level'
+      },
+      childKey: {
+        type: String,
+        default: 'childrens'
+      }
+    },
+    methods: {
+      childStyles (row) {
+        let size = row.parentIds.split(',').length - 1
+        return {'padding-left': (size > 0 ? size * 10 : 0) + 'px'}
+      },
+      iconClasses (row) {
+        return [!row._expanded ? 'el-icon-caret-right' : 'el-icon-caret-bottom']
+      },
+      iconStyles (row) {
+        return {'visibility': this.hasChild(row) ? 'visible' : 'hidden'}
+      },
+      hasChild (row) {
+        return (isArray(row[this.childKey]) && row[this.childKey].length >= 1) || false
+      },
+      // 切换处理
+      toggleHandle (index, row) {
+        if (this.hasChild(row)) {
+          let data = this.$parent.store.states.data.slice(0)
+          data[index]._expanded = !data[index]._expanded
+          if (data[index]._expanded) {
+            data = data.splice(0, index + 1).concat(row[this.childKey]).concat(data)
+          } else {
+            data = this.removeChildNode(data, row[this.treeKey])
+          }
+          this.$parent.store.commit('setData', data)
+          this.$nextTick(() => {
+            this.$parent.doLayout()
+          })
+        }
+      },
+      // 移除子节点
+      removeChildNode (data, parentId) {
+        let parentIds = isArray(parentId) ? parentId : [parentId]
+        if (parentId.length <= 0) {
+          return data
+        }
+        let ids = []
+        for (let i = 0; i < data.length; i++) {
+          if (parentIds.indexOf(data[i][this.parentKey]) !== -1 && parentIds.indexOf(data[i][this.treeKey]) === -1) {
+            ids.push(data.splice(i, 1)[0][this.treeKey])
+            i--
+          }
+        }
+        return this.removeChildNode(data, ids)
+      }
+    }
+  }
+</script>

+ 232 - 0
src/components/treeSelect/treeSelect.vue

@@ -0,0 +1,232 @@
+<template>
+  <el-select :value="valueTitle" :size="size"  :disabled="disabled" :clearable="clearable" :placeholder="placeholder" @clear="clearHandle">
+    <el-option :value="valueTitle"  :label="valueTitle" class="options">
+      <el-tree  id="tree-option"
+        ref="selectTree"
+        :accordion="accordion"
+        :data="optionData"
+        :show-checkbox="showCheckbox"
+        :props="props"
+        highlight-current
+        :node-key="props.value"    
+        :default-expanded-keys="defaultExpandedKey"
+        @node-click="handleNodeClick">
+      </el-tree>
+    </el-option>
+  </el-select>
+</template>
+
+<script>
+export default {
+  name: 'el-tree-select',
+  props: {
+    /* 配置项 */
+    props: {
+      type: Object,
+      default: () => {
+        return {
+          value: 'id',             // ID字段名
+          label: 'label',         // 显示名称
+          children: 'children'    // 子级字段名
+        }
+      }
+    },
+    /* 选项列表数据(树形结构的对象数组) */
+    data: {
+      type: Array,
+      default: () => { return [] }
+    },
+     /* 选项列表数据(树形结构的对象数组) */
+    list: {
+      type: Array,
+      default: () => { return null }
+    },
+    /* 初始值 */
+    value: {
+      type: String,
+      default: () => { return null }
+    },
+        /* 初始值 */
+    url: {
+      type: String,
+      default: () => { return null }
+    },
+    disabled: {
+      type: Boolean,
+      dafault: () => { return false }
+    },
+    showCheckbox: {
+      type: Boolean,
+      dafault: () => { return false }
+    },
+    /* 初始值 */
+    label: {
+      type: String,
+      default: () => { return null }
+    },
+    /* 可清空选项 */
+    clearable: {
+      type: Boolean,
+      default: () => { return true }
+    },
+    /* 自动收起 */
+    accordion: {
+      type: Boolean,
+      default: () => { return false }
+    },
+    size: {
+      type: String,
+      default: () => { return 'default' }
+    }
+  },
+  data () {
+    return {
+      valueId: this.value,    // 初始值
+      valueTitle: this.label,
+      defaultExpandedKey: [],
+      placeholder: '请选择',
+      valueData: this.data
+    }
+  },
+  created () {
+    if (this.url !== null) {
+      this.placeholder = '加载数据中...'
+      let interval = setInterval(() => {
+        this.placeholder = this.placeholder + '.'
+      }, 500)
+      this.$http({
+        url: this.url,
+        method: 'get'
+      }).then(({data}) => {
+        this.valueData = data.treeData
+        this.$nextTick(() => {
+          this.initHandle()
+          this.placeholder = '请选择'
+          clearInterval(interval)
+        })
+      })
+    } else {
+      this.valueData = this.data
+    }
+  },
+  methods: {
+    // 初始化值
+    initHandle () {
+      if (this.valueId && this.valueId !== '0' && this.$refs.selectTree.getNode(this.valueId)) {
+        this.valueTitle = this.$refs.selectTree.getNode(this.valueId).data[this.props.label]     // 初始化显示
+        this.$refs.selectTree.setCurrentKey(this.valueId)       // 设置默认选中
+        this.defaultExpandedKey = [this.valueId]      // 设置默认展开
+      }
+      this.initScroll()
+    },
+    // 初始化滚动条
+    initScroll () {
+      this.$nextTick(() => {
+        let scrollWrap = document.querySelectorAll('.el-scrollbar .el-select-dropdown__wrap')[0]
+        let scrollBar = document.querySelectorAll('.el-scrollbar .el-scrollbar__bar')
+        if (scrollWrap) { scrollWrap.style.cssText = 'margin: 0px; max-height: none; overflow: hidden;' }
+        if (scrollBar) {
+          scrollBar.forEach(ele => {
+          // eslint-disable-next-line no-return-assign
+            return ele.style.width = 0
+          })
+        }
+      })
+    },
+    // 切换选项
+    handleNodeClick (node) {
+      if (node['disabled']) {
+        this.$message.warning('不能选择根节点(' + node[this.props.label] + ')请重新选择。')
+        return
+      }
+      this.valueTitle = node[this.props.label]
+      this.valueId = node[this.props.value]
+      this.$emit('getValue', this.valueId, this.valueTitle, node)
+      this.defaultExpandedKey = []
+    },
+    // 清除选中
+    clearHandle () {
+      this.valueTitle = ''
+      this.valueId = null
+      this.defaultExpandedKey = []
+      this.clearSelected()
+      this.$emit('getValue', null, null, null)
+    },
+    /* 清空选中样式 */
+    clearSelected () {
+      let allNode = document.querySelectorAll('#tree-option .el-tree-node')
+      allNode.forEach((element) => element.classList.remove('is-current'))
+    }
+  },
+  watch: {
+    value () {
+      this.valueId = this.value
+      if (this.value === '' || this.value === null || this.value === undefined) {
+        this.clearHandle()
+      } else {
+        this.initHandle()
+      }
+    },
+    data () {
+      this.valueData = this.data
+    }
+  },
+  computed: {
+    optionData () {
+      if (this.list) {
+        let cloneData = JSON.parse(JSON.stringify(this.list))      // 对源数据深度克隆
+        return cloneData.filter(father => {                      // 循环所有项,并添加children属性
+          let branchArr = cloneData.filter(child => father.id === child.parentId)       // 返回每一项的子级数组
+            // eslint-disable-next-line no-unused-expressions
+          branchArr.length > 0 ? father.children = branchArr : ''   // 给父级添加一个children属性,并赋值
+          return father.parentId === '0'      // 返回第一层
+        })
+      } else {
+        return this.valueData
+      }
+    }
+  }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+  .el-select{
+    width: 100%;
+  }
+  .el-scrollbar .el-scrollbar__view .el-select-dropdown__item{
+    height: auto;
+    max-height: 274px;
+    padding: 0;
+    overflow: hidden;
+    overflow-y: auto;
+  }
+  .el-select-dropdown__item.selected{
+    font-weight: normal;
+  }
+  ul li >>>.el-tree .el-tree-node__content{
+    height:auto;
+    padding: 0 20px;
+  }
+  .el-tree-node__label{
+    font-weight: normal;
+  }
+  .el-tree >>>.is-current .el-tree-node__label{
+    color: #409EFF;
+    font-weight: 700;
+  }
+  .el-tree >>>.is-current .el-tree-node__children .el-tree-node__label{
+    color:#606266;
+    font-weight: normal;
+  }
+  /* 开发禁用 */
+  /* .el-tree-node:focus>.el-tree-node__content{
+    background-color:transparent;
+    background-color: #f5f7fa;
+    color: #c0c4cc;
+    cursor: not-allowed;
+  }
+  .el-tree-node__content:hover{
+    background-color: #f5f7fa;
+  } */
+</style>

+ 408 - 0
src/components/userSelect/UserSelectDialog.vue

@@ -0,0 +1,408 @@
+<template>
+  <div>
+    <el-dialog
+    title="用户选择"
+    width="70%"
+    :close-on-click-modal="false"
+    :append-to-body="true"
+    :visible.sync="visible">
+    <el-row :gutter="15">
+    <el-col :span="5">
+      <el-tag
+        closable
+        size="small" 
+        style="margin-bottom:5px"
+        v-if="selectOfficeName"
+        :disable-transitions="false"
+        @close="handleNodeClose">
+        {{selectOfficeName}}
+      </el-tag>
+      <el-input
+        placeholder="输入关键字进行过滤"
+        size="small"
+        v-model="filterText">
+      </el-input>
+      <el-tree
+        class="filter-tree"
+        :data="officeTreeData"
+        :props="{
+              value: 'id',             // ID字段名
+              label: 'name',         // 显示名称
+              children: 'childNodes'    // 子级字段名
+            }"
+        default-expand-all
+        :filter-node-method="filterNode"
+        :expand-on-click-node="false"
+        @node-click="handleNodeClick"
+        ref="officeTree">
+      </el-tree>
+    </el-col>
+    <el-col :span="14">
+     <el-form :inline="true" ref="searchForm" :model="searchForm" @keyup.enter.native="refreshList()" @submit.native.prevent>
+    
+         <el-form-item prop="loginName">
+          <el-input size="small" v-model="searchForm.loginName" placeholder="登录名" clearable></el-input>
+         </el-form-item>
+         <el-form-item prop="name">
+           <el-input size="small" v-model="searchForm.name" placeholder="姓名" clearable></el-input>
+        </el-form-item>
+      <el-form-item>
+        <el-button  type="primary" @click="refreshList()" size="small">查询</el-button>
+        <el-button @click="resetSearch()" size="small">重置</el-button>
+      </el-form-item>
+      </el-form>
+    <el-table
+      :data="dataList"
+      v-loading="loading"
+      border
+      size="medium"
+      ref="userTable"
+      @selection-change="selectionChangeHandle"
+      @sort-change="sortChangeHandle"
+      style="width: 100%;">
+      <el-table-column
+        type="selection"
+        header-align="center"
+        align="center"
+        width="50">
+      </el-table-column>
+      <el-table-column
+        prop="photo"
+        header-align="center"
+        align="center"
+        label="头像">
+        <template slot-scope="scope">
+          <img :src="scope.row.photo === ''?'/static/img/avatar.png':scope.row.photo" style="height:50px"/>
+        </template>
+      </el-table-column>
+      <el-table-column
+        prop="loginName"
+        header-align="center"
+        align="center"
+        sortable="custom"
+        min-width="90"
+        label="登录名">
+      </el-table-column>
+      <el-table-column
+        prop="name"
+        header-align="center"
+        align="真实姓名"
+        sortable="custom"
+        min-width="90"
+        label="用户名">
+      </el-table-column>
+      <el-table-column
+        prop="company.name"
+        header-align="center"
+        align="center"
+        sortable="custom"
+        min-width="110"
+        label="所属机构">
+      </el-table-column>
+      <el-table-column
+        prop="office.name"
+        header-align="center"
+        align="center"
+        sortable="custom"
+        min-width="110"
+        label="所属部门">
+      </el-table-column>
+      <el-table-column
+        prop="email"
+        header-align="center"
+        align="center"
+        sortable="custom"
+        min-width="80"
+        label="邮箱">
+      </el-table-column>
+      <el-table-column
+        prop="mobile"
+        header-align="center"
+        align="center"
+        sortable="custom"
+        min-width="90"
+        label="手机号">
+      </el-table-column>
+      <el-table-column
+        prop="loginFlag"
+        header-align="center"
+        align="center"
+        min-width="100"
+        label="状态">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.loginFlag === '1'" size="small" type="success">正常</el-tag>
+          <el-tag v-else-if="scope.row.loginFlag === '0'" size="small" type="danger">禁用</el-tag>
+        </template>
+      </el-table-column>
+    </el-table>
+    <el-pagination
+      @size-change="sizeChangeHandle"
+      @current-change="currentChangeHandle"
+      :current-page="pageNo"
+      :page-sizes="[5, 10, 50, 100]"
+      :page-size="pageSize"
+      :total="total"
+      layout="total, sizes, prev, pager, next, jumper">
+    </el-pagination>
+    </el-col>
+    <el-col :span="5">
+      <el-tag
+        :key="tag.id"
+        v-for="tag in dataListAllSelections"
+        closable
+        :disable-transitions="false"
+        @close="del(tag)">
+        {{tag.name}}
+      </el-tag>
+    </el-col>
+    </el-row>
+     <span slot="footer" class="dialog-footer">
+      <el-button @click="visible = false">关闭</el-button>
+      <el-button type="primary" @click="doSubmit()">确定</el-button>
+    </span>
+    </el-dialog>
+  
+  </div>
+</template>
+
+<script>
+  export default {
+    data () {
+      return {
+        searchForm: {
+          loginName: '',
+          company: {
+            id: ''
+          },
+          office: {
+            id: ''
+          },
+          name: ''
+        },
+        filterText: '',
+        dataListAllSelections: [],   // 所有选中的数据包含跨页数据
+        dataListSelections: [],
+        idKey: 'id', // 标识列表数据中每一行的唯一键的名称(需要按自己的数据改一下)
+        dataList: [],
+        dynamicTags: [],
+        officeTreeData: [],
+        pageNo: 1,
+        pageSize: 5,
+        total: 0,
+        orderBy: '',
+        loading: false,
+        visible: false,
+        selectOfficeName: ''
+      }
+    },
+    props: {
+      selectData: {
+        type: Array,
+        default: () => { return [] }
+      },
+      limit: {
+        type: Number,
+        default: 999999
+      }
+    },
+    watch: {
+      filterText (val) {
+        this.$refs.officeTree.filter(val)
+      }
+    },
+    methods: {
+      init () {
+        this.visible = true
+        this.$nextTick(() => {
+          this.dataListAllSelections = JSON.parse(JSON.stringify(this.selectData))
+          this.refreshTree()
+          this.resetSearch()
+        })
+      },
+           // 设置选中的方法
+      setSelectRow () {
+        if (!this.dataListAllSelections || this.dataListAllSelections.length <= 0) {
+          this.$refs.userTable.clearSelection()
+          return
+        }
+                // 标识当前行的唯一键的名称
+        let idKey = this.idKey
+        let selectAllIds = []
+        this.dataListAllSelections.forEach(row => {
+          selectAllIds.push(row[idKey])
+        })
+        this.$refs.userTable.clearSelection()
+        for (var i = 0; i < this.dataList.length; i++) {
+          if (selectAllIds.indexOf(this.dataList[i][idKey]) >= 0) {
+                        // 设置选中,记住table组件需要使用ref="table"
+            this.$refs.userTable.toggleRowSelection(this.dataList[i], true)
+          }
+        }
+      },
+            // 记忆选择核心方法
+      changePageCoreRecordData () {
+                // 标识当前行的唯一键的名称
+        let idKey = this.idKey
+        let that = this
+              // 如果总记忆中还没有选择的数据,那么就直接取当前页选中的数据,不需要后面一系列计算
+        if (this.dataListAllSelections.length <= 0) {
+          this.dataListSelections.forEach(row => {
+            that.dataListAllSelections.push(row)
+          })
+          return
+        }
+                // 总选择里面的key集合
+        let selectAllIds = []
+        this.dataListAllSelections.forEach(row => {
+          selectAllIds.push(row[idKey])
+        })
+        let selectIds = []
+                // 获取当前页选中的id
+        this.dataListSelections.forEach(row => {
+          selectIds.push(row[idKey])
+                  // 如果总选择里面不包含当前页选中的数据,那么就加入到总选择集合里
+          if (selectAllIds.indexOf(row[idKey]) < 0) {
+            that.dataListAllSelections.push(row)
+          }
+        })
+        let noSelectIds = []
+              // 得到当前页没有选中的id
+        this.dataList.forEach(row => {
+          if (selectIds.indexOf(row[idKey]) < 0) {
+            noSelectIds.push(row[idKey])
+          }
+        })
+        noSelectIds.forEach(id => {
+          if (selectAllIds.indexOf(id) >= 0) {
+            for (let i = 0; i < that.dataListAllSelections.length; i++) {
+              if (that.dataListAllSelections[i][idKey] === id) {
+                                // 如果总选择中有未被选中的,那么就删除这条
+                that.dataListAllSelections.splice(i, 1)
+                break
+              }
+            }
+          }
+        })
+      },
+     // 得到选中的所有数据
+      getAllSelectionData () {
+         // 再执行一次记忆勾选数据匹配,目的是为了在当前页操作勾选后直接获取选中数据
+        this.changePageCoreRecordData()
+      },
+      filterNode (value, data) {
+        if (!value) return true
+        return data.name.indexOf(value) !== -1
+      },
+      del (tag) {
+        this.dataListAllSelections.splice(this.dataListAllSelections.indexOf(tag), 1)
+        this.$nextTick(() => {
+          this.setSelectRow()
+        })
+      },
+      // 获取数据列表
+      refreshList () {
+        this.loading = true
+        this.$http({
+          url: '/sys/user/list',
+          method: 'get',
+          params: {
+            'pageNo': this.pageNo,
+            'pageSize': this.pageSize,
+            'orderBy': this.orderBy,
+            ...this.searchForm
+          }
+        }).then(({data}) => {
+          if (data && data.success) {
+            this.dataList = data.page.list
+            this.total = data.page.count
+            this.loading = false
+          }
+          this.$nextTick(() => {
+            this.setSelectRow()
+          })
+        })
+      },
+      refreshTree () {
+        this.$http({
+          url: `/sys/office/treeData`,
+          method: 'get'
+        }).then(({data}) => {
+          this.officeTreeData = data.treeData
+        })
+      },
+      // 每页数
+      sizeChangeHandle (val) {
+        this.pageSize = val
+        this.pageNo = 1
+        this.refreshList()
+        this.$nextTick(() => {
+          this.changePageCoreRecordData()
+        })
+      },
+      // 当前页
+      currentChangeHandle (val) {
+        this.pageNo = val
+        this.refreshList()
+        this.$nextTick(() => {
+          this.changePageCoreRecordData()
+        })
+      },
+      // 多选
+      selectionChangeHandle (val) {
+        this.dataListSelections = val
+        this.$nextTick(() => {
+          this.changePageCoreRecordData()
+        })
+      },
+       // 排序
+      sortChangeHandle (obj) {
+        if (obj.prop === 'office.name') {
+          obj.prop = 'o.name'
+        }
+        if (obj.prop === 'company.name') {
+          obj.prop = 'c.name'
+        }
+        if (obj.order === 'ascending') {
+          this.orderBy = obj.prop + ' asc'
+        } else if (obj.order === 'descending') {
+          this.orderBy = obj.prop + ' desc'
+        } else {
+          this.orderBy = ''
+        }
+        this.refreshList()
+      },
+      handleNodeClick (data) {
+        if (data.type === '1') {
+          this.searchForm.company.id = data.id
+          this.searchForm.office.id = ''
+        } else {
+          this.searchForm.company.id = ''
+          this.searchForm.office.id = data.id
+        }
+        this.selectOfficeName = '已选机构: ' + data.name
+        this.refreshList()
+      },
+      handleNodeClose () {
+        this.searchForm.office.id = ''
+        this.searchForm.company.id = ''
+        this.selectOfficeName = ''
+        this.refreshList()
+      },
+      resetSearch () {
+        this.searchForm.company.id = ''
+        this.searchForm.office.id = ''
+        this.selectOfficeName = ''
+        this.$refs.searchForm.resetFields()
+        this.refreshList()
+      },
+      doSubmit () {
+        if (this.limit < this.dataListAllSelections.length) {
+          this.$message.error(`你最多只能选择${this.limit}个用户`)
+          return
+        }
+        this.visible = false
+        this.$emit('doSubmit', this.dataListAllSelections)
+      }
+    }
+  }
+</script>

+ 86 - 0
src/components/userSelect/index.vue

@@ -0,0 +1,86 @@
+<template>
+<div>
+    <el-input placeholder="请选择" :size="size" :disabled="disabled"  :readonly="readonly" style="line-hight:40px" v-model="name" class="input-with-select">
+      <el-button slot="append" :disabled="disabled"  :readonly="readonly" @click="showUserSelect" icon="el-icon-search"></el-button>
+    </el-input>
+    <user-select ref="userSelect" @doSubmit="selectUsersToInput" :limit="limit" :selectData="selectData"></user-select>
+</div>
+</template>
+<script>
+import userSelect from './UserSelectDialog'
+export default {
+  data () {
+    return {
+      name: '',
+      labelValue: this.value,
+      selectData: []
+    }
+  },
+  props: {
+    limit: Number,
+    value: String,
+    size: String,
+    readonly: {
+      type: Boolean,
+      default: () => { return false }
+    },
+    disabled: {
+      type: Boolean,
+      default: () => { return false }
+    }
+  },
+  components: {
+    userSelect
+  },
+  created () {
+
+  },
+  watch: {
+    value: {
+      handler (newVal) {
+        this.selectData = []
+        if (newVal) {
+          newVal.split(',').forEach((id) => {
+            this.$http.get(`/sys/user/queryById?id=${id}`).then(({data}) => {
+              if (data.user && data.user.id !== '') {
+                this.selectData.push(data.user)
+              }
+            })
+          })
+        }
+      },
+      immediate: true,
+      deep: false
+    },
+    selectData: {
+      handler (newVal) {
+        this.name = newVal.map(user => { return user.name }).join(',')
+      },
+      immediate: true,
+      deep: false
+    }
+  },
+  methods: {
+    selectUsersToInput (users) {
+      this.selectData = users
+      this.labelValue = users.map(user => { return user.id }).join(',')
+      this.name = users.map(user => { return user.name }).join(',')
+      this.$emit('getValue', this.labelValue, this.name)
+    },
+    showUserSelect () {
+      this.$refs.userSelect.init()
+    }
+  }
+}
+</script>
+<style>
+  .el-form-item__content .el-input-group {
+      vertical-align: middle;
+  }
+ .el-tag + .el-tag {
+    margin-left: 5px;
+    margin-bottom: 5px;
+  }
+</style>
+
+

+ 13 - 0
src/directive/index.js

@@ -0,0 +1,13 @@
+import Vue from 'vue'
+Vue.directive('noMoreClick', {
+  inserted (el, binding) {
+    el.addEventListener('click', e => {
+      el.classList.add('is-disabled')
+      el.disabled = true
+      setTimeout(() => {
+        el.disabled = false
+        el.classList.remove('is-disabled')
+      }, 3000)
+    })
+  }
+})

+ 78 - 0
src/main.js

@@ -0,0 +1,78 @@
+import Vue from 'vue'
+import App from '@/App'
+import router from '@/router'
+import store from '@/store'
+import VueCookie from 'vue-cookie'
+import ElementUI from 'element-ui'
+import 'element-ui/lib/theme-chalk/index.css'
+import '@/assets/scss/index.scss'
+import httpRequest from '@/utils/httpRequest'
+import dictUtils from '@/utils/dictUtils'
+import utils from '@/utils'
+import '@/utils/filter'
+import validator from '@/utils/validator'
+import cloneDeep from 'lodash/cloneDeep'
+import lodash from 'lodash/object'
+import axios from 'axios'
+import moment from 'moment'
+import 'font-awesome/css/font-awesome.min.css'
+// import i18n from './lang' // Internationalization
+import VueClipboard from 'vue-clipboard2'
+import VCharts from 'v-charts'
+import VueParticles from 'vue-particles'
+import JeeplusGencode from 'jeeplus-gencode'
+import JeeplusFlow from 'jeeplus-flowable'
+import 'jeeplus-flowable/lib/jeeplus-flowable.css'
+import './directive'
+import FormMaking from 'jeeplus-form-make/dist/JpFormMaking.common'
+import 'jeeplus-form-make/dist/JpFormMaking.css'
+import VueEditor from 'vue2-editor'
+import VueI18n from 'vue-i18n'
+
+Vue.use(VueI18n)
+Vue.use(VueEditor)
+// Vue.config.lang = 'zh-CN'
+Vue.use(FormMaking)
+
+Vue.use(JeeplusGencode)
+Vue.use(JeeplusFlow)
+VueClipboard.config.autoSetContainer = true
+Vue.use(VueParticles)
+Vue.use(VCharts)
+Vue.use(VueClipboard)
+Vue.use(VueCookie)
+Vue.use(ElementUI)
+// Vue.use(ElementUI, {
+//   i18n: (key, value) => i18n.t(key, value)
+// })
+
+Vue.config.productionTip = false
+// 挂载全局
+Vue.prototype.$http = httpRequest // ajax请求方法
+Vue.prototype.hasPermission = utils.hasPermission // 权限方法
+Vue.prototype.treeDataTranslate = utils.treeDataTranslate // 树形数据转换
+Vue.prototype.$utils = utils
+Vue.prototype.$window = window
+Vue.prototype.$dictUtils = dictUtils
+Vue.prototype.recover = utils.recover
+Vue.prototype.recoverNotNull = utils.recoverNotNull
+Vue.prototype.$axios = axios
+Vue.prototype.validator = validator
+Vue.prototype.lodash = lodash
+Vue.prototype.moment = moment
+Vue.prototype.deepClone = utils.deepClone
+Vue.prototype.validatenull = utils.validatenull
+Vue.prototype.$events = new Vue()
+utils.printLogo()
+
+// 保存整站vuex本地储存初始状态
+window.SITE_CONFIG = {}
+window.SITE_CONFIG['storeState'] = cloneDeep(store.state)
+
+/* eslint-disable no-new */
+new Vue({
+  el: '#app',
+  router,
+  store,
+  render: h => h(App)
+})

+ 1 - 0
src/router/import-development.js

@@ -0,0 +1 @@
+module.exports = file => require('@/views/' + file + '.vue').default

+ 1 - 0
src/router/import-production.js

@@ -0,0 +1 @@
+module.exports = file => () => import('@/views/' + file + '.vue')

+ 159 - 0
src/router/index.js

@@ -0,0 +1,159 @@
+/**
+ * 全站路由配置
+ * 代码中路由统一使用path属性跳转
+ */
+import Vue from 'vue'
+import Router from 'vue-router'
+import http from '@/utils/httpRequest'
+import {isURL} from '@/utils/validate'
+import {clearLoginInfo} from '@/utils'
+
+Vue.use(Router)
+
+const routerPush = Router.prototype.push
+Router.prototype.push = function push (location) {
+  return routerPush.call(this, location).catch(error => error)
+}
+// 开发环境不使用懒加载
+const _import = require('./import-' + process.env.NODE_ENV)
+// 全局路由
+const globalRoutes = [
+  {path: '/login', component: _import('modules/sys/login/login'), name: 'login', meta: {title: '登录'}}
+]
+
+// 主入口路由
+const mainRoutes = {
+  path: '/',
+  component: _import('main'),
+  name: 'main',
+  redirect: {name: 'home'},
+  meta: {title: '整体布局'},
+  children: [
+    {path: '/home', component: _import('modules/sys/dashboard/analysis/index'), name: 'home', meta: {title: '首页', backgroundType: '2'}},
+    {path: '/form/generateList', component: _import('modules/form/GenerateList'), name: 'form-preview-list', meta: {title: '列表'}},
+    {path: '/flowable/task/TaskForm', component: _import('modules/flowable/task/TaskForm'), name: 'task-form', meta: {title: '流程表单'}},
+    {path: '/flowable/task/TaskFormDetail', component: _import('modules/flowable/task/TaskFormDetail'), name: 'task-form-detail', meta: {title: '流程表单详情'}},
+    {path: '/form/generateList', component: _import('modules/form/GenerateList'), name: 'form-preview-list', meta: {title: '列表'}},
+    {path: '/404', component: _import('common/404'), name: '404', meta: {title: '404未找到'}}
+
+  ],
+  beforeEnter (to, from, next) {
+    let token = Vue.cookie.get('token')
+    if (!token || !/\S/.test(token)) {
+      clearLoginInfo()
+      next({name: 'login'})
+    }
+    next()
+  }
+}
+
+const router = new Router({
+  mode: 'hash',
+  scrollBehavior: () => ({y: 0}),
+  isAddDynamicMenuRoutes: false, // 是否已经添加动态(菜单)路由
+  routes: globalRoutes.concat(mainRoutes)
+})
+
+router.beforeEach((to, from, next) => {
+  // 添加动态(菜单)路由
+  if (router.options.isAddDynamicMenuRoutes || fnCurrentRouteType(to, globalRoutes) === 'global') {
+    next()
+  } else {
+    http({
+      url: '/sys/user/getMenus',
+      method: 'get'
+    }).then(({data}) => {
+      if (data && data.success) {
+        fnAddDynamicMenuRoutes(data.routerList)
+        router.options.isAddDynamicMenuRoutes = true
+        sessionStorage.setItem('allMenuList', JSON.stringify(data.menuList || '[]'))
+        sessionStorage.setItem('permissions', JSON.stringify(data.permissions || '[]'))
+        sessionStorage.setItem('dictList', JSON.stringify(data.dictList || '[]'))
+        next({...to, replace: true})
+      } else {
+        sessionStorage.setItem('allMenuList', '[]')
+        sessionStorage.setItem('permissions', '[]')
+        sessionStorage.setItem('dictList', '[]')
+        next()
+      }
+    }).catch((e) => {
+      console.log(`%c${e} 请求菜单列表和权限失败,跳转至登录页!!`, 'color:blue')
+    })
+  }
+})
+
+/**
+ * 判断当前路由类型, global: 全局路由, main: 主入口路由
+ * @param {*} route 当前路由
+ */
+function fnCurrentRouteType (route, globalRoutes = []) {
+  let temp = []
+  for (let i = 0; i < globalRoutes.length; i++) {
+    if (route.path === globalRoutes[i].path) {
+      return 'global'
+    } else if (globalRoutes[i].childNodes && globalRoutes[i].childNodes.length >= 1) {
+      temp = temp.concat(globalRoutes[i].childNodes)
+    }
+  }
+  return temp.length >= 1 ? fnCurrentRouteType(route, temp) : 'main'
+}
+
+/**
+ * 添加动态(菜单)路由
+ * @param {*} menuList 菜单列表
+ * @param {*} routes 递归创建的动态(菜单)路由
+ */
+function fnAddDynamicMenuRoutes (menuList = [], routes = []) {
+  let temp = []
+  for (let i = 0; i < menuList.length; i++) {
+    if (menuList[i].childNodes && menuList[i].childNodes.length >= 1) {
+      temp = temp.concat(menuList[i].childNodes)
+    }
+
+    if (menuList[i].href && /\S/.test(menuList[i].href)) {
+      menuList[i].href = menuList[i].href.replace(/[/]$/, '')
+      const route = {
+        path: menuList[i].href.split('?')[0],
+        component: null,
+        name: menuList[i].href.replace(/^\//g, '').replace(/[/]/g, '-').replace(/[?]/g, '-').replace(/&/g, '-').replace(/=/g, '-'),
+        meta: {
+          parentIds: menuList[i].parentIds,
+          menuId: menuList[i].id,
+          title: menuList[i].name,
+          isDynamic: true,
+          type: menuList[i].target,
+          backgroundType: menuList[i].backgroundType,
+          iframeUrl: ''
+        }
+      }
+      // url以http[s]://开头, 通过iframe展示
+      if (isURL(menuList[i].href)) {
+        route.path = '/' + route.path
+        route.meta.iframeUrl = menuList[i].href
+        route['meta']['type'] = 'iframe'
+      } else if (menuList[i].target === 'iframe') {
+        route['meta']['iframeUrl'] = `${process.env.VUE_APP_SERVER_URL}${menuList[i].href}`
+      } else {
+        try {
+          route['component'] = _import(`modules${menuList[i].href.split('?')[0]}`) || null
+        } catch (e) {
+          console.log(e)
+        }
+      }
+      routes.push(route)
+    }
+  }
+  if (temp.length >= 1) {
+    fnAddDynamicMenuRoutes(temp, routes)
+  } else {
+    mainRoutes.name = 'main-dynamic'
+    mainRoutes.children = routes
+    router.addRoutes([
+      mainRoutes,
+      {path: '*', redirect: {name: '404'}}
+    ])
+    sessionStorage.setItem('dynamicMenuRoutes', JSON.stringify(mainRoutes.children || []))
+  }
+}
+
+export default router

+ 25 - 0
src/store/index.js

@@ -0,0 +1,25 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import cloneDeep from 'lodash/cloneDeep'
+import common from './modules/common'
+import user from './modules/user'
+import config from './modules/config'
+
+Vue.use(Vuex)
+
+export default new Vuex.Store({
+  modules: {
+    common,
+    user,
+    config
+  },
+  mutations: {
+    // 重置vuex本地储存状态
+    resetStore (state) {
+      Object.keys(state).forEach((key) => {
+        state[key] = cloneDeep(window.SITE_CONFIG['storeState'][key])
+      })
+    }
+  },
+  strict: process.env.NODE_ENV !== 'production'
+})

+ 61 - 0
src/store/modules/common.js

@@ -0,0 +1,61 @@
+export default {
+  namespaced: true,
+  state: {
+    // 页面文档可视高度(随窗口改变大小)
+    documentClientHeight: 0,
+    // 导航条, 布局风格, defalut(默认) / inverse(反向)
+    navbarLayoutType: localStorage.getItem('navbarLayoutType') || '1',
+    // 侧边栏, 布局皮肤, light(浅色) / dark(黑色)
+    sidebarLayoutSkin: localStorage.getItem('sidebarLayoutSkin') || '1',
+    // 侧边栏, 折叠状态
+    sidebarFold: false,
+    // 开启tab
+    isTab: localStorage.getItem('isTab') !== 'false',
+    // 侧边栏, 菜单
+    leftMenuList: [],
+    // 当前活动的tab标签页
+    menuActiveName: '',
+    // 侧边栏类型名
+    leftMenuCategory: '',
+    // 主入口标签页
+    mainTabs: [{name: 'home', title: '首页', fullPath: '/home'}],
+    mainTabsActiveName: 'home',
+    // 当前选中的top menu的index
+    topMenuActiveIndex: '0'
+  },
+  mutations: {
+    updateDocumentClientHeight (state, height) {
+      state.documentClientHeight = height
+    },
+    updateNavbarLayoutType (state, type) {
+      state.navbarLayoutType = type
+    },
+    updateSidebarLayoutSkin (state, skin) {
+      state.sidebarLayoutSkin = skin
+    },
+    updateSidebarFold (state, fold) {
+      state.sidebarFold = fold
+    },
+    updateLeftMenuList (state, list) {
+      state.leftMenuList = list
+    },
+    updateMenuActiveName (state, name) {
+      state.menuActiveName = name
+    },
+    updateMainTabs (state, tabs) {
+      state.mainTabs = tabs
+    },
+    updateMainTabsActiveName (state, name) {
+      state.mainTabsActiveName = name
+    },
+    updateLeftMenuCategory (state, leftMenuCategory) {
+      state.leftMenuCategory = leftMenuCategory
+    },
+    updateTopMenuActiveIndex (state, topMenuActiveIndex) {
+      state.topMenuActiveIndex = topMenuActiveIndex
+    },
+    updateIsTab (state, isTab) {
+      state.isTab = isTab
+    }
+  }
+}

+ 52 - 0
src/store/modules/config.js

@@ -0,0 +1,52 @@
+export default {
+  namespaced: true,
+  state: {
+    smtp: '', // 邮箱服务器地址
+    port: '', // 邮箱服务器端口
+    mailName: '', // 系统邮箱地址
+    mailPassword: '', // 系统邮箱密码
+   /*
+     阿里大鱼配置信息
+    */
+    accessKeyId: '', // 此处需要替换成开发者自己的AK(在阿里云访问控制台寻找)
+    accessKeySecret: '', // 此处需要替换成开发者自己的AK(在阿里云访问控制台寻找)
+    signature: '', // 必填:短信签名-可在短信控制台中找到
+    templateCode: '', // 必填:短信模板-可在短信控制台中找到-->
+   /*
+      外观配置
+    */
+    defaultTheme: localStorage.getItem('defaultTheme'), // 默认主题
+    defaultLayout: localStorage.getItem('defaultLayout'),
+    productName: '', // 产品名称
+    logo: '' // 产品logo: '',
+
+  },
+  mutations: {
+    updateDefaultTheme (state, val) {
+      state.defaultTheme = val
+    },
+    updateDefaultLayout (state, val) {
+      state.defaultLayout = val
+    },
+    updateProductName (state, val) {
+      state.productName = val
+    },
+    updateLogo (state, val) {
+      state.logo = val
+    },
+    updateConfig (state, config) {
+      state.smtp = config.smtp
+      state.port = config.port
+      state.mailName = config.mailName
+      state.mailPassword = config.mailPassword
+      state.accessKeyId = config.accessKeyId
+      state.accessKeySecret = config.accessKeySecret
+      state.signature = config.signature
+      state.templateCode = config.templateCode
+      state.defaultTheme = config.defaultTheme
+      state.defaultLayout = config.defaultLayout
+      state.productName = config.productName
+      state.logo = config.logo
+    }
+  }
+}

+ 29 - 0
src/store/modules/user.js

@@ -0,0 +1,29 @@
+export default {
+  namespaced: true,
+  state: {
+    id: '',
+    name: '',
+    loginName: '',
+    photo: ''
+  },
+  mutations: {
+    updateId (state, id) {
+      state.id = id
+    },
+    updateName (state, name) {
+      state.name = name
+    },
+    updateLoginName (state, loginName) {
+      state.loginName = loginName
+    },
+    updatePhoto (state, photo) {
+      state.photo = photo
+    },
+    updateUser (state, user) {
+      state.id = user.id
+      state.name = user.name
+      state.loginName = user.loginName
+      state.photo = user.photo
+    }
+  }
+}

+ 5 - 0
src/utils/bus.js

@@ -0,0 +1,5 @@
+/* eslint-disable */
+import Vue from 'vue'
+
+const bus = new Vue();
+export default bus;

+ 179 - 0
src/utils/common.js

@@ -0,0 +1,179 @@
+ /* eslint-disable */
+// @ts-nocheck
+const STORAGE_USER_KEY = 'STORAGE_USER_KEY'
+// const STORAGE_CARTLIST_KEY = 'STORAGE_CARTLIST_KEY'
+// const STORAGE_QUERYMYLIST_KEY = 'STORAGE_QUERYMYLIST_KEY'
+// import { Toast } from 'mint-ui';
+import axios from 'axios'
+const prefix = 'http://testapi.gu-dao.cn';
+// import authService from '@/api/authService.js'
+//定义一些常量
+var x_PI = 3.14159265358979324 * 3000.0 / 180.0;
+var PI = 3.1415926535897932384626;
+var a = 6378245.0;
+var ee = 0.00669342162296594323;
+
+
+export default {
+  // 获取
+  getLocal(key = STORAGE_USER_KEY) {
+    // console.log('get local operation')
+    return JSON.parse(window.localStorage.getItem(key))
+  },
+  // 设置用
+  /**
+   * @param {any} res
+   */
+  setLocal(res, key = STORAGE_USER_KEY, isSaveOldData = false) {
+    //第三个参数是true的话,会增加数据而不是重新设置,res必须是数组
+    if (isSaveOldData) {
+      if (this.getLocal(key)) {
+        let oldData = this.getLocal(key);
+        return window.localStorage.setItem(key, JSON.stringify(oldData.concat(res)))
+      }
+    }
+    return window.localStorage.setItem(key, JSON.stringify(res))
+  },
+  /**
+   * @param {any[]} obj
+   */
+  deepClone(obj) {
+    var o;
+    if (typeof obj == "object") {
+      if (obj === null) {
+        o = null;
+      } else {
+        if (obj instanceof Array) {
+          o = [];
+          for (var i = 0, len = obj.length; i < len; i++) {
+            o.push(this.deepClone(obj[i]));
+          }
+        } else {
+          o = {};
+          for (var j in obj) {
+            o[j] = this.deepClone(obj[j]);
+          }
+        }
+      }
+    } else {
+      o = obj;
+    }
+    return o;
+  },
+  getGuid() {
+    function S4() {
+      return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
+    }
+    return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
+  },
+  /**
+   * @param {string} value
+   */
+  isEmpty(value) {
+    let result = false;
+    if (value == null || value == undefined) {
+      result = true;
+    }
+    if (typeof value == 'string' && (value.replace(/\s+/g, "") == "" || value == "")) {
+      result = true;
+    }
+    if (typeof value == "object" && value instanceof Array && value.length === 0) {
+      result = true;
+    }
+    return result;
+  },
+  /**
+   * @param {any} time
+   */
+  formatTime(time) {
+    //   格式:yyyy-MM-dd hh:mm:ss
+    let date = new Date(Number(time));
+    let Y = date.getFullYear() + '-';
+    let M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';
+    let D = date.getDate() < 10 ? '0' + date.getDate() + ' ' : date.getDate() + ' ';
+    let h = date.getHours() < 10 ? '0' + date.getHours() + ':' : date.getHours() + ':';
+    let m = date.getMinutes() < 10 ? '0' + date.getMinutes() + ':' : date.getMinutes() + ':';
+    let s = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds();
+    return Y + M + D + h + m + s;
+  },
+  /**
+   * @param {any} time
+   */
+  formatDate(time) {
+    //   格式:yyyy-MM-dd
+    let date = new Date(Number(time));
+    let Y = date.getFullYear() + '-';
+    let M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';
+    let D = date.getDate() < 10 ? '0' + date.getDate() + ' ' : date.getDate() + ' ';
+    return Y + M + D;
+  },
+
+  /**
+    * 字符串转数组
+    * @param {*} value
+    * @returns
+    */
+  stringToArray(value) {
+    if (this.isEmpty(value)) return []
+    if (typeof value !== 'string') return []
+    if (value.indexOf(",") < 0) return [value]
+    let array = value.split(",");
+    return array
+  },
+  /**
+   * 数组转字符串
+   * @param {*} value
+   * @returns
+   */
+  arrayToString(value) {
+    if (typeof value == "object" && value instanceof Array) {
+      if (value.length == 0) return ""
+      let str = '';
+      value.forEach(element => {
+        str += element + ',';
+      })
+      return str.substr(0, str.length - 1);
+    } else {
+      return ""
+    }
+  },
+
+
+  /**
+   * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换
+   * 即谷歌、高德 转 百度
+   * @param {number} lng
+   * @param {number} lat
+   * @returns {*[]}
+   */
+  gcj02tobd09(lng, lat) {
+    var z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * x_PI);
+    var theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * x_PI);
+    var bd_lng = z * Math.cos(theta) + 0.0065;
+    var bd_lat = z * Math.sin(theta) + 0.006;
+    return [bd_lng, bd_lat]
+  },
+  /**
+   * @param {string} value
+   */
+  isPhone(value) {
+    var myreg = /^[1][3,4,5,7,8][0-9]{9}$/;
+    if (!myreg.test(value)) {
+      return false;
+    } else {
+      return true;
+    }
+  },
+  /**
+   * @param {string} value
+   */
+  isIDCardNo(value) {
+    var myreg = /^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$|^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/;
+    if (!myreg.test(value)) {
+      return false;
+    } else {
+      return true;
+    }
+  }
+
+}

+ 59 - 0
src/utils/dictUtils.js

@@ -0,0 +1,59 @@
+
+export function getDictLabel (type, value, defaultLabel) {
+  if ((!value && value !== 0) || (!type && type !== 0)) {
+    if (defaultLabel !== undefined) {
+      return defaultLabel
+    } else {
+      return '--'
+    }
+  }
+  let dictList = JSON.parse(sessionStorage.getItem('dictList') || '[]')
+  let dicts = dictList[type]
+  if (dicts) {
+    for (let i = 0; i < dicts.length; i++) {
+      if (dicts[i].value && dicts[i].value.toString() === value.toString()) {
+        return dicts[i].label
+      }
+    }
+  }
+  if (defaultLabel !== undefined) {
+    return defaultLabel
+  } else {
+    return '--'
+  }
+}
+
+export function getDictValue (type, label, defaultValue) {
+  if ((!label && label !== 0) || (!type && type !== 0)) {
+    if (defaultValue !== undefined) {
+      return defaultValue
+    } else {
+      return '--'
+    }
+  }
+  let dictList = JSON.parse(sessionStorage.getItem('dictList') || '[]')
+  let dicts = dictList[type]
+  if (dicts) {
+    for (let i = 0; i < dicts.length; i++) {
+      if (dicts[i].label && dicts[i].label.toString() === label.toString()) {
+        return dicts[i].value
+      }
+    }
+  }
+  if (defaultValue !== undefined) {
+    return defaultValue
+  } else {
+    return '--'
+  }
+}
+
+export function getDictList (type) {
+  if (!type && type !== 0) {
+    return []
+  }
+  let dictList = JSON.parse(sessionStorage.getItem('dictList') || '[]')
+  let dicts = dictList[type]
+  return dicts || []
+}
+
+export default {getDictLabel, getDictValue, getDictList}

+ 11 - 0
src/utils/filter.js

@@ -0,0 +1,11 @@
+import Vue from 'vue'
+import moment from 'moment'
+
+Vue.filter('formatDate', function (value, formatString) {
+  formatString = formatString || 'YYYY-MM-DD HH:mm:ss'
+  if (value) {
+    return moment(value).format(formatString)
+  } else {
+    return '--'
+  }
+})

+ 137 - 0
src/utils/httpRequest.js

@@ -0,0 +1,137 @@
+import Vue from 'vue'
+import axios from 'axios'
+import router from '@/router'
+import merge from 'lodash/merge'
+import { clearLoginInfo } from '@/utils'
+import qs from 'qs'
+import {
+  Message,
+  Loading
+} from 'element-ui'
+
+// 超时时间
+axios.defaults.timeout = 30000
+// 跨域请求,允许保存cookie
+axios.defaults.withCredentials = true
+// axios.defaults.headers = {'Content-Type': 'application/json; charset=utf-8'}
+axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8'
+// 非生产环境 && 开启代理, 接口前缀统一使用[/api]前缀做代理拦截!
+const BASE_URL = process.env.NODE_ENV !== 'production' ? process.env.VUE_APP_BASE_API : process.env.VUE_APP_SERVER_URL
+// 对面暴露的基础请求路径
+axios.BASE_URL = BASE_URL
+
+/**
+ * 请求拦截
+ */
+let loading
+axios.interceptors.request.use(config => {
+  let showLoading = false
+  if (config.loading === true) {
+    showLoading = true
+  }
+  if (showLoading) {
+    loading = Loading.service({
+      text: config.loadingText || 'Loading...',
+      spinner: 'el-icon-loading',
+      background: 'rgba(0, 0, 0, 0.7)'
+    })
+  }
+  // 请求头带上token
+  config.headers.token = Vue.cookie.get('token')
+  // 请求地址处理
+  config.url = BASE_URL + config.url
+  const type = config.method
+  const defaults = {}
+  const arrayFormat = config.headers.arrayFormat || 'indices'
+  if (type === 'post' && config.headers['Content-Type'] === 'application/x-www-form-urlencoded; charset=utf-8') {
+    // post请求参数处理
+    config.data = qs.stringify(config.data, { allowDots: true, arrayFormat: arrayFormat })
+  } else if (type === 'get') {
+    // get请求参数处理
+    config.params = qs.stringify(config.params, { allowDots: true, arrayFormat: arrayFormat })
+    config.params = qs.parse(config.params)
+    config.params = merge(defaults, config.params)
+  }
+  return config
+}, error => {
+  return Promise.reject(error)
+})
+
+/**
+ * 响应拦截
+ */
+axios.interceptors.response.use(response => {
+  if (loading) {
+    loading.close()
+  }
+  if (response.data && response.data.success === false) {
+    Message({
+      message: response.data.msg,
+      type: 'error',
+      showClose: true,
+      dangerouslyUseHTMLString: true,
+      duration: 3000,
+      customClass: 'zZindex'
+    })
+  }
+  return response
+}, error => {
+  if (loading) {
+    loading.close()
+  }
+  if (error.response.status === 401) { // 超时自动刷新
+    axios({
+      url: '/sys/refreshToken',
+      method: 'get',
+      params: { refreshToken: Vue.cookie.get('refreshToken') }
+    }).then(({ data }) => {
+      if (data && data.success) {
+        Vue.cookie.set('token', data.token)
+        Vue.cookie.set('refreshToken', data.refreshToken)
+      } else {
+        clearLoginInfo()
+        router.push({ name: 'login' })
+      }
+    })
+  } else if (
+    error.response.status === 402 ||
+    error.response.status === 403) { // 402 未登录或者refresh token过时, 403 账号在其他地方登录
+    clearLoginInfo()
+    router.push({ name: 'login' })
+    Message({
+      message: error.response.data.msg || error.response.data.exception,
+      type: 'error',
+      showClose: true,
+      dangerouslyUseHTMLString: true,
+      duration: 3000,
+      customClass: 'zZindex'
+    })
+  } else if (error.response.status === 404) { // 路径找不到
+    Message({
+      message: '404,路径找不到' + ':' + error.response.data.path,
+      type: 'error',
+      showClose: true,
+      duration: 3000
+    })
+  } else if (error.response.status === 504) {
+    Message({
+      message: '网络连接错误' + ':' + error.response.data,
+      type: 'error',
+      showClose: true,
+      duration: 3000,
+      customClass: 'zZindex'
+    })
+  } else {
+    Message({
+      message: error.response.data.msg || error.response.data.exception || error.response.data || error.response || error,
+      type: 'error',
+      showClose: true,
+      duration: 5000,
+      customClass: 'zZindex'
+    })
+  }
+
+  return Promise.reject(error)
+})
+
+export default axios

+ 281 - 0
src/utils/icons.js

@@ -0,0 +1,281 @@
+export default [
+  'el-icon-platform-eleme',
+  'el-icon-eleme',
+  'el-icon-delete-solid',
+  'el-icon-delete',
+  'el-icon-s-tools',
+  'el-icon-setting',
+  'el-icon-user-solid',
+  'el-icon-user',
+  'el-icon-phone',
+  'el-icon-phone-outline',
+  'el-icon-more',
+  'el-icon-more-outline',
+  'el-icon-star-on',
+  'el-icon-star-off',
+  'el-icon-s-goods',
+  'el-icon-goods',
+  'el-icon-warning',
+  'el-icon-warning-outline',
+  'el-icon-question',
+  'el-icon-info',
+  'el-icon-remove',
+  'el-icon-circle-plus',
+  'el-icon-success',
+  'el-icon-error',
+  'el-icon-zoom-in',
+  'el-icon-zoom-out',
+  'el-icon-remove-outline',
+  'el-icon-circle-plus-outline',
+  'el-icon-circle-check',
+  'el-icon-circle-close',
+  'el-icon-s-help',
+  'el-icon-help',
+  'el-icon-minus',
+  'el-icon-plus',
+  'el-icon-check',
+  'el-icon-close',
+  'el-icon-picture',
+  'el-icon-picture-outline',
+  'el-icon-picture-outline-round',
+  'el-icon-upload',
+  'el-icon-upload2',
+  'el-icon-download',
+  'el-icon-camera-solid',
+  'el-icon-camera',
+  'el-icon-video-camera-solid',
+  'el-icon-video-camera',
+  'el-icon-message-solid',
+  'el-icon-bell',
+  'el-icon-s-cooperation',
+  'el-icon-s-order',
+  'el-icon-s-platform',
+  'el-icon-s-fold',
+  'el-icon-s-unfold',
+  'el-icon-s-operation',
+  'el-icon-s-promotion',
+  'el-icon-s-home',
+  'el-icon-s-release',
+  'el-icon-s-ticket',
+  'el-icon-s-management',
+  'el-icon-s-open',
+  'el-icon-s-shop',
+  'el-icon-s-marketing',
+  'el-icon-s-flag',
+  'el-icon-s-comment',
+  'el-icon-s-finance',
+  'el-icon-s-claim',
+  'el-icon-s-custom',
+  'el-icon-s-opportunity',
+  'el-icon-s-data',
+  'el-icon-s-check',
+  'el-icon-s-grid',
+  'el-icon-menu',
+  'el-icon-share',
+  'el-icon-d-caret',
+  'el-icon-caret-left',
+  'el-icon-caret-right',
+  'el-icon-caret-bottom',
+  'el-icon-caret-top',
+  'el-icon-bottom-left',
+  'el-icon-bottom-right',
+  'el-icon-back',
+  'el-icon-right',
+  'el-icon-bottom',
+  'el-icon-top',
+  'el-icon-top-left',
+  'el-icon-top-right',
+  'el-icon-arrow-left',
+  'el-icon-arrow-right',
+  'el-icon-arrow-down',
+  'el-icon-arrow-up',
+  'el-icon-d-arrow-left',
+  'el-icon-d-arrow-right',
+  'el-icon-video-pause',
+  'el-icon-video-play',
+  'el-icon-refresh',
+  'el-icon-refresh-right',
+  'el-icon-refresh-left',
+  'el-icon-finished',
+  'el-icon-sort',
+  'el-icon-sort-up',
+  'el-icon-sort-down',
+  'el-icon-rank',
+  'el-icon-loading',
+  'el-icon-view',
+  'el-icon-c-scale-to-original',
+  'el-icon-date',
+  'el-icon-edit',
+  'el-icon-edit-outline',
+  'el-icon-folder',
+  'el-icon-folder-opened',
+  'el-icon-folder-add',
+  'el-icon-folder-remove',
+  'el-icon-folder-delete',
+  'el-icon-folder-checked',
+  'el-icon-tickets',
+  'el-icon-document-remove',
+  'el-icon-document-delete',
+  'el-icon-document-copy',
+  'el-icon-document-checked',
+  'el-icon-document',
+  'el-icon-document-add',
+  'el-icon-printer',
+  'el-icon-paperclip',
+  'el-icon-takeaway-box',
+  'el-icon-search',
+  'el-icon-monitor',
+  'el-icon-attract',
+  'el-icon-mobile',
+  'el-icon-scissors',
+  'el-icon-umbrella',
+  'el-icon-headset',
+  'el-icon-brush',
+  'el-icon-mouse',
+  'el-icon-coordinate',
+  'el-icon-magic-stick',
+  'el-icon-reading',
+  'el-icon-data-line',
+  'el-icon-data-board',
+  'el-icon-pie-chart',
+  'el-icon-data-analysis',
+  'el-icon-collection-tag',
+  'el-icon-film',
+  'el-icon-suitcase',
+  'el-icon-suitcase-1',
+  'el-icon-receiving',
+  'el-icon-collection',
+  'el-icon-files',
+  'el-icon-notebook-1',
+  'el-icon-notebook-2',
+  'el-icon-toilet-paper',
+  'el-icon-office-building',
+  'el-icon-school',
+  'el-icon-table-lamp',
+  'el-icon-house',
+  'el-icon-no-smoking',
+  'el-icon-smoking',
+  'el-icon-shopping-cart-full',
+  'el-icon-shopping-cart-1',
+  'el-icon-shopping-cart-2',
+  'el-icon-shopping-bag-1',
+  'el-icon-shopping-bag-2',
+  'el-icon-sold-out',
+  'el-icon-sell',
+  'el-icon-present',
+  'el-icon-box',
+  'el-icon-bank-card',
+  'el-icon-money',
+  'el-icon-coin',
+  'el-icon-wallet',
+  'el-icon-discount',
+  'el-icon-price-tag',
+  'el-icon-news',
+  'el-icon-guide',
+  'el-icon-male',
+  'el-icon-female',
+  'el-icon-thumb',
+  'el-icon-cpu',
+  'el-icon-link',
+  'el-icon-connection',
+  'el-icon-open',
+  'el-icon-turn-off',
+  'el-icon-set-up',
+  'el-icon-chat-round',
+  'el-icon-chat-line-round',
+  'el-icon-chat-square',
+  'el-icon-chat-dot-round',
+  'el-icon-chat-dot-square',
+  'el-icon-chat-line-square',
+  'el-icon-message',
+  'el-icon-postcard',
+  'el-icon-position',
+  'el-icon-turn-off-microphone',
+  'el-icon-microphone',
+  'el-icon-close-notification',
+  'el-icon-bangzhu',
+  'el-icon-time',
+  'el-icon-odometer',
+  'el-icon-crop',
+  'el-icon-aim',
+  'el-icon-switch-button',
+  'el-icon-full-screen',
+  'el-icon-copy-document',
+  'el-icon-mic',
+  'el-icon-stopwatch',
+  'el-icon-medal-1',
+  'el-icon-medal',
+  'el-icon-trophy',
+  'el-icon-trophy-1',
+  'el-icon-first-aid-kit',
+  'el-icon-discover',
+  'el-icon-place',
+  'el-icon-location',
+  'el-icon-location-outline',
+  'el-icon-location-information',
+  'el-icon-add-location',
+  'el-icon-delete-location',
+  'el-icon-map-location',
+  'el-icon-alarm-clock',
+  'el-icon-timer',
+  'el-icon-watch-1',
+  'el-icon-watch',
+  'el-icon-lock',
+  'el-icon-unlock',
+  'el-icon-key',
+  'el-icon-service',
+  'el-icon-mobile-phone',
+  'el-icon-bicycle',
+  'el-icon-truck',
+  'el-icon-ship',
+  'el-icon-basketball',
+  'el-icon-football',
+  'el-icon-soccer',
+  'el-icon-baseball',
+  'el-icon-wind-power',
+  'el-icon-light-rain',
+  'el-icon-lightning',
+  'el-icon-heavy-rain',
+  'el-icon-sunrise',
+  'el-icon-sunrise-1',
+  'el-icon-sunset',
+  'el-icon-sunny',
+  'el-icon-cloudy',
+  'el-icon-partly-cloudy',
+  'el-icon-cloudy-and-sunny',
+  'el-icon-moon',
+  'el-icon-moon-night',
+  'el-icon-dish',
+  'el-icon-dish-1',
+  'el-icon-food',
+  'el-icon-chicken',
+  'el-icon-fork-spoon',
+  'el-icon-knife-fork',
+  'el-icon-burger',
+  'el-icon-tableware',
+  'el-icon-sugar',
+  'el-icon-dessert',
+  'el-icon-ice-cream',
+  'el-icon-hot-water',
+  'el-icon-water-cup',
+  'el-icon-coffee-cup',
+  'el-icon-cold-drink',
+  'el-icon-goblet',
+  'el-icon-goblet-full',
+  'el-icon-goblet-square',
+  'el-icon-goblet-square-full',
+  'el-icon-refrigerator',
+  'el-icon-grape',
+  'el-icon-watermelon',
+  'el-icon-cherry',
+  'el-icon-apple',
+  'el-icon-pear',
+  'el-icon-orange',
+  'el-icon-coffee',
+  'el-icon-ice-tea',
+  'el-icon-ice-drink',
+  'el-icon-milk-tea',
+  'el-icon-potato-strips',
+  'el-icon-lollipop',
+  'el-icon-ice-cream-square',
+  'el-icon-ice-cream-round']

+ 773 - 0
src/utils/icons2.js

@@ -0,0 +1,773 @@
+export default [
+  'fa fa-american-sign-language-interpreting',
+  'fa fa-asl-interpreting',
+  'fa fa-assistive-listening-systems',
+  'fa fa-audio-description',
+  'fa fa-blind',
+  'fa fa-braille',
+  'fa fa-deaf',
+  'fa fa-deafness',
+  'fa fa-envira',
+  'fa fa-first-order',
+  'fa fa-gitlab',
+  'fa fa-glide',
+  'fa fa-glide-g',
+  'fa fa-hard-of-hearing',
+  'fa fa-low-vision',
+  'fa fa-pied-piper',
+  'fa fa-question-circle-o',
+  'fa fa-sign-language',
+  'fa fa-signing',
+  'fa fa-snapchat',
+  'fa fa-snapchat-ghost',
+  'fa fa-snapchat-square',
+  'fa fa-themeisle',
+  'fa fa-universal-access',
+  'fa fa-viadeo',
+  'fa fa-viadeo-square',
+  'fa fa-volume-control-phone',
+  'fa fa-wheelchair-alt',
+  'fa fa-wpbeginner',
+  'fa fa-wpforms',
+  'fa fa-yoast',
+  'fa fa-font-awesome',
+  'fa fa-google-plus-official',
+  'fa fa-bluetooth',
+  'fa fa-bluetooth-b',
+  'fa fa-codiepie',
+  'fa fa-credit-card-alt',
+  'fa fa-edge',
+  'fa fa-fort-awesome',
+  'fa fa-hashtag',
+  'fa fa-mixcloud',
+  'fa fa-modx',
+  'fa fa-pause-circle',
+  'fa fa-pause-circle-o',
+  'fa fa-percent',
+  'fa fa-product-hunt',
+  'fa fa-reddit-alien',
+  'fa fa-scribd',
+  'fa fa-shopping-bag',
+  'fa fa-shopping-basket',
+  'fa fa-stop-circle',
+  'fa fa-stop-circle-o',
+  'fa fa-usb',
+  'fa fa-500px',
+  'fa fa-amazon',
+  'fa fa-balance-scale',
+  'fa fa-battery-0',
+  'fa fa-battery-1',
+  'fa fa-battery-2',
+  'fa fa-battery-3',
+  'fa fa-battery-4',
+  'fa fa-battery-empty',
+  'fa fa-battery-full',
+  'fa fa-battery-half',
+  'fa fa-battery-quarter',
+  'fa fa-battery-three-quarters',
+  'fa fa-black-tie',
+  'fa fa-calendar-check-o',
+  'fa fa-calendar-minus-o',
+  'fa fa-calendar-plus-o',
+  'fa fa-calendar-times-o',
+  'fa fa-cc-diners-club',
+  'fa fa-cc-jcb',
+  'fa fa-chrome',
+  'fa fa-clone',
+  'fa fa-commenting',
+  'fa fa-commenting-o',
+  'fa fa-contao',
+  'fa fa-creative-commons',
+  'fa fa-expeditedssl',
+  'fa fa-firefox',
+  'fa fa-fonticons',
+  'fa fa-genderless',
+  'fa fa-get-pocket',
+  'fa fa-gg',
+  'fa fa-gg-circle',
+  'fa fa-hand-grab-o',
+  'fa fa-hand-lizard-o',
+  'fa fa-hand-paper-o',
+  'fa fa-hand-peace-o',
+  'fa fa-hand-pointer-o',
+  'fa fa-hand-rock-o',
+  'fa fa-hand-scissors-o',
+  'fa fa-hand-spock-o',
+  'fa fa-hand-stop-o',
+  'fa fa-hourglass',
+  'fa fa-hourglass-1',
+  'fa fa-hourglass-2',
+  'fa fa-hourglass-3',
+  'fa fa-hourglass-end',
+  'fa fa-hourglass-half',
+  'fa fa-hourglass-o',
+  'fa fa-hourglass-start',
+  'fa fa-houzz',
+  'fa fa-i-cursor',
+  'fa fa-industry',
+  'fa fa-internet-explorer',
+  'fa fa-map',
+  'fa fa-map-o',
+  'fa fa-map-pin',
+  'fa fa-map-signs',
+  'fa fa-mouse-pointer',
+  'fa fa-object-group',
+  'fa fa-object-ungroup',
+  'fa fa-odnoklassniki',
+  'fa fa-odnoklassniki-square',
+  'fa fa-opencart',
+  'fa fa-opera',
+  'fa fa-optin-monster',
+  'fa fa-registered',
+  'fa fa-safari',
+  'fa fa-sticky-note',
+  'fa fa-sticky-note-o',
+  'fa fa-television',
+  'fa fa-trademark',
+  'fa fa-tripadvisor',
+  'fa fa-tv',
+  'fa fa-vimeo',
+  'fa fa-wikipedia-w',
+  'fa fa-y-combinator',
+  'fa fa-yc',
+  'fa fa-bed',
+  'fa fa-buysellads',
+  'fa fa-cart-arrow-down',
+  'fa fa-cart-plus',
+  'fa fa-connectdevelop',
+  'fa fa-dashcube',
+  'fa fa-diamond',
+  'fa fa-facebook-official',
+  'fa fa-forumbee',
+  'fa fa-heartbeat',
+  'fa fa-bed',
+  'fa fa-leanpub',
+  'fa fa-mars',
+  'fa fa-mars-double',
+  'fa fa-mars-stroke',
+  'fa fa-mars-stroke-h',
+  'fa fa-mars-stroke-v',
+  'fa fa-medium',
+  'fa fa-mercury',
+  'fa fa-motorcycle',
+  'fa fa-neuter',
+  'fa fa-pinterest-p',
+  'fa fa-sellsy',
+  'fa fa-server',
+  'fa fa-ship',
+  'fa fa-shirtsinbulk',
+  'fa fa-simplybuilt',
+  'fa fa-skyatlas',
+  'fa fa-street-view',
+  'fa fa-subway',
+  'fa fa-train',
+  'fa fa-transgender',
+  'fa fa-transgender-alt',
+  'fa fa-user-plus',
+  'fa fa-user-secret',
+  'fa fa-user-times',
+  'fa fa-venus',
+  'fa fa-venus-double',
+  'fa fa-venus-mars',
+  'fa fa-viacoin',
+  'fa fa-whatsapp',
+  'fa fa-angellist',
+  'fa fa-area-chart',
+  'fa fa-at',
+  'fa fa-bell-slash',
+  'fa fa-bell-slash-o',
+  'fa fa-bicycle',
+  'fa fa-binoculars',
+  'fa fa-birthday-cake',
+  'fa fa-bus',
+  'fa fa-calculator',
+  'fa fa-cc',
+  'fa fa-cc-amex',
+  'fa fa-cc-discover',
+  'fa fa-cc-mastercard',
+  'fa fa-cc-paypal',
+  'fa fa-cc-stripe',
+  'fa fa-cc-visa',
+  'fa fa-copyright',
+  'fa fa-eyedropper',
+  'fa fa-futbol-o',
+  'fa fa-google-wallet',
+  'fa fa-ils',
+  'fa fa-ioxhost',
+  'fa fa-lastfm',
+  'fa fa-lastfm-square',
+  'fa fa-line-chart',
+  'fa fa-meanpath',
+  'fa fa-newspaper-o',
+  'fa fa-paint-brush',
+  'fa fa-paypal',
+  'fa fa-pie-chart',
+  'fa fa-plug',
+  'fa fa-shekel',
+  'fa fa-sheqel',
+  'fa fa-slideshare',
+  'fa fa-soccer-ball-o',
+  'fa fa-toggle-off',
+  'fa fa-toggle-on',
+  'fa fa-trash',
+  'fa fa-tty',
+  'fa fa-twitch',
+  'fa fa-wifi',
+  'fa fa-yelp',
+  'fa fa-adjust',
+  'fa fa-anchor',
+  'fa fa-archive',
+  'fa fa-arrows',
+  'fa fa-arrows-h',
+  'fa fa-arrows-v',
+  'fa fa-asterisk',
+  'fa fa-automobile',
+  'fa fa-ban',
+  'fa fa-bank',
+  'fa fa-bar-chart-o',
+  'fa fa-barcode',
+  'fa fa-bars',
+  'fa fa-beer',
+  'fa fa-bell',
+  'fa fa-bell-o',
+  'fa fa-bolt',
+  'fa fa-bomb',
+  'fa fa-book',
+  'fa fa-bookmark',
+  'fa fa-bookmark-o',
+  'fa fa-briefcase',
+  'fa fa-bug',
+  'fa fa-building',
+  'fa fa-building-o',
+  'fa fa-bullhorn',
+  'fa fa-bullseye',
+  'fa fa-cab',
+  'fa fa-calendar',
+  'fa fa-calendar-o',
+  'fa fa-camera',
+  'fa fa-camera-retro',
+  'fa fa-car',
+  'fa fa-caret-square-o-down',
+  'fa fa-caret-square-o-left',
+  'fa fa-caret-square-o-right',
+  'fa fa-caret-square-o-up',
+  'fa fa-certificate',
+  'fa fa-check',
+  'fa fa-check-circle',
+  'fa fa-check-circle-o',
+  'fa fa-check-square',
+  'fa fa-check-square-o',
+  'fa fa-child',
+  'fa fa-circle',
+  'fa fa-circle-o',
+  'fa fa-circle-o-notch',
+  'fa fa-circle-thin',
+  'fa fa-clock-o',
+  'fa fa-cloud',
+  'fa fa-cloud-download',
+  'fa fa-cloud-upload',
+  'fa fa-code',
+  'fa fa-code-fork',
+  'fa fa-coffee',
+  'fa fa-cog',
+  'fa fa-cogs',
+  'fa fa-comment',
+  'fa fa-comment-o',
+  'fa fa-comments',
+  'fa fa-comments-o',
+  'fa fa-compass',
+  'fa fa-credit-card',
+  'fa fa-crop',
+  'fa fa-crosshairs',
+  'fa fa-cube',
+  'fa fa-cubes',
+  'fa fa-cutlery',
+  'fa fa-dashboard',
+  'fa fa-database',
+  'fa fa-desktop',
+  'fa fa-dot-circle-o',
+  'fa fa-download',
+  'fa fa-edit',
+  'fa fa-ellipsis-h',
+  'fa fa-ellipsis-v',
+  'fa fa-envelope',
+  'fa fa-envelope-o',
+  'fa fa-envelope-square',
+  'fa fa-eraser',
+  'fa fa-exchange',
+  'fa fa-exclamation',
+  'fa fa-exclamation-circle',
+  'fa fa-exclamation-triangle',
+  'fa fa-external-link',
+  'fa fa-external-link-square',
+  'fa fa-eye',
+  'fa fa-eye-slash',
+  'fa fa-fax',
+  'fa fa-female',
+  'fa fa-fighter-jet',
+  'fa fa-file-archive-o',
+  'fa fa-file-audio-o',
+  'fa fa-file-code-o',
+  'fa fa-file-excel-o',
+  'fa fa-file-image-o',
+  'fa fa-file-movie-o',
+  'fa fa-file-pdf-o',
+  'fa fa-file-photo-o',
+  'fa fa-file-picture-o',
+  'fa fa-file-powerpoint-o',
+  'fa fa-file-sound-o',
+  'fa fa-file-video-o',
+  'fa fa-file-word-o',
+  'fa fa-file-zip-o',
+  'fa fa-film',
+  'fa fa-filter',
+  'fa fa-fire',
+  'fa fa-fire-extinguisher',
+  'fa fa-flag',
+  'fa fa-flag-checkered',
+  'fa fa-flag-o',
+  'fa fa-flash',
+  'fa fa-flask',
+  'fa fa-folder',
+  'fa fa-folder-o',
+  'fa fa-folder-open',
+  'fa fa-folder-open-o',
+  'fa fa-frown-o',
+  'fa fa-gamepad',
+  'fa fa-gavel',
+  'fa fa-gear',
+  'fa fa-gears',
+  'fa fa-gift',
+  'fa fa-glass',
+  'fa fa-globe',
+  'fa fa-graduation-cap',
+  'fa fa-group',
+  'fa fa-hdd-o',
+  'fa fa-headphones',
+  'fa fa-heart',
+  'fa fa-heart-o',
+  'fa fa-history',
+  'fa fa-home',
+  'fa fa-image',
+  'fa fa-inbox',
+  'fa fa-info',
+  'fa fa-info-circle',
+  'fa fa-institution',
+  'fa fa-key',
+  'fa fa-keyboard-o',
+  'fa fa-language',
+  'fa fa-laptop',
+  'fa fa-leaf',
+  'fa fa-legal',
+  'fa fa-lemon-o',
+  'fa fa-level-down',
+  'fa fa-level-up',
+  'fa fa-life-bouy',
+  'fa fa-life-ring',
+  'fa fa-life-saver',
+  'fa fa-lightbulb-o',
+  'fa fa-location-arrow',
+  'fa fa-lock',
+  'fa fa-magic',
+  'fa fa-magnet',
+  'fa fa-mail-forward',
+  'fa fa-mail-reply',
+  'fa fa-mail-reply-all',
+  'fa fa-male',
+  'fa fa-map-marker',
+  'fa fa-meh-o',
+  'fa fa-microphone',
+  'fa fa-microphone-slash',
+  'fa fa-minus',
+  'fa fa-minus-circle',
+  'fa fa-minus-square',
+  'fa fa-minus-square-o',
+  'fa fa-mobile',
+  'fa fa-mobile-phone',
+  'fa fa-money',
+  'fa fa-moon-o',
+  'fa fa-mortar-board',
+  'fa fa-music',
+  'fa fa-navicon',
+  'fa fa-paper-plane',
+  'fa fa-paper-plane-o',
+  'fa fa-paw',
+  'fa fa-pencil',
+  'fa fa-pencil-square',
+  'fa fa-pencil-square-o',
+  'fa fa-phone',
+  'fa fa-phone-square',
+  'fa fa-photo',
+  'fa fa-picture-o',
+  'fa fa-plane',
+  'fa fa-plus',
+  'fa fa-plus-circle',
+  'fa fa-plus-square',
+  'fa fa-plus-square-o',
+  'fa fa-power-off',
+  'fa fa-print',
+  'fa fa-puzzle-piece',
+  'fa fa-qrcode',
+  'fa fa-question',
+  'fa fa-question-circle',
+  'fa fa-quote-left',
+  'fa fa-quote-right',
+  'fa fa-random',
+  'fa fa-recycle',
+  'fa fa-refresh',
+  'fa fa-reorder',
+  'fa fa-reply',
+  'fa fa-reply-all',
+  'fa fa-retweet',
+  'fa fa-road',
+  'fa fa-rocket',
+  'fa fa-rss',
+  'fa fa-rss-square',
+  'fa fa-search',
+  'fa fa-search-minus',
+  'fa fa-search-plus',
+  'fa fa-send',
+  'fa fa-send-o',
+  'fa fa-share',
+  'fa fa-share-alt',
+  'fa fa-share-alt-square',
+  'fa fa-share-square',
+  'fa fa-share-square-o',
+  'fa fa-shield',
+  'fa fa-shopping-cart',
+  'fa fa-sign-in',
+  'fa fa-sign-out',
+  'fa fa-signal',
+  'fa fa-sitemap',
+  'fa fa-sliders',
+  'fa fa-smile-o',
+  'fa fa-sort',
+  'fa fa-sort-alpha-asc',
+  'fa fa-sort-alpha-desc',
+  'fa fa-sort-amount-asc',
+  'fa fa-sort-amount-desc',
+  'fa fa-sort-asc',
+  'fa fa-sort-desc',
+  'fa fa-sort-down',
+  'fa fa-sort-numeric-asc',
+  'fa fa-sort-numeric-desc',
+  'fa fa-sort-up',
+  'fa fa-space-shuttle',
+  'fa fa-spinner',
+  'fa fa-spoon',
+  'fa fa-square',
+  'fa fa-square-o',
+  'fa fa-star',
+  'fa fa-star-half',
+  'fa fa-star-half-empty',
+  'fa fa-star-half-full',
+  'fa fa-star-half-o',
+  'fa fa-star-o',
+  'fa fa-suitcase',
+  'fa fa-sun-o',
+  'fa fa-support',
+  'fa fa-tablet',
+  'fa fa-tachometer',
+  'fa fa-tag',
+  'fa fa-tags',
+  'fa fa-tasks',
+  'fa fa-taxi',
+  'fa fa-terminal',
+  'fa fa-thumb-tack',
+  'fa fa-thumbs-down',
+  'fa fa-thumbs-o-down',
+  'fa fa-thumbs-o-up',
+  'fa fa-thumbs-up',
+  'fa fa-ticket',
+  'fa fa-times',
+  'fa fa-times-circle',
+  'fa fa-times-circle-o',
+  'fa fa-tint',
+  'fa fa-toggle-down',
+  'fa fa-toggle-left',
+  'fa fa-toggle-right',
+  'fa fa-toggle-up',
+  'fa fa-trash-o',
+  'fa fa-tree',
+  'fa fa-trophy',
+  'fa fa-truck',
+  'fa fa-umbrella',
+  'fa fa-university',
+  'fa fa-unlock',
+  'fa fa-unlock-alt',
+  'fa fa-unsorted',
+  'fa fa-upload',
+  'fa fa-user',
+  'fa fa-users',
+  'fa fa-video-camera',
+  'fa fa-volume-down',
+  'fa fa-volume-off',
+  'fa fa-volume-up',
+  'fa fa-warning',
+  'fa fa-wheelchair',
+  'fa fa-wrench',
+  'fa fa-file',
+  'fa fa-file-archive-o',
+  'fa fa-file-audio-o',
+  'fa fa-file-code-o',
+  'fa fa-file-excel-o',
+  'fa fa-file-image-o',
+  'fa fa-file-movie-o',
+  'fa fa-file-o',
+  'fa fa-file-pdf-o',
+  'fa fa-file-photo-o',
+  'fa fa-file-picture-o',
+  'fa fa-file-powerpoint-o',
+  'fa fa-file-sound-o',
+  'fa fa-file-text',
+  'fa fa-file-text-o',
+  'fa fa-file-video-o',
+  'fa fa-file-word-o',
+  'fa fa-file-zip-o',
+  'fa fa-circle-o-notch',
+  'fa fa-cog',
+  'fa fa-gear',
+  'fa fa-refresh',
+  'fa fa-spinner',
+  'fa fa-check-square',
+  'fa fa-check-square-o',
+  'fa fa-circle',
+  'fa fa-circle-o',
+  'fa fa-dot-circle-o',
+  'fa fa-minus-square',
+  'fa fa-minus-square-o',
+  'fa fa-plus-square',
+  'fa fa-plus-square-o',
+  'fa fa-square',
+  'fa fa-square-o',
+  'fa fa-bitcoin',
+  'fa fa-btc',
+  'fa fa-cny',
+  'fa fa-dollar',
+  'fa fa-eur',
+  'fa fa-euro',
+  'fa fa-gbp',
+  'fa fa-inr',
+  'fa fa-jpy',
+  'fa fa-krw',
+  'fa fa-money',
+  'fa fa-rmb',
+  'fa fa-rouble',
+  'fa fa-rub',
+  'fa fa-ruble',
+  'fa fa-rupee',
+  'fa fa-try',
+  'fa fa-turkish-lira',
+  'fa fa-usd',
+  'fa fa-won',
+  'fa fa-yen',
+  'fa fa-align-center',
+  'fa fa-align-justify',
+  'fa fa-align-left',
+  'fa fa-align-right',
+  'fa fa-bold',
+  'fa fa-chain',
+  'fa fa-chain-broken',
+  'fa fa-clipboard',
+  'fa fa-columns',
+  'fa fa-copy',
+  'fa fa-cut',
+  'fa fa-dedent',
+  'fa fa-eraser',
+  'fa fa-file',
+  'fa fa-file-o',
+  'fa fa-file-text',
+  'fa fa-file-text-o',
+  'fa fa-files-o',
+  'fa fa-floppy-o',
+  'fa fa-font',
+  'fa fa-header',
+  'fa fa-indent',
+  'fa fa-italic',
+  'fa fa-link',
+  'fa fa-list',
+  'fa fa-list-alt',
+  'fa fa-list-ol',
+  'fa fa-list-ul',
+  'fa fa-outdent',
+  'fa fa-paperclip',
+  'fa fa-paragraph',
+  'fa fa-paste',
+  'fa fa-repeat',
+  'fa fa-rotate-left',
+  'fa fa-rotate-right',
+  'fa fa-save',
+  'fa fa-scissors',
+  'fa fa-strikethrough',
+  'fa fa-subscript',
+  'fa fa-superscript',
+  'fa fa-table',
+  'fa fa-text-height',
+  'fa fa-text-width',
+  'fa fa-th',
+  'fa fa-th-large',
+  'fa fa-th-list',
+  'fa fa-underline',
+  'fa fa-undo',
+  'fa fa-unlink',
+  'fa fa-angle-double-down',
+  'fa fa-angle-double-left',
+  'fa fa-angle-double-right',
+  'fa fa-angle-double-up',
+  'fa fa-angle-down',
+  'fa fa-angle-left',
+  'fa fa-angle-right',
+  'fa fa-angle-up',
+  'fa fa-arrow-circle-down',
+  'fa fa-arrow-circle-left',
+  'fa fa-arrow-circle-o-down',
+  'fa fa-arrow-circle-o-left',
+  'fa fa-arrow-circle-o-right',
+  'fa fa-arrow-circle-o-up',
+  'fa fa-arrow-circle-right',
+  'fa fa-arrow-circle-up',
+  'fa fa-arrow-down',
+  'fa fa-arrow-left',
+  'fa fa-arrow-right',
+  'fa fa-arrow-up',
+  'fa fa-arrows',
+  'fa fa-arrows-alt',
+  'fa fa-arrows-h',
+  'fa fa-arrows-v',
+  'fa fa-caret-down',
+  'fa fa-caret-left',
+  'fa fa-caret-right',
+  'fa fa-caret-up',
+  'fa fa-caret-square-o-left',
+  'fa fa-caret-square-o-right',
+  'fa fa-caret-square-o-up',
+  'fa fa-caret-square-o-down',
+  'fa fa-chevron-circle-down',
+  'fa fa-chevron-circle-left',
+  'fa fa-chevron-circle-right',
+  'fa fa-chevron-circle-up',
+  'fa fa-chevron-down',
+  'fa fa-chevron-left',
+  'fa fa-chevron-right',
+  'fa fa-chevron-up',
+  'fa fa-hand-o-down',
+  'fa fa-hand-o-left',
+  'fa fa-hand-o-right',
+  'fa fa-hand-o-up',
+  'fa fa-long-arrow-down',
+  'fa fa-long-arrow-left',
+  'fa fa-long-arrow-right',
+  'fa fa-long-arrow-up',
+  'fa fa-toggle-down',
+  'fa fa-toggle-left',
+  'fa fa-toggle-right',
+  'fa fa-toggle-up',
+  'fa fa-arrows-alt',
+  'fa fa-backward',
+  'fa fa-compress',
+  'fa fa-eject',
+  'fa fa-expand',
+  'fa fa-fast-backward',
+  'fa fa-fast-forward',
+  'fa fa-forward',
+  'fa fa-pause',
+  'fa fa-play',
+  'fa fa-play-circle',
+  'fa fa-play-circle-o',
+  'fa fa-step-backward',
+  'fa fa-step-forward',
+  'fa fa-stop',
+  'fa fa-youtube-play',
+  'fa fa-adn',
+  'fa fa-android',
+  'fa fa-apple',
+  'fa fa-behance',
+  'fa fa-behance-square',
+  'fa fa-bitbucket',
+  'fa fa-bitbucket-square',
+  'fa fa-bitcoin',
+  'fa fa-btc',
+  'fa fa-codepen',
+  'fa fa-css3',
+  'fa fa-delicious',
+  'fa fa-deviantart',
+  'fa fa-digg',
+  'fa fa-dribbble',
+  'fa fa-dropbox',
+  'fa fa-drupal',
+  'fa fa-empire',
+  'fa fa-facebook',
+  'fa fa-facebook-square',
+  'fa fa-flickr',
+  'fa fa-foursquare',
+  'fa fa-ge',
+  'fa fa-git',
+  'fa fa-git-square',
+  'fa fa-github',
+  'fa fa-github-alt',
+  'fa fa-github-square',
+  'fa fa-gittip',
+  'fa fa-google',
+  'fa fa-google-plus',
+  'fa fa-google-plus-square',
+  'fa fa-hacker-news',
+  'fa fa-html5',
+  'fa fa-instagram',
+  'fa fa-joomla',
+  'fa fa-jsfiddle',
+  'fa fa-linkedin',
+  'fa fa-linkedin-square',
+  'fa fa-linux',
+  'fa fa-maxcdn',
+  'fa fa-openid',
+  'fa fa-pagelines',
+  'fa fa-pied-piper',
+  'fa fa-pied-piper-alt',
+  'fa fa-pinterest',
+  'fa fa-pinterest-square',
+  'fa fa-qq',
+  'fa fa-ra',
+  'fa fa-rebel',
+  'fa fa-reddit',
+  'fa fa-reddit-square',
+  'fa fa-renren',
+  'fa fa-share-alt',
+  'fa fa-share-alt-square',
+  'fa fa-skype',
+  'fa fa-slack',
+  'fa fa-soundcloud',
+  'fa fa-spotify',
+  'fa fa-stack-exchange',
+  'fa fa-stack-overflow',
+  'fa fa-steam',
+  'fa fa-steam-square',
+  'fa fa-stumbleupon',
+  'fa fa-stumbleupon-circle',
+  'fa fa-tencent-weibo',
+  'fa fa-trello',
+  'fa fa-tumblr',
+  'fa fa-tumblr-square',
+  'fa fa-twitter',
+  'fa fa-twitter-square',
+  'fa fa-vimeo-square',
+  'fa fa-vine',
+  'fa fa-vk',
+  'fa fa-wechat',
+  'fa fa-weibo',
+  'fa fa-weixin',
+  'fa fa-windows',
+  'fa fa-wordpress',
+  'fa fa-xing',
+  'fa fa-xing-square',
+  'fa fa-yahoo',
+  'fa fa-youtube',
+  'fa fa-youtube-play',
+  'fa fa-youtube-square',
+  'fa fa-ambulance',
+  'fa fa-h-square',
+  'fa fa-hospital-o',
+  'fa fa-medkit',
+  'fa fa-plus-square',
+  'fa fa-stethoscope',
+  'fa fa-user-md',
+  'fa fa-wheelchair']

+ 266 - 0
src/utils/index.js

@@ -0,0 +1,266 @@
+import Vue from 'vue'
+import router from '@/router'
+import store from '@/store'
+import $http from './httpRequest'
+
+/**
+ * 是否有权限
+ * @param {*} key
+ */
+export function hasPermission (key) {
+  return JSON.parse(sessionStorage.getItem('permissions') || '[]').indexOf(key) !== -1 || false
+}
+/**
+ * 树形数据转换
+ * @param {*} data list数据
+ * @param {*} id 主键ID
+ * @param {*} pid 上级ID
+ * @param childrenKey 子list数据的key
+ */
+export function treeDataTranslate (data, id = 'id', pid = 'parentId', childrenKey = 'childNodes') {
+  let res = []
+  let temp = {}
+  for (let i = 0; i < data.length; i++) {
+    temp[data[i][id]] = data[i]
+  }
+  for (let k = 0; k < data.length; k++) {
+    if (temp[data[k][pid]] && data[k][id] !== data[k][pid]) {
+      if (!temp[data[k][pid]][childrenKey]) {
+        temp[data[k][pid]][childrenKey] = []
+      }
+      if (!temp[data[k][pid]]['_level']) {
+        temp[data[k][pid]]['_level'] = 1
+      }
+      data[k]['_level'] = temp[data[k][pid]]._level + 1
+      temp[data[k][pid]][childrenKey].push(data[k])
+    } else {
+      res.push(data[k])
+    }
+  }
+  return res
+}
+/**
+ * 清除登录信息
+ */
+export function clearLoginInfo () {
+  Vue.cookie.delete('token')
+  store.commit('resetStore')
+  router.options.isAddDynamicMenuRoutes = false
+}
+
+/**
+ * 表单对象赋值:
+ * 对目标对象存在且源对象同样存在的属性,全部覆盖;
+ * 目标对象不存在但是源对象存在的属性, 全部丢弃;
+ * 目标对象存在但是源对象不存在的属性,如果是字符串赋值为空串,其余类型赋值为undefined
+ */
+export function recover (target, source) {
+  if (target === undefined || target === null) { throw new TypeError('Cannot convert first argument to object') }
+  var to = Object(target)
+  if (source === undefined || source === null) { return to }
+  var keysArray = Object.keys(Object(target))
+  for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
+    var nextKey = keysArray[nextIndex]
+    var desc = Object.getOwnPropertyDescriptor(target, nextKey)
+    if (desc !== undefined && desc.enumerable) {
+      if (to.hasOwnProperty(nextKey)) {
+        if (to[nextKey] instanceof Array) {
+          to[nextKey] = source[nextKey]
+        } else if (to[nextKey] instanceof Object) {
+          recover(to[nextKey], source[nextKey])
+        } else if (source[nextKey] !== undefined) {
+          to[nextKey] = source[nextKey]
+        } else if (typeof (to[nextKey]) === 'string') {
+          to[nextKey] = ''
+        } else {
+          to[nextKey] = undefined
+        }
+      }
+    }
+  }
+  return to
+}
+
+/**
+ * 表单对象赋值:
+ * 对目标对象存在且源对象同样存在的属性,全部覆盖;
+ * 目标对象不存在但是源对象存在的属性, 全部丢弃;
+ * 目标对象存在但是源对象不存在的属性,保留目标对象的属性不做处理
+ */
+export function recoverNotNull (target, source) {
+  if (target === undefined || target === null) { throw new TypeError('Cannot convert first argument to object') }
+  var to = Object(target)
+  if (source === undefined || source === null) { return to }
+  var keysArray = Object.keys(Object(target))
+  for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
+    var nextKey = keysArray[nextIndex]
+    var desc = Object.getOwnPropertyDescriptor(target, nextKey)
+    if (desc !== undefined && desc.enumerable) {
+      if (to.hasOwnProperty(nextKey)) {
+        if (to[nextKey] instanceof Array) {
+          to[nextKey] = source[nextKey]
+        } else if (to[nextKey] instanceof Object) {
+          recover(to[nextKey], source[nextKey])
+        } else if (source[nextKey] !== undefined) {
+          to[nextKey] = source[nextKey]
+        }
+      }
+    }
+  }
+  return to
+}
+export function download (url) {
+  $http({
+    method: 'get',
+    url: url,
+    responseType: 'blob'
+  }).then(response => {
+    if (!response) {
+      return
+    }
+    let link = document.createElement('a')
+    link.href = window.URL.createObjectURL(new Blob([response.data]))
+    link.target = '_blank'
+    let filename = response.headers['content-disposition']
+    link.download = decodeURI(filename)
+    document.body.appendChild(link)
+    link.click()
+    document.body.removeChild(link)
+  // eslint-disable-next-line handle-callback-err
+  }).catch((error) => {
+
+  })
+}
+
+export function escapeHTML (a) {
+  a = '' + a
+  return a.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;')
+}
+/**
+* @function unescapeHTML 还原html脚本 < > & " '
+* @param a -
+*            字符串
+*/
+export function unescapeHTML (a) {
+  a = '' + a
+  return a.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&').replace(/&quot;/g, '"').replace(/&apos;/g, "'")
+}
+
+export function printLogo () {
+  console.info(
+    '%c欢迎使用%cJEEPLUS',
+    'color: #ffffff; background: #000000; padding:5px 10px 5px 10px;font-size:40px;border-radius:12px 0 0 12px;', 'color: #000000; background: #FE9A00; padding:5px 10px;font-size:40px;border-radius:0 12px 12px 0;')
+}
+
+/**
+ * 对象深拷贝
+ */
+export function deepClone (data) {
+  var type = getObjType(data)
+  var obj
+  if (type === 'array') {
+    obj = []
+  } else if (type === 'object') {
+    obj = {}
+  } else {
+    // 不再具有下一层次
+    return data
+  }
+  if (type === 'array') {
+    for (var i = 0, len = data.length; i < len; i++) {
+      data[i] = (function () {
+        if (data[i] === 0) {
+          return data[i]
+        }
+        return data[i]
+      }())
+      delete data[i].$parent
+      obj.push(deepClone(data[i]))
+    }
+  } else if (type === 'object') {
+    for (var key in data) {
+      delete data.$parent
+      obj[key] = deepClone(data[key])
+    }
+  }
+  return obj
+};
+
+export function getObjType (obj) {
+  var toString = Object.prototype.toString
+  var map = {
+    '[object Boolean]': 'boolean',
+    '[object Number]': 'number',
+    '[object String]': 'string',
+    '[object Function]': 'function',
+    '[object Array]': 'array',
+    '[object Date]': 'date',
+    '[object RegExp]': 'regExp',
+    '[object Undefined]': 'undefined',
+    '[object Null]': 'null',
+    '[object Object]': 'object'
+  }
+  if (obj instanceof Element) {
+    return 'element'
+  }
+  return map[toString.call(obj)]
+};
+export function validatenull (val) {
+  // 特殊判断
+  if (val && parseInt(val) === 0) return false
+  var list = ['$parent']
+  if (typeof val === 'boolean') {
+    return false
+  }
+  if (typeof val === 'number') {
+    return false
+  }
+  if (val instanceof Array) {
+    if (val.length === 0) return true
+  } else if (val instanceof Object) {
+    val = (0, deepClone)(val)
+    list.forEach(function (ele) {
+      delete val[ele]
+    })
+    if (JSON.stringify(val) === '{}') return true
+  } else {
+    if (val === 'null' || val == null || val === 'undefined' || val === undefined || val === '') {
+      return true
+    }
+    return false
+  }
+  return false
+}
+function handleImageAdded (file, Editor, cursorLocation, resetUploader) {
+  // An example of using FormData
+  // NOTE: Your key could be different such as:
+  // formData.append('file', file)
+  var formData = new FormData()
+  formData.append('file', file)
+
+  $http({
+    url: '/sys/file/webupload/upload?uploadPath=/vueEditor',
+    method: 'POST',
+    data: formData,
+    headers: { 'Content-Type': 'multipart/form-data' }
+  })
+    .then(result => {
+      let url = result.data.url // Get url from response
+      Editor.insertEmbed(cursorLocation, 'image', url)
+      resetUploader()
+    })
+    .catch(err => {
+      console.log(err)
+    })
+}
+function hashCode (str) {
+  var hash = 0
+  if (str.length === 0) return hash
+  for (let i = 0; i < str.length; i++) {
+    let char = str.charCodeAt(i)
+    hash = ((hash << 5) - hash) + char
+    hash = hash & hash // Convert to 32bit integer
+  }
+  return hash
+}
+export default {escapeHTML, hashCode, unescapeHTML, handleImageAdded, download, recover, recoverNotNull, hasPermission, treeDataTranslate, printLogo, deepClone, validatenull}

+ 425 - 0
src/utils/validate.js

@@ -0,0 +1,425 @@
+/**
+ * 邮箱
+ * @param {*} s
+ */
+export function isEmail (s) {
+  return /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})$/.test(s)
+}
+
+/**
+ * 手机号码
+ * @param {*} s
+ */
+export function isMobile (s) {
+  return /^1[0-9]{10}$/.test(s)
+}
+
+/**
+ * 电话号码
+ * @param {*} s
+ */
+export function isPhone (s) {
+  return /^([0-9]{3,4}-)?[0-9]{7,8}$/.test(s)
+}
+
+/**
+ * URL地址
+ * @param {*} s
+ */
+export function isURL (s) {
+  return /^http[s]?:\/\/.*/.test(s)
+}
+
+/* 小写字母 */
+export function isLowerCase (str) {
+  const reg = /^[a-z]+$/
+  return reg.test(str)
+}
+
+/* 大写字母 */
+export function isUpperCase (str) {
+  const reg = /^[A-Z]+$/
+  return reg.test(str)
+}
+
+/* 大小写字母 */
+export function isAlphabets (str) {
+  const reg = /^[A-Za-z]+$/
+  return reg.test(str)
+}
+
+/* 验证pad还是pc */
+export const isPc = function () {
+  const userAgentInfo = navigator.userAgent
+  const Agents = ['Android', 'iPhone',
+    'SymbianOS', 'Windows Phone',
+    'iPad', 'iPod']
+  let flag = true
+  for (let v = 0; v < Agents.length; v++) {
+    if (userAgentInfo.indexOf(Agents[v]) > 0) {
+      flag = false
+      break
+    }
+  }
+  return flag
+}
+
+/**
+ * 判断姓名是否正确
+ */
+export function isName (name) {
+  let regName = /^[\u4e00-\u9fa5]{2,4}$/
+  if (!regName.test(name)) return false
+  return true
+}
+
+/**
+ * 判断是否为整数
+ */
+export function isNum (num, type) {
+  let regName = /[^\d.]/g
+  if (type === 1) {
+    if (!regName.test(num)) return false
+  } else if (type === 2) {
+    regName = /[^\d]/g
+    if (!regName.test(num)) return false
+  }
+  return true
+}
+
+/**
+ * 判断是否为小数
+ */
+export function isFloat (num) {
+  let regName1 = /^\d+(\.\d+)?$/
+  let regName2 = /^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$/
+  if (regName1.test(num) || regName2.test(num)) return true
+  return false
+}
+
+/**
+ * 判断是否为空
+ */
+export function isNull (val) {
+  if (val instanceof Array) {
+    if (val.length === 0) return true
+  } else if (val instanceof Object) {
+    if (JSON.stringify(val) === '{}') return true
+  } else {
+    if (val === 'null' || val == null || val === 'undefined' || val === undefined || val === '') return true
+    return false
+  }
+  return false
+}
+
+/**
+ * 判断身份证号码
+ */
+export function isCardId (code) {
+  let msg = ''
+  const city = {
+    11: '北京',
+    12: '天津',
+    13: '河北',
+    14: '山西',
+    15: '内蒙古',
+    21: '辽宁',
+    22: '吉林',
+    23: '黑龙江 ',
+    31: '上海',
+    32: '江苏',
+    33: '浙江',
+    34: '安徽',
+    35: '福建',
+    36: '江西',
+    37: '山东',
+    41: '河南',
+    42: '湖北 ',
+    43: '湖南',
+    44: '广东',
+    45: '广西',
+    46: '海南',
+    50: '重庆',
+    51: '四川',
+    52: '贵州',
+    53: '云南',
+    54: '西藏 ',
+    61: '陕西',
+    62: '甘肃',
+    63: '青海',
+    64: '宁夏',
+    65: '新疆',
+    71: '台湾',
+    81: '香港',
+    82: '澳门',
+    91: '国外 '
+  }
+  if (!isNull(code)) {
+    if (code.length === 18) {
+      if (!code || !/(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(code)) {
+        msg = '证件号码格式错误'
+        return false
+      } else if (!city[code.substr(0, 2)]) {
+        msg = '地址编码错误'
+        return false
+      } else {
+        // 18位身份证需要验证最后一位校验位
+        code = code.split('')
+        // ∑(ai×Wi)(mod 11)
+        // 加权因子
+        let factor = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
+        // 校验位
+        let parity = [1, 0, 'X', 9, 8, 7, 6, 5, 4, 3, 2, 'x']
+        let sum = 0
+        let ai = 0
+        let wi = 0
+        for (let i = 0; i < 17; i++) {
+          ai = code[i]
+          wi = factor[i]
+          sum += ai * wi
+        }
+        let last = parity[sum % 11]
+        if ('' + last !== '' + code[17]) {
+          msg = '证件号码校验位错误'
+          return false
+        }
+      }
+    } else {
+      msg = '证件号码长度不为18位'
+      return false
+    }
+  } else {
+    msg = '证件号码不能为空'
+    return false
+  }
+  if (msg) {
+    console.log(msg)
+  }
+  return true
+}
+
+/*
+ * 整数必须为0
+ */
+export function isIntEqZero (value) {
+  value = parseInt(value)
+  return value === 0
+}
+
+/*
+ * 判断整数value是否大于0
+ * 整数必须大于0
+ */
+export function isIntGtZero (value) {
+  value = parseInt(value)
+  return value > 0
+}
+
+/*
+ * 判断整数value是否大于或等于0
+ * 整数必须大于或等于0
+ */
+export function isIntGteZero (value) {
+  value = parseInt(value)
+  return value >= 0
+}
+
+/*
+ * 判断整数value是否不等于0
+ * 整数必须不等于0
+ */
+export function isIntNEqZero (value) {
+  value = parseInt(value)
+  return value !== 0
+}
+
+/*
+ * 判断整数value是否不等于0
+ * 整数必须小于0
+ */
+export function isIntLtZero (value) {
+  value = parseInt(value)
+  return value < 0
+}
+
+/*
+ * 判断整数value是否小于或等于0
+ * 整数必须小于或等于0
+ */
+export function isIntLteZero (value) {
+  value = parseInt(value)
+  return value <= 0
+}
+
+/*
+ * 判断浮点数value是否等于0
+ * 浮点数必须为0
+ */
+export function isFloatEqZero (value) {
+  value = parseFloat(value)
+  return value === 0
+}
+
+/*
+ * 判断浮点数value是否大于0
+ * 浮点数必须大于0
+ */
+export function isFloatGtZero (value) {
+  value = parseFloat(value)
+  return value > 0
+}
+
+/*
+ * 判断浮点数value是否大于或等于0
+ * 浮点数必须大于或等于0
+ */
+export function isFloatGteZero (value) {
+  value = parseFloat(value)
+  return value >= 0
+}
+
+/*
+ * 判断浮点数value是否不等于0
+ * 浮点数必须不等于0
+ */
+export function isFloatNEqZero (value) {
+  value = parseFloat(value)
+  return value !== 0
+}
+
+/*
+ * 判断浮点数value是否小于0
+ * 浮点数必须小于0
+ */
+export function isFloatLtZero (value) {
+  value = parseFloat(value)
+  return value < 0
+}
+
+/*
+ * 判断浮点数value是否小于或等于0
+ * 浮点数必须小于或等于0
+ */
+export function isFloatLteZero (value) {
+  value = parseFloat(value)
+  return value <= 0
+}
+/*
+ * 匹配integer
+ * 匹配integer
+ */
+export function isInteger (value) {
+  // eslint-disable-next-line no-useless-escape
+  return (/^[-\+]?\d+$/.test(value) && parseInt(value) >= 0)
+}
+
+/*
+ * 判断数值类型,包括整数和浮点数
+ * 匹配数值类型,包括整数和浮点数
+ */
+export function isNumber (value) {
+  // eslint-disable-next-line no-useless-escape
+  return /^[-\+]?\d+$/.test(value) || /^[-\+]?\d+(\.\d+)?$/.test(value)
+}
+
+/*
+ * 只能输入[0-9]数字
+ * 只能输入[0-9]数字
+ */
+export function isDigits (value) {
+  return /^\d+$/.test(value)
+}
+
+/*
+ * 判断英文字符
+ * 只能包含英文字符。
+ */
+export function isEnglish (value) {
+  return /^[A-Za-z]+$/.test(value)
+}
+/*
+ * 联系电话(手机/电话皆可)验证
+ * 请正确填写您的联系方式
+ */
+export function isTel (value) {
+  var length = value.length
+  var mobile = /^(((13[0-9]{1})|(15[0-9]{1})|(18[0-9]{1}))+\d{8})$/
+  var tel = /^(\d{3,4}-?)?\d{7,9}$/g
+  return tel.test(value) || (length === 11 && mobile.test(value))
+}
+
+/*
+ * 匹配qq
+ * 匹配QQ
+ */
+export function isQq (value) {
+  return /^[1-9]\d{4,12}$/
+}
+
+/*
+ * 邮政编码验证
+ * 请正确填写您的邮政编码。
+ */
+export function isZipCode (value) {
+  var zip = /^[0-9]{6}$/
+  return zip.test(value)
+}
+
+/*
+ * 匹配密码,以字母开头,长度在6-12之间,只能包含字符、数字和下划线。
+ * 以字母开头,长度在6-12之间,只能包含字符、数字和下划线。
+ */
+export function isPwd (value) {
+  return /^[a-zA-Z]\\w{6,12}$/.test(value)
+}
+
+/*
+ * IP地址验证
+ * 请填写正确的IP地址。
+ */
+export function ip (value) {
+  return /^(([1-9]|([1-9]\d)|(1\d\d)|(2([0-4]\d|5[0-5])))\.)(([1-9]|([1-9]\d)|(1\d\d)|(2([0-4]\d|5[0-5])))\.){2}([1-9]|([1-9]\d)|(1\d\d)|(2([0-4]\d|5[0-5])))$/.test(value)
+}
+
+/*
+ * 字符验证,只能包含中文、英文、数字、下划线等字符。
+ * 只能包含中文、英文、数字、下划线等字符
+ */
+export function stringCheck (value) {
+  return /^[a-zA-Z0-9\u4e00-\u9fa5-_]+$/.test(value)
+}
+
+  /*
+ * 匹配汉字
+ * 匹配汉字
+ */
+export function isChinese (value) {
+  return /^[\u4e00-\u9fa5]+$/.test(value)
+}
+
+  /*
+ * 匹配中文(包括汉字和字符)
+ * 匹配中文(包括汉字和字符)
+ */
+export function isChineseChar (value) {
+  return /^[\u0391-\uFFE5]+$/.test(value)
+}
+
+  /*
+ * 判断是否为合法字符(a-zA-Z0-9-_)
+ * 判断是否为合法字符(a-zA-Z0-9-_)
+ */
+export function isRightfulString (value) {
+  return /^[A-Za-z0-9_-]+$/.test(value)
+}
+
+// 车牌号校验
+export function isPlateNo (plateNo) {
+  var re = /^[\u4e00-\u9fa5]{1}[A-Z]{1}[A-Z_0-9]{5}$/
+  if (re.test(plateNo)) {
+    return true
+  }
+  return false
+}
+
+export default {isAlphabets, isCardId, isChinese, isChineseChar, isDigits, isEmail, isEnglish, isFloat, stringCheck, isFloatGteZero, ip, isFloatEqZero, isFloatGtZero, isFloatLtZero, isFloatLteZero, isFloatNEqZero, isIntEqZero, isIntGtZero, isIntGteZero, isIntLtZero, isIntLteZero, isIntNEqZero, isInteger, isLowerCase, isMobile, isName, isNull, isNum, isNumber, isPc, isPhone, isPlateNo, isPwd, isQq, isRightfulString, isTel, isURL, isUpperCase, isZipCode}

+ 300 - 0
src/utils/validator.js

@@ -0,0 +1,300 @@
+import validate from './validate'
+
+var isMobile = (rule, value, callback) => {
+  if (value && !validate.isMobile(value)) {
+    callback(new Error('请输入正确的手机号!'))
+  } else {
+    callback()
+  }
+}
+
+var isPhone = (rule, value, callback) => {
+  if (value && !validate.isPhone(value)) {
+    callback(new Error('请输入正确的电话号码!'))
+  } else {
+    callback()
+  }
+}
+
+var isNum = (rule, value, callback) => {
+  if (value && !validate.isNum(value, 1)) {
+    callback(new Error('请输入整数!'))
+  } else {
+    callback()
+  }
+}
+
+var isEmail = (rule, value, callback) => {
+  if (value && !validate.isEmail(value)) {
+    callback(new Error('请输入有效的邮箱!'))
+  } else {
+    callback()
+  }
+}
+
+var isURL = (rule, value, callback) => {
+  if (value && !validate.isURL(value)) {
+    callback(new Error('请输入有效的URL!'))
+  } else {
+    callback()
+  }
+}
+
+var isLowerCase = (rule, value, callback) => {
+  if (value && !validate.isLowerCase(value)) {
+    callback(new Error('请输入小写字母!'))
+  } else {
+    callback()
+  }
+}
+
+var isUpperCase = (rule, value, callback) => {
+  if (value && !validate.isUpperCase(value)) {
+    callback(new Error('请输入大写字母!'))
+  } else {
+    callback()
+  }
+}
+
+var isAlphabets = (rule, value, callback) => {
+  if (value && !validate.isAlphabets(value)) {
+    callback(new Error('请输入大小写字母!'))
+  } else {
+    callback()
+  }
+}
+
+var isName = (rule, value, callback) => {
+  if (value && !validate.isName(value)) {
+    callback(new Error('请输入有效的姓名!'))
+  } else {
+    callback()
+  }
+}
+
+var isFloat = (rule, value, callback) => {
+  if (value && !validate.isFloat(value)) {
+    callback(new Error('请输入浮点数!'))
+  } else {
+    callback()
+  }
+}
+
+var isNull = (rule, value, callback) => {
+  if (value && !validate.isNull(value)) {
+    callback(new Error('必须为空!'))
+  } else {
+    callback()
+  }
+}
+
+var isCardId = (rule, value, callback) => {
+  if (value && !validate.isCardId(value)) {
+    callback(new Error('请输入合法的身份证号!'))
+  } else {
+    callback()
+  }
+}
+
+var isIntEqZero = (rule, value, callback) => {
+  if (value && !validate.isIntEqZero(value)) {
+    callback(new Error('请输入0!'))
+  } else {
+    callback()
+  }
+}
+var isIntGtZero = (rule, value, callback) => {
+  if (value && !validate.isIntGtZero(value)) {
+    callback(new Error('整数必须大于0!'))
+  } else {
+    callback()
+  }
+}
+var isIntGteZero = (rule, value, callback) => {
+  if (value && !validate.isIntGteZero(value)) {
+    callback(new Error('整数必须大于或等于0!'))
+  } else {
+    callback()
+  }
+}
+var isIntNEqZero = (rule, value, callback) => {
+  if (value && !validate.isIntNEqZero(value)) {
+    callback(new Error('整数必须不等于0!'))
+  } else {
+    callback()
+  }
+}
+
+var isIntLtZero = (rule, value, callback) => {
+  if (value && !validate.isIntLtZero(value)) {
+    callback(new Error('整数必须小于0!'))
+  } else {
+    callback()
+  }
+}
+
+var isIntLteZero = (rule, value, callback) => {
+  if (value && !validate.isIntLteZero(value)) {
+    callback(new Error('整数必须小于或等于0!'))
+  } else {
+    callback()
+  }
+}
+
+var isFloatEqZero = (rule, value, callback) => {
+  if (value && !validate.isFloatEqZero(value)) {
+    callback(new Error('浮点数必须为0!'))
+  } else {
+    callback()
+  }
+}
+
+var isFloatGtZero = (rule, value, callback) => {
+  if (value && !validate.isFloatGtZero(value)) {
+    callback(new Error('浮点数必须大于0!'))
+  } else {
+    callback()
+  }
+}
+var isFloatGteZero = (rule, value, callback) => {
+  if (value && !validate.isFloatGteZero(value)) {
+    callback(new Error('浮点数必须大于或等于0!'))
+  } else {
+    callback()
+  }
+}
+var isFloatNEqZero = (rule, value, callback) => {
+  if (value && !validate.isFloatNEqZero(value)) {
+    callback(new Error('浮点数必须不等于0!'))
+  } else {
+    callback()
+  }
+}
+
+var isFloatLtZero = (rule, value, callback) => {
+  if (value && !validate.isFloatLtZero(value)) {
+    callback(new Error('浮点数必须小于0!'))
+  } else {
+    callback()
+  }
+}
+
+var isFloatLteZero = (rule, value, callback) => {
+  if (value && !validate.isFloatLteZero(value)) {
+    callback(new Error('浮点数必须小于或等于0!'))
+  } else {
+    callback()
+  }
+}
+
+var isInteger = (rule, value, callback) => {
+  if (value && !validate.isInteger(value)) {
+    callback(new Error('必须为整数!'))
+  } else {
+    callback()
+  }
+}
+
+var isNumber = (rule, value, callback) => {
+  if (value && !validate.isNumber(value)) {
+    callback(new Error('请输入数字!'))
+  } else {
+    callback()
+  }
+}
+
+var isDigits = (rule, value, callback) => {
+  if (value && !validate.isDigits(value)) {
+    callback(new Error('只能输入[0-9]数字!'))
+  } else {
+    callback()
+  }
+}
+
+var isEnglish = (rule, value, callback) => {
+  if (value && !validate.isEnglish(value)) {
+    callback(new Error('只能包含英文字符。'))
+  } else {
+    callback()
+  }
+}
+var isTel = (rule, value, callback) => {
+  if (value && !validate.isTel(value)) {
+    callback(new Error('请正确填写您的联系方式'))
+  } else {
+    callback()
+  }
+}
+
+var isQq = (rule, value, callback) => {
+  if (value && !validate.isQq(value)) {
+    callback(new Error('请正确填写您QQ号码'))
+  } else {
+    callback()
+  }
+}
+
+var isZipCode = (rule, value, callback) => {
+  if (value && !validate.isZipCode(value)) {
+    callback(new Error('请正确填写您的邮政编码'))
+  } else {
+    callback()
+  }
+}
+
+var isPwd = (rule, value, callback) => {
+  if (value && !validate.isPwd(value)) {
+    callback(new Error('以字母开头,长度在6-12之间,只能包含字符、数字和下划线'))
+  } else {
+    callback()
+  }
+}
+
+var ip = (rule, value, callback) => {
+  if (value && !validate.ip(value)) {
+    callback(new Error('请填写正确的IP地址。'))
+  } else {
+    callback()
+  }
+}
+
+var stringCheck = (rule, value, callback) => {
+  if (value && !validate.stringCheck(value)) {
+    callback(new Error('只能包含中文、英文、数字、下划线等字符'))
+  } else {
+    callback()
+  }
+}
+
+var isChinese = (rule, value, callback) => {
+  if (value && !validate.isChinese(value)) {
+    callback(new Error('匹配汉字'))
+  } else {
+    callback()
+  }
+}
+
+var isChineseChar = (rule, value, callback) => {
+  if (value && !validate.isChineseChar(value)) {
+    callback(new Error('匹配中文(包括汉字和字符)'))
+  } else {
+    callback()
+  }
+}
+var isRightfulString = (rule, value, callback) => {
+  if (value && !validate.isRightfulString(value)) {
+    callback(new Error('判断是否为合法字符(a-zA-Z0-9-_)'))
+  } else {
+    callback()
+  }
+}
+
+var isPlateNo = (rule, value, callback) => {
+  if (value && !validate.isPlateNo(value)) {
+    callback(new Error('请输入合法车牌号'))
+  } else {
+    callback()
+  }
+}
+
+export default {isAlphabets, isCardId, isChinese, isChineseChar, ip, isFloatGteZero, isNum, stringCheck, isDigits, isEmail, isEnglish, isFloat, isFloatEqZero, isFloatGtZero, isFloatLtZero, isFloatLteZero, isFloatNEqZero, isIntEqZero, isIntGtZero, isIntGteZero, isIntLtZero, isIntLteZero, isIntNEqZero, isInteger, isLowerCase, isMobile, isName, isNull, isNumber, isPhone, isPlateNo, isPwd, isQq, isRightfulString, isTel, isURL, isUpperCase, isZipCode}

+ 68 - 0
src/views/common/404.vue

@@ -0,0 +1,68 @@
+<template>
+
+        <Exception
+            type="404"
+            :style="{minHeight: 500, height: '80%'}"
+          >
+          </Exception>
+</template>
+
+<script>
+  import Exception from '@/components/Exception/Exception'
+  export default {
+    components: {
+      Exception
+    }
+  }
+</script>
+
+<style lang="scss">
+  .jp-wrapper.jp-page--not-found {
+    position: absolute;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    overflow: hidden;
+
+    .jp-content__wrapper {
+      padding: 0;
+      margin: 0;
+      // background-color: #fff;
+    }
+
+    .jp-content {
+      position: fixed;
+      top: 15%;
+      left: 50%;
+      z-index: 2;
+      padding: 30px;
+      text-align: center;
+      transform: translate(-50%, 0);
+    }
+
+    .not-found-title {
+      margin: 20px 0 15px;
+      font-size: 10em;
+      font-weight: 400;
+      color: rgb(55, 71, 79);
+    }
+
+    .not-found-desc {
+      margin: 0 0 30px;
+      font-size: 26px;
+      text-transform: uppercase;
+      color: rgb(118, 131, 143);
+
+      > em {
+        font-style: normal;
+        color: #ee8145;
+      }
+
+    }
+    .not-found-btn-gohome {
+      margin-left: 30px;
+    }
+
+  }
+</style>

+ 116 - 0
src/views/layout/UpdatePassword.vue

@@ -0,0 +1,116 @@
+<template>
+  <el-dialog
+    title="修改密码"
+    :visible.sync="visible"
+    :append-to-body="true">
+    <el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()"
+             label-width="80px" @submit.native.prevent>
+      <el-form-item label="账号">
+        <span>{{ userName }}</span>
+      </el-form-item>
+      <el-form-item label="原密码" prop="password">
+        <el-input type="password" v-model="dataForm.password"></el-input>
+      </el-form-item>
+      <el-form-item label="新密码" prop="newPassword">
+        <el-input type="password" v-model="dataForm.newPassword"></el-input>
+      </el-form-item>
+      <el-form-item label="确认密码" prop="confirmPassword">
+        <el-input type="password" v-model="dataForm.confirmPassword"></el-input>
+      </el-form-item>
+    </el-form>
+    <span slot="footer" class="dialog-footer">
+      <el-button @click="visible = false">关闭</el-button>
+      <el-button type="primary" @click="dataFormSubmit()">确定</el-button>
+    </span>
+  </el-dialog>
+</template>
+
+<script>
+  import {clearLoginInfo} from '@/utils'
+
+  export default {
+    data () {
+      let validateConfirmPassword = (rule, value, callback) => {
+        if (this.dataForm.newPassword !== value) {
+          callback(new Error('确认密码与新密码不一致'))
+        } else {
+          callback()
+        }
+      }
+      return {
+        visible: false,
+        dataForm: {
+          password: '',
+          newPassword: '',
+          confirmPassword: ''
+        },
+        dataRule: {
+          password: [
+            {required: true, message: '原密码不能为空', trigger: 'blur'}
+          ],
+          newPassword: [
+            {required: true, message: '新密码不能为空', trigger: 'blur'}
+          ],
+          confirmPassword: [
+            {required: true, message: '确认密码不能为空', trigger: 'blur'},
+            {validator: validateConfirmPassword, trigger: 'blur'}
+          ]
+        }
+      }
+    },
+    computed: {
+      userName: {
+        get () {
+          return this.$store.state.user.name
+        }
+      },
+      mainTabs: {
+        get () {
+          return this.$store.state.common.mainTabs
+        },
+        set (val) {
+          this.$store.commit('common/updateMainTabs', val)
+        }
+      }
+    },
+    methods: {
+      // 初始化
+      init () {
+        this.visible = true
+        this.$nextTick(() => {
+          this.$refs['dataForm'].resetFields()
+        })
+      },
+      // 表单提交
+      dataFormSubmit () {
+        this.$refs['dataForm'].validate((valid) => {
+          if (valid) {
+            this.$http({
+              url: '/sys/user/savePwd',
+              method: 'post',
+              data: {
+                'oldPassword': this.dataForm.password,
+                'newPassword': this.dataForm.newPassword
+              }
+            }).then(({data}) => {
+              if (data && data.code === 0) {
+                this.$message({
+                  message: '操作成功',
+                  type: 'success',
+                  duration: 1500
+                })
+                this.visible = false
+                this.$nextTick(() => {
+                  this.mainTabs = []
+                  clearLoginInfo()
+                  this.$router.replace({name: 'login'})
+                })
+              }
+            })
+          }
+        })
+      }
+    }
+  }
+</script>
+

+ 278 - 0
src/views/layout/_common_center.vue

@@ -0,0 +1,278 @@
+<template>
+  <main class="jp-content" :class="{ 'jp-content--tabs': isTab }">
+    <!-- 主入口标签页 s -->
+        <div v-if="contextmenuFlag"
+            class="jp-tags__contentmenu"
+            :style="{left:contentmenuX+'px',top:contentmenuY+'px'}">
+              <el-dropdown-item  v-if="selectTabName !=='/home'" @click.native="tabsCloseCurrentHandle(selectTabName)">关闭当前标签页</el-dropdown-item>
+              <el-dropdown-item @click.native="tabsCloseOtherHandle(selectTabName)">关闭其它标签页</el-dropdown-item>
+              <el-dropdown-item @click.native="tabsCloseAllHandle">关闭全部标签页</el-dropdown-item>
+              <el-dropdown-item @click.native="$router.push({path: selectTabName}), contextmenuFlag = false">刷新当前标签页</el-dropdown-item>
+        </div>
+        
+      <el-tabs 
+        v-if="isTab"
+        type="card"
+        v-model="mainTabsActiveName"
+        @contextmenu.native="handleContextmenu"
+        @tab-click="selectedTabHandle"
+        @tab-remove="removeTabHandle">
+        <el-dropdown class="jp-tabs__tools" :show-timeout="0">
+          <el-button type="primary"  icon="el-icon-arrow-down el-icon--right" size="mini">菜单
+          </el-button>
+          <el-dropdown-menu slot="dropdown">
+            <el-dropdown-item v-if="mainTabsActiveName !=='/home'" @click.native="tabsCloseCurrentHandle('')">关闭当前标签页</el-dropdown-item>
+            <el-dropdown-item @click.native="tabsCloseOtherHandle('')">关闭其它标签页</el-dropdown-item>
+            <el-dropdown-item @click.native="tabsCloseAllHandle">关闭全部标签页</el-dropdown-item>
+            <el-dropdown-item @click.native="tabsRefreshCurrentHandle">刷新当前标签页</el-dropdown-item>
+          </el-dropdown-menu>
+        </el-dropdown>
+        <el-tab-pane
+          v-for="(item) in mainTabs"
+          :key="item.fullPath.replace('/', '-').replace('?', '-').replace('&', '-').replace('=', '-')"
+          :closable = "item.name !== 'home'"
+          :label="item.title"
+          :name="item.fullPath">
+          <div :class="$route.meta.backgroundType !== '2'? 'bg-white':''" :style="siteContentViewHeight">
+            <iframe
+              v-if="item.type === 'iframe'"
+              :src="item.iframeUrl"
+              width="100%" height="100%" frameborder="0" scrolling="yes">
+            </iframe>
+            <keep-alive v-else>
+              <router-view v-if="isRouterAlive && item.fullPath === mainTabsActiveName"/>
+            </keep-alive>
+          </div>
+        </el-tab-pane>
+      </el-tabs>
+      <!-- 主入口标签页 e -->
+      <div v-else>
+        <el-breadcrumb separator="/" style="padding-top:10px; padding-bottom:15px;">
+          <el-breadcrumb-item><router-link to="/home">首页</router-link></el-breadcrumb-item>
+          <el-breadcrumb-item :key="index" v-for="(breadcrumb, index) in breadcrumbs">{{breadcrumb}}</el-breadcrumb-item>
+      </el-breadcrumb>
+         <div :class="$route.meta.backgroundType !== '2'? 'bg-white':''" :style="siteContentViewHeight">
+          <iframe
+              v-if="$route.meta.type === 'iframe'"
+              :src="$route.meta.iframeUrl"
+              width="100%" height="100%" frameborder="0" scrolling="yes">
+            </iframe>
+            <keep-alive v-else>
+              <router-view v-if="isRouterAlive"/>
+            </keep-alive>
+        </div>
+      </div>
+  </main>
+</template>
+
+<script>
+  import {isURL} from '@/utils/validate'
+  import Vue from 'vue'
+  export default {
+    data () {
+      return {
+        contentmenuX: '',
+        contentmenuY: '',
+        contextmenuFlag: false,
+        isRouterAlive: true,
+        selectTabName: '',
+        breadcrumbs: [],
+        eventHub: new Vue()
+      }
+    },
+    computed: {
+      documentClientHeight: {
+        get () {
+          return this.$store.state.common.documentClientHeight
+        }
+      },
+      isTab () {
+        return this.$store.state.common.isTab
+      },
+      menuActiveName: {
+        get () {
+          return this.$store.state.common.menuActiveName
+        },
+        set (val) {
+          this.$store.commit('common/updateMenuActiveName', val)
+        }
+      },
+      mainTabs: {
+        get () {
+          return this.$store.state.common.mainTabs
+        },
+        set (val) {
+          this.$store.commit('common/updateMainTabs', val)
+        }
+      },
+      leftMenuCategory: {
+        get () {
+          return this.$store.state.common.leftMenuCategory
+        }
+      },
+      mainTabsActiveName: {
+        get () {
+          return this.$store.state.common.mainTabsActiveName
+        },
+        set (val) {
+          this.$store.commit('common/updateMainTabsActiveName', val)
+        }
+      },
+      siteContentViewHeight () {
+        let height = this.documentClientHeight - 122
+        return isURL(this.$route.meta.iframeUrl) ? 'height:' + height + 'px' : 'minHeight:' + height + 'px'
+      },
+      tagLen () {
+        return this.mainTabs.length || 0
+      }
+    },
+    created () {
+      this.$events.$on('closeTab', (name) => {
+        this.removeTabHandle(name)
+      })
+    },
+    beforeDestroy () {
+      this.$events.$off('closeTab', {})
+    },
+    watch: {
+      contextmenuFlag () {
+        window.addEventListener('mousedown', this.watchContextmenu)
+      },
+      $route: {
+        handler (val) {
+          this.breadcrumbs = []
+          if (val.meta && val.meta.parentIds) {
+            let ids = val.meta.parentIds.split(',')
+            ids.forEach((id) => {
+              if (id && id !== '0' && id !== '1') {
+                let obj = {title: ''}
+                this.getTitle(JSON.parse(sessionStorage.getItem('allMenuList') || '[]'), id, obj)
+                this.breadcrumbs.push(obj.title)
+              }
+            })
+            this.breadcrumbs.push(this.$route.query.title || this.$route.meta.title)
+          }
+        },
+        immediate: true,
+        deep: false
+      }
+    },
+    methods: {
+      // tabs, 选中tab
+      selectedTabHandle (tab) {
+        tab = this.mainTabs.filter(item => item.fullPath === tab.name)
+        if (!tab) {
+          tab = this.mainTabs.filter(item => item.name === tab.name)
+        }
+        if (tab.length >= 1) {
+          this.$router.push({path: tab[0].fullPath})
+        }
+      },
+      // tabs, 删除tab
+      removeTabHandle (tabName) {
+        var obj = this.mainTabs.filter(item => item.fullPath === tabName)[0]
+        var index = this.mainTabs.indexOf(obj)
+        this.mainTabs = this.mainTabs.filter(item => item.fullPath !== tabName)
+        if (this.mainTabs.length > 1) {
+          // 当前选中tab被删除
+          if (tabName === this.mainTabsActiveName) {
+            if (index < this.mainTabs.length) {
+              this.$router.push({path: this.mainTabs[index].fullPath}, () => {
+                this.mainTabsActiveName = this.$route.fullPath
+              })
+            } else {
+              this.$router.push({path: this.mainTabs[index - 1].fullPath}, () => {
+                this.mainTabsActiveName = this.$route.fullPath
+              })
+            }
+          }
+        } else {
+          this.menuActiveName = ''
+          this.$router.push({name: 'home'})
+        }
+      },
+      // 获取路由名字
+      getTitle (menus, id, obj) {
+        menus.forEach((menu) => {
+          if (menu.id === id) {
+            obj.title = menu.name
+          } else if (menu.childNodes) {
+            this.getTitle(menu.childNodes, id, obj)
+          }
+        })
+      },
+      // tabs, 关闭当前
+      tabsCloseCurrentHandle (tabName) {
+        this.contextmenuFlag = false
+        this.removeTabHandle(tabName || this.mainTabsActiveName)
+      },
+      // tabs, 关闭其它
+      tabsCloseOtherHandle (tabName) {
+        this.contextmenuFlag = false
+        this.mainTabs = this.mainTabs.filter(item => item.fullPath === (tabName || this.mainTabsActiveName) || item.name === 'home')
+        if (tabName) {
+          this.$router.push({path: tabName}, () => {
+            this.mainTabsActiveName = tabName
+          })
+        }
+      },
+      // tabs, 关闭全部
+      tabsCloseAllHandle () {
+        this.contextmenuFlag = false
+        // this.mainTabs = []
+        this.mainTabs = this.mainTabs.filter(item => item.name === 'home')
+        this.menuActiveName = 'home'
+        this.$router.push({name: 'home'})
+      },
+      handleContextmenu (event) {
+        let target = event.target
+        let flag = false
+        if (target && target.className && target.className.indexOf('el-tabs__item') > -1) flag = true
+        else if (target.parentNode && target.parentNode.className && target.parentNode.className.indexOf('el-tabs__item') > -1) {
+          target = target.parentNode
+          flag = true
+        }
+        if (flag) {
+          event.preventDefault()
+          event.stopPropagation()
+          this.contentmenuX = event.clientX
+          this.contentmenuY = event.clientY
+          this.selectTabName = target.getAttribute('aria-controls').slice(5)
+          this.contextmenuFlag = true
+        }
+      },
+      watchContextmenu (event) {
+        if (!event.target.className.includes('el-dropdown-menu__item') || event.button !== 0) {
+          this.contextmenuFlag = false
+        }
+        window.removeEventListener('mousedown', this.watchContextmenu)
+      },
+      reload () {
+        this.isRouterAlive = false
+        this.$nextTick(function () {
+          this.isRouterAlive = true
+        })
+      },
+      // tabs, 刷新当前
+      tabsRefreshCurrentHandle () {
+        this.contextmenuFlag = false
+        this.reload()
+      }
+    }
+  }
+</script>
+
+<style>
+.jp-tags__contentmenu {
+    position: fixed;
+    width: 150px;
+    background-color: #fff;
+    z-index: 1024;
+    border-radius: 5px;
+    -webkit-box-shadow: 1px 2px 10px #ccc;
+    box-shadow: 1px 2px 10px #ccc;
+}
+.jp-navbar__menu .el-badge__content.is-fixed {
+    top: 15px;
+}
+</style>

+ 147 - 0
src/views/layout/_common_left.vue

@@ -0,0 +1,147 @@
+<template>
+  <aside class="jp-sidebar" :style="sidebarLayoutSkin === '5' || sidebarLayoutSkin === '9' ?`background:${defaultTheme}`: ''" :class="'jp-sidebar--' + sidebarLayoutSkin">
+    <div class="jp-sidebar__inner">
+      <el-menu unique-opened
+               :default-active="menuActiveName || 'home'"
+               :collapse="sidebarFold"
+               :collapseTransition="false"
+               class="jp-sidebar__menu">
+        <li class="jp-menu-category" v-if="!sidebarFold">{{leftMenuCategory}}</li>
+        <sub-menu
+          v-for="menu in leftMenuList"
+          :key="menu.id"
+          :menu="menu"
+          :dynamicMenuRoutes="dynamicMenuRoutes">
+        </sub-menu>
+      </el-menu>
+    </div>
+  </aside>
+</template>
+<style>
+.jp-menu-category {
+    padding: 0 20px;
+    margin-top: 20px;
+    font-size: 12px;
+    line-height: 38px;
+    color: #76838f;
+    text-transform: uppercase;
+    -webkit-transition: all .25s,font .1s .15s,color .1s .15s;
+    transition: all .25s,font .1s .15s,color .1s .15s;
+}
+</style>
+
+<script>
+  import SubMenu from './_common_left_submenu'
+
+  export default {
+    data () {
+      return {
+        dynamicMenuRoutes: []
+      }
+    },
+    components: {
+      SubMenu
+    },
+    computed: {
+      sidebarLayoutSkin: {
+        get () {
+          return this.$store.state.common.sidebarLayoutSkin
+        }
+      },
+      sidebarFold: {
+        get () {
+          return this.$store.state.common.sidebarFold
+        }
+      },
+      isTab: {
+        get () {
+          return this.$store.state.common.isTab
+        }
+      },
+      leftMenuList: {
+        get () {
+          return this.$store.state.common.leftMenuList
+        },
+        set (val) {
+          this.$store.commit('common/updateLeftMenuList', val)
+        }
+      },
+      leftMenuCategory: {
+        get () {
+          return this.$store.state.common.leftMenuCategory
+        },
+        set (val) {
+          this.$store.commit('common/updateLeftMenuCategory', val)
+        }
+      },
+      menuActiveName: {
+        get () {
+          return this.$store.state.common.menuActiveName
+        },
+        set (val) {
+          this.$store.commit('common/updateMenuActiveName', val)
+        }
+      },
+      mainTabs: {
+        get () {
+          return this.$store.state.common.mainTabs
+        },
+        set (val) {
+          this.$store.commit('common/updateMainTabs', val)
+        }
+      },
+      defaultTheme () {
+        return this.$store.state.config.defaultTheme
+      },
+      mainTabsActiveName: {
+        get () {
+          return this.$store.state.common.mainTabsActiveName
+        },
+        set (val) {
+          this.$store.commit('common/updateMainTabsActiveName', val)
+        }
+      }
+    },
+    watch: {
+      $route: 'routeHandle'
+    },
+    created () {
+      // this.menuList = JSON.parse(sessionStorage.getItem('menuList') || '[]')
+      this.dynamicMenuRoutes = JSON.parse(sessionStorage.getItem('dynamicMenuRoutes') || '[]')
+      this.routeHandle(this.$route)
+    },
+    methods: {
+      // 路由操作
+      routeHandle (route) {
+        if (this.isTab) {
+          // tab选中, 不存在先添加
+          let tab = this.mainTabs.filter(item => item.fullPath === route.fullPath)[0]
+          if (!tab) {
+            if (route.meta.isDynamic) {
+              route = this.dynamicMenuRoutes.filter(item => item.name === route.name)[0]
+              if (!route) {
+                return console.error('未能找到可用标签页!')
+              }
+            }
+            tab = {
+              menuId: route.meta.menuId || route.name,
+              name: route.name,
+              title: this.$router.currentRoute.query.title || route.meta.title,
+              type: route.meta.type,
+              iframeUrl: route.meta.iframeUrl || '',
+              query: this.$router.currentRoute.query,
+              parmas: this.$router.currentRoute.parmas,
+              fullPath: this.$router.currentRoute.fullPath
+            }
+            this.mainTabs = this.mainTabs.concat(tab)
+          }
+          tab.title = this.$router.currentRoute.query.title || route.meta.title
+          this.menuActiveName = tab.menuId + ''
+          this.mainTabsActiveName = tab.fullPath
+          let topMenuActiveIndex = route.meta.parentIds && route.meta.parentIds.split(',').length > 2 ? route.meta.parentIds.split(',')[2] : '0'
+          this.$store.commit('common/updateTopMenuActiveIndex', topMenuActiveIndex)
+        }
+      }
+    }
+  }
+</script>

+ 62 - 0
src/views/layout/_common_left_submenu.vue

@@ -0,0 +1,62 @@
+<template>
+  <el-submenu 
+    v-if="menu.childNodes && menu.childNodes.length >= 1"
+    :index="menu.id + ''"
+    :popper-class="'jp-sidebar--' + sidebarLayoutSkin + '-popper'">
+    <template slot="title">
+      <i :class="`${menu.icon} jp-sidebar__menu-icon`" style=" display: inline-block!important;"></i>
+      <span>{{ menu.name }}</span>
+    </template>
+    <sub-menu
+      v-for="item in menu.childNodes"
+      :key="item.id"
+      :menu="item"
+      :dynamicMenuRoutes="dynamicMenuRoutes">
+    </sub-menu>
+  </el-submenu>
+  <el-menu-item v-else :index="menu.id + ''" @click="gotoRouteHandle(menu)">
+    <i :class="`${menu.icon} jp-sidebar__menu-icon`" style="display: inline-block!important;"></i>
+    <span slot="title">{{ menu.name }}</span>
+  </el-menu-item>
+</template>
+
+<script>
+  import SubMenu from './_common_left_submenu'
+
+  export default {
+    name: 'sub-menu',
+    props: {
+      menu: {
+        type: Object,
+        required: true
+      },
+      dynamicMenuRoutes: {
+        type: Array,
+        required: true
+      }
+    },
+    components: {
+      SubMenu
+    },
+    computed: {
+      sidebarLayoutSkin: {
+        get () {
+          return this.$store.state.common.sidebarLayoutSkin
+        }
+      }
+    },
+    methods: {
+      translateRouterPath (menu) {
+        return '/' + menu.href.replace(/^\//g, '')
+      },
+      // 通过menuId与动态(菜单)路由进行匹配跳转至指定路由
+      gotoRouteHandle (menu) {
+        const route = this.dynamicMenuRoutes.filter(item => item.meta.menuId === menu.id)
+        if (route.length >= 1) {
+          let routePath = this.translateRouterPath(menu)
+          this.$router.push({path: routePath})
+        }
+      }
+    }
+  }
+</script>

+ 237 - 0
src/views/layout/_common_right.vue

@@ -0,0 +1,237 @@
+<template>
+<div class="right">
+    <el-drawer
+    
+      :visible.sync="isRightVisible"
+      size="250px"
+      :modal="true"
+      show-close
+      :with-header="true"
+      :direction="direction">
+     <el-form style="padding:10px">  
+       <el-form-item>
+      <el-divider>导航模式</el-divider>
+      <el-radio-group v-model="defaultLayout">
+         <el-tooltip class="item" effect="dark" content="横向菜单" placement="top-start">
+           <el-radio label="top">
+            <img src="~@/assets/img/top_layout.svg"/>
+          </el-radio>
+         </el-tooltip>
+         <el-tooltip class="item" effect="dark" content="左侧菜单" placement="top-start">
+          <el-radio label="left">
+            <img src="~@/assets/img/left_layout.svg"/>
+          </el-radio>
+        </el-tooltip>
+      </el-radio-group>
+    </el-form-item>
+    <el-form-item>
+       <el-divider>主题风格</el-divider>
+      <el-radio-group v-model="navbarLayoutType">
+
+           <el-radio class="item m-b-20" label="1">
+            <img src="~@/assets/img/1.svg"/>
+          </el-radio>
+          <el-radio class="item m-b-20" label="3">
+            <img src="~@/assets/img/3.svg"/>
+          </el-radio>
+          <el-radio class="item m-b-20" label="7">
+            <img src="~@/assets/img/7.svg"/>
+          </el-radio>
+           <el-radio class="item m-b-20" label="4">
+            <img src="~@/assets/img/4.svg"/>
+          </el-radio>
+          <el-radio class="item m-b-20" label="2">
+            <img src="~@/assets/img/2.svg"/>
+          </el-radio>
+           <el-radio class="item m-b-20" label="8">
+            <img src="~@/assets/img/8.svg"/>
+          </el-radio>
+           <el-radio class="item m-b-20" label="5">
+            <img src="~@/assets/img/5.svg"/>
+          </el-radio>
+           <el-radio label="9">
+            <img src="~@/assets/img/9.svg"/>
+          </el-radio>
+          <el-radio label="6">
+            <img src="~@/assets/img/6.svg"/>
+          </el-radio>
+      </el-radio-group>
+    </el-form-item>
+     <el-form-item>
+          <el-divider>主题色</el-divider>
+          <div class="tag-group">
+            <el-tooltip effect="dark" :content="item.key" placement="top-start" v-for="(item, index) in colorList" :key="index">
+              <el-tag :color="item.color" class="themeColorTag" @click="defaultTheme = item.color">
+                <i class="el-icon-check themeColorFont" v-if="item.color === defaultTheme"></i>
+              </el-tag>
+            </el-tooltip>
+          </div>
+    </el-form-item>
+    <el-divider>其它设置</el-divider>
+    <el-form-item label="多页签模式"  class="title-setting">
+       <el-switch
+          v-model="isTab">
+        </el-switch>
+    </el-form-item>
+  </el-form>
+    </el-drawer>
+</div>
+</template>
+
+<script>
+  export default {
+    data () {
+      return {
+        isRightVisible: false,
+        direction: 'rtl',
+        colorList: [
+          {
+            key: '拂晓蓝(默认)', color: '#1890FF'
+          },
+          {
+            key: '薄暮', color: '#F5222D', label: '1'
+          },
+          {
+            key: '火山', color: '#FA541C', label: '2'
+          },
+          {
+            key: '日暮', color: '#FAAD14'
+          },
+          {
+            key: '明青', color: '#13C2C2'
+          },
+          {
+            key: '极光绿', color: '#52C41A'
+          },
+          {
+            key: '极客蓝', color: '#2F54EB'
+          },
+          {
+            key: '酱紫', color: '#722ED1'
+          },
+          {
+            key: '天空蓝', color: '#3e8df7'
+          },
+          {
+            key: '咖啡色', color: '#9a7b71'
+          },
+          {
+            key: '深湖蓝', color: '#07b2d3'
+          },
+          {
+            key: '原谅绿', color: '#0cc26c'
+          },
+          {
+            key: '古铜灰', color: '#757575'
+          },
+          {
+            key: '珊瑚紫', color: '#6779fa'
+          },
+          {
+            key: '橙黄', color: '#eb6607'
+          },
+          {
+            key: '粉红', color: '#f74584'
+          },
+          {
+            key: '青紫', color: '#9463f7'
+          },
+          {
+            key: '橄榄绿', color: '#16b2a3'
+          }
+        ]
+      }
+    },
+    computed: {
+      defaultLayout: {
+        get () {
+          return this.$store.state.config.defaultLayout
+        },
+        set (val) {
+          localStorage.setItem('defaultLayout', val)
+          this.$store.commit('config/updateDefaultLayout', val)
+        }
+      },
+      navbarLayoutType: {
+        get () {
+          return this.$store.state.common.navbarLayoutType
+        },
+        set (val) {
+          localStorage.setItem('navbarLayoutType', val)
+          this.$store.commit('common/updateNavbarLayoutType', val)
+          localStorage.setItem('sidebarLayoutSkin', val)
+          this.$store.commit('common/updateSidebarLayoutSkin', val)
+        }
+      },
+      // sidebarLayoutSkin: {
+      //   get () {
+      //     return this.$store.state.common.sidebarLayoutSkin
+      //   },
+      //   set (val) {
+      //     localStorage.setItem('sidebarLayoutSkin', val)
+      //     this.$store.commit('common/updateSidebarLayoutSkin', val)
+      //   }
+      // },
+      isTab: {
+        get () {
+          return this.$store.state.common.isTab
+        },
+        set (val) {
+          localStorage.setItem('isTab', val)
+          this.$store.commit('common/updateIsTab', val)
+        }
+      },
+      defaultTheme: {
+        get () {
+          return this.$store.state.config.defaultTheme
+        },
+        set (val) {
+          localStorage.setItem('defaultTheme', val)
+          this.$events.$emit('updateTheme', val)
+          return this.$store.commit('config/updateDefaultTheme', val)
+        }
+      }
+    },
+    methods: {
+      showRight () {
+        this.isRightVisible = true
+      }
+    }
+  }
+</script>
+<style>
+.right  .el-drawer__header {
+    margin-bottom: 0px;
+}
+.block{
+  display: block
+}
+.m-b-20 {
+  margin-bottom: 20px
+}
+.themeColorTag{
+  width:25px !important; 
+  height:25px !important; 
+  margin-left: 0px !important;
+  margin-bottom: 0px !important;
+}
+.themeColorFont{
+  position: absolute;
+  color: #fff;
+  margin-top: 3px;
+  margin-left: -6px;
+  font-weight: bold;
+  font-size: 16px;
+}
+.title-setting{
+    margin-bottom: 12px;
+    color: rgba(0,0,0,.85);
+    font-size: 14px;
+    line-height: 22px;
+}
+.title-setting .el-form-item__label{
+    color: rgba(0,0,0,.85);
+    font-size: 14px;
+    font-weight: bold;
+}
+</style>

+ 315 - 0
src/views/layout/_common_top.vue

@@ -0,0 +1,315 @@
+<template>
+  <nav ref="navbar" :style="`background:${defaultTheme}`" :class="'jp-navbar  jp-navbar--' + navbarLayoutType ">
+    <div class="jp-navbar__header">
+      <!-- <h1 class="jp-navbar__brand" @click="$router.push({ name: 'home' })">
+        <a class="jp-navbar__brand-lg" href="javascript:;">
+          <img height="50px" src='../../assets/img/logo.png'/>
+        </a>
+        <a class="jp-navbar__brand-mini" href="javascript:;">
+          <img :src="logo" height="40px" width="40px"/>
+        </a>
+      </h1> -->
+      <h1 class="jp-navbar__brand" @click="$router.push({ name: 'home' })">
+        <a class="jp-navbar__brand-lg" href="javascript:;"> <img :src="logo" height="40px" width="40px"/> {{productName}}</a>
+        <a class="jp-navbar__brand-mini" href="javascript:;">
+          <img :src="logo" height="40px" width="40px"/>
+        </a>
+      </h1>
+    </div>
+    <div class="jp-navbar__body clearfix" style="overflow:hidden">
+      
+      <el-menu
+        class="jp-navbar__menu"
+        mode="horizontal">
+        <el-menu-item class="jp-navbar__switch"  @click="sidebarFold = !sidebarFold">
+             <i  :class="sidebarFold ? 'fa fa-reorder':'fa fa-long-arrow-left'"></i>
+        </el-menu-item>
+      </el-menu>
+    
+      <el-menu class="jp-navbar__menu " :default-active="topMenuActiveIndex" ref="topMenu"  mode="horizontal">
+        <el-menu-item  class="el_menu_item" v-for="menu in topMenuList"
+          :index="menu.id"
+          :key="menu.id"
+          @click="showLeftMenu(menu)"
+          :ref="menu.id"
+          :menu="menu">
+           <i :class="`${menu.icon} jp-sidebar__menu-icon`" style="display: inline-block!important;"></i>
+          {{menu.name}}
+          </el-menu-item>
+
+        <el-submenu index="2" v-if="topHideMenuList.length != 0">
+          <template slot="title">更多</template>
+          <el-menu-item  v-for="menu in topHideMenuList"
+          :index="menu.id"
+          :key="menu.id"
+          :ref="menu.id"
+          @click="showLeftMenu(menu)"
+          :menu="menu">
+           <i :class="`${menu.icon} jp-sidebar__menu-icon`" style="display: inline-block!important;"></i>
+          {{menu.name}}
+          </el-menu-item>
+        </el-submenu>
+        </el-menu>
+      
+      <el-menu
+        class="jp-navbar__menu jp-navbar__menu--right"
+        mode="horizontal">
+         <el-menu-item>
+          <template slot="title">
+                <color-picker></color-picker>
+          </template>
+        </el-menu-item>
+        <el-menu-item>
+          <template slot="title">
+                <notice-icon
+                    class="action notice"
+                    :tabs="noticeTabs" >
+              </notice-icon>
+          </template>
+        </el-menu-item>
+        <el-menu-item class="jp-navbar__avatar">
+          <el-dropdown :show-timeout="0" placement="bottom">
+            <span class="el-dropdown-link">
+              <img :src="photo === ''?'/static/img/avatar.png':photo">{{ userName }}
+            </span>
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item @click.native="updatePasswordHandle()">修改密码</el-dropdown-item>
+              <el-dropdown-item @click.native="logoutHandle()">退出</el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
+        </el-menu-item>
+        <el-menu-item  @click="showRight">
+          <template slot="title">
+               <i class="el-icon-more rotate-90" @click="showRight"></i>
+          </template>
+        </el-menu-item>
+      </el-menu>
+    </div>
+    <!-- 弹窗, 修改密码 -->
+    <update-password v-if="updatePassowrdVisible" ref="updatePassowrd"></update-password>
+  </nav>
+</template>
+
+<script>
+  import UpdatePassword from './UpdatePassword'
+  import {clearLoginInfo} from '@/utils'
+  import NoticeIcon from '@/components/NoticeIcon'
+  import ColorPicker from '@/components/colors/ColorPicker'
+
+  export default {
+    data () {
+      return {
+        updatePassowrdVisible: false,
+        activeIndex: '',
+        topMenuList: [],
+        topHideMenuList: [],
+        allMenuList: [],
+        screenWidth: document.body.clientWidth,
+        noticeTabs: [
+          {
+            title: '通知',
+            count: 0,
+            list: [
+            ],
+            emptyText: '你已查看所有通知',
+            emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg'
+          },
+          {
+            title: '站内信',
+            count: 0,
+            list: [
+            ],
+            emptyText: '你已读完所有消息',
+            emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg'
+          }
+        ]
+      }
+    },
+    components: {
+      UpdatePassword,
+      ColorPicker,
+      NoticeIcon
+    },
+    computed: {
+      navbarLayoutType () {
+        return this.$store.state.common.navbarLayoutType
+      },
+      topMenuActiveIndex: {
+        get () {
+          return this.$store.state.common.topMenuActiveIndex
+        },
+        set (val) {
+          this.$store.commit('common/updateTopMenuActiveIndex', val)
+        }
+      },
+      sidebarFold: {
+        get () {
+          return this.$store.state.common.sidebarFold
+        },
+        set (val) {
+          this.$store.commit('common/updateSidebarFold', val)
+        }
+      },
+      mainTabs: {
+        get () {
+          return this.$store.state.common.mainTabs
+        },
+        set (val) {
+          this.$store.commit('common/updateMainTabs', val)
+        }
+      },
+      userName: {
+        get () {
+          return this.$store.state.user.name
+        }
+      },
+      photo: {
+        get () {
+          return this.$store.state.user.photo
+        }
+      },
+      logo () {
+        return this.$store.state.config.logo
+      },
+      defaultTheme () {
+        return this.$store.state.config.defaultTheme
+      },
+      productName () {
+        return this.$store.state.config.productName
+      },
+      defaultLayout () {
+        return this.$store.state.config.defaultLayout
+      }
+    },
+    created () {
+      this.allMenuList = JSON.parse(sessionStorage.getItem('allMenuList') || '[]')
+      if (this.defaultLayout === 'top') {
+        this.topMenuActiveIndex = this.allMenuList[0].id
+        this.showLeftMenu(this.allMenuList[0])
+      } else {
+        this.$store.commit('common/updateLeftMenuList', this.allMenuList)
+      }
+      this.$http({
+        url: '/notify/oaNotify/self/data?pageNo=1&pageSize=10&isSelf=true&readFlag=0',
+        method: 'get'
+      }).then(({data}) => {
+        this.noticeTabs[0].count = data.page.count
+        this.noticeTabs[0].url = '/notify/MyNotifyList'
+        this.noticeTabs[0].list = data.page.list.map((item) => {
+          return {
+            id: item.id,
+            avatar: item.createBy.photo,
+            title: item.title,
+            description: item.content,
+            datetime: item.createDate,
+            type: '通知'
+          }
+        })
+      })
+      this.$http({
+        url: '/mailBox/list?pageNo=1&pageSize=10&mail.title=&readstatus=0',
+        method: 'get'
+      }).then(({data}) => {
+        this.noticeTabs[1].count = data.page.count
+        this.noticeTabs[1].url = '/mailbox/index'
+        this.noticeTabs[1].list = data.page.list.map((item) => {
+          return {
+            id: item.id,
+            avatar: item.sender.photo,
+            title: item.mail.title,
+            description: item.mail.content,
+            datetime: item.sendtime,
+            type: '站内信'
+          }
+        })
+      })
+    },
+    mounted () {
+      if (this.defaultLayout === 'top') {
+        this.fixTopMenu()
+      }
+    },
+    watch: {
+      topMenuActiveIndex (val) {
+        this.topMenuList.forEach((menu) => {
+          if (menu.id === val) {
+            this.showLeftMenu(menu)
+          }
+        })
+        this.topHideMenuList.forEach((menu) => {
+          if (menu.id === val) {
+            this.showLeftMenu(menu)
+          }
+        })
+      },
+      defaultLayout (val) {
+        if (this.defaultLayout === 'top') {
+          let needSetLeft = true
+          this.allMenuList.forEach((item) => {
+            if (item.id === this.topMenuActiveIndex) {
+              this.showLeftMenu(item)
+              needSetLeft = false
+            }
+          })
+          if (needSetLeft) {
+            this.topMenuActiveIndex = this.allMenuList[0].id
+            this.showLeftMenu(this.allMenuList[0])
+          }
+          this.fixTopMenu()
+        } else {
+          this.topMenuList = []
+          this.topHideMenuList = []
+          this.$store.commit('common/updateLeftMenuCategory', '')
+          this.$store.commit('common/updateLeftMenuList', this.allMenuList)
+        }
+      }
+    },
+    methods: {
+      fixTopMenu () {
+        let width = window.getComputedStyle(this.$refs.navbar).width
+        let size = (parseInt(width) - 700) / 124
+        this.topMenuList = []
+        this.topHideMenuList = []
+        this.allMenuList.forEach((item, index) => {
+          if (index < size - 1) {
+            this.topMenuList.push(item)
+          } else {
+            this.topHideMenuList.push(item)
+          }
+        })
+      },
+      showRight () {
+        this.$emit('showRight', true)
+      },
+      showLeftMenu (menu) {
+        this.$store.commit('common/updateLeftMenuList', menu.childNodes)
+        this.$store.commit('common/updateLeftMenuCategory', menu.name)
+      },
+      // 修改密码
+      updatePasswordHandle () {
+        this.updatePassowrdVisible = true
+        this.$nextTick(() => {
+          this.$refs.updatePassowrd.init()
+        })
+      },
+      // 退出
+      logoutHandle () {
+        this.$confirm(`确定进行[退出]操作?`, '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        }).then(() => {
+          this.$http({
+            url: '/sys/logout',
+            method: 'get'
+          }).then(({data}) => {
+            if (data && data.success) {
+              clearLoginInfo()
+              this.$router.replace({name: 'login'})
+            }
+          })
+        })
+      }
+    }
+  }
+</script>

+ 96 - 0
src/views/main.vue

@@ -0,0 +1,96 @@
+<template>
+  <div
+    class="jp-wrapper"
+    :class="{ 'jp-sidebar--fold': sidebarFold }">
+    <template>
+      <main-navbar ref="navbar" @showRight="showRight" />
+      <main-sidebar/>
+      <div class="jp-content__wrapper" :style="{ 'min-height': documentClientHeight + 'px' }">
+        <main-content/>
+      </div>
+      <main-right ref="mainRight"/>
+    </template>
+  </div>
+</template>
+
+<script>
+  import MainNavbar from './layout/_common_top'
+  import MainSidebar from './layout/_common_left'
+  import MainContent from './layout/_common_center'
+  import MainRight from './layout/_common_right'
+  export default {
+    data () {
+      return {
+        isRightVisible: false
+      }
+    },
+    components: {
+      MainNavbar,
+      MainSidebar,
+      MainContent,
+      MainRight
+    },
+    computed: {
+      documentClientHeight: {
+        get () {
+          return this.$store.state.common.documentClientHeight
+        },
+        set (val) {
+          this.$store.commit('common/updateDocumentClientHeight', val)
+        }
+      },
+      sidebarFold: {
+        get () {
+          return this.$store.state.common.sidebarFold
+        }
+      }
+    },
+    created () {
+    },
+    mounted () {
+      this.getUserInfo()
+      this.getConfig()
+      this.resetDocumentClientHeight()
+    },
+    methods: {
+      // 重置窗口可视高度
+      resetDocumentClientHeight () {
+        this.documentClientHeight = document.documentElement['clientHeight']
+        window.onresize = () => {
+          this.documentClientHeight = document.documentElement['clientHeight']
+          this.$refs.navbar.fixTopMenu()
+        }
+      },
+      showRight (flag) {
+        this.$refs.mainRight.showRight()
+        this.isRightVisible = flag
+      },
+      // 获取当前登录用户信息
+      getUserInfo () {
+        this.$http({
+          url: '/sys/user/info',
+          method: 'get'
+        }).then(({data}) => {
+          if (data.success) {
+            this.$store.commit('user/updateUser', data.user)
+          }
+        })
+      },
+      // 获取产品name 和 logo
+      getConfig () {
+        this.$http.get('/sys/sysConfig/queryById').then(({data}) => {
+          if (data.success) {
+            this.$store.commit('config/updateProductName', data.config.productName)
+            this.$store.commit('config/updateLogo', data.config.logo)
+            if (!localStorage.getItem('defaultLayout')) {
+              this.$store.commit('config/updateDefaultLayout', data.config.defaultLayout)
+            }
+            if (!localStorage.getItem('defaultTheme')) {
+              this.$store.commit('config/updateDefaultTheme', data.config.defaultTheme)
+            }
+          }
+        })
+      }
+    }
+  }
+</script>

+ 132 - 0
src/views/modules/calendar/MyCalendar.vue

@@ -0,0 +1,132 @@
+<template>
+<div>
+  <FullCalendar defaultView="dayGridMonth" locale="zh-cn" :header="{
+        left: 'prev,next today',
+        center: 'title',
+        right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
+      }"
+       schedulerLicenseKey='GPL-My-Project-Is-Open-Source'
+       :firstDay="1"
+       :editable="true"
+       :droppable="true"
+       :resizeable="true"
+       :selectable="true"
+       @dateClick="handleDateClick"
+       @eventClick="handleEventClick"
+       @eventResizeStop = "handleEventResize"
+      :events="calendarEvents"
+       @eventDrop="handelEventDrop"
+      :buttonText="buttonText"  :plugins="calendarPlugins" />
+      <MyCalendarForm :start="start" :end="end" ref="myCalendarForm" @refreshDataList="refreshList"></MyCalendarForm>
+</div>
+</template>
+<script>
+  import MyCalendarForm from './MyCalendarForm'
+  import FullCalendar from '@fullcalendar/vue'
+  import dayGridPlugin from '@fullcalendar/daygrid'
+  import timeGridPlugin from '@fullcalendar/timegrid'
+  import resourceTimelinePlugin from '@fullcalendar/resource-timeline'
+  import interactionPlugin from '@fullcalendar/interaction'
+  export default {
+    data () {
+      return {
+        start: new Date(),
+        end: new Date(),
+        calendarPlugins: [dayGridPlugin, interactionPlugin, resourceTimelinePlugin, timeGridPlugin],
+        buttonText: {
+          today: '今天',
+          month: '月',
+          week: '周',
+          day: '天'
+        },
+        calendarEvents: [
+          { title: 'event 1', date: '2019-04-01' },
+          { title: 'event 2', date: '2019-04-02' }
+        ]
+      }
+    },
+    components: {
+      FullCalendar,
+      dayGridPlugin,
+      interactionPlugin,
+      MyCalendarForm
+    },
+    activated () {
+      this.refreshList()
+    },
+    methods: { // 选择月份
+      handleDateClick (arg) {
+        debugger
+        this.start = this.moment(arg.dateStr).format('YYYY-MM-DD HH:mm:ss')
+        this.end = this.moment(arg.dateStr).format('YYYY-MM-DD HH:mm:ss')
+        this.$refs.myCalendarForm.init('add', '', this.start, this.end)
+      },
+      handleEventClick (info) {
+        this.$refs.myCalendarForm.init('edit', info.event.id)
+      },
+      handelEventDrop (info) {
+        this.$http.post('/myCalendar/drag', {
+          id: info.event.id,
+          daydiff: info.delta.days,
+          minudiff: info.delta.milliseconds
+        }).then(({data}) => {
+          if (data.success) {
+            this.$message.success(data.msg)
+          }
+        })
+      },
+      handleEventResize (info) {
+        this.$http.post('/myCalendar/resize', {
+          id: info.event.id,
+          daydiff: info.delta.days,
+          minudiff: info.delta.milliseconds
+        }).then(({data}) => {
+          if (data.success) {
+            this.$message.success(data.msg)
+          }
+        })
+      },
+      refreshList () {
+        this.$http.get('/myCalendar/findList').then(({data}) => {
+          this.calendarEvents = data.list
+        })
+      }
+    }
+  }
+</script>
+<style lang='less'>
+    //用什么插件必须引入相应的样式表,否则不能正常显示
+    @import '~@fullcalendar/core/main.css';
+    @import '~@fullcalendar/daygrid/main.css';
+    @import '~@fullcalendar/resource-timeline/main.css';
+
+
+    #external-events {
+        padding: 0 10px;
+        background: #eee;
+        text-align: left;
+    }
+
+    #external-events h4 {
+        font-size: 16px;
+        margin-top: 0;
+        padding-top: 1em;
+    }
+
+    #external-events .fc-event {
+        margin: 10px 0;
+        cursor: pointer;
+    }
+
+    #external-events p {
+        margin: 1.5em 0;
+        font-size: 11px;
+        color: #666;
+    }
+
+    #external-events p input {
+        margin: 0;
+        vertical-align: middle;
+    }
+
+</style>

+ 138 - 0
src/views/modules/calendar/MyCalendarForm.vue

@@ -0,0 +1,138 @@
+<template>
+<div>
+  <el-dialog
+    :title="title"
+    :close-on-click-modal="false"
+    :visible.sync="visible">
+    <el-form :model="inputForm" ref="inputForm" v-loading="loading" :class="method==='view'?'readonly':''" :disabled="method==='view'" @keyup.enter.native="doSubmit()"
+             label-width="120px" @submit.native.prevent>
+      <el-row  :gutter="15">
+        <el-col :span="16">
+            <el-form-item label="日程内容" prop="title"
+                :rules="[
+                  {required: true, message:'日程内容不能为空', trigger:'blur'}
+                 ]">
+			        <el-input v-model="inputForm.title" placeholder=""     ></el-input>
+	         </el-form-item>
+        </el-col>
+        <el-col :span="16">
+            <el-form-item label="开始时间" prop="start"
+                :rules="[
+                  {required: true, message:'开始时间不能为空', trigger:'blur'}
+                 ]">
+		            <el-date-picker
+                v-model="inputForm.start"
+                type="datetime"
+                 value-format="yyyy-MM-dd hh:mm:ss"
+                placeholder="选择日期时间">
+              </el-date-picker>
+	         </el-form-item>
+        </el-col>
+        <el-col :span="16">
+            <el-form-item label="结束时间" prop="end"
+                :rules="[
+                  {required: true, message:'结束时间不能为空', trigger:'blur'}
+                 ]">
+			         <el-date-picker
+                v-model="inputForm.end"
+                type="datetime"
+                 value-format="yyyy-MM-dd hh:mm:ss"
+                placeholder="选择日期时间">
+              </el-date-picker>
+	         </el-form-item>
+        </el-col>
+        </el-row>
+    </el-form>
+    <span slot="footer" class="dialog-footer">
+       <el-button type="danger" v-if="method === 'edit'"  @click="del">删除</el-button>
+      <el-button @click="visible = false">关闭</el-button>
+      <el-button type="primary" v-if="method != 'view'" @click="doSubmit()" v-noMoreClick>确定</el-button>
+    </span>
+  </el-dialog>
+</div>
+</template>
+
+<script>
+  export default {
+    data () {
+      return {
+        title: '',
+        method: '',
+        visible: false,
+        loading: false,
+        inputForm: {
+          id: '',
+          title: '',
+          start: '',
+          end: ''
+        }
+      }
+    },
+    components: {
+    },
+    methods: {
+      init (method, id, start, end) {
+        this.method = method
+        this.inputForm.id = id
+        if (method === 'add') {
+          this.title = `新建日程`
+        } else if (method === 'edit') {
+          this.title = '修改日程'
+        } else if (method === 'view') {
+          this.title = '查看日程'
+        }
+        this.visible = true
+        this.loading = false
+        this.$nextTick(() => {
+          this.$refs.inputForm.resetFields()
+          if (method === 'edit' || method === 'view') { // 修改或者查看
+            this.loading = true
+            this.$http({
+              url: `/myCalendar/queryById?id=${this.inputForm.id}`,
+              method: 'get'
+            }).then(({data}) => {
+              this.inputForm = this.recover(this.inputForm, data.myCalendar)
+              this.loading = false
+            })
+          } else {
+            this.inputForm.start = start
+            this.inputForm.end = end
+          }
+        })
+      },
+      del () {
+        this.loading = true
+        this.$http.delete(
+          `/myCalendar/del?id=${this.inputForm.id}`
+        ).then(({data}) => {
+          if (data && data.success) {
+            this.visible = false
+            this.$message.success(data.msg)
+            this.$emit('refreshDataList')
+          }
+          this.loading = false
+        })
+      },
+      // 表单提交
+      doSubmit () {
+        this.$refs['inputForm'].validate((valid) => {
+          if (valid) {
+            this.loading = true
+            this.$http({
+              url: `/myCalendar/save`,
+              method: 'post',
+              data: this.inputForm
+            }).then(({data}) => {
+              if (data && data.success) {
+                this.visible = false
+                this.$message.success(data.msg)
+                this.$emit('refreshDataList')
+              }
+              this.loading = false
+            })
+          }
+        })
+      }
+    }
+  }
+</script>

+ 177 - 0
src/views/modules/car/CarForm.vue

@@ -0,0 +1,177 @@
+<template>
+  <div>
+    <el-dialog
+      :title="title"
+      :close-on-click-modal="false"
+      :visible.sync="visible">
+      <el-form :model="inputForm" ref="inputForm" v-loading="loading" :class="method==='view'?'readonly':''"  :disabled="method==='view'"
+               label-width="120px">
+        <el-row  :gutter="15">
+          <el-col :span="12">
+            <el-form-item label="归属部门" prop="office.id"
+                          :rules="[
+                  {required: true, message:'归属部门不能为空', trigger:'blur'}
+                 ]">
+              <SelectTree
+                ref="office"
+                :props="{
+                          value: 'id',             // ID字段名
+                          label: 'name',         // 显示名称
+                          children: 'childNodes'    // 子级字段名
+                        }"
+
+                url="/sys/office/treeData?type=2"
+                :value="inputForm.office.id"
+                :clearable="true"
+                :accordion="true"
+                @getValue="(value) => {inputForm.office.id=value}"/>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="员工" prop="tuser.id"
+                          :rules="[
+                  {required: true, message:'员工不能为空', trigger:'blur'}
+                 ]">
+              <user-select :limit='1' :value="inputForm.tuser.id" @getValue='(value) => {inputForm.tuser.id=value}'></user-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="归属区域" prop="area"
+                          :rules="[
+                 ]">
+              <CityPicker
+                style="width:100%"
+                :value="inputForm.area"
+                :clearable="true"
+                :accordion="true"
+                @getValue="(value) => {inputForm.area=value}"/>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="请假开始日期" prop="beginDate"
+                          :rules="[
+                  {required: true, message:'请假开始日期不能为空', trigger:'blur'},
+                  {validator: validator.date, trigger:'blur'}
+                 ]">
+              <el-date-picker
+                style="width: 100%;"
+                v-model="inputForm.beginDate"
+                type="datetime"
+                value-format="yyyy-MM-dd HH:mm:ss"
+                placeholder="选择日期时间">
+              </el-date-picker>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="请假结束日期" prop="endDate"
+                          :rules="[
+                  {required: true, message:'请假结束日期不能为空', trigger:'blur'},
+                  {validator: validator.date, trigger:'blur'}
+                 ]">
+              <el-date-picker
+                style="width: 100%;"
+                v-model="inputForm.endDate"
+                type="datetime"
+                value-format="yyyy-MM-dd HH:mm:ss"
+                placeholder="选择日期时间">
+              </el-date-picker>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="备注信息" prop="remarks"
+                          :rules="[
+                 ]">
+              <el-input type="textarea" v-model="inputForm.remarks" placeholder="请填写备注信息"     ></el-input>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <span slot="footer" class="dialog-footer">
+      <el-button @click="visible = false">关闭</el-button>
+      <el-button type="primary" v-if="method != 'view'" @click="doSubmit()" v-noMoreClick>确定</el-button>
+    </span>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+  import SelectTree from '@/components/treeSelect/treeSelect.vue'
+  import UserSelect from '@/components/userSelect'
+  import CityPicker from '@/components/cityPicker'
+  export default {
+    data () {
+      return {
+        title: '',
+        method: '',
+        visible: false,
+        loading: false,
+        inputForm: {
+          id: '',
+          office: {
+            id: ''
+          },
+          tuser: {
+            id: ''
+          },
+          area: '',
+          beginDate: '',
+          endDate: '',
+          remarks: ''
+        }
+      }
+    },
+    components: {
+      SelectTree,
+      UserSelect,
+      CityPicker
+    },
+    methods: {
+      init (method, id) {
+        this.method = method
+        this.inputForm.id = id
+        if (method === 'add') {
+          this.title = `新建请假表单`
+        } else if (method === 'edit') {
+          this.title = '修改请假表单'
+        } else if (method === 'view') {
+          this.title = '查看请假表单'
+        }
+        this.visible = true
+        this.loading = false
+        this.$nextTick(() => {
+          this.$refs.inputForm.resetFields()
+          if (method === 'edit' || method === 'view') { // 修改或者查看
+            this.loading = true
+            this.$http({
+              url: `/test/one/leaveForm/queryById?id=${this.inputForm.id}`,
+              method: 'get'
+            }).then(({data}) => {
+              this.inputForm = this.recover(this.inputForm, data.leaveForm)
+              this.loading = false
+            })
+          }
+        })
+      },
+      // 表单提交
+      doSubmit () {
+        this.$refs['inputForm'].validate((valid) => {
+          if (valid) {
+            this.loading = true
+            this.$http({
+              url: `/test/one/leaveForm/save`,
+              method: 'post',
+              data: this.inputForm
+            }).then(({data}) => {
+              if (data && data.success) {
+                this.visible = false
+                this.$message.success(data.msg)
+                this.$emit('refreshDataList')
+              }
+              this.loading = false
+            })
+          }
+        })
+      }
+    }
+  }
+</script>

+ 337 - 0
src/views/modules/car/CarList.vue

@@ -0,0 +1,337 @@
+<template>
+  <div>
+      <el-form :inline="true" v-show="isSearchCollapse" class="query-form" ref="searchForm" :model="searchForm" @keyup.enter.native="refreshList()" @submit.native.prevent>
+            <!-- 搜索框-->
+		     <el-form-item prop="office.id">
+        		<SelectTree
+                  ref="office.id"
+                  :props="{
+                      value: 'id',             // ID字段名
+                      label: 'name',         // 显示名称
+                      children: 'childNodes'    // 子级字段名
+                    }"
+                   size="small"
+                  :url="`/sys/office/treeData`"
+                  :value="searchForm.office.id"
+                  :clearable="true"
+                  :accordion="true"
+                  @getValue="(value) => {searchForm.office.id=value}"/>
+		     </el-form-item>
+		     <el-form-item prop="tuser.id">
+		        <user-select :limit='1' size="small" :value="searchForm.tuser.id" @getValue='(value) => {searchForm.tuser.id=value}'></user-select>
+		     </el-form-item>
+		     <el-form-item prop="area">
+                  <CityPicker
+                     style="width:100%"
+                    size="small"
+                    :value="searchForm.area"
+                    :clearable="true"
+                    :accordion="true"
+                    @getValue="(value) => {searchForm.area=value}"/>
+		     </el-form-item>
+		     <el-form-item prop="beginDate">
+        	     <el-date-picker
+                    v-model="searchForm.beginDate"
+                    type="daterange"
+                    size="small"
+                    align="right"
+                    value-format="yyyy-MM-dd hh:mm:ss"
+                    unlink-panels
+                    range-separator="至"
+                    start-placeholder="开始日期"
+                    end-placeholder="结束日期">
+                 </el-date-picker>
+		     </el-form-item>
+		     <el-form-item prop="endDate">
+                <el-date-picker
+                  v-model="searchForm.endDate"
+                  type="datetime"
+                  size="small"
+                  value-format="yyyy-MM-dd HH:mm:ss"
+                  placeholder="选择日期时间">
+                </el-date-picker>
+		     </el-form-item>
+		     <el-form-item prop="remarks">
+                <el-input size="small" v-model="searchForm.remarks" placeholder="备注信息" clearable></el-input>
+		     </el-form-item>
+          <el-form-item>
+            <el-button type="primary" @click="refreshList()" size="small">查询</el-button>
+            <el-button @click="resetSearch()" size="small">重置</el-button>
+          </el-form-item>
+      </el-form>
+        <!-- 导入导出-->
+      <el-form :inline="true" v-show="isImportCollapse"  class="query-form" ref="importForm">
+         <el-form-item>
+          <el-button type="default" @click="downloadTpl()" size="small">下载模板</el-button>
+         </el-form-item>
+         <el-form-item prop="loginName">
+            <el-upload
+              class="upload-demo"
+              :action="`${this.$http.BASE_URL}/test/one/leaveForm/import`"
+              :on-success="uploadSuccess"
+               :show-file-list="true">
+              <el-button size="small" type="primary">点击上传</el-button>
+              <div slot="tip" class="el-upload__tip">只允许导入“xls”或“xlsx”格式文件!</div>
+            </el-upload>
+        </el-form-item>
+      </el-form>
+      <el-row>
+        <el-button v-if="hasPermission('test:one:leaveForm:add')" type="primary" size="small" icon="el-icon-plus" @click="add()">新建</el-button>
+        <el-button v-if="hasPermission('test:one:leaveForm:edit')" type="warning" size="small" icon="el-icon-edit-outline" @click="edit()"
+         :disabled="dataListSelections.length != 1" plain>修改</el-button>
+        <el-button v-if="hasPermission('test:one:leaveForm:del')" type="danger"   size="small" icon="el-icon-delete" @click="del()"
+                  :disabled="dataListSelections.length <= 0" plain>删除
+        </el-button>
+        <el-button-group class="pull-right">
+            <el-button
+              type="default"
+              size="small"
+              icon="el-icon-search"
+              @click="isSearchCollapse = !isSearchCollapse, isImportCollapse=false">
+            </el-button>
+            <el-button v-if="hasPermission('test:one:leaveForm:import')" type="default" size="small" icon="el-icon-upload2" title="导入" @click="isImportCollapse = !isImportCollapse, isSearchCollapse=false"></el-button>
+            <el-button v-if="hasPermission('test:one:leaveForm:export')" type="default" size="small" icon="el-icon-download" title="导出" @click="exportExcel()"></el-button>
+            <el-button
+              type="default"
+              size="small"
+              icon="el-icon-refresh"
+              @click="refreshList">
+            </el-button>
+        </el-button-group>
+      </el-row>
+    <el-table
+      :data="dataList"
+      border
+      size="medium"
+      @selection-change="selectionChangeHandle"
+      @sort-change="sortChangeHandle"
+      v-loading="loading"
+      class="table">
+      <el-table-column
+        type="selection"
+        header-align="center"
+        align="center"
+        width="50">
+      </el-table-column>
+      <el-table-column
+        prop="office.name"
+        show-overflow-tooltip
+        sortable="custom"
+        label="归属部门">
+            <template slot-scope="scope">
+              <el-link  type="primary" :underline="false" v-if="hasPermission('test:one:leaveForm:edit')" @click="edit(scope.row.id)">{{scope.row.office.name}}</el-link>
+              <el-link  type="primary" :underline="false" v-else-if="hasPermission('test:one:leaveForm:view')"  @click="view(scope.row.id)">{{scope.row.office.name}}</el-link>
+              <span v-else>{{scope.row.office.name}}</span>
+            </template>
+      </el-table-column>
+      <el-table-column
+        prop="tuser.name"
+        show-overflow-tooltip
+        sortable="custom"
+        label="员工">
+      </el-table-column>
+	  <el-table-column
+        prop="area"
+        show-overflow-tooltip
+        sortable="custom"
+        label="归属区域">
+      </el-table-column>
+	  <el-table-column
+        prop="beginDate"
+        show-overflow-tooltip
+        sortable="custom"
+        label="请假开始日期">
+      </el-table-column>
+	  <el-table-column
+        prop="endDate"
+        show-overflow-tooltip
+        sortable="custom"
+        label="请假结束日期">
+      </el-table-column>
+	  <el-table-column
+        prop="remarks"
+        show-overflow-tooltip
+        sortable="custom"
+        label="备注信息">
+      </el-table-column>
+      <el-table-column
+        header-align="center"
+        align="center"
+        fixed="right"
+        width="200"
+        label="操作">
+        <template  slot-scope="scope">
+          <el-button v-if="hasPermission('test:one:leaveForm:view')" type="text" icon="el-icon-view" size="small" @click="view(scope.row.id)">查看</el-button>
+          <el-button v-if="hasPermission('test:one:leaveForm:edit')" type="text" icon="el-icon-edit" size="small" @click="edit(scope.row.id)">修改</el-button>
+          <el-button v-if="hasPermission('test:one:leaveForm:del')" type="text"  icon="el-icon-delete" size="small" @click="del(scope.row.id)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <el-pagination
+      @size-change="sizeChangeHandle"
+      @current-change="currentChangeHandle"
+      :current-page="pageNo"
+      :page-sizes="[10, 20, 50, 100]"
+      :page-size="pageSize"
+      :total="total"
+      background
+      layout="total, sizes, prev, pager, next, jumper">
+    </el-pagination>
+        <!-- 弹窗, 新增 / 修改 -->
+    <CarForm  ref="CarForm" @refreshDataList="refreshList"></CarForm>
+  </div>
+</template>
+
+<script>
+  import CarForm from './CarForm'
+  import SelectTree from '@/components/treeSelect/treeSelect.vue'
+  import UserSelect from '@/components/userSelect'
+  import CityPicker from '@/components/cityPicker'
+  export default {
+    data () {
+      return {
+        searchForm: {
+          office: {
+            id: ''
+          },
+          tuser: {
+            id: ''
+          },
+          area: '',
+          beginDate: '',
+          endDate: '',
+          remarks: ''
+        },
+        dataList: [],
+        pageNo: 1,
+        pageSize: 10,
+        total: 0,
+        orderBy: '',
+        dataListSelections: [],
+        isSearchCollapse: false,
+        isImportCollapse: false,
+        loading: false
+      }
+    },
+    components: {
+      SelectTree,
+      UserSelect,
+      CityPicker,
+      CarForm
+    },
+    activated () {
+      this.refreshList()
+    },
+
+    methods: {
+      // 获取数据列表
+      refreshList () {
+        this.loading = true
+        this.$http({
+          url: '/test/one/leaveForm/list',
+          method: 'get',
+          params: {
+            'pageNo': this.pageNo,
+            'pageSize': this.pageSize,
+            'orderBy': this.orderBy,
+            beginBeginDate: this.searchForm.beginDate[0],
+            endBeginDate: this.searchForm.beginDate[1],
+            ...this.lodash.omit(this.searchForm, 'beginDate')
+          }
+        }).then(({data}) => {
+          if (data && data.success) {
+            this.dataList = data.page.list
+            this.total = data.page.count
+            this.loading = false
+          }
+        })
+      },
+      // 每页数
+      sizeChangeHandle (val) {
+        this.pageSize = val
+        this.pageNo = 1
+        this.refreshList()
+      },
+      // 当前页
+      currentChangeHandle (val) {
+        this.pageNo = val
+        this.refreshList()
+      },
+      // 多选
+      selectionChangeHandle (val) {
+        this.dataListSelections = val
+      },
+
+    // 排序
+      sortChangeHandle (obj) {
+        if (obj.order === 'ascending') {
+          this.orderBy = obj.prop + ' asc'
+        } else if (obj.order === 'descending') {
+          this.orderBy = obj.prop + ' desc'
+        } else {
+          this.orderBy = ''
+        }
+        this.refreshList()
+      },
+      // 新增
+      add () {
+        this.$refs.CarForm.init('add', '')
+      },
+      // 修改
+      edit (id) {
+        id = id || this.dataListSelections.map(item => {
+          return item.id
+        })[0]
+        this.$refs.CarForm.init('edit', id)
+      },
+      // 查看
+      view (id) {
+        this.$refs.CarForm.init('view', id)
+      },
+      // 删除
+      del (id) {
+        let ids = id || this.dataListSelections.map(item => {
+          return item.id
+        }).join(',')
+        this.$confirm(`确定删除所选项吗?`, '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        }).then(() => {
+          this.loading = true
+          this.$http({
+            url: '/test/one/leaveForm/delete',
+            method: 'delete',
+            params: {'ids': ids}
+          }).then(({data}) => {
+            if (data && data.success) {
+              this.$message.success(data.msg)
+              this.refreshList()
+            }
+            this.loading = false
+          })
+        })
+      },
+      // 导入成功
+      uploadSuccess (res, file) {
+        if (res.success) {
+          this.$message.success({dangerouslyUseHTMLString: true,
+            message: res.msg})
+        } else {
+          this.$message.error(res.msg)
+        }
+      },
+      // 下载模板
+      downloadTpl () {
+        this.$utils.download('/test/one/leaveForm/import/template')
+      },
+      exportExcel () {
+        this.$utils.download('/test/one/leaveForm/export')
+      },
+      resetSearch () {
+        this.$refs.searchForm.resetFields()
+        this.refreshList()
+      }
+    }
+  }
+</script>

+ 11 - 0
src/views/modules/car/CarProject.vue

@@ -0,0 +1,11 @@
+<template>
+
+</template>
+
+<script>
+
+</script>
+
+<style>
+
+</style>

+ 406 - 0
src/views/modules/code/Guide.vue

@@ -0,0 +1,406 @@
+<template>
+  <div>
+    <el-row :gutter="10">
+      <el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" v-for="item in dataList" :key="item.id" style="padding-bottom:10px">
+        <el-card class="box-card jp-card jp-card-bordered" shadow="hover" >
+            <div class="">
+                <div class="jp-card-cover1">
+                    <i :class="item.icon" style="font-size: 64px;color:#409EFF;margin-right:20px"></i>
+                    <font style="font-weight: 700"> {{item.name}}</font>
+                </div>
+
+                <div class="jp-card-body">
+                    <div class="jp-card-meta">
+                        <div class="jp-card-meta-detail">
+                            <div class="jp-card-meta-description">{{item.remarks}}</div>
+                        </div>
+                    </div>
+                </div>
+                <ul class="jp-card-actions">
+                    <li style="width: 100%">
+                      <el-tooltip :content="item.tip" placement="top">
+                          <router-link :to="item.path" :disabled="item.disabled">
+                            <i class="fa fa-folder-open-o" :style="`${item.disabled?'color:grey':''}`"></i>
+                          </router-link>
+                      </el-tooltip>
+                    </li>
+                </ul>
+            </div>
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+  export default {
+    data () {
+      return {
+        dataList: [
+          {
+            name: '代码生成',
+            tip: '打开代码生成器',
+            remarks: '通过设置生成增删改查功能。',
+            icon: 'fa fa-code',
+            disabled: false,
+            path: '/gen/GenTableList'
+          },
+          {
+            name: '图表开发',
+            tip: '即将上线,敬请期待',
+            remarks: '快速可视化开发图表',
+            icon: 'fa fa-area-chart',
+            disabled: true,
+            path: ''
+
+          },
+          {
+            name: '报表开发',
+            tip: '打开报表设计器',
+            remarks: '通过设置快速开发报表。',
+            icon: 'fa fa-file-excel-o',
+            disabled: false,
+            path: '/reports/index'
+
+          },
+          {
+            name: '表单开发',
+            tip: '打开表单设计器',
+            remarks: '通过设置快速开发自定义表单。',
+            icon: 'fa fa-wpforms',
+            disabled: false,
+            path: '/form/MakeFormList'
+          },
+          {
+            name: '仪表盘开发',
+            tip: '即将上线,敬请期待',
+            remarks: '通过设置快速开发仪表盘。',
+            icon: 'fa fa-dashboard',
+            disabled: true,
+            path: ''
+          },
+          {
+            name: '数据管理',
+            tip: '打开数据管理',
+            remarks: '通过sql语句快速创建模型。',
+            icon: 'fa fa-database',
+            disabled: false,
+            path: '/database/datamodel/DataSetList'
+          }
+        ]
+      }
+    },
+    methods: {
+      // 新增
+      add () {
+        this.$refs.templateForm.init('add', '')
+      }
+    }
+  }
+</script>
+<style>
+
+.jp-card {
+    /* font-family: "Helvetica Neue", Helvetica, Tahoma, Arial, "Microsoft Yahei", "Hiragino Sans GB", "WenQuanYi Micro Hei", sans-serif; */
+    font-size: 14px;
+    line-height: 1.5;
+    color: rgba(0, 0, 0, .65);
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+    margin: 0;
+    padding: 0;
+    list-style: none;
+    background: #fff;
+    border-radius: 2px;
+    position: relative;
+    -webkit-transition: all .3s;
+    transition: all .3s
+}
+.jp-card .el-card__body {
+   padding: 0px !important;
+}
+.jp-card-hoverable {
+    cursor: pointer
+}
+
+.jp-card-hoverable:hover {
+    -webkit-box-shadow: 0 2px 8px rgba(0, 0, 0, .09);
+    box-shadow: 0 2px 8px rgba(0, 0, 0, .09);
+    border-color: rgba(0, 0, 0, .09)
+}
+
+.jp-card-bordered.el-card {
+    border: 1px solid #ccc
+}
+
+.jp-card-head {
+    background: #fff;
+    border-bottom: 1px solid #e8e8e8;
+    padding: 0 24px;
+    border-radius: 2px 2px 0 0;
+    zoom: 1;
+    margin-bottom: -1px;
+    min-height: 48px
+}
+
+.jp-card-head:after, .jp-card-head:before {
+    content: "";
+    display: table
+}
+
+.jp-card-head:after {
+    clear: both
+}
+
+.jp-card-head-wrapper {
+    display: -ms-flexbox;
+    display: flex
+}
+
+.jp-card-head-title {
+    font-size: 16px;
+    padding: 16px 0;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    white-space: nowrap;
+    color: rgba(0, 0, 0, .85);
+    font-weight: 500;
+    display: inline-block;
+    -ms-flex: 1 1;
+    flex: 1 1
+}
+
+.jp-card-head .jp-tabs {
+    margin-bottom: -17px;
+    clear: both
+}
+
+.jp-card-head .jp-tabs-bar {
+    border-bottom: 1px solid #e8e8e8
+}
+
+.jp-card-extra {
+    float: right;
+    padding: 17.5px 0;
+    text-align: right;
+    margin-left: auto
+}
+
+.jp-card-body {
+    padding: 24px;
+    zoom: 1
+}
+
+.jp-card-body:after, .jp-card-body:before {
+    content: "";
+    display: table
+}
+
+.jp-card-body:after {
+    clear: both
+}
+
+.jp-card-contain-grid:not(.jp-card-loading) {
+    margin: -1px 0 0 -1px;
+    padding: 0
+}
+
+.jp-card-grid {
+    border-radius: 0;
+    border: 0;
+    -webkit-box-shadow: 1px 0 0 0 #e8e8e8, 0 1px 0 0 #e8e8e8, 1px 1px 0 0 #e8e8e8, 1px 0 0 0 #e8e8e8 inset, 0 1px 0 0 #e8e8e8 inset;
+    box-shadow: 1px 0 0 0 #e8e8e8, 0 1px 0 0 #e8e8e8, 1px 1px 0 0 #e8e8e8, inset 1px 0 0 0 #e8e8e8, inset 0 1px 0 0 #e8e8e8;
+    width: 33.33%;
+    float: left;
+    padding: 24px;
+    -webkit-transition: all .3s;
+    transition: all .3s
+}
+
+.jp-card-grid:hover {
+    position: relative;
+    z-index: 1;
+    -webkit-box-shadow: 0 2px 8px rgba(0, 0, 0, .15);
+    box-shadow: 0 2px 8px rgba(0, 0, 0, .15)
+}
+
+.jp-card-contain-tabs .jp-card-head-title {
+    padding-bottom: 0;
+    min-height: 32px
+}
+
+.jp-card-contain-tabs .jp-card-extra {
+    padding-bottom: 0
+}
+
+.jp-card-cover > * {
+    width: 100%;
+    display: block
+}
+.jp-card-cover1{
+  padding: 20px;
+}
+
+.jp-card-actions {
+    border-top: 1px solid #e8e8e8;
+    background: #f5f8fa;
+    zoom: 1;
+    list-style: none;
+    margin: 0;
+    padding: 0
+}
+
+.jp-card-actions:after, .jp-card-actions:before {
+    content: "";
+    display: table
+}
+
+.jp-card-actions:after {
+    clear: both
+}
+
+.jp-card-actions > li {
+    float: left;
+    text-align: center;
+    margin: 12px 0;
+    color: rgba(0, 0, 0, .45)
+}
+
+.jp-card-actions > li > span {
+    display: inline-block;
+    font-size: 14px;
+    cursor: pointer;
+    line-height: 22px;
+    min-width: 32px;
+    position: relative
+}
+
+.jp-card-actions > li > span:hover {
+    color: #1890ff;
+    -webkit-transition: color .3s;
+    transition: color .3s
+}
+
+.jp-card-actions > li > span > .jp-icon {
+    font-size: 16px;
+    line-height: 22px;
+    display: block;
+    width: 100%
+}
+
+.jp-card-actions > li > span a {
+    color: rgba(0, 0, 0, .45);
+    line-height: 22px;
+    display: inline-block;
+    width: 100%
+}
+
+.jp-card-actions > li > span a:hover {
+    color: #1890ff
+}
+
+.jp-card-actions > li:not(:last-child) {
+    border-right: 1px solid #e8e8e8
+}
+
+.jp-card-wider-padding .jp-card-head {
+    padding: 0 32px
+}
+
+.jp-card-wider-padding .jp-card-body {
+    padding: 24px 32px
+}
+
+.jp-card-padding-transition .jp-card-body, .jp-card-padding-transition .jp-card-head {
+    -webkit-transition: padding .3s;
+    transition: padding .3s
+}
+
+.jp-card-type-inner .jp-card-head {
+    padding: 0 24px;
+    background: #fafafa
+}
+
+.jp-card-type-inner .jp-card-head-title {
+    padding: 12px 0;
+    font-size: 14px
+}
+
+.jp-card-type-inner .jp-card-body {
+    padding: 16px 24px
+}
+
+.jp-card-type-inner .jp-card-extra {
+    padding: 13.5px 0
+}
+
+.jp-card-meta {
+    margin: -4px 0;
+    zoom: 1
+}
+
+.jp-card-meta:after, .jp-card-meta:before {
+    content: "";
+    display: table
+}
+
+.jp-card-meta:after {
+    clear: both
+}
+
+.jp-card-meta-avatar {
+    padding-right: 16px;
+    float: left
+}
+
+.jp-card-meta-detail {
+    overflow: hidden
+}
+
+.jp-card-meta-detail > div:not(:last-child) {
+    margin-bottom: 8px
+}
+
+.jp-card-meta-title {
+    font-size: 16px;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    white-space: nowrap;
+    color: rgba(0, 0, 0, .85);
+    font-weight: 500
+}
+
+.jp-card-meta-description {
+    color: rgba(0, 0, 0, .45)
+}
+
+.jp-card-loading .jp-card-body {
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none
+}
+
+.jp-card-loading-content p {
+    margin: 0
+}
+
+.jp-card-loading-block {
+    height: 14px;
+    margin: 4px 0;
+    border-radius: 2px;
+    background: -webkit-gradient(linear, left top, right top, from(rgba(207, 216, 220, .2)), color-stop(rgba(207, 216, 220, .4)), to(rgba(207, 216, 220, .2)));
+    background: linear-gradient(90deg, rgba(207, 216, 220, .2), rgba(207, 216, 220, .4), rgba(207, 216, 220, .2));
+    -webkit-animation: card-loading 1.4s ease infinite;
+    animation: card-loading 1.4s ease infinite;
+    background-size: 600% 600%
+}
+.jp-card{
+    position: relative;
+    float: left;
+    width: 300px;
+    border: 1px solid #ccc;
+    margin: 20px 0 20px 20px;
+}
+
+</style>

+ 339 - 0
src/views/modules/database/datamodel/DataSetForm.vue

@@ -0,0 +1,339 @@
+<template>
+<div>      
+      <el-row :gutter="20" v-loading="loading">
+        <el-col :span="24">
+           <el-row :gutter="20">
+              <el-col :span="12"><h3>数据模型配置</h3></el-col>
+               <el-col :span="12" style="text-align:right">
+                  <el-button v-if="$route.query.method!='view'" type="primary" @click="doSubmit" size="small">保存数据源</el-button>
+                  <el-button type="info" size="small" @click="goBack">返回</el-button>
+                </el-col>
+           </el-row>
+        </el-col>
+        <el-col :span="12">
+          <el-form  :model="inputForm" ref="inputForm" v-loading="loading" label-width="150px">
+                        <el-form-item label="目标数据库" prop="db.id"
+                            :rules="[
+                              {required: true, message:'目标数据库不能为空', trigger:'blur'}
+                            ]">
+                          <SelectTree
+                            ref="db"
+                            :props="{
+                                value: 'id',             // ID字段名
+                                label: 'label',         // 显示名称
+                                children: 'childNodes'    // 子级字段名
+                              }"
+
+                            url="/database/datalink/dataSource/treeData2"
+                            :value="inputForm.db.id"
+                            :clearable="true"
+                            :accordion="true"
+                            @getValue="(value) => {inputForm.db.id=value}"/>
+                      </el-form-item>
+                        <el-form-item label="数据源名称" prop="name"
+                            :rules="[
+                              {required: true, message:'数据源名称不能为空', trigger:'blur'}
+                            ]">
+                          <el-input v-model="inputForm.name" placeholder="请填写数据源名称"     ></el-input>
+                      </el-form-item>
+                        <el-form-item label="sql语句" prop="sqlcmd"
+                            :rules="[
+                              {required: true, message:'sql语句不能为空', trigger:'blur'}
+                            ]">
+                          <editor v-model="inputForm.sqlcmd" @init="editorInitSQL" lang="sql" height="200px" style="border: 1px solid #d9d9d9;"></editor>
+                      </el-form-item>
+          </el-form>
+        </el-col>
+        <el-col :span="12">
+           <el-table
+            :data="paramForm.tableData"
+            max-height="250">
+             <el-table-column
+              type="index"
+              width="50">
+            </el-table-column>
+            <el-table-column
+              prop="field"
+              label="参数名">
+               <template slot-scope="scope">
+                <el-input v-model="scope.row.field" placeholder="请输入内容"></el-input>
+              </template>
+            </el-table-column>
+            <el-table-column
+              prop="defaultValue"
+              label="默认值">
+               <template slot-scope="scope">
+                <el-input v-model="scope.row.defaultValue" placeholder="请输入内容"></el-input>
+              </template>
+            </el-table-column>
+            <el-table-column
+              fixed="right"
+              label="操作">
+              <template slot-scope="scope">
+                <el-button
+                  @click.native.prevent="deleteRow(scope.$index, paramForm.tableData)"
+                  type="text"
+                  size="small">
+                  移除
+                </el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+           <el-button type="primary" size="small" @click="addRow" style="margin-top:10px;margin-bottom:10px">增加参数</el-button>
+           <el-alert
+            title="SQL中添加参数的方式:格式:{#参数名#},示例:select * from table where id = '{#ID#}'"
+            :closable="false"
+            type="success">
+          </el-alert>
+          <el-button type="primary" @click="doPreviewTable" size="small" style="margin-top:10px;margin-bottom:10px"><i class="fa fa-eye"></i> 解析</el-button>
+          <el-button type="primary" @click="doPreviewData" size="small" style="margin-top:10px;margin-bottom:10px"> <i class="fa fa-eye"></i> 预览数据</el-button>
+        </el-col>
+        <el-col :span="24">
+          <h3>数据源列配置</h3>
+           <el-table
+            :data="columnForm.columnList"
+            style="width: 100%">
+            <el-table-column
+              prop="name"
+              label="字段名"
+              >
+            </el-table-column>
+            <el-table-column
+              prop="type"
+              label="类型"
+              >
+            </el-table-column>
+            <el-table-column
+              prop="label"
+              label="标签">
+              <template slot-scope="scope">
+                <el-input v-model="scope.row.label"></el-input>
+              </template>
+            </el-table-column>
+             <el-table-column>
+                 <template slot="header" slot-scope="scope">
+                    <el-checkbox v-model="columnForm.isNeedAll" :indeterminate="isIndeterminate"  @change="handleCheckAllChange">参与分析</el-checkbox>
+                </template>
+                <template slot-scope="scope">
+                     <el-checkbox v-model="scope.row.isNeed" @change="handleCheckedNeedChange"></el-checkbox>
+                </template>
+            </el-table-column>
+          </el-table>
+        </el-col>
+      </el-row>
+
+
+    <el-dialog title="数据预览" :visible.sync="previewVisible">
+     <el-tabs type="border-card">
+        <el-tab-pane label="HTML">
+              <el-table
+                :data="previewData.json"
+                style="height:500px; width: 100%">
+                <el-table-column
+                  v-for="(value, key, index) in previewData.column"
+                  :key = "index"
+                  :prop="key"
+                  :label="key"
+                  width="180">
+                </el-table-column>
+              </el-table>
+        </el-tab-pane>
+        <el-tab-pane label="JSON">
+            <editor v-model="previewData.json_str" @init="editorInitJSON" lang="json" theme="twilight" width="100%" height="500px"></editor>
+        </el-tab-pane>
+        <el-tab-pane label="XML">
+            <editor v-model="previewData.xml" @init="editorInitXML" lang="xml" theme="twilight" width="100%" height="500px"></editor>
+        </el-tab-pane>
+      </el-tabs>
+    </el-dialog>
+</div>
+</template>
+
+<script>
+  import omit from 'lodash.omit'
+  import SelectTree from '@/components/treeSelect/treeSelect.vue'
+  export default {
+    data () {
+      return {
+        title: '',
+        method: '',
+        loading: false,
+        previewVisible: false,
+        isIndeterminate: false,
+        inputForm: {
+          id: '',
+          db: {
+            id: ''
+          },
+          name: '',
+          sqlcmd: ''
+        },
+        paramForm: {
+          tableData: []
+        },
+        previewData: {
+          column: [],
+          html: '',
+          json: [],
+          json_str: '',
+          xml: ''
+        },
+        columnForm: {
+          isNeedAll: true,
+          columnList: []
+        }
+      }
+    },
+    activated () {
+      this.inputForm.id = this.$route.query.id
+      this.$http({
+        url: `/database/datamodel/dataSet/queryById?id=${this.inputForm.id}`,
+        method: 'get'
+      }).then(({data}) => {
+        this.inputForm = this.recover(this.inputForm, data.dataSet)
+        this.columnForm.columnList = data.dataSet.columnList
+        this.paramForm.tableData = data.dataSet.params
+      })
+    },
+    components: {
+      SelectTree,
+      editor: require('vue2-ace-editor')
+    },
+    methods: {
+      deleteRow (index, rows) {
+        rows.splice(index, 1)
+      },
+      addRow () {
+        this.paramForm.tableData.push({field: '', defaultValue: ''})
+      },
+      doPreviewData: function () {
+        let fields = this.paramForm.tableData.map((row) => {
+          return row.field
+        })
+        let defaultValues = this.paramForm.tableData.map((row) => {
+          return row.defaultValue
+        })
+        defaultValues.push('')
+        this.$http({
+          url: '/database/datamodel/dataSet/exec',
+          method: 'post',
+          headers: {arrayFormat: 'brackets'},
+          data: {
+            db: this.inputForm.db.id,
+            sql: this.inputForm.sqlcmd,
+            field: fields,
+            defaultValue: defaultValues
+          }
+        }).then(({data}) => {
+          this.previewData.json = data.json
+          this.previewData.json_str = JSON.stringify(data.json)
+          this.previewData.xml = data.xml
+          this.previewData.column = data.json[0] || { }
+          this.previewVisible = true
+        })
+      },
+      doPreviewTable () {
+        let fields = this.paramForm.tableData.map((row) => {
+          return row.field
+        })
+        let defaultValues = this.paramForm.tableData.map((row) => {
+          return row.defaultValue
+        })
+        defaultValues.push('')
+        this.$http({
+          url: '/database/datamodel/dataSet/getMeta',
+          method: 'post',
+          headers: {arrayFormat: 'brackets'},
+          data: {
+            db: this.inputForm.db.id,
+            sql: this.inputForm.sqlcmd,
+            field: fields,
+            defaultValue: defaultValues
+          }
+        }).then(({data}) => {
+          this.columnForm.isNeedAll = true
+          data.columnList.forEach((column) => {
+            column.isNeed = true
+          })
+          this.columnForm.columnList = JSON.parse(JSON.stringify(data.columnList)).map((column) => {
+            return this.columnForm.columnList.filter((oldColumn) => {
+              return oldColumn.name === column.name
+            })[0] || column
+          })
+        })
+      },
+      handleCheckAllChange (val) {
+        this.columnForm.columnList.forEach((column) => {
+          column.isNeed = val
+        })
+        this.columnForm.columnList = JSON.parse(JSON.stringify(this.columnForm.columnList))
+        this.isIndeterminate = false
+      },
+      handleCheckedNeedChange (val) {
+        let trueCount = 0
+        let falseCount = 0
+        this.columnForm.columnList.forEach((column) => {
+          if (column.isNeed) {
+            trueCount++
+          } else {
+            falseCount++
+          }
+        })
+        this.isIndeterminate = trueCount > 0 && falseCount > 0
+        this.columnForm.isNeedAll = trueCount > 0
+      },
+      editorInitXML: function () {
+        require('brace/ext/language_tools') // language extension prerequsite...
+        require('brace/mode/xml')
+        require('brace/theme/twilight')
+      },
+      editorInitSQL: function () {
+        require('brace/ext/language_tools') // language extension prerequsite...
+        require('brace/mode/sql')
+        require('brace/theme/chrome')
+      },
+      editorInitJSON: function () {
+        require('brace/ext/language_tools') // language extension prerequsite...
+        require('brace/mode/json')
+        require('brace/theme/twilight')
+      },
+      // 表单提交
+      doSubmit () {
+        this.$refs['inputForm'].validate((valid) => {
+          if (valid) {
+            this.loading = true
+            let params = []
+            let columnList = []
+            this.paramForm.tableData.forEach((item, index) => {
+              item.sort = index
+              params.push(omit(item, 'id'))
+            })
+            this.columnForm.columnList.forEach((item, index) => {
+              item.sort = index
+              columnList.push(omit(item, 'id'))
+            })
+            this.$http({
+              url: `/database/datamodel/dataSet/save`,
+              method: 'post',
+              data: {
+                ...this.inputForm,
+                params: params,
+                columnList: columnList
+
+              }
+            }).then(({data}) => {
+              if (data && data.success) {
+                this.loading = false
+                this.goBack()
+              }
+            })
+          }
+        })
+      },
+      goBack () {
+        this.$events.$emit('closeTab', this.$route.fullPath)
+        this.$router.push('/database/datamodel/DataSetList')
+      }
+    }
+  }
+</script>

+ 244 - 0
src/views/modules/database/datamodel/DataSetList.vue

@@ -0,0 +1,244 @@
+<template>
+  <div>
+      <el-form :inline="true" v-show="isSearchCollapse" class="query-form" ref="searchForm" :model="searchForm" @keyup.enter.native="refreshList()" @submit.native.prevent>
+            <!-- 搜索框-->
+		     <el-form-item prop="db.id">
+                <el-input size="small" v-model="searchForm.db.id" placeholder="目标数据库" clearable></el-input>
+		     </el-form-item>
+		     <el-form-item prop="name">
+                <el-input size="small" v-model="searchForm.name" placeholder="数据源名称" clearable></el-input>
+		     </el-form-item>
+          <el-form-item>
+            <el-button  type="primary" @click="refreshList()" size="small">查询</el-button>
+            <el-button @click="resetSearch()" size="small">重置</el-button>
+          </el-form-item>
+      </el-form>
+      <el-row>
+        <el-button v-if="hasPermission('database:datamodel:dataSet:add')" type="primary" size="small" icon="el-icon-plus" @click="add()">新建</el-button>
+        <el-button v-if="hasPermission('database:datamodel:dataSet:edit')" type="success" size="small" icon="el-icon-edit-outline" @click="edit()"
+         :disabled="dataListSelections.length != 1" plain>修改</el-button>
+        <el-button v-if="hasPermission('database:datamodel:dataSet:del')" type="danger"   size="small" icon="el-icon-delete" @click="del()"
+                  :disabled="dataListSelections.length <= 0" plain>删除
+        </el-button>
+        <el-button-group class="pull-right">
+          <el-tooltip class="item" effect="dark" content="搜索" placement="top">
+            <el-button 
+              type="default"
+              size="small"
+              icon="el-icon-search"
+              @click="isSearchCollapse = !isSearchCollapse, isImportCollapse=false">
+            </el-button>
+          </el-tooltip>
+          <el-tooltip class="item" effect="dark" content="刷新" placement="top">
+            <el-button 
+              type="default"
+              size="small"
+              icon="el-icon-refresh"
+              @click="refreshList">
+            </el-button>
+          </el-tooltip>     
+        </el-button-group>
+      </el-row>
+    <el-table
+      :data="dataList"
+      border
+      @selection-change="selectionChangeHandle"
+      @sort-change="sortChangeHandle"
+      v-loading="loading"
+      size = "medium"
+      class="table">
+      <el-table-column
+        type="selection"
+        header-align="center"
+        align="center"
+        width="50">
+      </el-table-column>
+      <el-table-column
+        prop="name"
+        sortable="custom"
+        label="数据源名称">
+        <template slot-scope="scope">
+          <el-link  type="primary" :underline="false"  v-if="hasPermission('database:datamodel:dataSet:edit')" @click="edit(scope.row.id)">{{scope.row.name}}</el-link>
+          <el-link  type="primary" :underline="false"  v-else-if="hasPermission('database:datamodel:dataSet:view')"  @click="view(scope.row.id)">{{scope.row.name}}</el-link>
+          <span v-else>{{scope.row.name}}</span>
+        </template>
+      </el-table-column>
+	  <el-table-column
+        prop="db.name"
+        sortable="custom"
+        label="目标数据库">
+      </el-table-column>
+	  <el-table-column
+        prop="sqlcmd"
+        sortable="custom"
+        show-overflow-tooltip
+        label="sql语句">
+      </el-table-column>
+      <el-table-column
+        header-align="center"
+        align="center"
+        width="300"
+        label="操作">
+        <template  slot-scope="scope">
+          <el-button v-if="hasPermission('database:datamodel:dataSet:view')" type="text" icon="el-icon-view" size="mini" @click="view(scope.row.id)">查看</el-button>
+          <el-button v-if="hasPermission('database:datamodel:dataSet:edit')" type="text" icon="el-icon-edit" size="mini" @click="edit(scope.row.id)">修改</el-button>
+          <el-button type="text" size="mini" icon="el-icon-coin" @click="getDbInterface(scope.row.id)">获取数据接口</el-button>
+          <el-button v-if="hasPermission('database:datamodel:dataSet:del')" type="text" size="mini" icon="el-icon-delete" @click="del(scope.row.id)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <el-pagination
+      @size-change="sizeChangeHandle"
+      @current-change="currentChangeHandle"
+      :current-page="pageNo"
+      :page-sizes="[10, 20, 50, 100]"
+      :page-size="pageSize"
+      :total="total"
+      background
+      layout="total, sizes, prev, pager, next, jumper">
+    </el-pagination>
+
+    <el-dialog title="数据接口" :visible.sync="dialogInterfaceVisible">
+      <el-table :data="interfaceTable">
+        <el-table-column property="type" width="120px" label="接口格式"></el-table-column>
+        <el-table-column property="url" label="接口地址"></el-table-column>
+      </el-table>
+      <br>
+      <h4> 接口传递参数说明:/接口url?参数名=参数值, 如果不传递参数,则使用默认值</h4>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+  export default {
+    data () {
+      return {
+        searchForm: {
+          db: {
+            id: '',
+            name: ''
+          },
+          name: ''
+        },
+        dataList: [],
+        pageNo: 1,
+        pageSize: 10,
+        total: 0,
+        orderBy: '',
+        dataListSelections: [],
+        isSearchCollapse: false,
+        isImportCollapse: false,
+        dialogInterfaceVisible: false,
+        interfaceTable: [],
+        loading: false
+      }
+    },
+    activated () {
+      this.refreshList()
+    },
+
+    methods: {
+      // 获取数据列表
+      refreshList () {
+        this.loading = true
+        this.$http({
+          url: '/database/datamodel/dataSet/list',
+          method: 'get',
+          params: {
+            'pageNo': this.pageNo,
+            'pageSize': this.pageSize,
+            'orderBy': this.orderBy,
+            ...this.searchForm
+          }
+        }).then(({data}) => {
+          if (data && data.success) {
+            this.dataList = data.page.list
+            this.total = data.page.count
+            this.loading = false
+          }
+        })
+      },
+      // 每页数
+      sizeChangeHandle (val) {
+        this.pageSize = val
+        this.pageNo = 1
+        this.refreshList()
+      },
+      // 当前页
+      currentChangeHandle (val) {
+        this.pageNo = val
+        this.refreshList()
+      },
+      // 多选
+      selectionChangeHandle (val) {
+        this.dataListSelections = val
+      },
+
+    // 排序
+      sortChangeHandle (obj) {
+        if (obj.order === 'ascending') {
+          this.orderBy = obj.prop + ' asc'
+        } else if (obj.order === 'descending') {
+          this.orderBy = obj.prop + ' desc'
+        } else {
+          this.orderBy = ''
+        }
+        this.refreshList()
+      },
+      // 新增
+      add () {
+        this.$router.push('/database/datamodel/DataSetForm')
+      },
+      // 修改
+      edit (id) {
+        id = id || this.dataListSelections.map(item => {
+          return item.id
+        })[0]
+        this.$router.push({path: `/database/datamodel/DataSetForm`, query: {id: id, title: '数据模型配置'}})
+      },
+      // 查看
+      view (id) {
+        id = id || this.dataListSelections.map(item => {
+          return item.id
+        })[0]
+        this.$router.push({path: `/database/datamodel/DataSetForm`, query: {method: 'view', id: id, title: '数据模型配置'}})
+      },
+      // 删除
+      del (id) {
+        let ids = id || this.dataListSelections.map(item => {
+          return item.id
+        }).join(',')
+        this.$confirm(`确定删除所选项吗?`, '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        }).then(() => {
+          this.loading = true
+          this.$http({
+            url: '/database/datamodel/dataSet/delete',
+            method: 'delete',
+            params: {'ids': ids}
+          }).then(({data}) => {
+            if (data && data.success) {
+              this.$message.success(data.msg)
+              this.loading = false
+              this.refreshList()
+            }
+          })
+        })
+      },
+      // 导入成功
+      getDbInterface (id) {
+        this.dialogInterfaceVisible = true
+        this.interfaceTable = []
+        this.interfaceTable.push({type: 'JSON', url: `/database/datamodel/dataSet/getData/${id}/json`})
+        this.interfaceTable.push({type: 'XML', url: `/database/datamodel/dataSet/getData/${id}/xml`})
+        this.interfaceTable.push({type: 'TABLE', url: `/database/datamodel/dataSet/getData/${id}/html`})
+      },
+      resetSearch () {
+        this.$refs.searchForm.resetFields()
+        this.refreshList()
+      }
+    }
+  }
+</script>

+ 0 - 0
src/views/modules/database/link/DataSourceForm.vue


Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác