lex-xin 2 years ago
parent
commit
2fa3401434
31 changed files with 1524 additions and 1350 deletions
  1. 1 1
      package.json
  2. 214 214
      public/index.html
  3. 91 2
      src/components/VueFormMaking/components/GenerateFormItem.vue
  4. 101 95
      src/layout/components/Sidebar/SidebarItem.vue
  5. 54 54
      src/layout/components/Sidebar/index.vue
  6. 78 76
      src/permission.js
  7. 150 150
      src/store/modules/permission.js
  8. 185 170
      src/store/modules/user.js
  9. 195 185
      src/styles/admin.scss
  10. 108 92
      src/utils/costum.js
  11. 116 113
      src/views/dashboard/admin/components/RangeSubmit/index.vue
  12. 224 196
      src/views/dashboard/admin/index.vue
  13. 4 2
      src/views/process/list/handle.vue
  14. 3 0
      src/views/process/list/upcoming.vue
  15. 0 0
      web/index.html
  16. 0 0
      web/static/web/css/app.b15a4ab8.css
  17. 0 0
      web/static/web/css/chunk-07ce983f.61d75617.css
  18. 0 0
      web/static/web/css/chunk-d3212d36.0af89f2f.css
  19. 0 0
      web/static/web/css/chunk-fe33d916.dc680019.css
  20. 0 0
      web/static/web/js/app.43d1ab09.js
  21. 0 0
      web/static/web/js/app.c69fb5dc.js
  22. 0 0
      web/static/web/js/chunk-07ce983f.1dd83f64.js
  23. 0 0
      web/static/web/js/chunk-25041faa.8a855884.js
  24. 0 0
      web/static/web/js/chunk-41f6000b.9fc561d0.js
  25. 0 0
      web/static/web/js/chunk-50a983d1.babc8539.js
  26. 0 0
      web/static/web/js/chunk-6238fc07.d1aea56f.js
  27. 0 0
      web/static/web/js/chunk-6673a38f.185c55e2.js
  28. 0 0
      web/static/web/js/chunk-6bdc8299.0e9f01fd.js
  29. 0 0
      web/static/web/js/chunk-748b566e.1c84864c.js
  30. 0 0
      web/static/web/js/chunk-d3212d36.fba7337c.js
  31. 0 0
      web/static/web/js/chunk-fe33d916.fff128e5.js

+ 1 - 1
package.json

@@ -83,7 +83,7 @@
     "vuedraggable": "2.20.0",
     "vueify": "^9.4.1",
     "vuex": "3.1.0",
-    "xlsx": "0.14.1"
+    "xlsx": "^0.14.1"
   },
   "devDependencies": {
     "@babel/core": "7.0.0",

+ 214 - 214
public/index.html

@@ -1,215 +1,215 @@
-<!DOCTYPE html>
-<html>
-
-<head>
-  <meta charset="utf-8">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
-  <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="<%= BASE_URL %>favicon.ico">
-  <title><%= webpackConfig.name %> - ferry</title>
-  <meta name="keywords" content="ferry,vue,gin,go">
-  <meta name="description" content="OA 管理平台">
-  <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;
-      border-top-color: #FFF;
-      -webkit-animation: spin 2s linear infinite;
-      -ms-animation: spin 2s linear infinite;
-      -moz-animation: spin 2s linear infinite;
-      -o-animation: spin 2s linear infinite;
-      animation: spin 2s linear infinite;
-      z-index: 1001;
-    }
-
-    #loader:before {
-      content: "";
-      position: absolute;
-      top: 5px;
-      left: 5px;
-      right: 5px;
-      bottom: 5px;
-      border-radius: 50%;
-      border: 3px solid transparent;
-      border-top-color: #FFF;
-      -webkit-animation: spin 3s linear infinite;
-      -moz-animation: spin 3s linear infinite;
-      -o-animation: spin 3s linear infinite;
-      -ms-animation: spin 3s linear infinite;
-      animation: spin 3s linear infinite;
-    }
-
-    #loader:after {
-      content: "";
-      position: absolute;
-      top: 15px;
-      left: 15px;
-      right: 15px;
-      bottom: 15px;
-      border-radius: 50%;
-      border: 3px solid transparent;
-      border-top-color: #FFF;
-      -moz-animation: spin 1.5s linear infinite;
-      -o-animation: spin 1.5s linear infinite;
-      -ms-animation: spin 1.5s linear infinite;
-      -webkit-animation: spin 1.5s linear infinite;
-      animation: spin 1.5s linear infinite;
-    }
-
-
-    @-webkit-keyframes spin {
-      0% {
-        -webkit-transform: rotate(0deg);
-        -ms-transform: rotate(0deg);
-        transform: rotate(0deg);
-      }
-
-      100% {
-        -webkit-transform: rotate(360deg);
-        -ms-transform: rotate(360deg);
-        transform: rotate(360deg);
-      }
-    }
-
-    @keyframes spin {
-      0% {
-        -webkit-transform: rotate(0deg);
-        -ms-transform: rotate(0deg);
-        transform: rotate(0deg);
-      }
-
-      100% {
-        -webkit-transform: rotate(360deg);
-        -ms-transform: rotate(360deg);
-        transform: rotate(360deg);
-      }
-    }
-
-
-    #loader-wrapper .loader-section {
-      position: fixed;
-      top: 0;
-      width: 51%;
-      height: 100%;
-      background: #7171C6;
-      z-index: 1000;
-      -webkit-transform: translateX(0);
-      -ms-transform: translateX(0);
-      transform: translateX(0);
-    }
-
-    #loader-wrapper .loader-section.section-left {
-      left: 0;
-    }
-
-    #loader-wrapper .loader-section.section-right {
-      right: 0;
-    }
-
-
-    .loaded #loader-wrapper .loader-section.section-left {
-      -webkit-transform: translateX(-100%);
-      -ms-transform: translateX(-100%);
-      transform: translateX(-100%);
-      -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%);
-      -ms-transform: translateX(100%);
-      transform: translateX(100%);
-      -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%);
-      -ms-transform: translateY(-100%);
-      transform: translateY(-100%);
-      -webkit-transition: all 0.3s 1s ease-out;
-      transition: all 0.3s 1s ease-out;
-    }
-
-    .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">正在加载系统资源,请耐心等待</div>
-    </div>
-  </div>
-</body>
-
+<!DOCTYPE html>
+<html>
+
+<head>
+  <meta charset="utf-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+  <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="<%= BASE_URL %>favicon.ico">
+  <title><%= webpackConfig.name %> - ferry</title>
+  <meta name="keywords" content="ferry,vue,gin,go">
+  <meta name="description" content="OA 管理平台">
+  <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;
+      border-top-color: #FFF;
+      -webkit-animation: spin 2s linear infinite;
+      -ms-animation: spin 2s linear infinite;
+      -moz-animation: spin 2s linear infinite;
+      -o-animation: spin 2s linear infinite;
+      animation: spin 2s linear infinite;
+      z-index: 1001;
+    }
+
+    #loader:before {
+      content: "";
+      position: absolute;
+      top: 5px;
+      left: 5px;
+      right: 5px;
+      bottom: 5px;
+      border-radius: 50%;
+      border: 3px solid transparent;
+      border-top-color: #FFF;
+      -webkit-animation: spin 3s linear infinite;
+      -moz-animation: spin 3s linear infinite;
+      -o-animation: spin 3s linear infinite;
+      -ms-animation: spin 3s linear infinite;
+      animation: spin 3s linear infinite;
+    }
+
+    #loader:after {
+      content: "";
+      position: absolute;
+      top: 15px;
+      left: 15px;
+      right: 15px;
+      bottom: 15px;
+      border-radius: 50%;
+      border: 3px solid transparent;
+      border-top-color: #FFF;
+      -moz-animation: spin 1.5s linear infinite;
+      -o-animation: spin 1.5s linear infinite;
+      -ms-animation: spin 1.5s linear infinite;
+      -webkit-animation: spin 1.5s linear infinite;
+      animation: spin 1.5s linear infinite;
+    }
+
+
+    @-webkit-keyframes spin {
+      0% {
+        -webkit-transform: rotate(0deg);
+        -ms-transform: rotate(0deg);
+        transform: rotate(0deg);
+      }
+
+      100% {
+        -webkit-transform: rotate(360deg);
+        -ms-transform: rotate(360deg);
+        transform: rotate(360deg);
+      }
+    }
+
+    @keyframes spin {
+      0% {
+        -webkit-transform: rotate(0deg);
+        -ms-transform: rotate(0deg);
+        transform: rotate(0deg);
+      }
+
+      100% {
+        -webkit-transform: rotate(360deg);
+        -ms-transform: rotate(360deg);
+        transform: rotate(360deg);
+      }
+    }
+
+
+    #loader-wrapper .loader-section {
+      position: fixed;
+      top: 0;
+      width: 51%;
+      height: 100%;
+      background: #7171C6;
+      z-index: 1000;
+      -webkit-transform: translateX(0);
+      -ms-transform: translateX(0);
+      transform: translateX(0);
+    }
+
+    #loader-wrapper .loader-section.section-left {
+      left: 0;
+    }
+
+    #loader-wrapper .loader-section.section-right {
+      right: 0;
+    }
+
+
+    .loaded #loader-wrapper .loader-section.section-left {
+      -webkit-transform: translateX(-100%);
+      -ms-transform: translateX(-100%);
+      transform: translateX(-100%);
+      -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%);
+      -ms-transform: translateX(100%);
+      transform: translateX(100%);
+      -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%);
+      -ms-transform: translateY(-100%);
+      transform: translateY(-100%);
+      -webkit-transition: all 0.3s 1s ease-out;
+      transition: all 0.3s 1s ease-out;
+    }
+
+    .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">正在加载系统资源,请耐心等待</div>
+    </div>
+  </div>
+</body>
+
 </html>

