ming %!s(int64=3) %!d(string=hai) anos
pai
achega
3b3a268a1a
Modificáronse 100 ficheiros con 3703 adicións e 0 borrados
  1. 14 0
      .editorconfig
  2. 5 0
      .env.development
  3. 6 0
      .env.production
  4. 8 0
      .env.staging
  5. 4 0
      .eslintignore
  6. 198 0
      .eslintrc.js
  7. 16 0
      .gitignore
  8. 5 0
      .travis.yml
  9. 21 0
      LICENSE
  10. 102 0
      README-zh.md
  11. 14 0
      babel.config.js
  12. 35 0
      build/index.js
  13. 24 0
      jest.config.js
  14. 9 0
      jsconfig.json
  15. 57 0
      mock/index.js
  16. 81 0
      mock/mock-server.js
  17. 29 0
      mock/table.js
  18. 84 0
      mock/user.js
  19. 25 0
      mock/utils.js
  20. 62 0
      package.json
  21. 8 0
      postcss.config.js
  22. BIN=BIN
      public/favicon.ico
  23. 17 0
      public/index.html
  24. 11 0
      src/App.vue
  25. 9 0
      src/api/table.js
  26. 24 0
      src/api/user.js
  27. BIN=BIN
      src/assets/404_images/404.png
  28. BIN=BIN
      src/assets/404_images/404_cloud.png
  29. BIN=BIN
      src/assets/logo.png
  30. 78 0
      src/components/Breadcrumb/index.vue
  31. 44 0
      src/components/Hamburger/index.vue
  32. 62 0
      src/components/SvgIcon/index.vue
  33. 9 0
      src/icons/index.js
  34. 7 0
      src/icons/svg/alarmManage.svg
  35. 0 0
      src/icons/svg/dashboard.svg
  36. 7 0
      src/icons/svg/dataManage.svg
  37. 7 0
      src/icons/svg/deviceManage.svg
  38. 1 0
      src/icons/svg/example.svg
  39. 1 0
      src/icons/svg/eye-open.svg
  40. 1 0
      src/icons/svg/eye.svg
  41. 0 0
      src/icons/svg/form.svg
  42. 1 0
      src/icons/svg/link.svg
  43. 7 0
      src/icons/svg/monthReport.svg
  44. 1 0
      src/icons/svg/nested.svg
  45. 5 0
      src/icons/svg/operManage.svg
  46. 1 0
      src/icons/svg/password.svg
  47. 7 0
      src/icons/svg/patrolManage.svg
  48. 7 0
      src/icons/svg/planOutage.svg
  49. 7 0
      src/icons/svg/powerQuality.svg
  50. 7 0
      src/icons/svg/siteManage.svg
  51. 5 0
      src/icons/svg/systemManage.svg
  52. 1 0
      src/icons/svg/table.svg
  53. 1 0
      src/icons/svg/tree.svg
  54. 1 0
      src/icons/svg/user.svg
  55. 7 0
      src/icons/svg/形状 13 (1).svg
  56. 22 0
      src/icons/svgo.yml
  57. 40 0
      src/layout/components/AppMain.vue
  58. 139 0
      src/layout/components/Navbar.vue
  59. 26 0
      src/layout/components/Sidebar/FixiOSBug.js
  60. 41 0
      src/layout/components/Sidebar/Item.vue
  61. 43 0
      src/layout/components/Sidebar/Link.vue
  62. 83 0
      src/layout/components/Sidebar/Logo.vue
  63. 95 0
      src/layout/components/Sidebar/SidebarItem.vue
  64. 56 0
      src/layout/components/Sidebar/index.vue
  65. 3 0
      src/layout/components/index.js
  66. 93 0
      src/layout/index.vue
  67. 45 0
      src/layout/mixin/ResizeHandler.js
  68. 43 0
      src/main.js
  69. 64 0
      src/permission.js
  70. 521 0
      src/router/index.js
  71. 16 0
      src/settings.js
  72. 8 0
      src/store/getters.js
  73. 19 0
      src/store/index.js
  74. 48 0
      src/store/modules/app.js
  75. 32 0
      src/store/modules/settings.js
  76. 97 0
      src/store/modules/user.js
  77. 49 0
      src/styles/element-ui.scss
  78. 65 0
      src/styles/index.scss
  79. 28 0
      src/styles/mixin.scss
  80. 226 0
      src/styles/sidebar.scss
  81. 48 0
      src/styles/transition.scss
  82. 25 0
      src/styles/variables.scss
  83. 15 0
      src/utils/auth.js
  84. 10 0
      src/utils/get-page-title.js
  85. 117 0
      src/utils/index.js
  86. 85 0
      src/utils/request.js
  87. 20 0
      src/utils/validate.js
  88. 228 0
      src/views/404.vue
  89. 30 0
      src/views/alarmManage/index.vue
  90. 30 0
      src/views/dashboard/index.vue
  91. 5 0
      src/views/dataManage/chainAnalysis/index.vue
  92. 5 0
      src/views/dataManage/consumConfig/index.vue
  93. 5 0
      src/views/dataManage/demandAnalysis/index.vue
  94. 5 0
      src/views/dataManage/energyReport/index.vue
  95. 5 0
      src/views/dataManage/handOpera/index.vue
  96. 5 0
      src/views/dataManage/sameAnalysis/index.vue
  97. 5 0
      src/views/deviceManage/attribTemplate/index.vue
  98. 5 0
      src/views/deviceManage/channelList/index.vue
  99. 5 0
      src/views/deviceManage/communicateEquip/index.vue
  100. 5 0
      src/views/deviceManage/powerEquip/index.vue

+ 14 - 0
.editorconfig

@@ -0,0 +1,14 @@
+# http://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.md]
+insert_final_newline = false
+trim_trailing_whitespace = false

+ 5 - 0
.env.development

@@ -0,0 +1,5 @@
+# just a flag
+ENV = 'development'
+
+# base api
+VUE_APP_BASE_API = '/dev-api'

+ 6 - 0
.env.production

@@ -0,0 +1,6 @@
+# just a flag
+ENV = 'production'
+
+# base api
+VUE_APP_BASE_API = '/prod-api'
+

+ 8 - 0
.env.staging

@@ -0,0 +1,8 @@
+NODE_ENV = production
+
+# just a flag
+ENV = 'staging'
+
+# base api
+VUE_APP_BASE_API = '/stage-api'
+

+ 4 - 0
.eslintignore

@@ -0,0 +1,4 @@
+build/*.js
+src/assets
+public
+dist

+ 198 - 0
.eslintrc.js

@@ -0,0 +1,198 @@
+module.exports = {
+  root: true,
+  parserOptions: {
+    parser: 'babel-eslint',
+    sourceType: 'module'
+  },
+  env: {
+    browser: true,
+    node: true,
+    es6: true,
+  },
+  extends: ['plugin:vue/recommended', 'eslint:recommended'],
+
+  // add your custom rules here
+  //it is base on https://github.com/vuejs/eslint-config-vue
+  rules: {
+    "vue/max-attributes-per-line": [2, {
+      "singleline": 10,
+      "multiline": {
+        "max": 1,
+        "allowFirstLine": false
+      }
+    }],
+    "vue/singleline-html-element-content-newline": "off",
+    "vue/multiline-html-element-content-newline":"off",
+    "vue/name-property-casing": ["error", "PascalCase"],
+    "vue/no-v-html": "off",
+    'accessor-pairs': 2,
+    'arrow-spacing': [2, {
+      'before': true,
+      'after': true
+    }],
+    'block-spacing': [2, 'always'],
+    'brace-style': [2, '1tbs', {
+      'allowSingleLine': true
+    }],
+    'camelcase': [0, {
+      'properties': 'always'
+    }],
+    'comma-dangle': [2, 'never'],
+    'comma-spacing': [2, {
+      'before': false,
+      'after': true
+    }],
+    'comma-style': [2, 'last'],
+    'constructor-super': 2,
+    'curly': [2, 'multi-line'],
+    'dot-location': [2, 'property'],
+    'eol-last': 2,
+    'eqeqeq': ["error", "always", {"null": "ignore"}],
+    'generator-star-spacing': [2, {
+      'before': true,
+      'after': true
+    }],
+    'handle-callback-err': [2, '^(err|error)$'],
+    'indent': [2, 2, {
+      'SwitchCase': 1
+    }],
+    'jsx-quotes': [2, 'prefer-single'],
+    'key-spacing': [2, {
+      'beforeColon': false,
+      'afterColon': true
+    }],
+    'keyword-spacing': [2, {
+      'before': true,
+      'after': true
+    }],
+    'new-cap': [2, {
+      'newIsCap': true,
+      'capIsNew': false
+    }],
+    'new-parens': 2,
+    'no-array-constructor': 2,
+    'no-caller': 2,
+    'no-console': 'off',
+    'no-class-assign': 2,
+    'no-cond-assign': 2,
+    'no-const-assign': 2,
+    'no-control-regex': 0,
+    'no-delete-var': 2,
+    'no-dupe-args': 2,
+    'no-dupe-class-members': 2,
+    'no-dupe-keys': 2,
+    'no-duplicate-case': 2,
+    'no-empty-character-class': 2,
+    'no-empty-pattern': 2,
+    'no-eval': 2,
+    'no-ex-assign': 2,
+    'no-extend-native': 2,
+    'no-extra-bind': 2,
+    'no-extra-boolean-cast': 2,
+    'no-extra-parens': [2, 'functions'],
+    'no-fallthrough': 2,
+    'no-floating-decimal': 2,
+    'no-func-assign': 2,
+    'no-implied-eval': 2,
+    'no-inner-declarations': [2, 'functions'],
+    'no-invalid-regexp': 2,
+    'no-irregular-whitespace': 2,
+    'no-iterator': 2,
+    'no-label-var': 2,
+    'no-labels': [2, {
+      'allowLoop': false,
+      'allowSwitch': false
+    }],
+    'no-lone-blocks': 2,
+    'no-mixed-spaces-and-tabs': 2,
+    'no-multi-spaces': 2,
+    'no-multi-str': 2,
+    'no-multiple-empty-lines': [2, {
+      'max': 1
+    }],
+    'no-native-reassign': 2,
+    'no-negated-in-lhs': 2,
+    'no-new-object': 2,
+    'no-new-require': 2,
+    'no-new-symbol': 2,
+    'no-new-wrappers': 2,
+    'no-obj-calls': 2,
+    'no-octal': 2,
+    'no-octal-escape': 2,
+    'no-path-concat': 2,
+    'no-proto': 2,
+    'no-redeclare': 2,
+    'no-regex-spaces': 2,
+    'no-return-assign': [2, 'except-parens'],
+    'no-self-assign': 2,
+    'no-self-compare': 2,
+    'no-sequences': 2,
+    'no-shadow-restricted-names': 2,
+    'no-spaced-func': 2,
+    'no-sparse-arrays': 2,
+    'no-this-before-super': 2,
+    'no-throw-literal': 2,
+    'no-trailing-spaces': 2,
+    'no-undef': 2,
+    'no-undef-init': 2,
+    'no-unexpected-multiline': 2,
+    'no-unmodified-loop-condition': 2,
+    'no-unneeded-ternary': [2, {
+      'defaultAssignment': false
+    }],
+    'no-unreachable': 2,
+    'no-unsafe-finally': 2,
+    'no-unused-vars': [2, {
+      'vars': 'all',
+      'args': 'none'
+    }],
+    'no-useless-call': 2,
+    'no-useless-computed-key': 2,
+    'no-useless-constructor': 2,
+    'no-useless-escape': 0,
+    'no-whitespace-before-property': 2,
+    'no-with': 2,
+    'one-var': [2, {
+      'initialized': 'never'
+    }],
+    'operator-linebreak': [2, 'after', {
+      'overrides': {
+        '?': 'before',
+        ':': 'before'
+      }
+    }],
+    'padded-blocks': [2, 'never'],
+    'quotes': [2, 'single', {
+      'avoidEscape': true,
+      'allowTemplateLiterals': true
+    }],
+    'semi': [2, 'never'],
+    'semi-spacing': [2, {
+      'before': false,
+      'after': true
+    }],
+    'space-before-blocks': [2, 'always'],
+    'space-before-function-paren': [2, 'never'],
+    'space-in-parens': [2, 'never'],
+    'space-infix-ops': 2,
+    'space-unary-ops': [2, {
+      'words': true,
+      'nonwords': false
+    }],
+    'spaced-comment': [2, 'always', {
+      'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
+    }],
+    'template-curly-spacing': [2, 'never'],
+    'use-isnan': 2,
+    'valid-typeof': 2,
+    'wrap-iife': [2, 'any'],
+    'yield-star-spacing': [2, 'both'],
+    'yoda': [2, 'never'],
+    'prefer-const': 2,
+    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
+    'object-curly-spacing': [2, 'always', {
+      objectsInObjects: false
+    }],
+    'array-bracket-spacing': [2, 'never']
+  }
+}

+ 16 - 0
.gitignore

@@ -0,0 +1,16 @@
+.DS_Store
+node_modules/
+dist/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+package-lock.json
+tests/**/coverage/
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln

+ 5 - 0
.travis.yml

@@ -0,0 +1,5 @@
+language: node_js
+node_js: 10
+script: npm run test
+notifications:
+  email: false

+ 21 - 0
LICENSE

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

+ 102 - 0
README-zh.md