+ 91 - 2
src/components/VueFormMaking/components/GenerateFormItem.vue

@@ -24,7 +24,10 @@
       <template v-else-if="widget.type=='file'">
         <div v-for="(uploadUrlItem, uploadUrlIndex) of dataModel" :key="uploadUrlIndex">
           <i style="color: #909399;" class="el-icon-document" />
-          <a :href="uploadUrlItem.url" target="_blank">{{ uploadUrlItem.name || uploadUrlItem.url }}</a>
+          <span>{{ uploadUrlItem.name || uploadUrlItem.url }}</span>
+          <el-button round size="mini" @click="onDownload(uploadUrlItem, 'download')">下载</el-button>
+          <el-button round type="primary" @click="onDownload(uploadUrlItem)" v-if="checkFileSuffix(uploadUrlItem.url)" size="mini">预览</el-button>
+          <!-- <a :href="uploadUrlItem.url" target="_blank">{{ uploadUrlItem.name || uploadUrlItem.url }}</a> -->
         </div>
       </template>
 
@@ -360,10 +363,24 @@
       </template>
     </template>
 
+    <el-drawer
+        :title="'预览'"
+        direction="rtl"
+        :visible.sync="open"
+        size="50%"
+      >
+        <div>
+          <iframe :src="previewUrl"></iframe>
+          <div style="text-align: center">
+            <el-button type="primary" @click="open = false">确定</el-button>
+          </div>
+        </div>
+      </el-drawer>
   </el-form-item>
 </template>
 
 <script>
+import XLSX from 'xlsx'
 import FmUpload from './Upload'
 import FileUpload from './Upload/file'
 import {
@@ -379,6 +396,7 @@ export default {
   props: ['widget', 'models', 'propValue', 'parentForm', 'remote', 'data', 'disabled', 'preview', 'isLabel', 'subformIndex', 'subformModel', 'organList'],
   data() {
     return {
+      open: false,
       showStatus: true,
       widgetLabelWidth: '',
       dataModel: this.subformIndex===undefined?
@@ -387,7 +405,8 @@ export default {
       dataModelStr: null,
       tableData: [],
       cooperationList: [],
-      selectLoading: true
+      selectLoading: true,
+      previewUrl: '',
     }
   },
   watch: {
@@ -501,7 +520,76 @@ export default {
 
     this.handleDisplayVerifiy()
   },
+  mounted() {
+  },
   methods: {
+    // 获取学校列表
+    onDownload(item, type) {
+      console.log(item)
+      if(type == 'download') {
+        window.location.href = item.url
+        return
+      }
+      this.previewUrl = 'https://view.officeapps.live.com/op/view.aspx?src=' + item.url
+      window.open(this.previewUrl)
+    },
+    readWorkbookFromRemoteFile: function (url) {
+        var xhr = new XMLHttpRequest()
+        xhr.open('get', url, true)
+        xhr.responseType = 'arraybuffer'
+        let _this = this
+        xhr.onload = function (e) {
+          var binary = "";
+          console.log(xhr)
+          if (xhr.status === 200) {
+            var bytes  = new Uint8Array(xhr.response)
+            var length = bytes.byteLength;
+            for (var i = 0; i < length; i++) {
+              binary += String.fromCharCode(bytes[i]);
+            }
+            var wb = XLSX.read(binary, { type: "binary" });
+            var wsname = wb.SheetNames[0];
+            var ws = wb.Sheets[wsname];
+            console.log(ws)
+            var HTML = XLSX.utils.sheet_to_html(ws);
+            if (_this.$refs.preview) {
+              _this.$refs.preview.innerHTML = HTML;
+            }
+          }
+        }
+        xhr.send()
+      },
+      getData() {
+        let that = this;
+        let element = that.$refs.preview;
+        // Transform(element);
+        var initScale = 1;
+        this.af = new AlloyFinger(element, {
+          // rotate: function (evt) {  //实现旋转
+          //   element.rotateZ += evt.angle;
+          // },
+          multipointStart: function () {
+              initScale = element.scaleX;
+          },
+          pinch: function (evt) {   //实现缩放
+            element.scaleX = element.scaleY = initScale * evt.zoom;
+          },
+          pressMove: function (evt) {   //实现移动
+            element.translateX += evt.deltaX;
+            element.translateY += evt.deltaY;
+            evt.preventDefault();
+          },
+        });
+      },
+    checkFileSuffix(url) {
+      let urlArr = url.split('.')
+      let suffix = urlArr[urlArr.length-1]
+      if(suffix == 'xlsx' || suffix == 'xls' || suffix == 'doc' || suffix == 'docx' || suffix == 'pdf') {
+        return true
+      } else {
+        return false
+      }
+    },
     // select 选择时触发
     onChangeSelect() {
       let relationArray = []
@@ -639,4 +727,5 @@ export default {
   .preview-cascader-class .el-input.is-disabled .el-input__suffix .el-input__suffix-inner .el-input__icon.el-icon-arrow-down:before {
     content: ''
   }
+
 </style>

+ 101 - 95
src/layout/components/Sidebar/SidebarItem.vue

@@ -1,95 +1,101 @@
-<template>
-  <div v-if="!item.hidden" class="menu-wrapper">
-    <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
-      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
-        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
-          <item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
-        </el-menu-item>
-      </app-link>
-    </template>
-
-    <el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
-      <template slot="title">
-        <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
-      </template>
-      <sidebar-item
-        v-for="child in item.children"
-        :key="child.path"
-        :is-nest="true"
-        :item="child"
-        :base-path="resolvePath(child.path)"
-        class="nest-menu"
-      />
-    </el-submenu>
-  </div>
-</template>
-
-<script>
-import path from 'path'
-import { isExternal } from '@/utils/validate'
-import Item from './Item'
-import AppLink from './Link'
-import FixiOSBug from './FixiOSBug'
-
-export default {
-  name: 'SidebarItem',
-  components: { Item, AppLink },
-  mixins: [FixiOSBug],
-  props: {
-    // route object
-    item: {
-      type: Object,
-      required: true
-    },
-    isNest: {
-      type: Boolean,
-      default: false
-    },
-    basePath: {
-      type: String,
-      default: ''
-    }
-  },
-  data() {
-    // To fix https://github.com/PanJiaChen/vue-admin-template/issues/237
-    // TODO: refactor with render function
-    this.onlyOneChild = null
-    return {}
-  },
-  methods: {
-    hasOneShowingChild(children = [], parent) {
-      const showingChildren = children.filter(item => {
-        if (item.hidden) {
-          return false
-        } else {
-          // Temp set(will be used if only has one showing child)
-          this.onlyOneChild = item
-          return true
-        }
-      })
-
-      // When there is only one child router, the child router is displayed by default
-      if (showingChildren.length === 1) {
-        return true
-      }
-
-      // Show parent if there are no child router to display
-      if (showingChildren.length === 0) {
-        this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }
-        return true
-      }
-
-      return false
-    },
-    resolvePath(routePath) {
-      if (isExternal(routePath)) {
-        return routePath
-      }
-      if (isExternal(this.basePath)) {
-        return this.basePath
-      }
-      return path.resolve(this.basePath, routePath)
-    }
-  }
-}
-</script>
+<template>
+  <div v-if="!item.hidden" class="menu-wrapper">
+    <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
+      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
+        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
+          <item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
+        </el-menu-item>
+      </app-link>
+    </template>
+
+    <el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
+      <template slot="title">
+        <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
+      </template>
+      <sidebar-item
+        v-for="child in item.children"
+        :key="child.path"
+        :is-nest="true"
+        :item="child"
+        :base-path="resolvePath(child.path)"
+        class="nest-menu"
+      />
+    </el-submenu>
+  </div>
+</template>
+
+<script>
+import path from 'path'
+import { isExternal } from '@/utils/validate'
+import Item from './Item'
+import AppLink from './Link'
+import FixiOSBug from './FixiOSBug'
+
+export default {
+  name: 'SidebarItem',
+  components: { Item, AppLink },
+  mixins: [FixiOSBug],
+  props: {
+    // route object
+    item: {
+      type: Object,
+      required: true
+    },
+    isNest: {
+      type: Boolean,
+      default: false
+    },
+    basePath: {
+      type: String,
+      default: ''
+    }
+  },
+  data() {
+    // To fix https://github.com/PanJiaChen/vue-admin-template/issues/237
+    // TODO: refactor with render function
+    this.onlyOneChild = null
+    return {}
+  },
+  methods: {
+    hasOneShowingChild(children = [], parent) {
+      const showingChildren = children.filter(item => {
+        if (item.hidden) {
+          return false
+        } else {
+          // Temp set(will be used if only has one showing child)
+          this.onlyOneChild = item
+          return true
+        }
+      })
+
+      // When there is only one child router, the child router is displayed by default
+      if (showingChildren.length === 1) {
+        return true
+      }
+
+      // Show parent if there are no child router to display
+      if (showingChildren.length === 0) {
+        this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }
+        return true
+      }
+
+      return false
+    },
+    resolvePath(routePath) {
+      if (isExternal(routePath)) {
+        return routePath
+      }
+      if (isExternal(this.basePath)) {
+        return this.basePath
+      }
+      return path.resolve(this.basePath, routePath)
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.menu-wrapper {
+  position: relative;
+}
+</style>

+ 54 - 54
src/layout/components/Sidebar/index.vue

@@ -1,54 +1,54 @@
-<template>
-  <div :class="{'has-logo':showLogo}">
-    <logo v-if="showLogo" :collapse="isCollapse" />
-    <el-scrollbar wrap-class="scrollbar-wrapper">
-      <el-menu
-        :default-active="activeMenu"
-        :collapse="isCollapse"
-        :background-color="variables.menuBg"
-        :text-color="variables.menuText"
-        :unique-opened="false"
-        :active-text-color="variables.menuActiveText"
-        :collapse-transition="false"
-        mode="vertical"
-      >
-        <sidebar-item v-for="route in permission_routes" :key="route.path" :item="route" :base-path="route.path" />
-      </el-menu>
-    </el-scrollbar>
-  </div>
-</template>
-
-<script>
-import { mapGetters } from 'vuex'
-import Logo from './Logo'
-import SidebarItem from './SidebarItem'
-import variables from '@/styles/variables.scss'
-
-export default {
-  components: { SidebarItem, Logo },
-  computed: {
-    ...mapGetters([
-      'permission_routes',
-      'sidebar'
-    ]),
-    activeMenu() {
-      const route = this.$route
-      const { meta, path } = route
-      // if set path, the sidebar will highlight the path you set
-      if (meta.activeMenu) {
-        return meta.activeMenu
-      }
-      return path
-    },
-    showLogo() {
-      return this.$store.state.settings.sidebarLogo
-    },
-    variables() {
-      return variables
-    },
-    isCollapse() {
-      return !this.sidebar.opened
-    }
-  }
-}
-</script>
+<template>
+  <div :class="{'has-logo':showLogo}">
+    <logo v-if="showLogo" :collapse="isCollapse" />
+    <el-scrollbar wrap-class="scrollbar-wrapper">
+      <el-menu
+        :default-active="activeMenu"
+        :collapse="isCollapse"
+        :background-color="variables.menuBg"
+        :text-color="variables.menuText"
+        :unique-opened="true"
+        :active-text-color="variables.menuActiveText"
+        :collapse-transition="false"
+        mode="vertical"
+      >
+        <sidebar-item v-for="route in permission_routes" :key="route.path" :item="route" :base-path="route.path" />
+      </el-menu>
+    </el-scrollbar>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import Logo from './Logo'
+import SidebarItem from './SidebarItem'
+import variables from '@/styles/variables.scss'
+
+export default {
+  components: { SidebarItem, Logo },
+  computed: {
+    ...mapGetters([
+      'permission_routes',
+      'sidebar'
+    ]),
+    activeMenu() {
+      const route = this.$route
+      const { meta, path } = route
+      // if set path, the sidebar will highlight the path you set
+      if (meta.activeMenu) {
+        return meta.activeMenu
+      }
+      return path
+    },
+    showLogo() {
+      return this.$store.state.settings.sidebarLogo
+    },
+    variables() {
+      return variables
+    },
+    isCollapse() {
+      return !this.sidebar.opened
+    }
+  }
+}
+</script>

+ 78 - 76
src/permission.js

@@ -1,76 +1,78 @@
-import router from './router'
-import store from './store'
-import { Message } from 'element-ui'
-import NProgress from 'nprogress' // progress bar
-import 'nprogress/nprogress.css' // progress bar style
-import { getToken } from '@/utils/auth' // get token from cookie
-import getPageTitle from '@/utils/get-page-title'
-
-NProgress.configure({ showSpinner: false }) // NProgress Configuration
-
-store.dispatch('settings/getSystemSettings')
-
-const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist
-
-router.beforeEach(async(to, from, next) => {
-  // start progress bar
-  NProgress.start()
-
-  // set page title
-  document.title = getPageTitle(to.meta.title, store.state.settings.title)
-
-  // determine whether the user has logged in
-  const hasToken = getToken()
-
-  if (hasToken) {
-    if (to.path === '/login') {
-      // if is logged in, redirect to the home page
-      next({ path: '/' })
-      NProgress.done()
-    } else {
-      // determine whether the user has obtained his permission roles through getInfo
-      const hasRoles = store.getters.roles && store.getters.roles.length > 0
-      if (hasRoles) {
-        next()
-      } else {
-        try {
-          // get user info
-          // note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
-          const { roles } = await store.dispatch('user/getInfo')
-
-          // generate accessible routes map based on roles
-          const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
-
-          // dynamically add accessible routes
-          router.addRoutes(accessRoutes)
-
-          // hack method to ensure that addRoutes is complete
-          // set the replace: true, so the navigation will not leave a history record
-          next({ ...to, replace: true })
-        } catch (error) {
-          // remove token and go to login page to re-login
-          // await store.dispatch('user/resetToken')
-          Message.error(error || 'Has Error')
-          next(`/login?redirect=${to.path}`)
-          NProgress.done()
-        }
-      }
-    }
-  } else {
-    /* has no token*/
-
-    if (whiteList.indexOf(to.path) !== -1) {
-      // in the free login whitelist, go directly
-      next()
-    } else {
-      // other pages that do not have permission to access are redirected to the login page.
-      next(`/login?redirect=${to.path}`)
-      NProgress.done()
-    }
-  }
-})
-
-router.afterEach(() => {
-  // finish progress bar
-  NProgress.done()
-})
+import router from './router'
+import store from './store'
+import { Message } from 'element-ui'
+import NProgress from 'nprogress' // progress bar
+import 'nprogress/nprogress.css' // progress bar style
+import { getToken } from '@/utils/auth' // get token from cookie
+import getPageTitle from '@/utils/get-page-title'
+
+NProgress.configure({ showSpinner: false }) // NProgress Configuration
+
+store.dispatch('settings/getSystemSettings')
+
+const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist
+
+router.beforeEach(async(to, from, next) => {
+  // start progress bar
+  NProgress.start()
+
+  // set page title
+  document.title = getPageTitle(to.meta.title, store.state.settings.title)
+
+  // determine whether the user has logged in
+  const hasToken = getToken()
+
+  if (hasToken) {
+    if (to.path === '/login') {
+      // if is logged in, redirect to the home page
+      next({ path: '/' })
+      NProgress.done()
+    } else {
+      // determine whether the user has obtained his permission roles through getInfo
+      const hasRoles = store.getters.roles && store.getters.roles.length > 0
+      if (hasRoles) {
+        next()
+      } else {
+        try {
+          // get user info
+          // note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
+          const { roles } = await store.dispatch('user/getInfo')
+
+          // generate accessible routes map based on roles
+          const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
+
+          await store.dispatch('user/getInitData')
+
+          // dynamically add accessible routes
+          router.addRoutes(accessRoutes)
+
+          // hack method to ensure that addRoutes is complete
+          // set the replace: true, so the navigation will not leave a history record
+          next({ ...to, replace: true })
+        } catch (error) {
+          // remove token and go to login page to re-login
+          // await store.dispatch('user/resetToken')
+          Message.error(error || 'Has Error')
+          next(`/login?redirect=${to.path}`)
+          NProgress.done()
+        }
+      }
+    }
+  } else {
+    /* has no token*/
+
+    if (whiteList.indexOf(to.path) !== -1) {
+      // in the free login whitelist, go directly
+      next()
+    } else {
+      // other pages that do not have permission to access are redirected to the login page.
+      next(`/login?redirect=${to.path}`)
+      NProgress.done()
+    }
+  }
+})
+
+router.afterEach(() => {
+  // finish progress bar
+  NProgress.done()
+})

+ 150 - 150
src/store/modules/permission.js

@@ -1,150 +1,150 @@
-import { asyncRoutes, constantRoutes } from '@/router'
-import { getRoutes } from '@/api/system/role'
-import Layout from '@/layout'
-// import sysuserindex from '@/views/sysuser/index'
-
-/**
- * Use meta.role to determine if the current user has permission
- * @param roles
- * @param route
- */
-function hasPermission(roles, route) {
-  if (route.meta && route.meta.roles) {
-    return roles.some(role => route.meta.roles.includes(role))
-  } else {
-    return true
-  }
-}
-
-/**
- * Use names to determine if the current user has permission
- * @param names
- * @param route
- */
-function hasPathPermission(paths, route) {
-  if (route.path) {
-    return paths.some(path => route.path === path.path)
-  } else {
-    return true
-  }
-}
-
-/**
-  * 后台查询的菜单数据拼装成路由格式的数据
-  * @param routes
-  */
-export function generaMenu(routes, data) {
-  data.forEach(item => {
-    const menu = {
-      path: item.path,
-      component: item.component === 'Layout' ? Layout : loadView(item.component),
-      hidden: item.visible !== '0',
-      children: [],
-      name: item.menuName,
-      meta: {
-        title: item.title,
-        icon: item.icon,
-        noCache: true
-      }
-    }
-    if (item.children) {
-      generaMenu(menu.children, item.children)
-    }
-    routes.push(menu)
-  })
-}
-
-export const loadView = (view) => { // 路由懒加载
-  return (resolve) => require(['@/views' + view], resolve)
-  // return () => import(`@/views${view}`)
-}
-
-/**
- * Filter asynchronous routing tables by recursion
- * @param routes asyncRoutes
- * @param roles
- */
-export function filterAsyncRoutes(routes, roles) {
-  const res = []
-
-  routes.forEach(route => {
-    const tmp = { ...route }
-    if (hasPermission(roles, tmp)) {
-      if (tmp.children) {
-        tmp.children = filterAsyncRoutes(tmp.children, roles)
-      }
-      res.push(tmp)
-    }
-  })
-
-  return res
-}
-
-/**
- * Filter asynchronous routing tables by recursion
- * @param routes asyncRoutes
- * @param components
- */
-export function filterAsyncPathRoutes(routes, paths) {
-  const res = []
-
-  routes.forEach(route => {
-    const tmp = { ...route }
-    if (hasPathPermission(paths, tmp)) {
-      if (tmp.children) {
-        tmp.children = filterAsyncPathRoutes(tmp.children, paths)
-      }
-      res.push(tmp)
-    }
-  })
-
-  return res
-}
-
-const state = {
-  routes: [],
-  addRoutes: []
-}
-
-const mutations = {
-  SET_ROUTES: (state, routes) => {
-    state.addRoutes = routes
-    state.routes = constantRoutes.concat(routes)
-  }
-}
-
-const actions = {
-  generateRoutes({ commit }, roles) {
-    return new Promise(resolve => {
-      const loadMenuData = []
-
-      getRoutes().then(response => {
-        // console.log(JSON.stringify(response))
-        let data = response
-        if (response.code !== 200) {
-          this.$message({
-            message: '菜单数据加载异常',
-            type: 0
-          })
-        } else {
-          data = response.data
-          Object.assign(loadMenuData, data)
-
-          generaMenu(asyncRoutes, loadMenuData)
-          asyncRoutes.push({ path: '*', redirect: '/404', hidden: true })
-          commit('SET_ROUTES', asyncRoutes)
-          resolve(asyncRoutes)
-        }
-      }).catch(error => {
-        console.log(error)
-      })
-    })
-  }
-}
-
-export default {
-  namespaced: true,
-  state,
-  mutations,
-  actions
-}
+import { asyncRoutes, constantRoutes } from '@/router'
+import { getRoutes } from '@/api/system/role'
+import Layout from '@/layout'
+// import sysuserindex from '@/views/sysuser/index'
+
+/**
+ * Use meta.role to determine if the current user has permission
+ * @param roles
+ * @param route
+ */
+function hasPermission(roles, route) {
+  if (route.meta && route.meta.roles) {
+    return roles.some(role => route.meta.roles.includes(role))
+  } else {
+    return true
+  }
+}
+
+/**
+ * Use names to determine if the current user has permission
+ * @param names
+ * @param route
+ */
+function hasPathPermission(paths, route) {
+  if (route.path) {
+    return paths.some(path => route.path === path.path)
+  } else {
+    return true
+  }
+}
+
+/**
+  * 后台查询的菜单数据拼装成路由格式的数据
+  * @param routes
+  */
+export function generaMenu(routes, data) {
+  data.forEach(item => {
+    const menu = {
+      path: item.path,
+      component: item.component === 'Layout' ? Layout : loadView(item.component),
+      hidden: item.visible !== '0',
+      children: [],
+      name: item.menuName,
+      meta: {
+        title: item.title,
+        icon: item.icon,
+        noCache: true
+      }
+    }
+    if (item.children) {
+      generaMenu(menu.children, item.children)
+    }
+    routes.push(menu)
+  })
+}
+
+export const loadView = (view) => { // 路由懒加载
+  return (resolve) => require(['@/views' + view], resolve)
+  // return () => import(`@/views${view}`)
+}
+
+/**
+ * Filter asynchronous routing tables by recursion
+ * @param routes asyncRoutes
+ * @param roles
+ */
+export function filterAsyncRoutes(routes, roles) {
+  const res = []
+
+  routes.forEach(route => {
+    const tmp = { ...route }
+    if (hasPermission(roles, tmp)) {
+      if (tmp.children) {
+        tmp.children = filterAsyncRoutes(tmp.children, roles)
+      }
+      res.push(tmp)
+    }
+  })
+
+  return res
+}
+
+/**
+ * Filter asynchronous routing tables by recursion
+ * @param routes asyncRoutes
+ * @param components
+ */
+export function filterAsyncPathRoutes(routes, paths) {
+  const res = []
+
+  routes.forEach(route => {
+    const tmp = { ...route }
+    if (hasPathPermission(paths, tmp)) {
+      if (tmp.children) {
+        tmp.children = filterAsyncPathRoutes(tmp.children, paths)
+      }
+      res.push(tmp)
+    }
+  })
+
+  return res
+}
+
+const state = {
+  routes: [],
+  addRoutes: []
+}
+
+const mutations = {
+  SET_ROUTES: (state, routes) => {
+    state.addRoutes = routes
+    state.routes = constantRoutes.concat(routes)
+  }
+}
+
+const actions = {
+  generateRoutes({ commit }, roles) {
+    return new Promise(resolve => {
+      const loadMenuData = []
+
+      getRoutes().then(response => {
+        // console.log(JSON.stringify(response))
+        let data = response
+        if (response.code !== 200) {
+          this.$message({
+            message: '菜单数据加载异常',
+            type: 0
+          })
+        } else {
+          data = response.data
+          Object.assign(loadMenuData, data)
+
+          generaMenu(asyncRoutes, loadMenuData)
+          asyncRoutes.push({ path: '*', redirect: '/404', hidden: true })
+          commit('SET_ROUTES', asyncRoutes)
+          resolve(asyncRoutes)
+        }
+      }).catch(error => {
+        console.log(error)
+      })
+    })
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}

+ 185 - 170
src/store/modules/user.js

@@ -1,170 +1,185 @@
-import { login, logout, getInfo } from '@/api/user'
-import { getToken, setToken, removeToken } from '@/utils/auth'
-import router, { resetRouter } from '@/router'
-// refreshtoken
-const state = {
-  token: getToken(),
-  name: '',
-  avatar: '',
-  introduction: '',
-  roles: [],
-  permissions: [],
-  permisaction: []
-}
-
-const mutations = {
-  SET_TOKEN: (state, token) => {
-    state.token = token
-  },
-  SET_INTRODUCTION: (state, introduction) => {
-    state.introduction = introduction
-  },
-  SET_NAME: (state, name) => {
-    state.name = name
-  },
-  SET_USERID: (state, userId) => {
-    state.userId = userId
-  },
-  SET_AVATAR: (state, avatar) => {
-    if (avatar.indexOf('http') !== -1) {
-      state.avatar = avatar
-    } else {
-      state.avatar = process.env.VUE_APP_BASE_API + avatar
-    }
-  },
-  SET_ROLES: (state, roles) => {
-    state.roles = roles
-  },
-  SET_PERMISSIONS: (state, permisaction) => {
-    state.permisaction = permisaction
-  },
-  SET_REFRESH_TOKEN: (state, refreshToken) => {
-    state.refreshToken = refreshToken
-  },
-  SET_EXPIRES_IN: (state, expiresIn) => {
-    state.expiresIn = expiresIn
-  }
-}
-
-const actions = {
-  // user login
-  login({ commit }, userInfo) {
-    const { username, password } = userInfo
-    return new Promise((resolve, reject) => {
-      login({ username: username.trim(), password: password, clientId: 'system', clientSecret: 'system' }).then(response => {
-        const { data } = response
-        if (response.code == 200) {
-          console.log(data)
-          const token = data.authentication.access_token
-          commit('SET_REFRESH_TOKEN', data.authentication.refresh_token)
-          commit('SET_EXPIRES_IN', data.authentication.expires_in)
-          commit('SET_TOKEN', token)
-          setToken(token)
-          resolve()
-        }
-        // const { token } = response
-      }).catch(error => {
-        reject(error)
-      })
-    })
-  },
-
-  // get user info
-  getInfo({ commit, state }) {
-    return new Promise((resolve, reject) => {
-      getInfo().then(response => {
-        if (!response || !response.data) {
-          commit('SET_TOKEN', '')
-          removeToken()
-          resolve()
-        }
-
-        const { userId, roles, name, avatar, introduction, permissions } = response.data
-
-        // roles must be a non-empty array
-        if (!roles || roles.length <= 0) {
-          reject('getInfo: roles must be a non-null array!')
-        }
-        commit('SET_PERMISSIONS', permissions)
-        commit('SET_ROLES', roles)
-        commit('SET_NAME', name)
-        commit('SET_USERID', userId)
-        commit('SET_AVATAR', avatar)
-        commit('SET_INTRODUCTION', introduction)
-        resolve(response)
-      }).catch(() => {
-        commit('SET_TOKEN', '')
-        setToken('')
-        router.push({ path: '/' })
-      })
-    })
-  },
-  // 退出系统
-  LogOut({ commit, state }) {
-    return new Promise((resolve, reject) => {
-      logout(state.token).then(() => {
-        commit('SET_TOKEN', '')
-        commit('SET_ROLES', [])
-        commit('SET_PERMISSIONS', [])
-        removeToken()
-        resolve()
-      }).catch(error => {
-        reject(error)
-      })
-    })
-  },
-  // 刷新token
-  // refreshToken({ commit, state }) {
-  //   return new Promise((resolve, reject) => {
-  //     refreshtoken({ token: state.token }).then(response => {
-  //       const { token } = response
-  //       commit('SET_TOKEN', token)
-  //       setToken(token)
-  //       resolve()
-  //     }).catch(error => {
-  //       reject(error)
-  //     })
-  //   })
-  // },
-
-  // remove token
-  resetToken({ commit }) {
-    return new Promise(resolve => {
-      commit('SET_TOKEN', '')
-      removeToken()
-      resolve()
-    })
-  },
-
-  // dynamically modify permissions
-  changeRoles({ commit, dispatch }, role) {
-    return new Promise(async resolve => {
-      const token = role + '-token'
-
-      commit('SET_TOKEN', token)
-      setToken(token)
-
-      const { roles } = await dispatch('getInfo')
-
-      resetRouter()
-
-      // generate accessible routes map based on roles
-      const accessRoutes = await dispatch('permission/generateRoutes', roles, { root: true })
-
-      // dynamically add accessible routes
-      router.addRoutes(accessRoutes)
-
-      // reset visited views and cached views
-      dispatch('tagsView/delAllViews', null, { root: true })
-
-      resolve()
-    })
-  }
-}
-
-export default {
-  namespaced: true,
-  state,
-  mutations,
-  actions
-}
+import { login, logout, getInfo } from '@/api/user'
+import { initData } from '@/api/dashboard'
+import { getToken, setToken, removeToken } from '@/utils/auth'
+import { initNumberHtml } from '@/utils/costum'
+import router, { resetRouter } from '@/router'
+// refreshtoken
+const state = {
+  token: getToken(),
+  name: '',
+  avatar: '',
+  introduction: '',
+  roles: [],
+  permissions: [],
+  permisaction: []
+}
+
+const mutations = {
+  SET_TOKEN: (state, token) => {
+    state.token = token
+  },
+  SET_INTRODUCTION: (state, introduction) => {
+    state.introduction = introduction
+  },
+  SET_NAME: (state, name) => {
+    state.name = name
+  },
+  SET_USERID: (state, userId) => {
+    state.userId = userId
+  },
+  SET_AVATAR: (state, avatar) => {
+    if (avatar.indexOf('http') !== -1) {
+      state.avatar = avatar
+    } else {
+      state.avatar = process.env.VUE_APP_BASE_API + avatar
+    }
+  },
+  SET_ROLES: (state, roles) => {
+    state.roles = roles
+  },
+  SET_PERMISSIONS: (state, permisaction) => {
+    state.permisaction = permisaction
+  },
+  SET_REFRESH_TOKEN: (state, refreshToken) => {
+    state.refreshToken = refreshToken
+  },
+  SET_EXPIRES_IN: (state, expiresIn) => {
+    state.expiresIn = expiresIn
+  }
+}
+
+const actions = {
+  // user login
+  login({ commit }, userInfo) {
+    const { username, password } = userInfo
+    return new Promise((resolve, reject) => {
+      login({ username: username.trim(), password: password, clientId: 'system', clientSecret: 'system' }).then(response => {
+        const { data } = response
+        if (response.code == 200) {
+          console.log(data)
+          const token = data.authentication.access_token
+          commit('SET_REFRESH_TOKEN', data.authentication.refresh_token)
+          commit('SET_EXPIRES_IN', data.authentication.expires_in)
+          commit('SET_TOKEN', token)
+          setToken(token)
+          resolve()
+        }
+        // const { token } = response
+      }).catch(error => {
+        reject(error)
+      })
+    })
+  },
+
+  // get user info
+  getInfo({ commit, state }) {
+    return new Promise((resolve, reject) => {
+      getInfo().then(response => {
+        if (!response || !response.data) {
+          commit('SET_TOKEN', '')
+          removeToken()
+          resolve()
+        }
+
+        const { userId, roles, name, avatar, introduction, permissions } = response.data
+
+        // roles must be a non-empty array
+        if (!roles || roles.length <= 0) {
+          reject('getInfo: roles must be a non-null array!')
+        }
+        commit('SET_PERMISSIONS', permissions)
+        commit('SET_ROLES', roles)
+        commit('SET_NAME', name)
+        commit('SET_USERID', userId)
+        commit('SET_AVATAR', avatar)
+        commit('SET_INTRODUCTION', introduction)
+        resolve(response)
+      }).catch(() => {
+        commit('SET_TOKEN', '')
+        setToken('')
+        router.push({ path: '/' })
+      })
+    })
+  },
+  getInitData({ commit, state }) {
+    return new Promise((resolve, reject) => {
+      initData().then(response => {
+        const result = response.data
+        // 添加我的代办显示数据
+        if(result.count.upcoming && result.count.upcoming > 0) {
+          initNumberHtml(result.count.upcoming)
+        }
+      })
+      resolve(response)
+    }).catch(() => {
+    })
+  },
+  // 退出系统
+  LogOut({ commit, state }) {
+    return new Promise((resolve, reject) => {
+      logout(state.token).then(() => {
+        commit('SET_TOKEN', '')
+        commit('SET_ROLES', [])
+        commit('SET_PERMISSIONS', [])
+        removeToken()
+        resolve()
+      }).catch(error => {
+        reject(error)
+      })
+    })
+  },
+  // 刷新token
+  // refreshToken({ commit, state }) {
+  //   return new Promise((resolve, reject) => {
+  //     refreshtoken({ token: state.token }).then(response => {
+  //       const { token } = response
+  //       commit('SET_TOKEN', token)
+  //       setToken(token)
+  //       resolve()
+  //     }).catch(error => {
+  //       reject(error)
+  //     })
+  //   })
+  // },
+
+  // remove token
+  resetToken({ commit }) {
+    return new Promise(resolve => {
+      commit('SET_TOKEN', '')
+      removeToken()
+      resolve()
+    })
+  },
+
+  // dynamically modify permissions
+  changeRoles({ commit, dispatch }, role) {
+    return new Promise(async resolve => {
+      const token = role + '-token'
+
+      commit('SET_TOKEN', token)
+      setToken(token)
+
+      const { roles } = await dispatch('getInfo')
+
+      resetRouter()
+
+      // generate accessible routes map based on roles
+      const accessRoutes = await dispatch('permission/generateRoutes', roles, { root: true })
+
+      // dynamically add accessible routes
+      router.addRoutes(accessRoutes)
+
+      // reset visited views and cached views
+      dispatch('tagsView/delAllViews', null, { root: true })
+
+      resolve()
+    })
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}

+ 195 - 185
src/styles/admin.scss

@@ -1,186 +1,196 @@
- /**
- * 通用css样式布局处理
- * Copyright (c) 2019 ruoyi
- */
-
- /** 基础通用 **/
-.pt5 {
-	padding-top: 5px;
-}
-.pr5 {
-	padding-right: 5px;
-}
-.pb5 {
-	padding-bottom: 5px;
-}
-.mt5 {
-	margin-top: 5px;
-}
-.mr5 {
-	margin-right: 5px;
-}
-.mb5 {
-	margin-bottom: 5px;
-}
-.mb8 {
-	margin-bottom: 8px;
-}
-.ml5 {
-	margin-left: 5px;
-}
-.mt10 {
-	margin-top: 10px;
-}
-.mr10 {
-	margin-right: 10px;
-}
-.mb10 {
-	margin-bottom: 10px;
-}
-.ml0 {
-	margin-left: 10px;
-}
-.mt20 {
-	margin-top: 20px;
-}
-.mr20 {
-	margin-right: 20px;
-}
-.mb20 {
-	margin-bottom: 20px;
-}
-.m20 {
-	margin-left: 20px;
-}
-
-.el-table .el-table__header-wrapper th {
-	word-break: break-word;
-	background-color: #f8f8f9;
-	color: #515a6e;
-	height: 40px;
-	font-size: 13px;
-}
-
-/** 表单布局 **/
-.form-header {
-    font-size:15px;
-	color:#6379bb;
-	border-bottom:1px solid #ddd;
-	margin:8px 10px 25px 10px;
-	padding-bottom:5px
-}
-
-/** 表格布局 **/
-.pagination-container {
-	position: relative;
-	height: 25px;
-	margin-bottom: 10px;
-	margin-top: 15px;
-	padding: 10px 20px !important;
-}
-
-.pagination-container .el-pagination {
-	right: 0;
-	position: absolute;
-}
-
-.el-table .fixed-width .el-button--mini {
-	color: #409EFF;
-	padding-left: 0;
-	padding-right: 0;
-	width: inherit;
-}
-
-.el-tree-node__content > .el-checkbox {
-	margin-right: 8px;
-}
-
-.list-group-striped > .list-group-item {
-	border-left: 0;
-	border-right: 0;
-	border-radius: 0;
-	padding-left: 0;
-	padding-right: 0;
-}
-
-.list-group {
-	padding-left: 0px;
-	list-style: none;
-}
-
-.list-group-item {
-	border-bottom: 1px solid #e7eaec;
-	border-top: 1px solid #e7eaec;
-	margin-bottom: -1px;
-	padding: 11px 0px;
-	font-size: 13px;
-}
-
-.pull-right {
-	float: right !important;
-}
-
-.el-card__header {
-	padding: 14px 15px 7px;
-	min-height: 40px;
-}
-
-.remove-padding-bottom > .el-card__body {
-	padding-bottom: 5px;
-}
-
-.card-box {
-	padding-right: 15px;
-	padding-left: 15px;
-	margin-bottom: 10px;
-}
-  
-/* text color */
-.text-navy {
-	color: #1ab394;
-}
-
-.text-primary {
-	color: inherit;
-}
-
-.text-success {
-	color: #1c84c6;
-}
-
-.text-info {
-	color: #23c6c8;
-}
-
-.text-warning {
-	color: #f8ac59;
-}
-
-.text-danger {
-	color: #ed5565;
-}
-
-.text-muted {
-	color: #888888;
-}
-
-/* image */
-.img-circle {
-	border-radius: 50%;
-}
-
-.img-lg {
-	width: 120px;
-	height: 120px;
-}
-
-.avatar-upload-preview {
-	position: absolute;
-	top: 50%;
-	transform: translate(50%, -50%);
-	width: 180px;
-	height: 180px;
-	border-radius: 50%;
-	box-shadow: 0 0 4px #ccc;
-	overflow: hidden;
+ /**
+ * 通用css样式布局处理
+ * Copyright (c) 2019 ruoyi
+ */
+
+ /** 基础通用 **/
+.pt5 {
+	padding-top: 5px;
+}
+.pr5 {
+	padding-right: 5px;
+}
+.pb5 {
+	padding-bottom: 5px;
+}
+.mt5 {
+	margin-top: 5px;
+}
+.mr5 {
+	margin-right: 5px;
+}
+.mb5 {
+	margin-bottom: 5px;
+}
+.mb8 {
+	margin-bottom: 8px;
+}
+.ml5 {
+	margin-left: 5px;
+}
+.mt10 {
+	margin-top: 10px;
+}
+.mr10 {
+	margin-right: 10px;
+}
+.mb10 {
+	margin-bottom: 10px;
+}
+.ml0 {
+	margin-left: 10px;
+}
+.mt20 {
+	margin-top: 20px;
+}
+.mr20 {
+	margin-right: 20px;
+}
+.mb20 {
+	margin-bottom: 20px;
+}
+.m20 {
+	margin-left: 20px;
+}
+
+.el-table .el-table__header-wrapper th {
+	word-break: break-word;
+	background-color: #f8f8f9;
+	color: #515a6e;
+	height: 40px;
+	font-size: 13px;
+}
+
+/** 表单布局 **/
+.form-header {
+    font-size:15px;
+	color:#6379bb;
+	border-bottom:1px solid #ddd;
+	margin:8px 10px 25px 10px;
+	padding-bottom:5px
+}
+
+/** 表格布局 **/
+.pagination-container {
+	position: relative;
+	height: 25px;
+	margin-bottom: 10px;
+	margin-top: 15px;
+	padding: 10px 20px !important;
+}
+
+.pagination-container .el-pagination {
+	right: 0;
+	position: absolute;
+}
+
+.el-table .fixed-width .el-button--mini {
+	color: #409EFF;
+	padding-left: 0;
+	padding-right: 0;
+	width: inherit;
+}
+
+.el-tree-node__content > .el-checkbox {
+	margin-right: 8px;
+}
+
+.list-group-striped > .list-group-item {
+	border-left: 0;
+	border-right: 0;
+	border-radius: 0;
+	padding-left: 0;
+	padding-right: 0;
+}
+
+.list-group {
+	padding-left: 0px;
+	list-style: none;
+}
+
+.list-group-item {
+	border-bottom: 1px solid #e7eaec;
+	border-top: 1px solid #e7eaec;
+	margin-bottom: -1px;
+	padding: 11px 0px;
+	font-size: 13px;
+}
+
+.pull-right {
+	float: right !important;
+}
+
+.el-card__header {
+	padding: 14px 15px 7px;
+	min-height: 40px;
+}
+
+.remove-padding-bottom > .el-card__body {
+	padding-bottom: 5px;
+}
+
+.card-box {
+	padding-right: 15px;
+	padding-left: 15px;
+	margin-bottom: 10px;
+}
+  
+/* text color */
+.text-navy {
+	color: #1ab394;
+}
+
+.text-primary {
+	color: inherit;
+}
+
+.text-success {
+	color: #1c84c6;
+}
+
+.text-info {
+	color: #23c6c8;
+}
+
+.text-warning {
+	color: #f8ac59;
+}
+
+.text-danger {
+	color: #ed5565;
+}
+
+.text-muted {
+	color: #888888;
+}
+
+/* image */
+.img-circle {
+	border-radius: 50%;
+}
+
+.img-lg {
+	width: 120px;
+	height: 120px;
+}
+
+.avatar-upload-preview {
+	position: absolute;
+	top: 50%;
+	transform: translate(50%, -50%);
+	width: 180px;
+	height: 180px;
+	border-radius: 50%;
+	box-shadow: 0 0 4px #ccc;
+	overflow: hidden;
+}
+
+#addSpan {
+  padding: 2px 10px;
+  background-color: red;
+  color: #fff;
+  border-radius: 15px;
+  text-align: center;
+	font-size: 12px;
+	margin-left: 8px;
 }

+ 108 - 92
src/utils/costum.js

@@ -1,92 +1,108 @@
-
-// 日期格式化
-export function parseTime(time, pattern) {
-  if (arguments.length === 0 || !time) {
-    return null
-  }
-  const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
-  let date
-  if (typeof time === 'object') {
-    date = time
-  } else {
-    if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
-      time = parseInt(time)
-    }
-    if ((typeof time === 'number') && (time.toString().length === 10)) {
-      time = time * 1000
-    }
-    date = new Date(time)
-  }
-  const formatObj = {
-    y: date.getFullYear(),
-    m: date.getMonth() + 1,
-    d: date.getDate(),
-    h: date.getHours(),
-    i: date.getMinutes(),
-    s: date.getSeconds(),
-    a: date.getDay()
-  }
-  const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
-    let value = formatObj[key]
-    // Note: getDay() returns 0 on Sunday
-    if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
-    if (result.length > 0 && value < 10) {
-      value = '0' + value
-    }
-    return value || 0
-  })
-  return time_str
-}
-
-// 表单重置
-export function resetForm(refName) {
-  if (this.$refs[refName]) {
-    this.$refs[refName].resetFields()
-  }
-}
-
-// 添加日期范围
-export function addDateRange(params, dateRange) {
-  var search = params
-  search.beginTime = ''
-  search.endTime = ''
-  if (dateRange != null && dateRange !== '') {
-    search.beginTime = this.dateRange[0]
-    search.endTime = this.dateRange[1]
-  }
-  return search
-}
-
-// 回显数据字典
-export function selectDictLabel(datas, value) {
-  var actions = []
-  Object.keys(datas).map((key) => {
-    if (datas[key].dictValue === ('' + value)) {
-      actions.push(datas[key].dictLabel)
-      return false
-    }
-  })
-  return actions.join('')
-}
-
-// 字符串格式化(%s )
-export function sprintf(str) {
-  var args = arguments; var flag = true; var i = 1
-  str = str.replace(/%s/g, function() {
-    var arg = args[i++]
-    if (typeof arg === 'undefined') {
-      flag = false
-      return ''
-    }
-    return arg
-  })
-  return flag ? str : ''
-}
-
-// 转换字符串,undefined,null等转化为""
-export function praseStrEmpty(str) {
-  if (!str || str === 'undefined' || str === 'null') {
-    return ''
-  }
-  return str
-}
+
+// 日期格式化
+export function parseTime(time, pattern) {
+  if (arguments.length === 0 || !time) {
+    return null
+  }
+  const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
+  let date
+  if (typeof time === 'object') {
+    date = time
+  } else {
+    if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
+      time = parseInt(time)
+    }
+    if ((typeof time === 'number') && (time.toString().length === 10)) {
+      time = time * 1000
+    }
+    date = new Date(time)
+  }
+  const formatObj = {
+    y: date.getFullYear(),
+    m: date.getMonth() + 1,
+    d: date.getDate(),
+    h: date.getHours(),
+    i: date.getMinutes(),
+    s: date.getSeconds(),
+    a: date.getDay()
+  }
+  const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
+    let value = formatObj[key]
+    // Note: getDay() returns 0 on Sunday
+    if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
+    if (result.length > 0 && value < 10) {
+      value = '0' + value
+    }
+    return value || 0
+  })
+  return time_str
+}
+
+// 表单重置
+export function resetForm(refName) {
+  if (this.$refs[refName]) {
+    this.$refs[refName].resetFields()
+  }
+}
+
+// 添加日期范围
+export function addDateRange(params, dateRange) {
+  var search = params
+  search.beginTime = ''
+  search.endTime = ''
+  if (dateRange != null && dateRange !== '') {
+    search.beginTime = this.dateRange[0]
+    search.endTime = this.dateRange[1]
+  }
+  return search
+}
+
+// 回显数据字典
+export function selectDictLabel(datas, value) {
+  var actions = []
+  Object.keys(datas).map((key) => {
+    if (datas[key].dictValue === ('' + value)) {
+      actions.push(datas[key].dictLabel)
+      return false
+    }
+  })
+  return actions.join('')
+}
+
+// 字符串格式化(%s )
+export function sprintf(str) {
+  var args = arguments; var flag = true; var i = 1
+  str = str.replace(/%s/g, function() {
+    var arg = args[i++]
+    if (typeof arg === 'undefined') {
+      flag = false
+      return ''
+    }
+    return arg
+  })
+  return flag ? str : ''
+}
+
+// 转换字符串,undefined,null等转化为""
+export function praseStrEmpty(str) {
+  if (!str || str === 'undefined' || str === 'null') {
+    return ''
+  }
+  return str
+}
+
+// 处理待办事项
+export function initNumberHtml(val) {
+  if(val) {
+    if(!document.querySelector('#addSpan')) {
+      let span = document.createElement('span')
+      span.innerHTML = val
+      span.id = 'addSpan'
+      document.querySelector('a[href="#/process/upcoming"] li').appendChild(span)
+    } else {
+      document.querySelector('#addSpan').innerHTML = val
+    }
+  } else {
+    document.querySelector('#addSpan').remove()
+  }
+}

+ 116 - 113
src/views/dashboard/admin/components/RangeSubmit/index.vue

@@ -1,113 +1,116 @@
-<template>
-  <div style="padding: 0 25px 20px 20px;" :class="className" :style="{height:height,width:width}" />
-</template>
-
-<script>
-import echarts from 'echarts'
-import resize from '../mixins/resize'
-
-export default {
-  mixins: [resize],
-  props: {
-    className: {
-      type: String,
-      default: 'chart'
-    },
-    width: {
-      type: String,
-      default: '100%'
-    },
-    height: {
-      type: String,
-      default: '350px'
-    },
-    autoResize: {
-      type: Boolean,
-      default: true
-    },
-    statisticsData: {
-      type: Object,
-      default: () => ({}),
-      required: true
-    }
-  },
-  data() {
-    return {
-      chart: null
-    }
-  },
-  watch: {
-    statisticsData: {
-      deep: true,
-      handler(val) {
-        this.setOptions(val)
-      }
-    }
-  },
-  mounted() {
-    this.$nextTick(() => {
-      this.initChart()
-    })
-  },
-  beforeDestroy() {
-    if (!this.chart) {
-      return
-    }
-    this.chart.dispose()
-    this.chart = null
-  },
-  methods: {
-    initChart() {
-      this.chart = echarts.init(this.$el, 'macarons')
-      this.setOptions(this.statisticsData)
-    },
-    setOptions({ expectedData, actualData } = {}) {
-      this.chart.setOption({
-        title: {
-          textStyle: {
-            fontSize: 15
-          }
-        },
-        tooltip: {
-          trigger: 'axis'
-        },
-        legend: {
-          data: ['工单总数', '未结束', '已结束']
-        },
-        grid: {
-          left: '25',
-          right: '35',
-          bottom: '20',
-          top: '50',
-          containLabel: true
-        },
-        xAxis: {
-          type: 'category',
-          boundaryGap: false,
-          data: this.statisticsData.datetime
-        },
-        yAxis: {
-          type: 'value'
-        },
-        series: [
-          {
-            name: '工单总数',
-            type: 'line',
-            data: this.statisticsData.total
-          },
-          {
-            name: '未结束',
-            type: 'line',
-            data: this.statisticsData.processing
-          },
-          {
-            name: '已结束',
-            type: 'line',
-            data: this.statisticsData.overs
-          }
-        ]
-      })
-    }
-  }
-}
-</script>
+<template>
+  <div style="padding: 0 25px 20px 20px;" :class="className" :style="{height:height,width:width}" />
+</template>
+
+<script>
+import echarts from 'echarts'
+import resize from '../mixins/resize'
+
+export default {
+  mixins: [resize],
+  props: {
+    className: {
+      type: String,
+      default: 'chart'
+    },
+    width: {
+      type: String,
+      default: '100%'
+    },
+    height: {
+      type: String,
+      default: '350px'
+    },
+    autoResize: {
+      type: Boolean,
+      default: true
+    },
+    statisticsData: {
+      type: Object,
+      default: () => ({}),
+      required: true
+    }
+  },
+  data() {
+    return {
+      chart: null
+    }
+  },
+  watch: {
+    statisticsData: {
+      deep: true,
+      handler(val) {
+        this.setOptions(val)
+      }
+    }
+  },
+  mounted() {
+    this.$nextTick(() => {
+      this.initChart()
+    })
+  },
+  beforeDestroy() {
+    if (!this.chart) {
+      return
+    }
+    this.chart.dispose()
+    this.chart = null
+  },
+  methods: {
+    initChart() {
+      this.chart = echarts.init(this.$el, 'macarons')
+      this.setOptions(this.statisticsData)
+    },
+    setOptions({ expectedData, actualData } = {}) {
+      this.chart.setOption({
+        title: {
+          textStyle: {
+            fontSize: 15
+          }
+        },
+        tooltip: {
+          trigger: 'axis'
+        },
+        legend: {
+          data: ['工单总数', '未结束', '已结束']
+        },
+        grid: {
+          left: '25',
+          right: '35',
+          bottom: '20',
+          top: '50',
+          containLabel: true
+        },
+        xAxis: {
+          type: 'category',
+          boundaryGap: false,
+          data: this.statisticsData.datetime
+        },
+        yAxis: {
+          type: 'value'
+        },
+        series: [
+          {
+            name: '工单总数',
+            type: 'line',
+            data: this.statisticsData.total,
+            smooth: true
+          },
+          {
+            name: '未结束',
+            type: 'line',
+            data: this.statisticsData.processing,
+            smooth: true
+          },
+          {
+            name: '已结束',
+            type: 'line',
+            data: this.statisticsData.overs,
+            smooth: true
+          }
+        ]
+      })
+    }
+  }
+}
+</script>

+ 224 - 196
src/views/dashboard/admin/index.vue

@@ -1,196 +1,224 @@
-<template>
-  <div class="dashboard-editor-container">
-    <el-row :gutter="12">
-      <el-col :sm="24" :xs="24" :md="6" :xl="6" :lg="6" :style="{ marginBottom: '12px' }">
-        <chart-card title="工单总数" :total="dashboardValue.count.all">
-          <!-- <el-tooltip slot="action" class="item" effect="dark" content="指标说明" placement="top-start">
-            <i class="el-icon-warning-outline" />
-          </el-tooltip> -->
-        </chart-card>
-      </el-col>
-      <el-col :sm="24" :xs="24" :md="6" :xl="6" :lg="6" :style="{ marginBottom: '12px' }">
-        <chart-card title="我创建的" :total="dashboardValue.count.my_create">
-          <!-- <el-tooltip slot="action" class="item" effect="dark" content="指标说明" placement="top-start">
-            <i class="el-icon-warning-outline" />
-          </el-tooltip> -->
-        </chart-card>
-      </el-col>
-      <el-col :sm="24" :xs="24" :md="6" :xl="6" :lg="6" :style="{ marginBottom: '12px' }">
-        <chart-card title="我相关的" :total="dashboardValue.count.related">
-          <!-- <el-tooltip slot="action" class="item" effect="dark" content="指标说明" placement="top-start">
-            <i class="el-icon-warning-outline" />
-          </el-tooltip> -->
-        </chart-card>
-      </el-col>
-      <el-col :sm="24" :xs="24" :md="6" :xl="6" :lg="6" :style="{ marginBottom: '12px' }">
-        <chart-card title="我的待办" :total="dashboardValue.count.upcoming">
-          <!-- <el-tooltip slot="action" class="item" effect="dark" content="指标说明" placement="top-start">
-            <i class="el-icon-warning-outline" />
-          </el-tooltip> -->
-        </chart-card>
-      </el-col>
-    </el-row>
-
-    <el-card :bordered="false" :body-style="{padding: '5'}" :style="{ marginBottom: '12px', textAlign: 'center' }">
-      <el-date-picker
-        v-model="querys"
-        type="daterange"
-        align="right"
-        unlink-panels
-        range-separator="至"
-        start-placeholder="开始日期"
-        end-placeholder="结束日期"
-        :picker-options="pickerOptions"
-        @change="timeScreening"
-      />
-    </el-card>
-
-    <el-card :bordered="false" :body-style="{padding: '0'}" :style="{ marginBottom: '12px' }">
-      <div class="salesCard">
-        <div>
-          <h4 :style="{ marginBottom: '20px' }" style="margin-left: 20px;">提交工单统计</h4>
-          <RangeSubmit :statistics-data="dashboardValue.submit" />
-        </div>
-      </div>
-    </el-card>
-
-    <el-row>
-      <el-col :span="8">
-        <el-card :bordered="false" :body-style="{padding: '0'}">
-          <div class="salesCard leaderboard">
-            <rank-list title="热门流程排行榜 Top 10" :list="dashboardValue.ranks" />
-          </div>
-        </el-card>
-      </el-col>
-      <el-col :span="8" style="padding-left: 12px;">
-        <el-card :bordered="false" :body-style="{padding: '0'}">
-          <div class="salesCard leaderboard">
-            <HandleRank title="处理工单人员排行榜" :list="dashboardValue.handle" />
-          </div>
-        </el-card>
-      </el-col>
-      <el-col :span="8" style="padding-left: 12px;">
-        <el-card :bordered="false" :body-style="{padding: '0'}">
-          <div class="salesCard leaderboard">
-            <HandlePeriod title="工单处理耗时排行榜" :list="dashboardValue.period" />
-          </div>
-        </el-card>
-      </el-col>
-    </el-row>
-  </div>
-</template>
-
-<script>
-import ChartCard from './components/ChartCard'
-import RankList from './components/RankList/index'
-import RangeSubmit from './components/RangeSubmit'
-import HandleRank from './components/HandleRank'
-import HandlePeriod from './components/HandlePeriod'
-
-import { initData } from '@/api/dashboard'
-
-export default {
-  name: 'DashboardAdmin',
-  components: {
-    ChartCard,
-    RankList,
-    RangeSubmit,
-    HandleRank,
-    HandlePeriod
-  },
-  data() {
-    return {
-      dashboardValue: {
-        count: {}
-      },
-      rankList: [],
-      submitData: [],
-      querys: '',
-      queryList: {},
-      pickerOptions: {
-        shortcuts: [{
-          text: '最近一周',
-          onClick(picker) {
-            const end = new Date()
-            const start = new Date()
-            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
-            picker.$emit('pick', [start, end])
-          }
-        }, {
-          text: '最近一个月',
-          onClick(picker) {
-            const end = new Date()
-            const start = new Date()
-            start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
-            picker.$emit('pick', [start, end])
-          }
-        }, {
-          text: '最近三个月',
-          onClick(picker) {
-            const end = new Date()
-            const start = new Date()
-            start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
-            picker.$emit('pick', [start, end])
-          }
-        }]
-      }
-    }
-  },
-  created() {
-    this.getInitData()
-  },
-  methods: {
-    getInitData() {
-      initData(this.queryList).then(response => {
-        this.dashboardValue = response.data
-      })
-    },
-    timeScreening() {
-      console.log(this.querys)
-      if (this.querys.length > 1) {
-        this.queryList.start_time = this.querys[0]
-        this.queryList.end_time = this.querys[1]
-        this.getInitData()
-      }
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.dashboard-editor-container {
-  padding: 12px;
-  background-color: rgb(240, 242, 245);
-  position: relative;
-
-  .github-corner {
-    position: absolute;
-    top: 0;
-    border: 0;
-    right: 0;
-  }
-
-  .chart-wrapper {
-    background: #fff;
-    padding: 16px 16px 0;
-    margin-bottom: 32px;
-  }
-}
-
-/deep/ .el-tabs__item{
-   padding-left: 16px!important;
-   height: 50px;
-   line-height: 50px;
-}
-
-@media (max-width:1024px) {
-  .chart-wrapper {
-    padding: 8px;
-  }
-}
-
-.leaderboard {
-  height: 448px;
-  overflow: auto;
-}
-</style>
+<template>
+  <div class="dashboard-editor-container">
+    <el-row :gutter="12">
+      <el-col :sm="24" :xs="24" :md="6" :xl="6" :lg="6" :style="{ marginBottom: '12px' }">
+        <chart-card>
+          <template #title>
+            <span :class="dashboardValue.count.upcoming > 0 ? 'myUpcoming' : null">我的待办</span>
+          </template>
+          <template #total>
+            <router-link to="/process/upcoming">{{ dashboardValue.count.upcoming }}</router-link>
+          </template>
+          <!-- <el-tooltip slot="action" class="item" effect="dark" content="指标说明" placement="top-start">
+            <i class="el-icon-warning-outline" />
+          </el-tooltip> -->
+        </chart-card>
+      </el-col>
+      <el-col :sm="24" :xs="24" :md="6" :xl="6" :lg="6" :style="{ marginBottom: '12px' }">
+        <chart-card title="工单总数" :total="dashboardValue.count.all">
+          <!-- <el-tooltip slot="action" class="item" effect="dark" content="指标说明" placement="top-start">
+            <i class="el-icon-warning-outline" />
+          </el-tooltip> -->
+        </chart-card>
+      </el-col>
+      <el-col :sm="24" :xs="24" :md="6" :xl="6" :lg="6" :style="{ marginBottom: '12px' }">
+        <chart-card title="我创建的" :total="dashboardValue.count.my_create">
+          <!-- <el-tooltip slot="action" class="item" effect="dark" content="指标说明" placement="top-start">
+            <i class="el-icon-warning-outline" />
+          </el-tooltip> -->
+        </chart-card>
+      </el-col>
+      <el-col :sm="24" :xs="24" :md="6" :xl="6" :lg="6" :style="{ marginBottom: '12px' }">
+        <chart-card title="我相关的" :total="dashboardValue.count.related">
+          <!-- <el-tooltip slot="action" class="item" effect="dark" content="指标说明" placement="top-start">
+            <i class="el-icon-warning-outline" />
+          </el-tooltip> -->
+        </chart-card>
+      </el-col>
+    </el-row>
+
+    <el-card :bordered="false" :body-style="{padding: '5'}" :style="{ marginBottom: '12px', textAlign: 'center' }">
+      <el-date-picker
+        v-model="querys"
+        type="daterange"
+        align="right"
+        unlink-panels
+        range-separator="至"
+        start-placeholder="开始日期"
+        end-placeholder="结束日期"
+        :picker-options="pickerOptions"
+        @change="timeScreening"
+      />
+    </el-card>
+
+    <el-card :bordered="false" :body-style="{padding: '0'}" :style="{ marginBottom: '12px' }">
+      <div class="salesCard">
+        <div>
+          <h4 :style="{ marginBottom: '20px' }" style="margin-left: 20px;">提交工单统计</h4>
+          <RangeSubmit :statistics-data="dashboardValue.submit" />
+        </div>
+      </div>
+    </el-card>
+
+    <el-row>
+      <el-col :span="8">
+        <el-card :bordered="false" :body-style="{padding: '0'}">
+          <div class="salesCard leaderboard">
+            <rank-list title="热门流程排行榜 Top 10" :list="dashboardValue.ranks" />
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :span="8" style="padding-left: 12px;">
+        <el-card :bordered="false" :body-style="{padding: '0'}">
+          <div class="salesCard leaderboard">
+            <HandleRank title="处理工单人员排行榜" :list="dashboardValue.handle" />
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :span="8" style="padding-left: 12px;">
+        <el-card :bordered="false" :body-style="{padding: '0'}">
+          <div class="salesCard leaderboard">
+            <HandlePeriod title="工单处理耗时排行榜" :list="dashboardValue.period" />
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script>
+import ChartCard from './components/ChartCard'
+import RankList from './components/RankList/index'
+import RangeSubmit from './components/RangeSubmit'
+import HandleRank from './components/HandleRank'
+import HandlePeriod from './components/HandlePeriod'
+import { initNumberHtml } from '@/utils/costum'
+
+import { initData } from '@/api/dashboard'
+
+export default {
+  name: 'DashboardAdmin',
+  components: {
+    ChartCard,
+    RankList,
+    RangeSubmit,
+    HandleRank,
+    HandlePeriod
+  },
+  data() {
+    return {
+      dashboardValue: {
+        count: {}
+      },
+      rankList: [],
+      submitData: [],
+      querys: '',
+      queryList: {},
+      pickerOptions: {
+        shortcuts: [{
+          text: '最近一周',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
+            picker.$emit('pick', [start, end])
+          }
+        }, {
+          text: '最近一个月',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
+            picker.$emit('pick', [start, end])
+          }
+        }, {
+          text: '最近三个月',
+          onClick(picker) {
+            const end = new Date()
+            const start = new Date()
+            start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
+            picker.$emit('pick', [start, end])
+          }
+        }]
+      }
+    }
+  },
+  created() {
+    this.getInitData()
+  },
+  methods: {
+    async getInitData() {
+      await initData(this.queryList).then(response => {
+        this.dashboardValue = response.data
+      })
+      // 添加我的代办显示数据
+      if(this.dashboardValue.count.upcoming && this.dashboardValue.count.upcoming > 0) {
+        initNumberHtml(this.dashboardValue.count.upcoming)
+      }
+    },
+    timeScreening() {
+      if (this.querys && this.querys.length > 1) {
+        this.queryList.start_time = this.querys[0]
+        this.queryList.end_time = this.querys[1]
+      } else {
+        this.queryList.start_time = null
+        this.queryList.end_time = null
+      }
+      this.getInitData()
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.dashboard-editor-container {
+  padding: 12px;
+  background-color: rgb(240, 242, 245);
+  position: relative;
+
+  .github-corner {
+    position: absolute;
+    top: 0;
+    border: 0;
+    right: 0;
+  }
+
+  .chart-wrapper {
+    background: #fff;
+    padding: 16px 16px 0;
+    margin-bottom: 32px;
+  }
+}
+
+/deep/ .el-tabs__item{
+   padding-left: 16px!important;
+   height: 50px;
+   line-height: 50px;
+}
+
+@media (max-width:1024px) {
+  .chart-wrapper {
+    padding: 8px;
+  }
+}
+
+.leaderboard {
+  height: 448px;
+  overflow: auto;
+}
+
+.myUpcoming {
+  color: #1890ff;
+  font-weight: 500;
+  animation: flash 1.4s ease infinite;
+}
+@keyframes flash{
+    from {
+        opacity: .4;
+    }
+    to {
+        opacity: 1;
+    }
+}
+
+</style>

+ 4 - 2
src/views/process/list/handle.vue

@@ -197,7 +197,7 @@ import {
   queryUserInfo,
   queryAllOrgan
 } from '@/api/process/work-order'
-
+import store from '@/store'
 import { getInfo } from '@/api/user'
 
 import { listUser } from '@/api/system/sysuser'
@@ -566,9 +566,11 @@ export default {
               if (response.code === 200) {
               // this.$router.push({ name: 'upcoming' })
 
-              await asyncPlayLog({ workOrderId: parseInt(this.$route.query.workOrderId), fileUrl: fileList.join(',') })
+                await asyncPlayLog({ workOrderId: parseInt(this.$route.query.workOrderId), fileUrl: fileList.join(',') })
               // window.location.reload()
                 this.getProcessNodeList()
+
+                await store.dispatch('user/initUserInfo')
               }
             })
           })

+ 3 - 0
src/views/process/list/upcoming.vue

@@ -109,6 +109,7 @@
 <script>
 import { workOrderList, inversionWorkOrder } from '@/api/process/work-order'
 import { listUser } from '@/api/system/sysuser'
+import { initNumberHtml } from '@/utils/costum'
 
 // 搜索
 import WorkOrderSearch from './components/search/index'
@@ -159,6 +160,8 @@ export default {
         this.queryParams.pageSize = response.data.per_page
         this.total = response.data.total_count
         this.loading = false
+
+        initNumberHtml(response.data.total_count)
       })
     },
     handleSearch(val) {

File diff suppressed because it is too large
+ 0 - 0
web/index.html


File diff suppressed because it is too large
+ 0 - 0
web/static/web/css/app.b15a4ab8.css


File diff suppressed because it is too large
+ 0 - 0
web/static/web/css/chunk-07ce983f.61d75617.css


File diff suppressed because it is too large
+ 0 - 0
web/static/web/css/chunk-d3212d36.0af89f2f.css


+ 0 - 0
web/static/web/css/chunk-4a6b3f46.dc680019.css → web/static/web/css/chunk-fe33d916.dc680019.css


File diff suppressed because it is too large
+ 0 - 0
web/static/web/js/app.43d1ab09.js


File diff suppressed because it is too large
+ 0 - 0
web/static/web/js/app.c69fb5dc.js


File diff suppressed because it is too large
+ 0 - 0
web/static/web/js/chunk-07ce983f.1dd83f64.js


File diff suppressed because it is too large
+ 0 - 0
web/static/web/js/chunk-25041faa.8a855884.js


File diff suppressed because it is too large
+ 0 - 0
web/static/web/js/chunk-41f6000b.9fc561d0.js


File diff suppressed because it is too large
+ 0 - 0
web/static/web/js/chunk-50a983d1.babc8539.js


File diff suppressed because it is too large
+ 0 - 0
web/static/web/js/chunk-6238fc07.d1aea56f.js


File diff suppressed because it is too large
+ 0 - 0
web/static/web/js/chunk-6673a38f.185c55e2.js


File diff suppressed because it is too large
+ 0 - 0
web/static/web/js/chunk-6bdc8299.0e9f01fd.js


File diff suppressed because it is too large
+ 0 - 0
web/static/web/js/chunk-748b566e.1c84864c.js


File diff suppressed because it is too large
+ 0 - 0
web/static/web/js/chunk-d3212d36.fba7337c.js


File diff suppressed because it is too large
+ 0 - 0
web/static/web/js/chunk-fe33d916.fff128e5.js


Some files were not shown because too many files changed in this diff