@@ -0,0 +1,102 @@
+# vue-admin-template
+
+> 这是一个极简的 vue admin 管理后台。它只包含了 Element UI & axios & iconfont & permission control & lint,这些搭建后台必要的东西。
+
+[线上地址](http://panjiachen.github.io/vue-admin-template)
+
+[国内访问](https://panjiachen.gitee.io/vue-admin-template)
+
+目前版本为 `v4.0+` 基于 `vue-cli` 进行构建,若你想使用旧版本,可以切换分支到[tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0),它不依赖 `vue-cli`。
+
+## Extra
+
+如果你想要根据用户角色来动态生成侧边栏和 router,你可以使用该分支[permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control)
+
+## 相关项目
+
+- [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
+
+- [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
+
+- [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template)
+
+- [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312)
+
+写了一个系列的教程配套文章,如何从零构建后一个完整的后台项目:
+
+- [手摸手,带你用 vue 撸后台 系列一(基础篇)](https://juejin.im/post/59097cd7a22b9d0065fb61d2)
+- [手摸手,带你用 vue 撸后台 系列二(登录权限篇)](https://juejin.im/post/591aa14f570c35006961acac)
+- [手摸手,带你用 vue 撸后台 系列三 (实战篇)](https://juejin.im/post/593121aa0ce4630057f70d35)
+- [手摸手,带你用 vue 撸后台 系列四(vueAdmin 一个极简的后台基础模板,专门针对本项目的文章,算作是一篇文档)](https://juejin.im/post/595b4d776fb9a06bbe7dba56)
+- [手摸手,带你封装一个 vue component](https://segmentfault.com/a/1190000009090836)
+
+## Build Setup
+
+```bash
+# 克隆项目
+git clone https://github.com/PanJiaChen/vue-admin-template.git
+
+# 进入项目目录
+cd vue-admin-template
+
+# 安装依赖
+npm install
+
+# 建议不要直接使用 cnpm 安装以来,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
+npm install --registry=https://registry.npm.taobao.org
+
+# 启动服务
+npm run dev
+```
+
+浏览器访问 [http://localhost:9528](http://localhost:9528)
+
+## 发布
+
+```bash
+# 构建测试环境
+npm run build:stage
+
+# 构建生产环境
+npm run build:prod
+```
+
+## 其它
+
+```bash
+# 预览发布环境效果
+npm run preview
+
+# 预览发布环境效果 + 静态资源分析
+npm run preview -- --report
+
+# 代码格式检查
+npm run lint
+
+# 代码格式检查并自动修复
+npm run lint -- --fix
+```
+
+更多信息请参考 [使用文档](https://panjiachen.github.io/vue-element-admin-site/zh/)
+
+## 购买贴纸
+
+你也可以通过 购买[官方授权的贴纸](https://smallsticker.com/product/vue-element-admin) 的方式来支持 vue-element-admin - 每售出一张贴纸,我们将获得 2 元的捐赠。
+
+## Demo
+
+![demo](https://github.com/PanJiaChen/PanJiaChen.github.io/blob/master/images/demo.gif)
+
+## Browsers support
+
+Modern browsers and Internet Explorer 10+.
+
+| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
+| --------- | --------- | --------- | --------- |
+| IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions
+
+## License
+
+[MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license.
+
+Copyright (c) 2017-present PanJiaChen

+ 14 - 0
babel.config.js

@@ -0,0 +1,14 @@
+module.exports = {
+  presets: [
+    // https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app
+    '@vue/cli-plugin-babel/preset'
+  ],
+  'env': {
+    'development': {
+      // babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().
+      // This plugin can significantly increase the speed of hot updates, when you have a large number of pages.
+      // https://panjiachen.github.io/vue-element-admin-site/guide/advanced/lazy-loading.html
+      'plugins': ['dynamic-import-node']
+    }
+  }
+}

+ 35 - 0
build/index.js

@@ -0,0 +1,35 @@
+const { run } = require('runjs')
+const chalk = require('chalk')
+const config = require('../vue.config.js')
+const rawArgv = process.argv.slice(2)
+const args = rawArgv.join(' ')
+
+if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
+  const report = rawArgv.includes('--report')
+
+  run(`vue-cli-service build ${args}`)
+
+  const port = 9526
+  const publicPath = config.publicPath
+
+  var connect = require('connect')
+  var serveStatic = require('serve-static')
+  const app = connect()
+
+  app.use(
+    publicPath,
+    serveStatic('./dist', {
+      index: ['index.html', '/']
+    })
+  )
+
+  app.listen(port, function () {
+    console.log(chalk.green(`> Preview at  http://localhost:${port}${publicPath}`))
+    if (report) {
+      console.log(chalk.green(`> Report at  http://localhost:${port}${publicPath}report.html`))
+    }
+
+  })
+} else {
+  run(`vue-cli-service build ${args}`)
+}

+ 24 - 0
jest.config.js

@@ -0,0 +1,24 @@
+module.exports = {
+  moduleFileExtensions: ['js', 'jsx', 'json', 'vue'],
+  transform: {
+    '^.+\\.vue$': 'vue-jest',
+    '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$':
+      'jest-transform-stub',
+    '^.+\\.jsx?$': 'babel-jest'
+  },
+  moduleNameMapper: {
+    '^@/(.*)$': '<rootDir>/src/$1'
+  },
+  snapshotSerializers: ['jest-serializer-vue'],
+  testMatch: [
+    '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
+  ],
+  collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'],
+  coverageDirectory: '<rootDir>/tests/unit/coverage',
+  // 'collectCoverage': true,
+  'coverageReporters': [
+    'lcov',
+    'text-summary'
+  ],
+  testURL: 'http://localhost/'
+}

+ 9 - 0
jsconfig.json

@@ -0,0 +1,9 @@
+{
+  "compilerOptions": {
+    "baseUrl": "./",
+    "paths": {
+        "@/*": ["src/*"]
+    }
+  },
+  "exclude": ["node_modules", "dist"]
+}

+ 57 - 0
mock/index.js

@@ -0,0 +1,57 @@
+const Mock = require('mockjs')
+const { param2Obj } = require('./utils')
+
+const user = require('./user')
+const table = require('./table')
+
+const mocks = [
+  ...user,
+  ...table
+]
+
+// for front mock
+// please use it cautiously, it will redefine XMLHttpRequest,
+// which will cause many of your third-party libraries to be invalidated(like progress event).
+function mockXHR() {
+  // mock patch
+  // https://github.com/nuysoft/Mock/issues/300
+  Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
+  Mock.XHR.prototype.send = function() {
+    if (this.custom.xhr) {
+      this.custom.xhr.withCredentials = this.withCredentials || false
+
+      if (this.responseType) {
+        this.custom.xhr.responseType = this.responseType
+      }
+    }
+    this.proxy_send(...arguments)
+  }
+
+  function XHR2ExpressReqWrap(respond) {
+    return function(options) {
+      let result = null
+      if (respond instanceof Function) {
+        const { body, type, url } = options
+        // https://expressjs.com/en/4x/api.html#req
+        result = respond({
+          method: type,
+          body: JSON.parse(body),
+          query: param2Obj(url)
+        })
+      } else {
+        result = respond
+      }
+      return Mock.mock(result)
+    }
+  }
+
+  for (const i of mocks) {
+    Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response))
+  }
+}
+
+module.exports = {
+  mocks,
+  mockXHR
+}
+

+ 81 - 0
mock/mock-server.js

@@ -0,0 +1,81 @@
+const chokidar = require('chokidar')
+const bodyParser = require('body-parser')
+const chalk = require('chalk')
+const path = require('path')
+const Mock = require('mockjs')
+
+const mockDir = path.join(process.cwd(), 'mock')
+
+function registerRoutes(app) {
+  let mockLastIndex
+  const { mocks } = require('./index.js')
+  const mocksForServer = mocks.map(route => {
+    return responseFake(route.url, route.type, route.response)
+  })
+  for (const mock of mocksForServer) {
+    app[mock.type](mock.url, mock.response)
+    mockLastIndex = app._router.stack.length
+  }
+  const mockRoutesLength = Object.keys(mocksForServer).length
+  return {
+    mockRoutesLength: mockRoutesLength,
+    mockStartIndex: mockLastIndex - mockRoutesLength
+  }
+}
+
+function unregisterRoutes() {
+  Object.keys(require.cache).forEach(i => {
+    if (i.includes(mockDir)) {
+      delete require.cache[require.resolve(i)]
+    }
+  })
+}
+
+// for mock server
+const responseFake = (url, type, respond) => {
+  return {
+    url: new RegExp(`${process.env.VUE_APP_BASE_API}${url}`),
+    type: type || 'get',
+    response(req, res) {
+      console.log('request invoke:' + req.path)
+      res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
+    }
+  }
+}
+
+module.exports = app => {
+  // parse app.body
+  // https://expressjs.com/en/4x/api.html#req.body
+  app.use(bodyParser.json())
+  app.use(bodyParser.urlencoded({
+    extended: true
+  }))
+
+  const mockRoutes = registerRoutes(app)
+  var mockRoutesLength = mockRoutes.mockRoutesLength
+  var mockStartIndex = mockRoutes.mockStartIndex
+
+  // watch files, hot reload mock server
+  chokidar.watch(mockDir, {
+    ignored: /mock-server/,
+    ignoreInitial: true
+  }).on('all', (event, path) => {
+    if (event === 'change' || event === 'add') {
+      try {
+        // remove mock routes stack
+        app._router.stack.splice(mockStartIndex, mockRoutesLength)
+
+        // clear routes cache
+        unregisterRoutes()
+
+        const mockRoutes = registerRoutes(app)
+        mockRoutesLength = mockRoutes.mockRoutesLength
+        mockStartIndex = mockRoutes.mockStartIndex
+
+        console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed  ${path}`))
+      } catch (error) {
+        console.log(chalk.redBright(error))
+      }
+    }
+  })
+}

+ 29 - 0
mock/table.js

@@ -0,0 +1,29 @@
+const Mock = require('mockjs')
+
+const data = Mock.mock({
+  'items|30': [{
+    id: '@id',
+    title: '@sentence(10, 20)',
+    'status|1': ['published', 'draft', 'deleted'],
+    author: 'name',
+    display_time: '@datetime',
+    pageviews: '@integer(300, 5000)'
+  }]
+})
+
+module.exports = [
+  {
+    url: '/vue-admin-template/table/list',
+    type: 'get',
+    response: config => {
+      const items = data.items
+      return {
+        code: 20000,
+        data: {
+          total: items.length,
+          items: items
+        }
+      }
+    }
+  }
+]

+ 84 - 0
mock/user.js

@@ -0,0 +1,84 @@
+
+const tokens = {
+  admin: {
+    token: 'admin-token'
+  },
+  editor: {
+    token: 'editor-token'
+  }
+}
+
+const users = {
+  'admin-token': {
+    roles: ['admin'],
+    introduction: 'I am a super administrator',
+    avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
+    name: 'Super Admin'
+  },
+  'editor-token': {
+    roles: ['editor'],
+    introduction: 'I am an editor',
+    avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
+    name: 'Normal Editor'
+  }
+}
+
+module.exports = [
+  // user login
+  {
+    url: '/vue-admin-template/user/login',
+    type: 'post',
+    response: config => {
+      const { username } = config.body
+      const token = tokens[username]
+
+      // mock error
+      if (!token) {
+        return {
+          code: 60204,
+          message: 'Account and password are incorrect.'
+        }
+      }
+
+      return {
+        code: 20000,
+        data: token
+      }
+    }
+  },
+
+  // get user info
+  {
+    url: '/vue-admin-template/user/info\.*',
+    type: 'get',
+    response: config => {
+      const { token } = config.query
+      const info = users[token]
+
+      // mock error
+      if (!info) {
+        return {
+          code: 50008,
+          message: 'Login failed, unable to get user details.'
+        }
+      }
+
+      return {
+        code: 20000,
+        data: info
+      }
+    }
+  },
+
+  // user logout
+  {
+    url: '/vue-admin-template/user/logout',
+    type: 'post',
+    response: _ => {
+      return {
+        code: 20000,
+        data: 'success'
+      }
+    }
+  }
+]

+ 25 - 0
mock/utils.js

@@ -0,0 +1,25 @@
+/**
+ * @param {string} url
+ * @returns {Object}
+ */
+function param2Obj(url) {
+  const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
+  if (!search) {
+    return {}
+  }
+  const obj = {}
+  const searchArr = search.split('&')
+  searchArr.forEach(v => {
+    const index = v.indexOf('=')
+    if (index !== -1) {
+      const name = v.substring(0, index)
+      const val = v.substring(index + 1, v.length)
+      obj[name] = val
+    }
+  })
+  return obj
+}
+
+module.exports = {
+  param2Obj
+}

+ 62 - 0
package.json

@@ -0,0 +1,62 @@
+{
+  "name": "vue-admin-template",
+  "version": "4.4.0",
+  "description": "A vue admin template with Element UI & axios & iconfont & permission control & lint",
+  "author": "Pan <panfree23@gmail.com>",
+  "scripts": {
+    "dev": "vue-cli-service serve",
+    "build:prod": "vue-cli-service build",
+    "build:stage": "vue-cli-service build --mode staging",
+    "preview": "node build/index.js --preview",
+    "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml",
+    "lint": "eslint --ext .js,.vue src",
+    "test:unit": "jest --clearCache && vue-cli-service test:unit",
+    "test:ci": "npm run lint && npm run test:unit"
+  },
+  "dependencies": {
+    "axios": "0.18.1",
+    "core-js": "3.6.5",
+    "element-ui": "2.13.2",
+    "js-cookie": "2.2.0",
+    "normalize.css": "7.0.0",
+    "nprogress": "0.2.0",
+    "path-to-regexp": "2.4.0",
+    "vue": "2.6.10",
+    "vue-router": "3.0.6",
+    "vuex": "3.1.0"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "4.4.4",
+    "@vue/cli-plugin-eslint": "4.4.4",
+    "@vue/cli-plugin-unit-jest": "4.4.4",
+    "@vue/cli-service": "4.4.4",
+    "@vue/test-utils": "1.0.0-beta.29",
+    "autoprefixer": "9.5.1",
+    "babel-eslint": "10.1.0",
+    "babel-jest": "23.6.0",
+    "babel-plugin-dynamic-import-node": "2.3.3",
+    "chalk": "2.4.2",
+    "connect": "3.6.6",
+    "eslint": "6.7.2",
+    "eslint-plugin-vue": "6.2.2",
+    "html-webpack-plugin": "3.2.0",
+    "mockjs": "1.0.1-beta3",
+    "runjs": "4.3.2",
+    "sass": "1.26.8",
+    "sass-loader": "8.0.2",
+    "script-ext-html-webpack-plugin": "2.1.3",
+    "serve-static": "1.13.2",
+    "svg-sprite-loader": "4.1.3",
+    "svgo": "1.2.2",
+    "vue-template-compiler": "2.6.10"
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions"
+  ],
+  "engines": {
+    "node": ">=8.9",
+    "npm": ">= 3.0.0"
+  },
+  "license": "MIT"
+}

+ 8 - 0
postcss.config.js

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

BIN=BIN
public/favicon.ico


+ 17 - 0
public/index.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+    <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 %></title>
+  </head>
+  <body>
+    <noscript>
+      <strong>We're sorry but <%= webpackConfig.name %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+    </noscript>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+  </body>
+</html>

+ 11 - 0
src/App.vue

@@ -0,0 +1,11 @@
+<template>
+  <div id="app">
+    <router-view />
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'App'
+}
+</script>

+ 9 - 0
src/api/table.js

@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+export function getList(params) {
+  return request({
+    url: '/vue-admin-template/table/list',
+    method: 'get',
+    params
+  })
+}

+ 24 - 0
src/api/user.js

@@ -0,0 +1,24 @@
+import request from '@/utils/request'
+
+export function login(data) {
+  return request({
+    url: '/vue-admin-template/user/login',
+    method: 'post',
+    data
+  })
+}
+
+export function getInfo(token) {
+  return request({
+    url: '/vue-admin-template/user/info',
+    method: 'get',
+    params: { token }
+  })
+}
+
+export function logout() {
+  return request({
+    url: '/vue-admin-template/user/logout',
+    method: 'post'
+  })
+}

BIN=BIN
src/assets/404_images/404.png


BIN=BIN
src/assets/404_images/404_cloud.png


BIN=BIN
src/assets/logo.png


+ 78 - 0
src/components/Breadcrumb/index.vue

@@ -0,0 +1,78 @@
+<template>
+  <el-breadcrumb class="app-breadcrumb" separator="/">
+    <transition-group name="breadcrumb">
+      <el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
+        <span v-if="item.redirect==='noRedirect'||index==levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
+        <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
+      </el-breadcrumb-item>
+    </transition-group>
+  </el-breadcrumb>
+</template>
+
+<script>
+import pathToRegexp from 'path-to-regexp'
+
+export default {
+  data() {
+    return {
+      levelList: null
+    }
+  },
+  watch: {
+    $route() {
+      this.getBreadcrumb()
+    }
+  },
+  created() {
+    this.getBreadcrumb()
+  },
+  methods: {
+    getBreadcrumb() {
+      // only show routes with meta.title
+      let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
+      const first = matched[0]
+
+      // if (!this.isDashboard(first)) {
+      //   matched = [{ path: '/dashboard', meta: { title: 'Dashboard' }}].concat(matched)
+      // }
+
+      this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
+    },
+    isDashboard(route) {
+      const name = route && route.name
+      if (!name) {
+        return false
+      }
+      return name.trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase()
+    },
+    pathCompile(path) {
+      // To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
+      const { params } = this.$route
+      var toPath = pathToRegexp.compile(path)
+      return toPath(params)
+    },
+    handleLink(item) {
+      const { redirect, path } = item
+      if (redirect) {
+        this.$router.push(redirect)
+        return
+      }
+      this.$router.push(this.pathCompile(path))
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.app-breadcrumb.el-breadcrumb {
+  display: inline-block;
+  font-size: 14px;
+  line-height: 50px;
+  margin-left: 8px;
+
+  .no-redirect {
+    color: #97a8be;
+    cursor: text;
+  }
+}
+</style>

+ 44 - 0
src/components/Hamburger/index.vue

@@ -0,0 +1,44 @@
+<template>
+  <div style="padding: 0 15px;" @click="toggleClick">
+    <svg
+      :class="{'is-active':isActive}"
+      class="hamburger"
+      viewBox="0 0 1024 1024"
+      xmlns="http://www.w3.org/2000/svg"
+      width="64"
+      height="64"
+    >
+      <path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
+    </svg>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'Hamburger',
+  props: {
+    isActive: {
+      type: Boolean,
+      default: false
+    }
+  },
+  methods: {
+    toggleClick() {
+      this.$emit('toggleClick')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.hamburger {
+  display: inline-block;
+  vertical-align: middle;
+  width: 20px;
+  height: 20px;
+}
+
+.hamburger.is-active {
+  transform: rotate(180deg);
+}
+</style>

+ 62 - 0
src/components/SvgIcon/index.vue

@@ -0,0 +1,62 @@
+<template>
+  <div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
+  <svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
+    <use :xlink:href="iconName" />
+  </svg>
+</template>
+
+<script>
+// doc: https://panjiachen.github.io/vue-element-admin-site/feature/component/svg-icon.html#usage
+import { isExternal } from '@/utils/validate'
+
+export default {
+  name: 'SvgIcon',
+  props: {
+    iconClass: {
+      type: String,
+      required: true
+    },
+    className: {
+      type: String,
+      default: ''
+    }
+  },
+  computed: {
+    isExternal() {
+      return isExternal(this.iconClass)
+    },
+    iconName() {
+      return `#icon-${this.iconClass}`
+    },
+    svgClass() {
+      if (this.className) {
+        return 'svg-icon ' + this.className
+      } else {
+        return 'svg-icon'
+      }
+    },
+    styleExternalIcon() {
+      return {
+        mask: `url(${this.iconClass}) no-repeat 50% 50%`,
+        '-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.svg-icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+
+.svg-external-icon {
+  background-color: currentColor;
+  mask-size: cover!important;
+  display: inline-block;
+}
+</style>

+ 9 - 0
src/icons/index.js

@@ -0,0 +1,9 @@
+import Vue from 'vue'
+import SvgIcon from '@/components/SvgIcon'// svg component
+
+// register globally
+Vue.component('svg-icon', SvgIcon)
+
+const req = require.context('./svg', false, /\.svg$/)
+const requireAll = requireContext => requireContext.keys().map(requireContext)
+requireAll(req)

+ 7 - 0
src/icons/svg/alarmManage.svg

@@ -0,0 +1,7 @@
+<svg 
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="16px" height="16px">
+<path fill-rule="evenodd" 
+ d="M15.467,7.724 L13.867,7.724 C13.572,7.724 13.333,7.477 13.333,7.172 C13.333,6.868 13.572,6.621 13.867,6.621 L15.467,6.621 C15.761,6.621 16.000,6.868 16.000,7.172 C16.000,7.477 15.761,7.724 15.467,7.724 ZM13.067,3.862 C12.625,3.862 12.267,3.491 12.267,3.034 C12.267,2.577 12.625,2.207 13.067,2.207 C13.508,2.207 13.867,2.577 13.867,3.034 C13.867,3.491 13.508,3.862 13.067,3.862 ZM13.074,12.690 L13.333,12.690 C14.217,12.690 14.933,13.431 14.933,14.345 C14.933,15.259 14.217,16.000 13.333,16.000 L2.667,16.000 C1.783,16.000 1.067,15.259 1.067,14.345 C1.067,13.431 1.783,12.690 2.667,12.690 L2.941,12.690 L2.941,8.276 C2.941,5.381 5.209,3.034 8.007,3.034 C10.806,3.034 13.074,5.381 13.074,8.276 L13.074,12.690 ZM2.133,14.345 C2.133,14.650 2.372,14.896 2.667,14.896 L13.333,14.896 C13.628,14.896 13.867,14.650 13.867,14.345 C13.867,14.040 13.628,13.793 13.333,13.793 L2.667,13.793 C2.372,13.793 2.133,14.040 2.133,14.345 ZM8.007,4.138 C5.798,4.138 4.007,5.991 4.007,8.276 L4.007,12.690 L7.467,12.690 L7.467,10.393 C6.697,10.154 6.133,9.425 6.133,8.552 C6.133,7.485 6.969,6.621 8.000,6.621 C9.031,6.621 9.867,7.485 9.867,8.552 C9.867,9.425 9.303,10.154 8.533,10.393 L8.533,12.690 L12.007,12.690 L12.007,8.276 C12.007,5.991 10.217,4.138 8.007,4.138 ZM8.000,9.379 C8.442,9.379 8.800,9.009 8.800,8.552 C8.800,8.095 8.442,7.724 8.000,7.724 C7.558,7.724 7.200,8.095 7.200,8.552 C7.200,9.009 7.558,9.379 8.000,9.379 ZM8.000,2.759 C7.705,2.759 7.467,2.512 7.467,2.207 L7.467,0.552 C7.467,0.247 7.705,-0.000 8.000,-0.000 C8.294,-0.000 8.533,0.247 8.533,0.552 L8.533,2.207 C8.533,2.512 8.294,2.759 8.000,2.759 ZM2.933,3.862 C2.491,3.862 2.133,3.491 2.133,3.034 C2.133,2.577 2.491,2.207 2.933,2.207 C3.375,2.207 3.733,2.577 3.733,3.034 C3.733,3.491 3.375,3.862 2.933,3.862 ZM2.667,7.172 C2.667,7.477 2.428,7.724 2.133,7.724 L0.533,7.724 C0.239,7.724 -0.000,7.477 -0.000,7.172 C-0.000,6.868 0.239,6.621 0.533,6.621 L2.133,6.621 C2.428,6.621 2.667,6.868 2.667,7.172 Z"/>
+</svg>

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
src/icons/svg/dashboard.svg


+ 7 - 0
src/icons/svg/dataManage.svg

@@ -0,0 +1,7 @@
+<svg 
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="16px" height="16px">
+<path fill-rule="evenodd"
+ d="M7.687,0.978 L7.687,8.312 L15.022,8.312 L15.022,8.489 C15.022,12.596 11.618,16.000 7.511,16.000 C3.404,16.000 -0.000,12.596 -0.000,8.489 C-0.000,4.442 3.306,1.077 7.333,0.980 L7.511,0.978 L7.687,0.978 L7.687,0.978 ZM6.356,2.442 L6.239,2.465 C3.421,3.074 1.330,5.589 1.330,8.489 C1.330,11.912 4.087,14.668 7.511,14.668 C10.413,14.668 12.927,12.578 13.535,9.761 L13.559,9.644 L7.511,9.644 C6.858,9.644 6.400,9.215 6.360,8.581 L6.357,8.489 L6.356,2.442 ZM8.978,0.000 C12.829,0.000 15.905,3.016 15.999,6.842 L16.000,7.022 L16.000,7.199 L8.802,7.199 L8.802,0.001 L8.978,0.001 L8.978,0.000 ZM10.132,1.464 L10.132,5.867 L14.536,5.867 L14.513,5.752 C14.045,3.639 12.362,1.956 10.248,1.488 L10.132,1.464 Z"/>
+</svg>

+ 7 - 0
src/icons/svg/deviceManage.svg

@@ -0,0 +1,7 @@
+<svg 
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="16px" height="13px">
+<path fill-rule="evenodd"
+ d="M14.698,13.000 L1.302,13.000 C0.583,13.000 -0.001,12.434 -0.001,11.736 L-0.001,8.486 C-0.001,7.788 0.583,7.222 1.302,7.222 L14.698,7.222 C15.417,7.222 16.000,7.788 16.000,8.486 L16.000,11.736 C16.000,12.434 15.417,13.000 14.698,13.000 ZM14.884,8.486 C14.884,8.386 14.801,8.306 14.698,8.306 L1.302,8.306 C1.199,8.306 1.116,8.386 1.116,8.486 L1.116,11.736 C1.116,11.836 1.199,11.917 1.302,11.917 L14.698,11.917 C14.801,11.917 14.884,11.836 14.884,11.736 L14.884,8.486 ZM13.209,10.653 C12.798,10.653 12.465,10.329 12.465,9.930 C12.465,9.532 12.798,9.208 13.209,9.208 C13.620,9.208 13.954,9.532 13.954,9.930 C13.954,10.329 13.620,10.653 13.209,10.653 ZM14.698,5.778 L1.302,5.778 C0.583,5.778 -0.001,5.212 -0.001,4.514 L-0.001,1.264 C-0.001,0.566 0.583,-0.000 1.302,-0.000 L14.698,-0.000 C15.417,-0.000 16.000,0.566 16.000,1.264 L16.000,4.514 C16.000,5.212 15.417,5.778 14.698,5.778 ZM14.884,1.264 C14.884,1.164 14.801,1.083 14.698,1.083 L1.302,1.083 C1.199,1.083 1.116,1.164 1.116,1.264 L1.116,4.514 C1.116,4.614 1.199,4.694 1.302,4.694 L14.698,4.694 C14.801,4.694 14.884,4.614 14.884,4.514 L14.884,1.264 ZM2.790,3.431 C2.379,3.431 2.046,3.107 2.046,2.708 C2.046,2.708 2.046,2.708 2.046,2.708 C2.046,2.309 2.379,1.986 2.791,1.986 C3.202,1.986 3.535,2.309 3.534,2.708 C3.534,2.708 3.534,2.709 3.534,2.709 C3.534,3.108 3.201,3.431 2.790,3.431 Z"/>
+</svg>

+ 1 - 0
src/icons/svg/example.svg

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M96.258 57.462h31.421C124.794 27.323 100.426 2.956 70.287.07v31.422a32.856 32.856 0 0 1 25.971 25.97zm-38.796-25.97V.07C27.323 2.956 2.956 27.323.07 57.462h31.422a32.856 32.856 0 0 1 25.97-25.97zm12.825 64.766v31.421c30.46-2.885 54.507-27.253 57.713-57.712H96.579c-2.886 13.466-13.146 23.726-26.292 26.291zM31.492 70.287H.07c2.886 30.46 27.253 54.507 57.713 57.713V96.579c-13.466-2.886-23.726-13.146-26.291-26.292z"/></svg>

+ 1 - 0
src/icons/svg/eye-open.svg

@@ -0,0 +1 @@
+<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><defs><style/></defs><path d="M512 128q69.675 0 135.51 21.163t115.498 54.997 93.483 74.837 73.685 82.006 51.67 74.837 32.17 54.827L1024 512q-2.347 4.992-6.315 13.483T998.87 560.17t-31.658 51.669-44.331 59.99-56.832 64.34-69.504 60.16-82.347 51.5-94.848 34.687T512 896q-69.675 0-135.51-21.163t-115.498-54.826-93.483-74.326-73.685-81.493-51.67-74.496-32.17-54.997L0 513.707q2.347-4.992 6.315-13.483t18.816-34.816 31.658-51.84 44.331-60.33 56.832-64.683 69.504-60.331 82.347-51.84 94.848-34.816T512 128.085zm0 85.333q-46.677 0-91.648 12.331t-81.152 31.83-70.656 47.146-59.648 54.485-48.853 57.686-37.675 52.821-26.325 43.99q12.33 21.674 26.325 43.52t37.675 52.351 48.853 57.003 59.648 53.845T339.2 767.02t81.152 31.488T512 810.667t91.648-12.331 81.152-31.659 70.656-46.848 59.648-54.186 48.853-57.344 37.675-52.651T927.957 512q-12.33-21.675-26.325-43.648t-37.675-52.65-48.853-57.345-59.648-54.186-70.656-46.848-81.152-31.659T512 213.334zm0 128q70.656 0 120.661 50.006T682.667 512 632.66 632.661 512 682.667 391.339 632.66 341.333 512t50.006-120.661T512 341.333zm0 85.334q-35.328 0-60.33 25.002T426.666 512t25.002 60.33T512 597.334t60.33-25.002T597.334 512t-25.002-60.33T512 426.666z"/></svg>

+ 1 - 0
src/icons/svg/eye.svg

@@ -0,0 +1 @@
+<svg width="128" height="64" xmlns="http://www.w3.org/2000/svg"><path d="M127.072 7.994c1.37-2.208.914-5.152-.914-6.87-2.056-1.717-4.797-1.226-6.396.982-.229.245-25.586 32.382-55.74 32.382-29.24 0-55.74-32.382-55.968-32.627-1.6-1.963-4.57-2.208-6.397-.49C-.17 3.086-.399 6.275 1.2 8.238c.457.736 5.94 7.36 14.62 14.72L4.17 35.96c-1.828 1.963-1.6 5.152.228 6.87.457.98 1.6 1.471 2.742 1.471s2.284-.49 3.198-1.472l12.564-13.983c5.94 4.416 13.021 8.587 20.788 11.53l-4.797 17.418c-.685 2.699.686 5.397 3.198 6.133h1.37c2.057 0 3.884-1.472 4.341-3.68L52.6 42.83c3.655.736 7.538 1.227 11.422 1.227 3.883 0 7.767-.49 11.422-1.227l4.797 17.173c.457 2.208 2.513 3.68 4.34 3.68.457 0 .914 0 1.143-.246 2.513-.736 3.883-3.434 3.198-6.133l-4.797-17.172c7.767-2.944 14.848-7.114 20.788-11.53l12.336 13.738c.913.981 2.056 1.472 3.198 1.472s2.284-.49 3.198-1.472c1.828-1.963 1.828-4.906.228-6.87l-11.65-13.001c9.366-7.36 14.849-14.474 14.849-14.474z"/></svg>

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
src/icons/svg/form.svg


+ 1 - 0
src/icons/svg/link.svg

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M115.625 127.937H.063V12.375h57.781v12.374H12.438v90.813h90.813V70.156h12.374z"/><path d="M116.426 2.821l8.753 8.753-56.734 56.734-8.753-8.745z"/><path d="M127.893 37.982h-12.375V12.375H88.706V0h39.187z"/></svg>

+ 7 - 0
src/icons/svg/monthReport.svg

@@ -0,0 +1,7 @@
+<svg 
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="14px" height="16px">
+<path fill-rule="evenodd"
+ d="M12.727,15.998 L1.272,15.998 C0.570,15.998 -0.001,15.446 -0.001,14.767 L-0.001,2.460 C-0.001,1.782 0.570,1.230 1.272,1.230 L2.980,1.230 C3.243,0.515 3.943,-0.001 4.772,-0.001 L9.227,-0.001 C10.056,-0.001 10.755,0.515 11.019,1.230 L12.727,1.230 C13.429,1.230 14.000,1.782 14.000,2.460 L14.000,14.767 C14.000,15.446 13.429,15.998 12.727,15.998 ZM9.227,1.230 L4.772,1.230 C4.421,1.230 4.136,1.506 4.136,1.845 C4.136,2.184 4.421,2.460 4.772,2.460 L9.227,2.460 C9.578,2.460 9.863,2.184 9.863,1.845 C9.863,1.506 9.578,1.230 9.227,1.230 ZM12.727,2.460 L11.019,2.460 C10.755,3.175 10.056,3.691 9.227,3.691 L4.772,3.691 C3.943,3.691 3.243,3.175 2.980,2.460 L1.272,2.460 L1.272,14.767 L12.727,14.767 L12.727,2.460 ZM10.130,7.855 L3.869,7.855 L3.869,6.530 L10.130,6.530 L10.130,7.855 ZM10.130,11.311 L3.869,11.311 L3.869,9.986 L10.130,9.986 L10.130,11.311 Z"/>
+</svg>

+ 1 - 0
src/icons/svg/nested.svg

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.002 9.2c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-5.043-3.58-9.132-7.997-9.132S.002 4.157.002 9.2zM31.997.066h95.981V18.33H31.997V.066zm0 45.669c0 5.044 3.58 9.132 7.998 9.132 4.417 0 7.997-4.088 7.997-9.132 0-3.263-1.524-6.278-3.998-7.91-2.475-1.63-5.524-1.63-7.998 0-2.475 1.632-4 4.647-4 7.91zM63.992 36.6h63.986v18.265H63.992V36.6zm-31.995 82.2c0 5.043 3.58 9.132 7.998 9.132 4.417 0 7.997-4.089 7.997-9.132 0-5.044-3.58-9.133-7.997-9.133s-7.998 4.089-7.998 9.133zm31.995-9.131h63.986v18.265H63.992V109.67zm0-27.404c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-3.263-1.524-6.277-3.998-7.909-2.475-1.631-5.524-1.631-7.998 0-2.475 1.632-4 4.646-4 7.91zm31.995-9.13h31.991V91.4H95.987V73.135z"/></svg>

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 5 - 0
src/icons/svg/operManage.svg


+ 1 - 0
src/icons/svg/password.svg

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M108.8 44.322H89.6v-5.36c0-9.04-3.308-24.163-25.6-24.163-23.145 0-25.6 16.881-25.6 24.162v5.361H19.2v-5.36C19.2 15.281 36.798 0 64 0c27.202 0 44.8 15.281 44.8 38.961v5.361zm-32 39.356c0-5.44-5.763-9.832-12.8-9.832-7.037 0-12.8 4.392-12.8 9.832 0 3.682 2.567 6.808 6.407 8.477v11.205c0 2.718 2.875 4.962 6.4 4.962 3.524 0 6.4-2.244 6.4-4.962V92.155c3.833-1.669 6.393-4.795 6.393-8.477zM128 64v49.201c0 8.158-8.645 14.799-19.2 14.799H19.2C8.651 128 0 121.359 0 113.201V64c0-8.153 8.645-14.799 19.2-14.799h89.6c10.555 0 19.2 6.646 19.2 14.799z"/></svg>

+ 7 - 0
src/icons/svg/patrolManage.svg

@@ -0,0 +1,7 @@
+<svg 
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="14px" height="16px">
+<path fill-rule="evenodd" 
+ d="M13.869,8.244 L10.196,12.610 C10.086,12.742 9.927,12.810 9.767,12.810 C9.640,12.810 9.512,12.767 9.408,12.680 C9.170,12.482 9.139,12.130 9.338,11.894 L13.010,7.527 C13.209,7.291 13.562,7.260 13.799,7.458 C14.036,7.656 14.067,8.008 13.869,8.244 ZM3.928,10.255 L7.272,10.255 C7.582,10.255 7.832,10.505 7.832,10.813 C7.832,11.121 7.582,11.371 7.272,11.371 L3.928,11.371 C3.619,11.371 3.368,11.121 3.368,10.813 C3.368,10.505 3.619,10.255 3.928,10.255 ZM3.928,6.262 L8.766,6.262 C9.075,6.262 9.326,6.512 9.326,6.820 C9.326,7.128 9.075,7.378 8.766,7.378 L3.928,7.378 C3.619,7.378 3.368,7.128 3.368,6.820 C3.368,6.512 3.619,6.262 3.928,6.262 ZM12.134,5.890 C11.824,5.890 11.574,5.640 11.574,5.332 L11.574,2.238 L9.982,2.238 C9.749,2.884 9.128,3.347 8.400,3.347 L4.293,3.347 C3.565,3.347 2.944,2.884 2.711,2.238 L1.120,2.238 L1.120,14.884 L11.574,14.884 L11.574,13.968 C11.574,13.660 11.824,13.410 12.134,13.410 C12.443,13.410 12.694,13.660 12.694,13.968 L12.694,15.442 C12.694,15.750 12.443,16.000 12.134,16.000 L0.560,16.000 C0.250,16.000 -0.000,15.750 -0.000,15.442 L-0.000,1.680 C-0.000,1.372 0.250,1.122 0.560,1.122 L2.707,1.122 C2.936,0.469 3.561,-0.000 4.293,-0.000 L8.400,-0.000 C9.133,-0.000 9.757,0.469 9.986,1.122 L12.134,1.122 C12.443,1.122 12.694,1.372 12.694,1.680 L12.694,5.332 C12.694,5.640 12.443,5.890 12.134,5.890 ZM8.400,1.116 L4.293,1.116 C3.985,1.116 3.733,1.366 3.733,1.674 C3.733,1.981 3.985,2.232 4.293,2.232 L8.400,2.232 C8.709,2.232 8.960,1.981 8.960,1.674 C8.960,1.366 8.709,1.116 8.400,1.116 Z"/>
+</svg>

+ 7 - 0
src/icons/svg/planOutage.svg

@@ -0,0 +1,7 @@
+<svg 
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="16px" height="16px">
+<path fill-rule="evenodd" 
+ d="M14.545,-0.000 C15.349,-0.000 16.000,0.651 16.000,1.455 L16.000,14.545 C16.000,15.349 15.349,16.000 14.545,16.000 L1.454,16.000 C0.651,16.000 -0.000,15.349 -0.000,14.545 L-0.000,1.455 C-0.000,0.651 0.651,-0.000 1.454,-0.000 L14.545,-0.000 ZM14.545,1.455 L1.454,1.455 L1.454,14.545 L14.545,14.545 L14.545,1.455 ZM5.091,8.727 C6.296,8.727 7.273,9.704 7.273,10.909 C7.273,12.114 6.296,13.091 5.091,13.091 C3.886,13.091 2.909,12.114 2.909,10.909 C2.909,9.704 3.886,8.727 5.091,8.727 ZM13.091,10.182 L13.091,11.636 L8.727,11.636 L8.727,10.182 L13.091,10.182 ZM5.091,10.182 C4.689,10.182 4.364,10.507 4.364,10.909 C4.364,11.311 4.689,11.636 5.091,11.636 C5.493,11.636 5.818,11.311 5.818,10.909 C5.818,10.507 5.493,10.182 5.091,10.182 ZM6.633,3.548 L7.663,4.577 L4.577,7.662 L2.519,5.605 L3.548,4.577 L4.577,5.605 L6.633,3.549 L6.633,3.548 ZM13.091,5.091 L13.091,6.545 L8.727,6.545 L8.727,5.091 L13.091,5.091 Z"/>
+</svg>

+ 7 - 0
src/icons/svg/powerQuality.svg

@@ -0,0 +1,7 @@
+<svg 
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="13px" height="16px">
+<path fill-rule="evenodd"
+ d="M6.882,3.293 L5.633,5.759 C5.424,6.170 5.407,6.653 5.587,7.078 C5.767,7.504 6.123,7.823 6.563,7.954 L10.058,8.995 L5.035,12.758 L6.053,11.146 C6.311,10.736 6.367,10.240 6.204,9.784 C6.041,9.329 5.685,8.981 5.228,8.834 L1.801,7.727 L6.882,3.293 M9.103,-0.000 C9.011,-0.000 8.916,0.032 8.830,0.106 L0.145,7.690 C-0.106,7.908 -0.021,8.320 0.292,8.421 L4.891,9.905 C5.156,9.990 5.268,10.306 5.119,10.541 L2.088,15.338 C1.890,15.650 2.144,15.999 2.444,15.999 C2.526,15.999 2.614,15.973 2.694,15.912 L11.830,9.066 C12.110,8.857 12.031,8.414 11.698,8.312 L6.877,6.876 C6.622,6.800 6.500,6.509 6.621,6.272 L9.480,0.626 C9.641,0.310 9.387,-0.000 9.103,-0.000 Z"/>
+</svg>

+ 7 - 0
src/icons/svg/siteManage.svg

@@ -0,0 +1,7 @@
+<svg 
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="16px" height="16px">
+<path fill-rule="evenodd"
+ d="M15.379,9.548 L13.380,9.548 C13.384,9.501 13.384,9.454 13.380,9.407 C13.189,8.432 12.494,7.630 11.552,7.299 C11.497,7.283 11.439,7.275 11.381,7.275 L8.624,7.275 L8.624,6.446 L10.623,6.446 C10.968,6.446 11.248,6.168 11.248,5.825 L11.248,5.825 L11.248,0.612 C11.248,0.269 10.968,-0.009 10.623,-0.009 L10.623,-0.009 L5.376,-0.009 C5.031,-0.009 4.751,0.269 4.751,0.612 L4.751,5.830 C4.751,6.172 5.031,6.451 5.376,6.451 L7.375,6.451 L7.375,7.279 L4.514,7.279 C3.064,7.366 2.669,9.217 2.627,9.428 C2.623,9.468 2.623,9.508 2.627,9.548 L0.620,9.548 C0.275,9.548 -0.005,9.826 -0.005,10.169 L-0.005,10.169 L-0.005,15.386 C-0.005,15.729 0.275,16.007 0.620,16.007 L5.867,16.007 C6.212,16.007 6.492,15.729 6.492,15.386 L6.492,10.169 C6.492,9.826 6.212,9.548 5.867,9.548 L5.867,9.548 L3.885,9.548 C3.989,9.134 4.255,8.538 4.551,8.517 L11.256,8.517 C11.670,8.726 11.973,9.101 12.089,9.548 L10.132,9.548 C9.787,9.548 9.507,9.826 9.507,10.169 L9.507,10.169 L9.507,15.386 C9.507,15.729 9.787,16.007 10.132,16.007 L15.379,16.007 C15.724,16.007 16.004,15.729 16.004,15.386 L16.004,10.169 C16.004,9.826 15.724,9.548 15.379,9.548 L15.379,9.548 ZM6.000,1.233 L9.999,1.233 L9.999,5.208 L6.000,5.208 L6.000,1.233 ZM5.242,14.765 L1.244,14.765 L1.244,10.790 L5.242,10.790 L5.242,14.765 ZM14.755,14.765 L10.756,14.765 L10.756,10.790 L14.755,10.790 L14.755,14.765 Z"/>
+</svg>

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 5 - 0
src/icons/svg/systemManage.svg


+ 1 - 0
src/icons/svg/table.svg

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/></svg>

+ 1 - 0
src/icons/svg/tree.svg

@@ -0,0 +1 @@
+<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M126.713 90.023c.858.985 1.287 2.134 1.287 3.447v29.553c0 1.423-.429 2.6-1.287 3.53-.858.93-1.907 1.395-3.146 1.395H97.824c-1.145 0-2.146-.465-3.004-1.395-.858-.93-1.287-2.107-1.287-3.53V93.47c0-.875.19-1.696.572-2.462.382-.766.906-1.368 1.573-1.806a3.84 3.84 0 0 1 2.146-.657h9.725V69.007a3.84 3.84 0 0 0-.43-1.806 3.569 3.569 0 0 0-1.143-1.313 2.714 2.714 0 0 0-1.573-.492h-36.47v23.149h9.725c1.144 0 2.145.492 3.004 1.478.858.985 1.287 2.134 1.287 3.447v29.553c0 .876-.191 1.696-.573 2.463-.38.766-.905 1.368-1.573 1.806a3.84 3.84 0 0 1-2.145.656H51.915a3.84 3.84 0 0 1-2.145-.656c-.668-.438-1.216-1.04-1.645-1.806a4.96 4.96 0 0 1-.644-2.463V93.47c0-1.313.43-2.462 1.288-3.447.858-.986 1.907-1.478 3.146-1.478h9.582v-23.15h-37.9c-.953 0-1.74.356-2.359 1.068-.62.711-.93 1.56-.93 2.544v19.538h9.726c1.239 0 2.264.492 3.074 1.478.81.985 1.216 2.134 1.216 3.447v29.553c0 1.423-.405 2.6-1.216 3.53-.81.93-1.835 1.395-3.074 1.395H4.29c-.476 0-.93-.082-1.358-.246a4.1 4.1 0 0 1-1.144-.657 4.658 4.658 0 0 1-.93-1.067 5.186 5.186 0 0 1-.643-1.395 5.566 5.566 0 0 1-.215-1.56V93.47c0-.437.048-.875.143-1.313a3.95 3.95 0 0 1 .429-1.15c.19-.328.429-.656.715-.984.286-.329.572-.602.858-.821.286-.22.62-.383 1.001-.493.382-.11.763-.164 1.144-.164h9.726V61.619c0-.985.31-1.833.93-2.544.619-.712 1.358-1.068 2.216-1.068h44.335V39.62h-9.582c-1.24 0-2.288-.492-3.146-1.477a5.09 5.09 0 0 1-1.287-3.448V5.14c0-1.423.429-2.627 1.287-3.612.858-.985 1.907-1.477 3.146-1.477h25.743c.763 0 1.478.246 2.145.739a5.17 5.17 0 0 1 1.573 1.888c.382.766.573 1.587.573 2.462v29.553c0 1.313-.43 2.463-1.287 3.448-.859.985-1.86 1.477-3.004 1.477h-9.725v18.389h42.762c.954 0 1.74.355 2.36 1.067.62.711.93 1.56.93 2.545v26.925h9.582c1.239 0 2.288.492 3.146 1.478z"/></svg>

+ 1 - 0
src/icons/svg/user.svg

@@ -0,0 +1 @@
+<svg width="130" height="130" xmlns="http://www.w3.org/2000/svg"><path d="M63.444 64.996c20.633 0 37.359-14.308 37.359-31.953 0-17.649-16.726-31.952-37.359-31.952-20.631 0-37.36 14.303-37.358 31.952 0 17.645 16.727 31.953 37.359 31.953zM80.57 75.65H49.434c-26.652 0-48.26 18.477-48.26 41.27v2.664c0 9.316 21.608 9.325 48.26 9.325H80.57c26.649 0 48.256-.344 48.256-9.325v-2.663c0-22.794-21.605-41.271-48.256-41.271z" stroke="#979797"/></svg>

+ 7 - 0
src/icons/svg/形状 13 (1).svg

@@ -0,0 +1,7 @@
+<svg 
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="16px" height="13px">
+<path fill-rule="evenodd"
+ d="M14.698,13.000 L1.302,13.000 C0.583,13.000 -0.001,12.434 -0.001,11.736 L-0.001,8.486 C-0.001,7.788 0.583,7.222 1.302,7.222 L14.698,7.222 C15.417,7.222 16.000,7.788 16.000,8.486 L16.000,11.736 C16.000,12.434 15.417,13.000 14.698,13.000 ZM14.884,8.486 C14.884,8.386 14.801,8.306 14.698,8.306 L1.302,8.306 C1.199,8.306 1.116,8.386 1.116,8.486 L1.116,11.736 C1.116,11.836 1.199,11.917 1.302,11.917 L14.698,11.917 C14.801,11.917 14.884,11.836 14.884,11.736 L14.884,8.486 ZM13.209,10.653 C12.798,10.653 12.465,10.329 12.465,9.930 C12.465,9.532 12.798,9.208 13.209,9.208 C13.620,9.208 13.954,9.532 13.954,9.930 C13.954,10.329 13.620,10.653 13.209,10.653 ZM14.698,5.778 L1.302,5.778 C0.583,5.778 -0.001,5.212 -0.001,4.514 L-0.001,1.264 C-0.001,0.566 0.583,-0.000 1.302,-0.000 L14.698,-0.000 C15.417,-0.000 16.000,0.566 16.000,1.264 L16.000,4.514 C16.000,5.212 15.417,5.778 14.698,5.778 ZM14.884,1.264 C14.884,1.164 14.801,1.083 14.698,1.083 L1.302,1.083 C1.199,1.083 1.116,1.164 1.116,1.264 L1.116,4.514 C1.116,4.614 1.199,4.694 1.302,4.694 L14.698,4.694 C14.801,4.694 14.884,4.614 14.884,4.514 L14.884,1.264 ZM2.790,3.431 C2.379,3.431 2.046,3.107 2.046,2.708 C2.046,2.708 2.046,2.708 2.046,2.708 C2.046,2.309 2.379,1.986 2.791,1.986 C3.202,1.986 3.535,2.309 3.534,2.708 C3.534,2.708 3.534,2.709 3.534,2.709 C3.534,3.108 3.201,3.431 2.790,3.431 Z"/>
+</svg>

+ 22 - 0
src/icons/svgo.yml

@@ -0,0 +1,22 @@
+# replace default config
+
+# multipass: true
+# full: true
+
+plugins:
+
+  # - name
+  #
+  # or:
+  # - name: false
+  # - name: true
+  #
+  # or:
+  # - name:
+  #     param1: 1
+  #     param2: 2
+
+- removeAttrs:
+    attrs:
+      - 'fill'
+      - 'fill-rule'

+ 40 - 0
src/layout/components/AppMain.vue

@@ -0,0 +1,40 @@
+<template>
+  <section class="app-main">
+    <transition name="fade-transform" mode="out-in">
+      <router-view :key="key" />
+    </transition>
+  </section>
+</template>
+
+<script>
+export default {
+  name: 'AppMain',
+  computed: {
+    key() {
+      return this.$route.path
+    }
+  }
+}
+</script>
+
+<style scoped>
+.app-main {
+  /*50 = navbar  */
+  min-height: calc(100vh - 50px);
+  width: 100%;
+  position: relative;
+  overflow: hidden;
+}
+.fixed-header+.app-main {
+  padding-top: 50px;
+}
+</style>
+
+<style lang="scss">
+// fix css style bug in open el-dialog
+.el-popup-parent--hidden {
+  .fixed-header {
+    padding-right: 15px;
+  }
+}
+</style>

+ 139 - 0
src/layout/components/Navbar.vue

@@ -0,0 +1,139 @@
+<template>
+  <div class="navbar">
+    <hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
+
+    <breadcrumb class="breadcrumb-container" />
+
+    <div class="right-menu">
+      <el-dropdown class="avatar-container" trigger="click">
+        <div class="avatar-wrapper">
+          <img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">
+          <i class="el-icon-caret-bottom" />
+        </div>
+        <el-dropdown-menu slot="dropdown" class="user-dropdown">
+          <router-link to="/">
+            <el-dropdown-item>
+              Home
+            </el-dropdown-item>
+          </router-link>
+          <a target="_blank" href="https://github.com/PanJiaChen/vue-admin-template/">
+            <el-dropdown-item>Github</el-dropdown-item>
+          </a>
+          <a target="_blank" href="https://panjiachen.github.io/vue-element-admin-site/#/">
+            <el-dropdown-item>Docs</el-dropdown-item>
+          </a>
+          <el-dropdown-item divided @click.native="logout">
+            <span style="display:block;">Log Out</span>
+          </el-dropdown-item>
+        </el-dropdown-menu>
+      </el-dropdown>
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import Breadcrumb from '@/components/Breadcrumb'
+import Hamburger from '@/components/Hamburger'
+
+export default {
+  components: {
+    Breadcrumb,
+    Hamburger
+  },
+  computed: {
+    ...mapGetters([
+      'sidebar',
+      'avatar'
+    ])
+  },
+  methods: {
+    toggleSideBar() {
+      this.$store.dispatch('app/toggleSideBar')
+    },
+    async logout() {
+      await this.$store.dispatch('user/logout')
+      this.$router.push(`/login?redirect=${this.$route.fullPath}`)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.navbar {
+  height: 50px;
+  overflow: hidden;
+  position: relative;
+  background: #fff;
+  box-shadow: 0 1px 4px rgba(0,21,41,.08);
+
+  .hamburger-container {
+    line-height: 46px;
+    height: 100%;
+    float: left;
+    cursor: pointer;
+    transition: background .3s;
+    -webkit-tap-highlight-color:transparent;
+
+    &:hover {
+      background: rgba(0, 0, 0, .025)
+    }
+  }
+
+  .breadcrumb-container {
+    float: left;
+  }
+
+  .right-menu {
+    float: right;
+    height: 100%;
+    line-height: 50px;
+
+    &:focus {
+      outline: none;
+    }
+
+    .right-menu-item {
+      display: inline-block;
+      padding: 0 8px;
+      height: 100%;
+      font-size: 18px;
+      color: #5a5e66;
+      vertical-align: text-bottom;
+
+      &.hover-effect {
+        cursor: pointer;
+        transition: background .3s;
+
+        &:hover {
+          background: rgba(0, 0, 0, .025)
+        }
+      }
+    }
+
+    .avatar-container {
+      margin-right: 30px;
+
+      .avatar-wrapper {
+        margin-top: 5px;
+        position: relative;
+
+        .user-avatar {
+          cursor: pointer;
+          width: 40px;
+          height: 40px;
+          border-radius: 10px;
+        }
+
+        .el-icon-caret-bottom {
+          cursor: pointer;
+          position: absolute;
+          right: -20px;
+          top: 25px;
+          font-size: 12px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 26 - 0
src/layout/components/Sidebar/FixiOSBug.js

@@ -0,0 +1,26 @@
+export default {
+  computed: {
+    device() {
+      return this.$store.state.app.device
+    }
+  },
+  mounted() {
+    // In order to fix the click on menu on the ios device will trigger the mouseleave bug
+    // https://github.com/PanJiaChen/vue-element-admin/issues/1135
+    this.fixBugIniOS()
+  },
+  methods: {
+    fixBugIniOS() {
+      const $subMenu = this.$refs.subMenu
+      if ($subMenu) {
+        const handleMouseleave = $subMenu.handleMouseleave
+        $subMenu.handleMouseleave = (e) => {
+          if (this.device === 'mobile') {
+            return
+          }
+          handleMouseleave(e)
+        }
+      }
+    }
+  }
+}

+ 41 - 0
src/layout/components/Sidebar/Item.vue

@@ -0,0 +1,41 @@
+<script>
+export default {
+  name: 'MenuItem',
+  functional: true,
+  props: {
+    icon: {
+      type: String,
+      default: ''
+    },
+    title: {
+      type: String,
+      default: ''
+    }
+  },
+  render(h, context) {
+    const { icon, title } = context.props
+    const vnodes = []
+
+    if (icon) {
+      if (icon.includes('el-icon')) {
+        vnodes.push(<i class={[icon, 'sub-el-icon']} />)
+      } else {
+        vnodes.push(<svg-icon icon-class={icon}/>)
+      }
+    }
+
+    if (title) {
+      vnodes.push(<span slot='title'>{(title)}</span>)
+    }
+    return vnodes
+  }
+}
+</script>
+
+<style scoped>
+.sub-el-icon {
+  color: currentColor;
+  width: 1em;
+  height: 1em;
+}
+</style>

+ 43 - 0
src/layout/components/Sidebar/Link.vue

@@ -0,0 +1,43 @@
+<template>
+  <component :is="type" v-bind="linkProps(to)">
+    <slot />
+  </component>
+</template>
+
+<script>
+import { isExternal } from '@/utils/validate'
+
+export default {
+  props: {
+    to: {
+      type: String,
+      required: true
+    }
+  },
+  computed: {
+    isExternal() {
+      return isExternal(this.to)
+    },
+    type() {
+      if (this.isExternal) {
+        return 'a'
+      }
+      return 'router-link'
+    }
+  },
+  methods: {
+    linkProps(to) {
+      if (this.isExternal) {
+        return {
+          href: to,
+          target: '_blank',
+          rel: 'noopener'
+        }
+      }
+      return {
+        to: to
+      }
+    }
+  }
+}
+</script>

+ 83 - 0
src/layout/components/Sidebar/Logo.vue

@@ -0,0 +1,83 @@
+<template>
+  <div class="sidebar-logo-container" :class="{'collapse':collapse}">
+    <transition name="sidebarLogoFade">
+      <router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
+        <img v-if="logo" :src="logo" class="sidebar-logo">
+        <h1 v-else class="sidebar-title">{{ title }} </h1>
+      </router-link>
+      <router-link v-else key="expand" class="sidebar-logo-link" to="/">
+        <img v-if="logo"  :src="logo" class="sidebar-logo">
+        <!-- <h1 class="sidebar-title">{{ title }} </h1> -->
+      </router-link>
+    </transition>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'SidebarLogo',
+  props: {
+    collapse: {
+      type: Boolean,
+      required: true
+    }
+  },
+  data() {
+    return {
+      title: 'Vue Admin Template',
+      // logo: 'https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png',
+       logo:require("@/assets/logo.png"),
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.sidebarLogoFade-enter-active {
+  transition: opacity 1.5s;
+}
+
+.sidebarLogoFade-enter,
+.sidebarLogoFade-leave-to {
+  opacity: 0;
+}
+
+.sidebar-logo-container {
+  position: relative;
+  width: 100%;
+  height: 50px;
+  line-height: 50px;
+  background: #fff;
+  text-align: center;
+  overflow: hidden;
+
+  & .sidebar-logo-link {
+    height: 100%;
+    width: 100%;
+
+    & .sidebar-logo {
+      width: 139px;
+      height: 32px;
+      vertical-align: middle;
+      margin-right: 12px;
+    }
+
+    & .sidebar-title {
+      display: inline-block;
+      margin: 0;
+      color: #fff;
+      font-weight: 600;
+      line-height: 50px;
+      font-size: 14px;
+      font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
+      vertical-align: middle;
+    }
+  }
+
+  &.collapse {
+    .sidebar-logo {
+      margin-right: 0px;
+    }
+  }
+}
+</style>

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

@@ -0,0 +1,95 @@
+<template>
+  <div v-if="!item.hidden">
+    <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>

+ 56 - 0
src/layout/components/Sidebar/index.vue

@@ -0,0 +1,56 @@
+<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 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([
+      'sidebar'
+    ]),
+    routes() {
+      return this.$router.options.routes
+    },
+    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>

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

@@ -0,0 +1,3 @@
+export { default as Navbar } from './Navbar'
+export { default as Sidebar } from './Sidebar'
+export { default as AppMain } from './AppMain'

+ 93 - 0
src/layout/index.vue

@@ -0,0 +1,93 @@
+<template>
+  <div :class="classObj" class="app-wrapper">
+    <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
+    <sidebar class="sidebar-container" />
+    <div class="main-container">
+      <div :class="{'fixed-header':fixedHeader}">
+        <navbar />
+      </div>
+      <app-main />
+    </div>
+  </div>
+</template>
+
+<script>
+import { Navbar, Sidebar, AppMain } from './components'
+import ResizeMixin from './mixin/ResizeHandler'
+
+export default {
+  name: 'Layout',
+  components: {
+    Navbar,
+    Sidebar,
+    AppMain
+  },
+  mixins: [ResizeMixin],
+  computed: {
+    sidebar() {
+      return this.$store.state.app.sidebar
+    },
+    device() {
+      return this.$store.state.app.device
+    },
+    fixedHeader() {
+      return this.$store.state.settings.fixedHeader
+    },
+    classObj() {
+      return {
+        hideSidebar: !this.sidebar.opened,
+        openSidebar: this.sidebar.opened,
+        withoutAnimation: this.sidebar.withoutAnimation,
+        mobile: this.device === 'mobile'
+      }
+    }
+  },
+  methods: {
+    handleClickOutside() {
+      this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  @import "~@/styles/mixin.scss";
+  @import "~@/styles/variables.scss";
+
+  .app-wrapper {
+    @include clearfix;
+    position: relative;
+    height: 100%;
+    width: 100%;
+    &.mobile.openSidebar{
+      position: fixed;
+      top: 0;
+    }
+  }
+  .drawer-bg {
+    background: #000;
+    opacity: 0.3;
+    width: 100%;
+    top: 0;
+    height: 100%;
+    position: absolute;
+    z-index: 999;
+  }
+
+  .fixed-header {
+    position: fixed;
+    top: 0;
+    right: 0;
+    z-index: 9;
+    width: calc(100% - #{$sideBarWidth});
+    transition: width 0.28s;
+  }
+
+  .hideSidebar .fixed-header {
+    width: calc(100% - 54px)
+  }
+
+  .mobile .fixed-header {
+    width: 100%;
+  }
+</style>

+ 45 - 0
src/layout/mixin/ResizeHandler.js

@@ -0,0 +1,45 @@
+import store from '@/store'
+
+const { body } = document
+const WIDTH = 992 // refer to Bootstrap's responsive design
+
+export default {
+  watch: {
+    $route(route) {
+      if (this.device === 'mobile' && this.sidebar.opened) {
+        store.dispatch('app/closeSideBar', { withoutAnimation: false })
+      }
+    }
+  },
+  beforeMount() {
+    window.addEventListener('resize', this.$_resizeHandler)
+  },
+  beforeDestroy() {
+    window.removeEventListener('resize', this.$_resizeHandler)
+  },
+  mounted() {
+    const isMobile = this.$_isMobile()
+    if (isMobile) {
+      store.dispatch('app/toggleDevice', 'mobile')
+      store.dispatch('app/closeSideBar', { withoutAnimation: true })
+    }
+  },
+  methods: {
+    // use $_ for mixins properties
+    // https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
+    $_isMobile() {
+      const rect = body.getBoundingClientRect()
+      return rect.width - 1 < WIDTH
+    },
+    $_resizeHandler() {
+      if (!document.hidden) {
+        const isMobile = this.$_isMobile()
+        store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
+
+        if (isMobile) {
+          store.dispatch('app/closeSideBar', { withoutAnimation: true })
+        }
+      }
+    }
+  }
+}

+ 43 - 0
src/main.js

@@ -0,0 +1,43 @@
+import Vue from 'vue'
+
+import 'normalize.css/normalize.css' // A modern alternative to CSS resets
+
+import ElementUI from 'element-ui'
+import 'element-ui/lib/theme-chalk/index.css'
+import locale from 'element-ui/lib/locale/lang/en' // lang i18n
+
+import '@/styles/index.scss' // global css
+
+import App from './App'
+import store from './store'
+import router from './router'
+
+import '@/icons' // icon
+import '@/permission' // permission control
+
+/**
+ * If you don't want to use mock-server
+ * you want to use MockJs for mock api
+ * you can execute: mockXHR()
+ *
+ * Currently MockJs will be used in the production environment,
+ * please remove it before going online ! ! !
+ */
+if (process.env.NODE_ENV === 'production') {
+  const { mockXHR } = require('../mock')
+  mockXHR()
+}
+
+// set ElementUI lang to EN
+Vue.use(ElementUI, { locale })
+// 如果想要中文版 element-ui,按如下方式声明
+// Vue.use(ElementUI)
+
+Vue.config.productionTip = false
+
+new Vue({
+  el: '#app',
+  router,
+  store,
+  render: h => h(App)
+})

+ 64 - 0
src/permission.js

@@ -0,0 +1,64 @@
+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
+
+const whiteList = ['/login'] // no redirect whitelist
+
+router.beforeEach(async(to, from, next) => {
+  // start progress bar
+  NProgress.start()
+
+  // set page title
+  document.title = getPageTitle(to.meta.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 {
+      const hasGetUserInfo = store.getters.name
+      if (hasGetUserInfo) {
+        next()
+      } else {
+        try {
+          // get user info
+          await store.dispatch('user/getInfo')
+
+          next()
+        } 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()
+})

+ 521 - 0
src/router/index.js

@@ -0,0 +1,521 @@
+import Vue from 'vue'
+import Router from 'vue-router'
+
+Vue.use(Router)
+
+/* Layout */
+import Layout from '@/layout'
+
+/**
+ * Note: sub-menu only appear when route children.length >= 1
+ * Detail see: https://panjiachen.github.io/vue-element-admin-site/guide/essentials/router-and-nav.html
+ *
+ * hidden: true                   if set true, item will not show in the sidebar(default is false)
+ * alwaysShow: true               if set true, will always show the root menu
+ *                                if not set alwaysShow, when item has more than one children route,
+ *                                it will becomes nested mode, otherwise not show the root menu
+ * redirect: noRedirect           if set noRedirect will no redirect in the breadcrumb
+ * name:'router-name'             the name is used by <keep-alive> (must set!!!)
+ * meta : {
+    roles: ['admin','editor']    control the page roles (you can set multiple roles)
+    title: 'title'               the name show in sidebar and breadcrumb (recommend set)
+    icon: 'svg-name'/'el-icon-x' the icon show in the sidebar
+    breadcrumb: false            if set false, the item will hidden in breadcrumb(default is true)
+    activeMenu: '/example/list'  if set path, the sidebar will highlight the path you set
+  }
+ */
+
+/**
+ * constantRoutes
+ * a base page that does not have permission requirements
+ * all roles can be accessed
+ */
+export const constantRoutes = [{
+        path: '/login',
+        component: () =>
+            import ('@/views/login/index'),
+        hidden: true
+    },
+
+    {
+        path: '/404',
+        component: () =>
+            import ('@/views/404'),
+        hidden: true
+    },
+
+    // {
+    //     path: '/',
+    //     component: Layout,
+    //     redirect: '/dashboard',
+    //     children: [{
+    //         path: 'dashboard',
+    //         name: 'Dashboard',
+    //         component: () =>
+    //             import ('@/views/dashboard/index'),
+    //         meta: { title: 'Dashboard', icon: 'dashboard' }
+    //     }]
+    // },
+
+    // {
+    //     path: '/example',
+    //     component: Layout,
+    //     redirect: '/example/table',
+    //     name: 'Example',
+    //     meta: { title: 'Table', icon: 'el-icon-s-help' },
+    //     children: [{
+    //             path: 'table',
+    //             name: 'Table',
+    //             component: () =>
+    //                 import ('@/views/table/index'),
+    //             meta: { title: 'Table', icon: 'table' }
+    //         },
+    //         {
+    //             path: 'tree',
+    //             name: 'Tree',
+    //             component: () =>
+    //                 import ('@/views/tree/index'),
+    //             meta: { title: 'Tree', icon: 'tree' }
+    //         }
+    //     ]
+    // },
+
+    // {
+    //     path: '/form',
+    //     component: Layout,
+    //     children: [{
+    //         path: 'index',
+    //         name: 'Form',
+    //         component: () =>
+    //             import ('@/views/form/index'),
+    //         meta: { title: 'Form', icon: 'form' }
+    //     }]
+    // },
+
+
+
+    // my start
+
+    {
+        path: '/',
+        component: Layout,
+        redirect: '/alarmManage',
+        children: [{
+            path: 'alarmManage',
+            name: 'alarmManage',
+            component: () =>
+                import ('@/views/alarmManage/index'), //aa页面路径
+            meta: { title: '告警管理', icon: 'alarmManage' } //aa title 菜单标题&meta title
+        }]
+    },
+    {
+        path: '/siteManage',
+        component: Layout,
+        redirect: '/siteManage/basicInfo',
+        name: 'siteManage',
+        meta: { title: '站点管理', icon: 'siteManage' },
+        children: [{
+                path: 'basicInfo',
+                name: 'basicInfo',
+                component: () =>
+                    import ('@/views/siteManage/basicInfo/index'),
+                meta: { title: '基本信息' }
+            },
+            {
+                path: 'watchDog',
+                name: 'watchDog',
+                component: () =>
+                    import ('@/views/siteManage/watchDog/index'),
+                meta: { title: '监控设备' }
+            },
+            {
+                path: 'variableList',
+                name: 'variableList',
+                component: () =>
+                    import ('@/views/siteManage/variableList/index'),
+                meta: { title: '变量列表' }
+            },
+            {
+                path: 'rateConfig',
+                name: 'rateConfig',
+                component: () =>
+                    import ('@/views/siteManage/rateConfig/index'),
+                meta: { title: '费率配置' }
+            },
+            {
+                path: 'camera',
+                name: 'camera',
+                component: () =>
+                    import ('@/views/siteManage/camera/index'),
+                meta: { title: '摄像头' }
+            },
+            {
+                path: 'powerScore',
+                name: 'powerScore',
+                component: () =>
+                    import ('@/views/siteManage/powerScore/index'),
+                meta: { title: '电能质量评分' }
+            }
+        ]
+    },
+
+    {
+        path: '/stationManage',
+        component: Layout,
+        children: [{
+            path: 'index',
+            name: 'StationManage',
+            component: () =>
+                import ('@/views/stationManage/index'),
+            meta: { title: '台区管理', icon: 'form' }
+        }]
+    },
+    {
+        path: '/deviceManage',
+        component: Layout,
+        redirect: '/deviceManage/powerEquip',
+        name: 'deviceManage',
+        meta: { title: '设备管理', icon: 'deviceManage' },
+        children: [{
+                path: 'powerEquip',
+                name: 'powerEquip',
+                component: () =>
+                    import ('@/views/deviceManage/powerEquip/index'),
+                meta: { title: '电力监测设备' }
+            },
+            {
+                path: 'videoEquip',
+                name: 'videoEquip',
+                component: () =>
+                    import ('@/views/deviceManage/videoEquip/index'),
+                meta: { title: '视频监测设备' }
+            },
+            {
+                path: 'communicateEquip',
+                name: 'communicateEquip',
+                component: () =>
+                    import ('@/views/deviceManage/communicateEquip/index'),
+                meta: { title: '通信设备' }
+            },
+            {
+                path: 'channelList',
+                name: 'channelList',
+                component: () =>
+                    import ('@/views/deviceManage/channelList/index'),
+                meta: { title: '通道列表' }
+            },
+            {
+                path: 'attribTemplate',
+                name: 'attribTemplate',
+                component: () =>
+                    import ('@/views/deviceManage/attribTemplate/index'),
+                meta: { title: '属性模板' }
+            }
+        ]
+    },
+    {
+        path: '/dataManage',
+        component: Layout,
+        redirect: '/dataManage/sameAnalysis',
+        name: 'dataManage',
+        meta: { title: '数据管理', icon: 'dataManage' },
+        children: [{
+                path: 'sameAnalysis',
+                name: 'sameAnalysis',
+                component: () =>
+                    import ('@/views/dataManage/sameAnalysis/index'),
+                meta: { title: '同比分析报表' }
+            },
+            {
+                path: 'chainAnalysis',
+                name: 'chainAnalysis',
+                component: () =>
+                    import ('@/views/dataManage/chainAnalysis/index'),
+                meta: { title: '环比分析报表' }
+            },
+            {
+                path: 'handOpera',
+                name: 'handOpera',
+                component: () =>
+                    import ('@/views/dataManage/handOpera/index'),
+                meta: { title: '手动抄表' }
+            },
+            {
+                path: 'energyReport',
+                name: 'energyReport',
+                component: () =>
+                    import ('@/views/dataManage/energyReport/index'),
+                meta: { title: '用能月报' }
+            },
+            {
+                path: 'demandAnalysis',
+                name: 'demandAnalysis',
+                component: () =>
+                    import ('@/views/dataManage/demandAnalysis/index'),
+                meta: { title: '需量分析' }
+            }, {
+                path: 'consumConfig',
+                name: 'consumConfig',
+                component: () =>
+                    import ('@/views/dataManage/consumConfig/index'),
+                meta: { title: '能耗分析配置' }
+            }
+        ]
+    },
+    {
+        path: '/powerQuality',
+        component: Layout,
+        redirect: '/powerQuality/harmonicReport',
+        name: 'powerQuality',
+        meta: { title: '电能质量', icon: 'powerQuality' },
+        children: [{
+                path: 'harmonicReport',
+                name: 'harmonicReport',
+                component: () =>
+                    import ('@/views/powerQuality/harmonicReport/index'),
+                meta: { title: '谐波报表' }
+            },
+            {
+                path: 'realTimeMonitoring',
+                name: 'realTimeMonitoring',
+                component: () =>
+                    import ('@/views/powerQuality/realTimeMonitoring/index'),
+                meta: { title: '实时监测' }
+            },
+            {
+                path: 'asseReport',
+                name: 'asseReport',
+                component: () =>
+                    import ('@/views/powerQuality/asseReport/index'),
+                meta: { title: '评估报告' }
+            },
+            {
+                path: 'unbalanceAnalysis',
+                name: 'unbalanceAnalysis',
+                component: () =>
+                    import ('@/views/powerQuality/unbalanceAnalysis/index'),
+                meta: { title: '三相不平衡分析' }
+            },
+
+        ]
+    },
+    {
+        path: '/monthReport',
+        component: Layout,
+        children: [{
+            path: 'index',
+            name: 'monthReport',
+            component: () =>
+                import ('@/views/monthReport/index'),
+            meta: { title: '月度报告', icon: 'monthReport' }
+        }]
+    },
+    {
+        path: '/planOutage',
+        component: Layout,
+        children: [{
+            path: 'index',
+            name: 'planOutage',
+            component: () =>
+                import ('@/views/planOutage/index'),
+            meta: { title: '计划停电', icon: 'planOutage' }
+        }]
+    },
+    {
+        path: '/patrolManage',
+        component: Layout,
+        redirect: '/patrolManage/patrolPlan',
+        name: 'patrolManage',
+        meta: { title: '巡检管理', icon: 'patrolManage' },
+        children: [{
+                path: 'patrolPlan',
+                name: 'patrolPlan',
+                component: () =>
+                    import ('@/views/patrolManage/patrolPlan/index'),
+                meta: { title: '巡检计划' }
+            },
+            {
+                path: 'patrolRecord',
+                name: 'patrolRecord',
+                component: () =>
+                    import ('@/views/patrolManage/patrolRecord/index'),
+                meta: { title: '巡检记录' }
+            },
+            {
+                path: 'patrolContent',
+                name: 'patrolContent',
+                component: () =>
+                    import ('@/views/patrolManage/patrolContent/index'),
+                meta: { title: '巡检内容' }
+            },
+            {
+                path: 'checkEntries',
+                name: 'checkEntries',
+                component: () =>
+                    import ('@/views/patrolManage/checkEntries/index'),
+                meta: { title: '检查条目' }
+            },
+
+        ]
+    },
+    {
+        path: '/operManage',
+        component: Layout,
+        redirect: '/operManage/siteAchives',
+        name: 'operManage',
+        meta: { title: '运维管理', icon: 'operManage' },
+        children: [{
+                path: 'siteAchives',
+                name: 'siteAchives',
+                component: () =>
+                    import ('@/views/operManage/siteAchives/index'),
+                meta: { title: '现场档案' }
+            },
+            {
+                path: 'defectManage',
+                name: 'defectManage',
+                component: () =>
+                    import ('@/views/operManage/defectManage/index'),
+                meta: { title: '缺陷管理' }
+            },
+            {
+                path: 'workManage',
+                name: 'workManage',
+                component: () =>
+                    import ('@/views/operManage/workManage/index'),
+                meta: { title: '工单管理' }
+            },
+            {
+                path: 'operStatistics',
+                name: 'operStatistics',
+                component: () =>
+                    import ('@/views/operManage/operStatistics/index'),
+                meta: { title: '运维统计' }
+            },
+            {
+                path: 'workStatistics',
+                name: 'workStatistics',
+                component: () =>
+                    import ('@/views/operManage/workStatistics/index'),
+                meta: { title: '工作量统计 ' }
+            },
+
+        ]
+    },
+    {
+        path: '/systemManage',
+        component: Layout,
+        redirect: '/systemManage/userManage',
+        name: 'systemManage',
+        meta: { title: '系统管理', icon: 'systemManage' },
+        children: [{
+                path: 'userManage',
+                name: 'userManage',
+                component: () =>
+                    import ('@/views/systemManage/userManage/index'),
+                meta: { title: '用户管理' }
+            },
+            {
+                path: 'rolePermission',
+                name: 'rolePermission',
+                component: () =>
+                    import ('@/views/systemManage/rolePermission/index'),
+                meta: { title: '角色权限' }
+            },
+        ]
+    },
+
+    //my end
+
+
+    // {
+    //     path: '/nested',
+    //     component: Layout,
+    //     redirect: '/nested/menu1',
+    //     name: 'Nested',
+    //     meta: {
+    //         title: 'Nested',
+    //         icon: 'nested'
+    //     },
+    //     children: [{
+    //             path: 'menu1',
+    //             component: () =>
+    //                 import ('@/views/nested/menu1/index'), // Parent router-view
+    //             name: 'Menu1',
+    //             meta: { title: 'Menu1' },
+    //             children: [{
+    //                     path: 'menu1-1',
+    //                     component: () =>
+    //                         import ('@/views/nested/menu1/menu1-1'),
+    //                     name: 'Menu1-1',
+    //                     meta: { title: 'Menu1-1' }
+    //                 },
+    //                 {
+    //                     path: 'menu1-2',
+    //                     component: () =>
+    //                         import ('@/views/nested/menu1/menu1-2'),
+    //                     name: 'Menu1-2',
+    //                     meta: { title: 'Menu1-2' },
+    //                     children: [{
+    //                             path: 'menu1-2-1',
+    //                             component: () =>
+    //                                 import ('@/views/nested/menu1/menu1-2/menu1-2-1'),
+    //                             name: 'Menu1-2-1',
+    //                             meta: { title: 'Menu1-2-1' }
+    //                         },
+    //                         {
+    //                             path: 'menu1-2-2',
+    //                             component: () =>
+    //                                 import ('@/views/nested/menu1/menu1-2/menu1-2-2'),
+    //                             name: 'Menu1-2-2',
+    //                             meta: { title: 'Menu1-2-2' }
+    //                         }
+    //                     ]
+    //                 },
+    //                 {
+    //                     path: 'menu1-3',
+    //                     component: () =>
+    //                         import ('@/views/nested/menu1/menu1-3'),
+    //                     name: 'Menu1-3',
+    //                     meta: { title: 'Menu1-3' }
+    //                 }
+    //             ]
+    //         },
+    //         {
+    //             path: 'menu2',
+    //             component: () =>
+    //                 import ('@/views/nested/menu2/index'),
+    //             name: 'Menu2',
+    //             meta: { title: 'menu2' }
+    //         }
+    //     ]
+    // },
+
+    // {
+    //     path: 'external-link',
+    //     component: Layout,
+    //     children: [{
+    //         path: 'https://panjiachen.github.io/vue-element-admin-site/#/',
+    //         meta: { title: 'External Link', icon: 'link' }
+    //     }]
+    // },
+
+    // 404 page must be placed at the end !!!
+    { path: '*', redirect: '/404', hidden: true }
+]
+
+const createRouter = () => new Router({
+    // mode: 'history', // require service support
+    scrollBehavior: () => ({ y: 0 }),
+    routes: constantRoutes
+})
+
+const router = createRouter()
+
+// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
+export function resetRouter() {
+    const newRouter = createRouter()
+    router.matcher = newRouter.matcher // reset router
+}
+
+export default router

+ 16 - 0
src/settings.js

@@ -0,0 +1,16 @@
+module.exports = {
+
+    title: 'admin',
+
+    /**
+     * @type {boolean} true | false
+     * @description Whether fix the header
+     */
+    fixedHeader: false,
+
+    /**
+     * @type {boolean} true | false
+     * @description Whether show the logo in sidebar
+     */
+    sidebarLogo: true
+}

+ 8 - 0
src/store/getters.js

@@ -0,0 +1,8 @@
+const getters = {
+  sidebar: state => state.app.sidebar,
+  device: state => state.app.device,
+  token: state => state.user.token,
+  avatar: state => state.user.avatar,
+  name: state => state.user.name
+}
+export default getters

+ 19 - 0
src/store/index.js

@@ -0,0 +1,19 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import getters from './getters'
+import app from './modules/app'
+import settings from './modules/settings'
+import user from './modules/user'
+
+Vue.use(Vuex)
+
+const store = new Vuex.Store({
+  modules: {
+    app,
+    settings,
+    user
+  },
+  getters
+})
+
+export default store

+ 48 - 0
src/store/modules/app.js

@@ -0,0 +1,48 @@
+import Cookies from 'js-cookie'
+
+const state = {
+  sidebar: {
+    opened: Cookies.get('sidebarStatus') ? !!+Cookies.get('sidebarStatus') : true,
+    withoutAnimation: false
+  },
+  device: 'desktop'
+}
+
+const mutations = {
+  TOGGLE_SIDEBAR: state => {
+    state.sidebar.opened = !state.sidebar.opened
+    state.sidebar.withoutAnimation = false
+    if (state.sidebar.opened) {
+      Cookies.set('sidebarStatus', 1)
+    } else {
+      Cookies.set('sidebarStatus', 0)
+    }
+  },
+  CLOSE_SIDEBAR: (state, withoutAnimation) => {
+    Cookies.set('sidebarStatus', 0)
+    state.sidebar.opened = false
+    state.sidebar.withoutAnimation = withoutAnimation
+  },
+  TOGGLE_DEVICE: (state, device) => {
+    state.device = device
+  }
+}
+
+const actions = {
+  toggleSideBar({ commit }) {
+    commit('TOGGLE_SIDEBAR')
+  },
+  closeSideBar({ commit }, { withoutAnimation }) {
+    commit('CLOSE_SIDEBAR', withoutAnimation)
+  },
+  toggleDevice({ commit }, device) {
+    commit('TOGGLE_DEVICE', device)
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}

+ 32 - 0
src/store/modules/settings.js

@@ -0,0 +1,32 @@
+import defaultSettings from '@/settings'
+
+const { showSettings, fixedHeader, sidebarLogo } = defaultSettings
+
+const state = {
+  showSettings: showSettings,
+  fixedHeader: fixedHeader,
+  sidebarLogo: sidebarLogo
+}
+
+const mutations = {
+  CHANGE_SETTING: (state, { key, value }) => {
+    // eslint-disable-next-line no-prototype-builtins
+    if (state.hasOwnProperty(key)) {
+      state[key] = value
+    }
+  }
+}
+
+const actions = {
+  changeSetting({ commit }, data) {
+    commit('CHANGE_SETTING', data)
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}
+

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

@@ -0,0 +1,97 @@
+import { login, logout, getInfo } from '@/api/user'
+import { getToken, setToken, removeToken } from '@/utils/auth'
+import { resetRouter } from '@/router'
+
+const getDefaultState = () => {
+  return {
+    token: getToken(),
+    name: '',
+    avatar: ''
+  }
+}
+
+const state = getDefaultState()
+
+const mutations = {
+  RESET_STATE: (state) => {
+    Object.assign(state, getDefaultState())
+  },
+  SET_TOKEN: (state, token) => {
+    state.token = token
+  },
+  SET_NAME: (state, name) => {
+    state.name = name
+  },
+  SET_AVATAR: (state, avatar) => {
+    state.avatar = avatar
+  }
+}
+
+const actions = {
+  // user login
+  login({ commit }, userInfo) {
+    const { username, password } = userInfo
+    return new Promise((resolve, reject) => {
+      login({ username: username.trim(), password: password }).then(response => {
+        const { data } = response
+        commit('SET_TOKEN', data.token)
+        setToken(data.token)
+        resolve()
+      }).catch(error => {
+        reject(error)
+      })
+    })
+  },
+
+  // get user info
+  getInfo({ commit, state }) {
+    return new Promise((resolve, reject) => {
+      getInfo(state.token).then(response => {
+        const { data } = response
+
+        if (!data) {
+          return reject('Verification failed, please Login again.')
+        }
+
+        const { name, avatar } = data
+
+        commit('SET_NAME', name)
+        commit('SET_AVATAR', avatar)
+        resolve(data)
+      }).catch(error => {
+        reject(error)
+      })
+    })
+  },
+
+  // user logout
+  logout({ commit, state }) {
+    return new Promise((resolve, reject) => {
+      logout(state.token).then(() => {
+        removeToken() // must remove  token  first
+        resetRouter()
+        commit('RESET_STATE')
+        resolve()
+      }).catch(error => {
+        reject(error)
+      })
+    })
+  },
+
+  // remove token
+  resetToken({ commit }) {
+    return new Promise(resolve => {
+      removeToken() // must remove  token  first
+      commit('RESET_STATE')
+      resolve()
+    })
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}
+

+ 49 - 0
src/styles/element-ui.scss

@@ -0,0 +1,49 @@
+// cover some element-ui styles
+
+.el-breadcrumb__inner,
+.el-breadcrumb__inner a {
+  font-weight: 400 !important;
+}
+
+.el-upload {
+  input[type="file"] {
+    display: none !important;
+  }
+}
+
+.el-upload__input {
+  display: none;
+}
+
+
+// to fixed https://github.com/ElemeFE/element/issues/2461
+.el-dialog {
+  transform: none;
+  left: 0;
+  position: relative;
+  margin: 0 auto;
+}
+
+// refine element ui upload
+.upload-container {
+  .el-upload {
+    width: 100%;
+
+    .el-upload-dragger {
+      width: 100%;
+      height: 200px;
+    }
+  }
+}
+
+// dropdown
+.el-dropdown-menu {
+  a {
+    display: block
+  }
+}
+
+// to fix el-date-picker css style
+.el-range-separator {
+  box-sizing: content-box;
+}

+ 65 - 0
src/styles/index.scss

@@ -0,0 +1,65 @@
+@import './variables.scss';
+@import './mixin.scss';
+@import './transition.scss';
+@import './element-ui.scss';
+@import './sidebar.scss';
+
+body {
+  height: 100%;
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-font-smoothing: antialiased;
+  text-rendering: optimizeLegibility;
+  font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
+}
+
+label {
+  font-weight: 700;
+}
+
+html {
+  height: 100%;
+  box-sizing: border-box;
+}
+
+#app {
+  height: 100%;
+}
+
+*,
+*:before,
+*:after {
+  box-sizing: inherit;
+}
+
+a:focus,
+a:active {
+  outline: none;
+}
+
+a,
+a:focus,
+a:hover {
+  cursor: pointer;
+  color: inherit;
+  text-decoration: none;
+}
+
+div:focus {
+  outline: none;
+}
+
+.clearfix {
+  &:after {
+    visibility: hidden;
+    display: block;
+    font-size: 0;
+    content: " ";
+    clear: both;
+    height: 0;
+  }
+}
+
+// main-container global css
+.app-container {
+  padding: 20px;
+}

+ 28 - 0
src/styles/mixin.scss

@@ -0,0 +1,28 @@
+@mixin clearfix {
+  &:after {
+    content: "";
+    display: table;
+    clear: both;
+  }
+}
+
+@mixin scrollBar {
+  &::-webkit-scrollbar-track-piece {
+    background: #d3dce6;
+  }
+
+  &::-webkit-scrollbar {
+    width: 6px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: #99a9bf;
+    border-radius: 20px;
+  }
+}
+
+@mixin relative {
+  position: relative;
+  width: 100%;
+  height: 100%;
+}

+ 226 - 0
src/styles/sidebar.scss

@@ -0,0 +1,226 @@
+#app {
+
+  .main-container {
+    min-height: 100%;
+    transition: margin-left .28s;
+    margin-left: $sideBarWidth;
+    position: relative;
+  }
+
+  .sidebar-container {
+    transition: width 0.28s;
+    width: $sideBarWidth !important;
+    background-color: $menuBg;
+    height: 100%;
+    position: fixed;
+    font-size: 0px;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    z-index: 1001;
+    overflow: hidden;
+
+    // reset element-ui css
+    .horizontal-collapse-transition {
+      transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
+    }
+
+    .scrollbar-wrapper {
+      overflow-x: hidden !important;
+    }
+
+    .el-scrollbar__bar.is-vertical {
+      right: 0px;
+    }
+
+    .el-scrollbar {
+      height: 100%;
+    }
+
+    &.has-logo {
+      .el-scrollbar {
+        height: calc(100% - 50px);
+      }
+    }
+
+    .is-horizontal {
+      display: none;
+    }
+
+    a {
+      display: inline-block;
+      width: 100%;
+      overflow: hidden;
+    }
+
+    .svg-icon {
+      margin-right: 16px;
+    }
+
+    .sub-el-icon {
+      margin-right: 12px;
+      margin-left: -2px;
+    }
+
+    .el-menu {
+      border: none;
+      height: 100%;
+      width: 100% !important;
+    }
+
+    // menu hover
+    .submenu-title-noDropdown,
+    .el-submenu__title {
+      &:hover {
+        background-color: $menuHover !important;
+      }
+    }
+
+    .is-active>.el-submenu__title {
+      color: $subMenuActiveText !important;
+    }
+
+    & .nest-menu .el-submenu>.el-submenu__title,
+    & .el-submenu .el-menu-item {
+      min-width: $sideBarWidth !important;
+      background-color: $subMenuBg !important;
+
+      &:hover {
+        background-color: $subMenuHover !important;
+      }
+    }
+  }
+
+  .hideSidebar {
+    .sidebar-container {
+      width: 54px !important;
+    }
+
+    .main-container {
+      margin-left: 54px;
+    }
+
+    .submenu-title-noDropdown {
+      padding: 0 !important;
+      position: relative;
+
+      .el-tooltip {
+        padding: 0 !important;
+
+        .svg-icon {
+          margin-left: 20px;
+        }
+
+        .sub-el-icon {
+          margin-left: 19px;
+        }
+      }
+    }
+
+    .el-submenu {
+      overflow: hidden;
+
+      &>.el-submenu__title {
+        padding: 0 !important;
+
+        .svg-icon {
+          margin-left: 20px;
+        }
+
+        .sub-el-icon {
+          margin-left: 19px;
+        }
+
+        .el-submenu__icon-arrow {
+          display: none;
+        }
+      }
+    }
+
+    .el-menu--collapse {
+      .el-submenu {
+        &>.el-submenu__title {
+          &>span {
+            height: 0;
+            width: 0;
+            overflow: hidden;
+            visibility: hidden;
+            display: inline-block;
+          }
+        }
+      }
+    }
+  }
+
+  .el-menu--collapse .el-menu .el-submenu {
+    min-width: $sideBarWidth !important;
+  }
+
+  // mobile responsive
+  .mobile {
+    .main-container {
+      margin-left: 0px;
+    }
+
+    .sidebar-container {
+      transition: transform .28s;
+      width: $sideBarWidth !important;
+    }
+
+    &.hideSidebar {
+      .sidebar-container {
+        pointer-events: none;
+        transition-duration: 0.3s;
+        transform: translate3d(-$sideBarWidth, 0, 0);
+      }
+    }
+  }
+
+  .withoutAnimation {
+
+    .main-container,
+    .sidebar-container {
+      transition: none;
+    }
+  }
+}
+
+// when menu collapsed
+.el-menu--vertical {
+  &>.el-menu {
+    .svg-icon {
+      margin-right: 16px;
+    }
+    .sub-el-icon {
+      margin-right: 12px;
+      margin-left: -2px;
+    }
+  }
+
+  .nest-menu .el-submenu>.el-submenu__title,
+  .el-menu-item {
+    &:hover {
+      // you can use $subMenuHover
+      background-color: $menuHover !important;
+    }
+  }
+
+  // the scroll bar appears when the subMenu is too long
+  >.el-menu--popup {
+    max-height: 100vh;
+    overflow-y: auto;
+
+    &::-webkit-scrollbar-track-piece {
+      background: #d3dce6;
+    }
+
+    &::-webkit-scrollbar {
+      width: 6px;
+    }
+
+    &::-webkit-scrollbar-thumb {
+      background: #99a9bf;
+      border-radius: 20px;
+    }
+  }
+}

+ 48 - 0
src/styles/transition.scss

@@ -0,0 +1,48 @@
+// global transition css
+
+/* fade */
+.fade-enter-active,
+.fade-leave-active {
+  transition: opacity 0.28s;
+}
+
+.fade-enter,
+.fade-leave-active {
+  opacity: 0;
+}
+
+/* fade-transform */
+.fade-transform-leave-active,
+.fade-transform-enter-active {
+  transition: all .5s;
+}
+
+.fade-transform-enter {
+  opacity: 0;
+  transform: translateX(-30px);
+}
+
+.fade-transform-leave-to {
+  opacity: 0;
+  transform: translateX(30px);
+}
+
+/* breadcrumb transition */
+.breadcrumb-enter-active,
+.breadcrumb-leave-active {
+  transition: all .5s;
+}
+
+.breadcrumb-enter,
+.breadcrumb-leave-active {
+  opacity: 0;
+  transform: translateX(20px);
+}
+
+.breadcrumb-move {
+  transition: all .5s;
+}
+
+.breadcrumb-leave-active {
+  position: absolute;
+}

+ 25 - 0
src/styles/variables.scss

@@ -0,0 +1,25 @@
+// sidebar
+$menuText:#bfcbd9;
+$menuActiveText:#409EFF;
+$subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951
+
+$menuBg:#304156;
+$menuHover:#263445;
+
+$subMenuBg:#1f2d3d;
+$subMenuHover:#001528;
+
+$sideBarWidth: 210px;
+
+// the :export directive is the magic sauce for webpack
+// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
+:export {
+  menuText: $menuText;
+  menuActiveText: $menuActiveText;
+  subMenuActiveText: $subMenuActiveText;
+  menuBg: $menuBg;
+  menuHover: $menuHover;
+  subMenuBg: $subMenuBg;
+  subMenuHover: $subMenuHover;
+  sideBarWidth: $sideBarWidth;
+}

+ 15 - 0
src/utils/auth.js

@@ -0,0 +1,15 @@
+import Cookies from 'js-cookie'
+
+const TokenKey = 'vue_admin_template_token'
+
+export function getToken() {
+  return Cookies.get(TokenKey)
+}
+
+export function setToken(token) {
+  return Cookies.set(TokenKey, token)
+}
+
+export function removeToken() {
+  return Cookies.remove(TokenKey)
+}

+ 10 - 0
src/utils/get-page-title.js

@@ -0,0 +1,10 @@
+import defaultSettings from '@/settings'
+
+const title = defaultSettings.title || 'Vue Admin Template'
+
+export default function getPageTitle(pageTitle) {
+    if (pageTitle) {
+        return `${pageTitle} - ${title}`
+    }
+    return `${title}`
+}

+ 117 - 0
src/utils/index.js

@@ -0,0 +1,117 @@
+/**
+ * Created by PanJiaChen on 16/11/18.
+ */
+
+/**
+ * Parse the time to string
+ * @param {(Object|string|number)} time
+ * @param {string} cFormat
+ * @returns {string | null}
+ */
+export function parseTime(time, cFormat) {
+  if (arguments.length === 0 || !time) {
+    return null
+  }
+  const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
+  let date
+  if (typeof time === 'object') {
+    date = time
+  } else {
+    if ((typeof time === 'string')) {
+      if ((/^[0-9]+$/.test(time))) {
+        // support "1548221490638"
+        time = parseInt(time)
+      } else {
+        // support safari
+        // https://stackoverflow.com/questions/4310953/invalid-date-in-safari
+        time = time.replace(new RegExp(/-/gm), '/')
+      }
+    }
+
+    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(/{([ymdhisa])+}/g, (result, key) => {
+    const value = formatObj[key]
+    // Note: getDay() returns 0 on Sunday
+    if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value ] }
+    return value.toString().padStart(2, '0')
+  })
+  return time_str
+}
+
+/**
+ * @param {number} time
+ * @param {string} option
+ * @returns {string}
+ */
+export function formatTime(time, option) {
+  if (('' + time).length === 10) {
+    time = parseInt(time) * 1000
+  } else {
+    time = +time
+  }
+  const d = new Date(time)
+  const now = Date.now()
+
+  const diff = (now - d) / 1000
+
+  if (diff < 30) {
+    return '刚刚'
+  } else if (diff < 3600) {
+    // less 1 hour
+    return Math.ceil(diff / 60) + '分钟前'
+  } else if (diff < 3600 * 24) {
+    return Math.ceil(diff / 3600) + '小时前'
+  } else if (diff < 3600 * 24 * 2) {
+    return '1天前'
+  }
+  if (option) {
+    return parseTime(time, option)
+  } else {
+    return (
+      d.getMonth() +
+      1 +
+      '月' +
+      d.getDate() +
+      '日' +
+      d.getHours() +
+      '时' +
+      d.getMinutes() +
+      '分'
+    )
+  }
+}
+
+/**
+ * @param {string} url
+ * @returns {Object}
+ */
+export function param2Obj(url) {
+  const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
+  if (!search) {
+    return {}
+  }
+  const obj = {}
+  const searchArr = search.split('&')
+  searchArr.forEach(v => {
+    const index = v.indexOf('=')
+    if (index !== -1) {
+      const name = v.substring(0, index)
+      const val = v.substring(index + 1, v.length)
+      obj[name] = val
+    }
+  })
+  return obj
+}

+ 85 - 0
src/utils/request.js

@@ -0,0 +1,85 @@
+import axios from 'axios'
+import { MessageBox, Message } from 'element-ui'
+import store from '@/store'
+import { getToken } from '@/utils/auth'
+
+// create an axios instance
+const service = axios.create({
+  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
+  // withCredentials: true, // send cookies when cross-domain requests
+  timeout: 5000 // request timeout
+})
+
+// request interceptor
+service.interceptors.request.use(
+  config => {
+    // do something before request is sent
+
+    if (store.getters.token) {
+      // let each request carry token
+      // ['X-Token'] is a custom headers key
+      // please modify it according to the actual situation
+      config.headers['X-Token'] = getToken()
+    }
+    return config
+  },
+  error => {
+    // do something with request error
+    console.log(error) // for debug
+    return Promise.reject(error)
+  }
+)
+
+// response interceptor
+service.interceptors.response.use(
+  /**
+   * If you want to get http information such as headers or status
+   * Please return  response => response
+  */
+
+  /**
+   * Determine the request status by custom code
+   * Here is just an example
+   * You can also judge the status by HTTP Status Code
+   */
+  response => {
+    const res = response.data
+
+    // if the custom code is not 20000, it is judged as an error.
+    if (res.code !== 20000) {
+      Message({
+        message: res.message || 'Error',
+        type: 'error',
+        duration: 5 * 1000
+      })
+
+      // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
+      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
+        // to re-login
+        MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
+          confirmButtonText: 'Re-Login',
+          cancelButtonText: 'Cancel',
+          type: 'warning'
+        }).then(() => {
+          store.dispatch('user/resetToken').then(() => {
+            location.reload()
+          })
+        })
+      }
+      return Promise.reject(new Error(res.message || 'Error'))
+    } else {
+      return res
+    }
+  },
+  error => {
+    console.log('err' + error) // for debug
+    Message({
+      message: error.message,
+      type: 'error',
+      duration: 5 * 1000
+    })
+    return Promise.reject(error)
+  }
+)
+
+export default service

+ 20 - 0
src/utils/validate.js

@@ -0,0 +1,20 @@
+/**
+ * Created by PanJiaChen on 16/11/18.
+ */
+
+/**
+ * @param {string} path
+ * @returns {Boolean}
+ */
+export function isExternal(path) {
+  return /^(https?:|mailto:|tel:)/.test(path)
+}
+
+/**
+ * @param {string} str
+ * @returns {Boolean}
+ */
+export function validUsername(str) {
+  const valid_map = ['admin', 'editor']
+  return valid_map.indexOf(str.trim()) >= 0
+}

+ 228 - 0
src/views/404.vue

@@ -0,0 +1,228 @@
+<template>
+  <div class="wscn-http404-container">
+    <div class="wscn-http404">
+      <div class="pic-404">
+        <img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404">
+        <img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404">
+        <img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404">
+        <img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404">
+      </div>
+      <div class="bullshit">
+        <div class="bullshit__oops">OOPS!</div>
+        <div class="bullshit__info">All rights reserved
+          <a style="color:#20a0ff" href="https://wallstreetcn.com" target="_blank">wallstreetcn</a>
+        </div>
+        <div class="bullshit__headline">{{ message }}</div>
+        <div class="bullshit__info">Please check that the URL you entered is correct, or click the button below to return to the homepage.</div>
+        <a href="" class="bullshit__return-home">Back to home</a>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+
+export default {
+  name: 'Page404',
+  computed: {
+    message() {
+      return 'The webmaster said that you can not enter this page...'
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.wscn-http404-container{
+  transform: translate(-50%,-50%);
+  position: absolute;
+  top: 40%;
+  left: 50%;
+}
+.wscn-http404 {
+  position: relative;
+  width: 1200px;
+  padding: 0 50px;
+  overflow: hidden;
+  .pic-404 {
+    position: relative;
+    float: left;
+    width: 600px;
+    overflow: hidden;
+    &__parent {
+      width: 100%;
+    }
+    &__child {
+      position: absolute;
+      &.left {
+        width: 80px;
+        top: 17px;
+        left: 220px;
+        opacity: 0;
+        animation-name: cloudLeft;
+        animation-duration: 2s;
+        animation-timing-function: linear;
+        animation-fill-mode: forwards;
+        animation-delay: 1s;
+      }
+      &.mid {
+        width: 46px;
+        top: 10px;
+        left: 420px;
+        opacity: 0;
+        animation-name: cloudMid;
+        animation-duration: 2s;
+        animation-timing-function: linear;
+        animation-fill-mode: forwards;
+        animation-delay: 1.2s;
+      }
+      &.right {
+        width: 62px;
+        top: 100px;
+        left: 500px;
+        opacity: 0;
+        animation-name: cloudRight;
+        animation-duration: 2s;
+        animation-timing-function: linear;
+        animation-fill-mode: forwards;
+        animation-delay: 1s;
+      }
+      @keyframes cloudLeft {
+        0% {
+          top: 17px;
+          left: 220px;
+          opacity: 0;
+        }
+        20% {
+          top: 33px;
+          left: 188px;
+          opacity: 1;
+        }
+        80% {
+          top: 81px;
+          left: 92px;
+          opacity: 1;
+        }
+        100% {
+          top: 97px;
+          left: 60px;
+          opacity: 0;
+        }
+      }
+      @keyframes cloudMid {
+        0% {
+          top: 10px;
+          left: 420px;
+          opacity: 0;
+        }
+        20% {
+          top: 40px;
+          left: 360px;
+          opacity: 1;
+        }
+        70% {
+          top: 130px;
+          left: 180px;
+          opacity: 1;
+        }
+        100% {
+          top: 160px;
+          left: 120px;
+          opacity: 0;
+        }
+      }
+      @keyframes cloudRight {
+        0% {
+          top: 100px;
+          left: 500px;
+          opacity: 0;
+        }
+        20% {
+          top: 120px;
+          left: 460px;
+          opacity: 1;
+        }
+        80% {
+          top: 180px;
+          left: 340px;
+          opacity: 1;
+        }
+        100% {
+          top: 200px;
+          left: 300px;
+          opacity: 0;
+        }
+      }
+    }
+  }
+  .bullshit {
+    position: relative;
+    float: left;
+    width: 300px;
+    padding: 30px 0;
+    overflow: hidden;
+    &__oops {
+      font-size: 32px;
+      font-weight: bold;
+      line-height: 40px;
+      color: #1482f0;
+      opacity: 0;
+      margin-bottom: 20px;
+      animation-name: slideUp;
+      animation-duration: 0.5s;
+      animation-fill-mode: forwards;
+    }
+    &__headline {
+      font-size: 20px;
+      line-height: 24px;
+      color: #222;
+      font-weight: bold;
+      opacity: 0;
+      margin-bottom: 10px;
+      animation-name: slideUp;
+      animation-duration: 0.5s;
+      animation-delay: 0.1s;
+      animation-fill-mode: forwards;
+    }
+    &__info {
+      font-size: 13px;
+      line-height: 21px;
+      color: grey;
+      opacity: 0;
+      margin-bottom: 30px;
+      animation-name: slideUp;
+      animation-duration: 0.5s;
+      animation-delay: 0.2s;
+      animation-fill-mode: forwards;
+    }
+    &__return-home {
+      display: block;
+      float: left;
+      width: 110px;
+      height: 36px;
+      background: #1482f0;
+      border-radius: 100px;
+      text-align: center;
+      color: #ffffff;
+      opacity: 0;
+      font-size: 14px;
+      line-height: 36px;
+      cursor: pointer;
+      animation-name: slideUp;
+      animation-duration: 0.5s;
+      animation-delay: 0.3s;
+      animation-fill-mode: forwards;
+    }
+    @keyframes slideUp {
+      0% {
+        transform: translateY(60px);
+        opacity: 0;
+      }
+      100% {
+        transform: translateY(0);
+        opacity: 1;
+      }
+    }
+  }
+}
+</style>

+ 30 - 0
src/views/alarmManage/index.vue

@@ -0,0 +1,30 @@
+<template>
+  <div class="dashboard-container">
+    <div class="dashboard-text">告警管理</div>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+
+export default {
+  name: 'Dashboard',
+  computed: {
+    ...mapGetters([
+      'name'
+    ])
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.dashboard {
+  &-container {
+    margin: 30px;
+  }
+  &-text {
+    font-size: 30px;
+    line-height: 46px;
+  }
+}
+</style>

+ 30 - 0
src/views/dashboard/index.vue

@@ -0,0 +1,30 @@
+<template>
+  <div class="dashboard-container">
+    <div class="dashboard-text">name: {{ name }}</div>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+
+export default {
+  name: 'Dashboard',
+  computed: {
+    ...mapGetters([
+      'name'
+    ])
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.dashboard {
+  &-container {
+    margin: 30px;
+  }
+  &-text {
+    font-size: 30px;
+    line-height: 46px;
+  }
+}
+</style>

+ 5 - 0
src/views/dataManage/chainAnalysis/index.vue

@@ -0,0 +1,5 @@
+<template>
+  <div style="padding:30px;">
+   环比分析报表
+  </div>
+</template>

+ 5 - 0
src/views/dataManage/consumConfig/index.vue

@@ -0,0 +1,5 @@
+<template>
+  <div style="padding:30px;">
+   能耗分析配置
+  </div>
+</template>

+ 5 - 0
src/views/dataManage/demandAnalysis/index.vue

@@ -0,0 +1,5 @@
+<template>
+  <div style="padding:30px;">
+   需量分析
+  </div>
+</template>

+ 5 - 0
src/views/dataManage/energyReport/index.vue

@@ -0,0 +1,5 @@
+<template>
+  <div style="padding:30px;">
+   用能月报
+  </div>
+</template>

+ 5 - 0
src/views/dataManage/handOpera/index.vue

@@ -0,0 +1,5 @@
+<template>
+  <div style="padding:30px;">
+   手动抄表
+  </div>
+</template>

+ 5 - 0
src/views/dataManage/sameAnalysis/index.vue

@@ -0,0 +1,5 @@
+<template>
+  <div style="padding:30px;">
+   同比分析报表
+  </div>
+</template>

+ 5 - 0
src/views/deviceManage/attribTemplate/index.vue

@@ -0,0 +1,5 @@
+<template>
+  <div style="padding:30px;">
+   属性模板
+  </div>
+</template>

+ 5 - 0
src/views/deviceManage/channelList/index.vue

@@ -0,0 +1,5 @@
+<template>
+  <div style="padding:30px;">
+   通道列表
+  </div>
+</template>

+ 5 - 0
src/views/deviceManage/communicateEquip/index.vue

@@ -0,0 +1,5 @@
+<template>
+  <div style="padding:30px;">
+   通信设备
+  </div>
+</template>

+ 5 - 0
src/views/deviceManage/powerEquip/index.vue

@@ -0,0 +1,5 @@
+<template>
+  <div style="padding:30px;">
+   电力监测设备
+  </div>
+</template>

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio