Browse Source

Signed-off-by: zhaobao <528046418@qq.com>

zhaobao 2 năm trước cách đây
commit
e57c45be86
100 tập tin đã thay đổi với 5601 bổ sung0 xóa
  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. 45 0
      README-zh.md
  11. 14 0
      babel.config.js
  12. 35 0
      build/index.js
  13. 0 0
      c
  14. 24 0
      jest.config.js
  15. 9 0
      jsconfig.json
  16. 57 0
      mock/index.js
  17. 81 0
      mock/mock-server.js
  18. 29 0
      mock/table.js
  19. 84 0
      mock/user.js
  20. 25 0
      mock/utils.js
  21. 72 0
      package.json
  22. 8 0
      postcss.config.js
  23. BIN
      public/favicon.ico
  24. 17 0
      public/index.html
  25. 11 0
      src/App.vue
  26. 19 0
      src/api/alarm-statistics.js
  27. 15 0
      src/api/equipment-monitoring.js
  28. 9 0
      src/api/table.js
  29. 24 0
      src/api/user.js
  30. BIN
      src/assets/404_images/404.png
  31. BIN
      src/assets/404_images/404_cloud.png
  32. BIN
      src/assets/images/fireWater.png
  33. BIN
      src/assets/images/fireWater_active.png
  34. BIN
      src/assets/images/gasMonitor.png
  35. BIN
      src/assets/images/gasMonitor_active.png
  36. BIN
      src/assets/images/nav1.png
  37. BIN
      src/assets/images/nav1_active.png
  38. BIN
      src/assets/images/nav3.png
  39. BIN
      src/assets/images/nav3_active.png
  40. BIN
      src/assets/images/nav5.png
  41. BIN
      src/assets/images/nav5_active.png
  42. BIN
      src/assets/images/no-data.png
  43. BIN
      src/assets/images/nocamera.jpg
  44. BIN
      src/assets/images/title_border_line.png
  45. BIN
      src/assets/images/top_btn_bg.png
  46. BIN
      src/assets/images/top_btn_bg_active.png
  47. BIN
      src/assets/images/unit_bg_all.jpg
  48. BIN
      src/assets/images/unit_tibg2.png
  49. BIN
      src/assets/images/unit_top_bg.png
  50. BIN
      src/assets/images/videoAI.png
  51. BIN
      src/assets/images/videoAI_active.png
  52. 78 0
      src/components/Breadcrumb/index.vue
  53. 44 0
      src/components/Hamburger/index.vue
  54. 62 0
      src/components/SvgIcon/index.vue
  55. 9 0
      src/icons/index.js
  56. 0 0
      src/icons/svg/dashboard.svg
  57. 1 0
      src/icons/svg/example.svg
  58. 1 0
      src/icons/svg/eye-open.svg
  59. 1 0
      src/icons/svg/eye.svg
  60. 0 0
      src/icons/svg/form.svg
  61. 1 0
      src/icons/svg/link.svg
  62. 1 0
      src/icons/svg/nested.svg
  63. 1 0
      src/icons/svg/password.svg
  64. 1 0
      src/icons/svg/table.svg
  65. 1 0
      src/icons/svg/tree.svg
  66. 1 0
      src/icons/svg/user.svg
  67. 22 0
      src/icons/svgo.yml
  68. 1084 0
      src/json/d.json
  69. 41 0
      src/layout/components/AppMain.vue
  70. 119 0
      src/layout/components/AppTop.vue
  71. 2 0
      src/layout/components/index.js
  72. 110 0
      src/layout/index.vue
  73. 45 0
      src/layout/mixin/ResizeHandler.js
  74. 53 0
      src/main.js
  75. 17 0
      src/permission.js
  76. 116 0
      src/router/index.js
  77. 16 0
      src/settings.js
  78. 19 0
      src/store/getters.js
  79. 22 0
      src/store/index.js
  80. 86 0
      src/store/modules/alarmStatistics.js
  81. 48 0
      src/store/modules/app.js
  82. 458 0
      src/store/modules/equipmentMonitoring.js
  83. 32 0
      src/store/modules/settings.js
  84. 49 0
      src/styles/element-ui.scss
  85. 65 0
      src/styles/index.scss
  86. 28 0
      src/styles/mixin.scss
  87. 226 0
      src/styles/sidebar.scss
  88. 48 0
      src/styles/transition.scss
  89. 25 0
      src/styles/variables.scss
  90. 49 0
      src/utils/auth.js
  91. 10 0
      src/utils/get-page-title.js
  92. 12 0
      src/utils/global-config.js
  93. 117 0
      src/utils/index.js
  94. 73 0
      src/utils/request.js
  95. 20 0
      src/utils/validate.js
  96. 99 0
      src/utils/ws.js
  97. 228 0
      src/views/404.vue
  98. 619 0
      src/views/alarm-statistics/components/DataChart.vue
  99. 303 0
      src/views/alarm-statistics/components/DataList.vue
  100. 283 0
      src/views/alarm-statistics/components/LeftStatistics.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 = '/'
+

+ 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.

+ 45 - 0
README-zh.md

@@ -0,0 +1,45 @@
+## Build Setup
+
+```bash
+# 安装依赖
+npm install
+
+# 建议不要直接使用 cnpm 安装以来,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
+npm install --registry=https://registry.npm.taobao.org
+
+# 启动服务
+npm run dev
+```
+
+浏览器访问 [http://localhost:9528](http://localhost:9528?gId=186884525522945&cId=186884525522945)
+
+## 发布
+
+```bash
+# 构建测试环境
+npm run build:stage
+
+# 构建生产环境
+npm run build:prod
+```
+
+## 其它
+
+```bash
+# 预览发布环境效果
+npm run preview
+
+# 预览发布环境效果 + 静态资源分析
+npm run preview -- --report
+
+# 代码格式检查
+npm run lint
+
+# 代码格式检查并自动修复
+npm run lint -- --fix
+
+ps
+
+项目下载依赖失败->检查网络判断是否丢包【无效】->cnpm下载【无效】->http://39.99.81.6:3000/zhaobao/document下载zhihuixiaofang文件下面的备用依赖,【无效】->执行cnpm/yarn/npm install
+-->依然无效-》重构去吧!
+```

+ 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','transform-object-rest-spread']
+    }
+  }
+}

+ 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}`)
+}

+ 0 - 0
c


+ 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
+}

+ 72 - 0
package.json

@@ -0,0 +1,72 @@
+{
+  "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": {
+    "@vue/composition-api": "^1.0.0-rc.10",
+    "axios": "0.18.1",
+    "core-js": "3.6.5",
+    "echarts": "^5.1.1",
+    "echarts-liquidfill": "^3.0.0",
+    "element-ui": "2.13.2",
+    "happy-utils": "^1.4.0",
+    "jquery": "^3.6.0",
+    "js-cookie": "2.2.0",
+    "normalize.css": "7.0.0",
+    "nprogress": "0.2.0",
+    "path-to-regexp": "2.4.0",
+    "swiper": "^5.2.0",
+    "vue": "2.6.10",
+    "vue-awesome-swiper": "^4.1.1",
+    "vue-echarts": "^6.0.0-rc.5",
+    "vue-router": "3.0.6",
+    "vuescroll": "^4.17.3",
+    "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",
+    "babel-plugin-transform-object-rest-spread": "^6.26.0",
+    "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
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>我们很抱歉但是<%= webpackConfig.name %> 如果不启用 JavaScript,则无法正常工作。请启用它以继续.</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>

+ 19 - 0
src/api/alarm-statistics.js

@@ -0,0 +1,19 @@
+import request from '@/utils/request'
+// import data from '@/json/d.json'
+
+/**
+ * 获取推送数据
+ * @param params
+ * @returns {AxiosPromise}
+ */
+export function getAlarmStatisticsData(params) {
+    // data.data.hash = new Date().getTime()
+    // return Promise.resolve(data);
+    return request({
+        url: '/3rd/fire/alarmAnalysis',
+        method: 'get',
+        params
+    })
+}
+
+

+ 15 - 0
src/api/equipment-monitoring.js

@@ -0,0 +1,15 @@
+import request from '@/utils/request'
+
+/**
+ * 获取推送数据
+ * @param params
+ * @returns {AxiosPromise}
+ */
+export function getEquipmentMonitoringData(params) {
+  return request({
+    url: '/3rd/bigScreenData/bigScreenMessage',
+    method: 'get',
+    params
+  })
+}
+

+ 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
src/assets/404_images/404.png


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


BIN
src/assets/images/fireWater.png


BIN
src/assets/images/fireWater_active.png


BIN
src/assets/images/gasMonitor.png


BIN
src/assets/images/gasMonitor_active.png


BIN
src/assets/images/nav1.png


BIN
src/assets/images/nav1_active.png


BIN
src/assets/images/nav3.png


BIN
src/assets/images/nav3_active.png


BIN
src/assets/images/nav5.png


BIN
src/assets/images/nav5_active.png


BIN
src/assets/images/no-data.png


BIN
src/assets/images/nocamera.jpg


BIN
src/assets/images/title_border_line.png


BIN
src/assets/images/top_btn_bg.png


BIN
src/assets/images/top_btn_bg_active.png


BIN
src/assets/images/unit_bg_all.jpg


BIN
src/assets/images/unit_tibg2.png


BIN
src/assets/images/unit_top_bg.png


BIN
src/assets/images/videoAI.png


BIN
src/assets/images/videoAI_active.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)

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
src/icons/svg/dashboard.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>

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 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>

+ 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>

+ 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>

+ 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>

+ 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'

+ 1084 - 0
src/json/d.json

@@ -0,0 +1,1084 @@
+{
+  "code": 1,
+  "data": {
+    "zhfx": "sadasd",
+    "alarmCountByDateAndLevel": [
+      {
+        "alarmCount": 2,
+        "alarmLevel": "3",
+        "alarmDate": "2021-06-01"
+      },
+      {
+        "alarmCount": 1,
+        "alarmLevel": "3",
+        "alarmDate": "2021-06-02"
+      },
+      {
+        "alarmCount": 4,
+        "alarmLevel": "3",
+        "alarmDate": "2021-06-03"
+      },
+      {
+        "alarmCount": 3,
+        "alarmLevel": "3",
+        "alarmDate": "2021-06-04"
+      },
+      {
+        "alarmCount": 1,
+        "alarmLevel": "3",
+        "alarmDate": "2021-06-05"
+      },
+      {
+        "alarmCount": 10,
+        "alarmLevel": "3",
+        "alarmDate": "2021-06-06"
+      },
+      {
+        "alarmCount": 25,
+        "alarmLevel": "3",
+        "alarmDate": "2021-06-07"
+      }
+    ],
+    "alarmCountByType": [
+      {
+        "typeName": "水压超上限",
+        "alarmCount": 4,
+        "typeId": "11"
+      },
+      {
+        "typeName": "消控主机",
+        "alarmCount": 40,
+        "typeId": "6"
+      }
+    ],
+    "alarmReasonType": [
+      {
+        "label": "用火不慎",
+        "value": "1"
+      },
+      {
+        "label": "电气",
+        "value": "2"
+      },
+      {
+        "label": "吸烟",
+        "value": "3"
+      },
+      {
+        "label": "玩火",
+        "value": "4"
+      },
+      {
+        "label": "自燃",
+        "value": "5"
+      },
+      {
+        "label": "放火",
+        "value": "6"
+      },
+      {
+        "label": "生产作业",
+        "value": "7"
+      },
+      {
+        "label": "其他",
+        "value": "8"
+      }
+    ],
+    "alarmCountByNomalAreas": [
+      {
+        "alarmCount": 2,
+        "zoneName": "5号门诊楼2层"
+      },
+      {
+        "alarmCount": 0,
+        "zoneName": "5号门诊楼3层"
+      },
+      {
+        "alarmCount": 1,
+        "zoneName": "5号门诊楼4层"
+      },
+      {
+        "alarmCount": 4,
+        "zoneName": "5号门诊楼5层"
+      },
+      {
+        "alarmCount": 34,
+        "zoneName": "5号门诊楼1层"
+      }
+    ],
+    "zhfx": "本月为夏天,15点是火警的高发阶段,本月是最主要原因(13次报警)。需重点关注。",
+    "alarmCountByLevel": [
+      {
+        "alarmCount": 45,
+        "alarmLevel": "3"
+      }
+    ],
+    "alarmCountByHour": [
+      {
+        "alarmCount": 3,
+        "alarmHour": "07"
+      },
+      {
+        "alarmCount": 2,
+        "alarmHour": "08"
+      },
+      {
+        "alarmCount": 3,
+        "alarmHour": "09"
+      },
+      {
+        "alarmCount": 4,
+        "alarmHour": "10"
+      },
+      {
+        "alarmCount": 12,
+        "alarmHour": "14"
+      },
+      {
+        "alarmCount": 13,
+        "alarmHour": "15"
+      },
+      {
+        "alarmCount": 1,
+        "alarmHour": "16"
+      },
+      {
+        "alarmCount": 2,
+        "alarmHour": "17"
+      },
+      {
+        "alarmCount": 5,
+        "alarmHour": "20"
+      },
+      {
+        "alarmCount": 1,
+        "alarmHour": "21"
+      }
+    ],
+    "fireData": [
+      {
+        "alarmContent": "水压超上限报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1622552058949,
+        "alarmTime": 1622551980000,
+        "alarmTypeName": "水压超上限",
+        "deviceCode": "202102766630",
+        "sendCount": 0,
+        "deviceId": "2c911eb7776200b60177625c71340008",
+        "orgId": 523,
+        "alarmType": "11",
+        "handleState": 0,
+        "deviceChildCode": "0",
+        "rawId": 2745,
+        "zoneId": "2c911eb7773c3ee001775c07f5a80018",
+        "devicename": "检验科喷淋末端",
+        "alarmLevel": "3",
+        "id": "375ffc97aaad4f05acc291b52de0d65a",
+        "zoneName": "5号门诊楼5层"
+      },
+      {
+        "alarmContent": "5层0507病点型感烟(050064)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1622552509856,
+        "alarmTime": 1622552508000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "050064",
+        "rawId": 2765,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "6320e7865c0346a8b9d63e357448022c",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "门1妇科走廊手动按钮(301020)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1622625832008,
+        "alarmTime": 1622625820000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "301020",
+        "rawId": 5396,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "e6566a5576be47b4950c3269d25fb2ea",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "2层214病房点型感烟(020045)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1622682098823,
+        "alarmTime": 1622682087000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "020045",
+        "rawId": 7195,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "1390874e7d504cdf853f915e5d07b93b",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "门2理疗科点型感烟(302010)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1622687935458,
+        "alarmTime": 1622687923000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "302010",
+        "rawId": 7390,
+        "zoneId": "2c911eb7773c3ee001775c07517a0015",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "002fb2681af3484a8465921d4d7be721",
+        "zoneName": "5号门诊楼2层"
+      },
+      {
+        "alarmContent": "门2针灸科1点型感烟(302002)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1622701275989,
+        "alarmTime": 1622683737000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "302002",
+        "rawId": 7253,
+        "zoneId": "2c911eb7773c3ee001775c07517a0015",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "db3dc5fcb4d0448384b94bda64229c18",
+        "zoneName": "5号门诊楼2层"
+      },
+      {
+        "alarmContent": "水压超上限报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1622724848997,
+        "alarmTime": 1622724780000,
+        "alarmTypeName": "水压超上限",
+        "deviceCode": "202102766630",
+        "sendCount": 0,
+        "deviceId": "2c911eb7776200b60177625c71340008",
+        "orgId": 523,
+        "alarmType": "11",
+        "handleState": 0,
+        "deviceChildCode": "0",
+        "rawId": 8633,
+        "zoneId": "2c911eb7773c3ee001775c07f5a80018",
+        "devicename": "检验科喷淋末端",
+        "alarmLevel": "3",
+        "id": "7b527a8bdd144edf95613038cd852b80",
+        "zoneName": "5号门诊楼5层"
+      },
+      {
+        "alarmContent": "门1急诊7点型感烟(301089)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1622767086864,
+        "alarmTime": 1622767075000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "301089",
+        "rawId": 10210,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "a720a0fcccfb4b6bb1d806f7b3c6c8b5",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "门2针灸科1点型感烟(302002)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1622770265552,
+        "alarmTime": 1622770254000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "302002",
+        "rawId": 10335,
+        "zoneId": "2c911eb7773c3ee001775c07517a0015",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "1c4f853c19e9490ab922943594f92c00",
+        "zoneName": "5号门诊楼2层"
+      },
+      {
+        "alarmContent": "水压超上限报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1622811238385,
+        "alarmTime": 1622811180000,
+        "alarmTypeName": "水压超上限",
+        "deviceCode": "202102766630",
+        "sendCount": 0,
+        "deviceId": "2c911eb7776200b60177625c71340008",
+        "orgId": 523,
+        "alarmType": "11",
+        "handleState": 0,
+        "deviceChildCode": "0",
+        "rawId": 11983,
+        "zoneId": "2c911eb7773c3ee001775c07f5a80018",
+        "devicename": "检验科喷淋末端",
+        "alarmLevel": "3",
+        "id": "895b6b8e85464d35bf5647766b5bbe3e",
+        "zoneName": "5号门诊楼5层"
+      },
+      {
+        "alarmContent": "水压超上限报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1622897638962,
+        "alarmTime": 1622897520000,
+        "alarmTypeName": "水压超上限",
+        "deviceCode": "202102766630",
+        "sendCount": 0,
+        "deviceId": "2c911eb7776200b60177625c71340008",
+        "orgId": 523,
+        "alarmType": "11",
+        "handleState": 0,
+        "deviceChildCode": "0",
+        "rawId": 16222,
+        "zoneId": "2c911eb7773c3ee001775c07f5a80018",
+        "devicename": "检验科喷淋末端",
+        "alarmLevel": "3",
+        "id": "b6946e35f8eb4c1489f4beb976004d62",
+        "zoneName": "5号门诊楼5层"
+      },
+      {
+        "alarmContent": "门1中间通道点型感烟(301067)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1622937482763,
+        "alarmTime": 1622937471000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "301067",
+        "rawId": 18124,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "80ebd8d6ebe945d397744f08837c5c27",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "门1中间通道点型感烟(301067)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1622937498768,
+        "alarmTime": 1622937497000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "301067",
+        "rawId": 18128,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "b727d19f37b0445096d1d897108b8fac",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "门1中间通道点型感烟(301067)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1622937545923,
+        "alarmTime": 1622937544000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "301067",
+        "rawId": 18136,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "b102e90338834173a6a4d5c2cb81c150",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "门1中间通道点型感烟(301067)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1622937791608,
+        "alarmTime": 1622937780000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "301067",
+        "rawId": 18155,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "b6b7c371d98648a88d7621068541c705",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "门三肿瘤9病房点型感烟(303101)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1622941839142,
+        "alarmTime": 1622941827000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "303101",
+        "rawId": 18360,
+        "zoneId": "2c911eb7773c3ee001775c0783c40016",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "58ce10c602b7448cae6abac42705fee9",
+        "zoneName": "5号门诊楼3层"
+      },
+      {
+        "alarmContent": "门4肿瘤科走廊点型感烟(304011)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1622944848688,
+        "alarmTime": 1622944837000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "304011",
+        "rawId": 18519,
+        "zoneId": "2c911eb7773c3ee001775c07bb410017",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "12a309d9d26142beac370717db6efe5f",
+        "zoneName": "5号门诊楼4层"
+      },
+      {
+        "alarmContent": "门诊4层肿瘤科点型感烟(304021)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1622945043943,
+        "alarmTime": 1622945032000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "304021",
+        "rawId": 18532,
+        "zoneId": "2c911eb7773c3ee001775c07bb410017",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "70855a2fc6de4e219b03840ca5099810",
+        "zoneName": "5号门诊楼4层"
+      },
+      {
+        "alarmContent": "门1中间通道点型感烟(301067)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1622947682387,
+        "alarmTime": 1622947671000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "301067",
+        "rawId": 18675,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "29cc3d46ebf94c3eaf7bd8f53a33ff84",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "门1中间通道点型感烟(301067)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1622973595996,
+        "alarmTime": 1622973584000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "301067",
+        "rawId": 20071,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "692cab7bc7e74c3c859dad36d38916e2",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "水压超上限报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1622984028421,
+        "alarmTime": 1622983920000,
+        "alarmTypeName": "水压超上限",
+        "deviceCode": "202102766630",
+        "sendCount": 0,
+        "deviceId": "2c911eb7776200b60177625c71340008",
+        "orgId": 523,
+        "alarmType": "11",
+        "handleState": 0,
+        "deviceChildCode": "0",
+        "rawId": 20616,
+        "zoneId": "2c911eb7773c3ee001775c07f5a80018",
+        "devicename": "检验科喷淋末端",
+        "alarmLevel": "3",
+        "id": "f6e71e3223314ac5a30ca72cc96bc9ab",
+        "zoneName": "5号门诊楼5层"
+      },
+      {
+        "alarmContent": "门1急诊7点型感烟(301089)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623048185017,
+        "alarmTime": 1623048194000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "301089",
+        "rawId": 23791,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "9d88cf0ea57a4681b9abd79496a6da49",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "地下室西配电东点型感烟(990215)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623048185787,
+        "alarmTime": 1623048194000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "990215",
+        "rawId": 23793,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "24e306849f8f47bd824820bd30e69313",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "12层东电梯间点型感烟(120140)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623048682108,
+        "alarmTime": 1623048680000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "120140",
+        "rawId": 23829,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "55970f45bbea406e8489e750d9a355a7",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "11层东走廊手动按钮(110091)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623048764280,
+        "alarmTime": 1623048752000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "110091",
+        "rawId": 23840,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "53766948075c4c8f8a2a70c7cab19a54",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "11层东货梯间点型感烟(110090)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623048784060,
+        "alarmTime": 1623048782000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "110090",
+        "rawId": 23845,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "39cd54daab5f4185a5eabd6887452cd3",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "10层点型感烟(100001)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623049033942,
+        "alarmTime": 1623049022000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "100001",
+        "rawId": 23861,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "93c57e2ecf3c4c3798cafd0848484869",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "10层东走廊手动按钮(100041)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623049035627,
+        "alarmTime": 1623049032000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "100041",
+        "rawId": 23863,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "3e5f6acf01e14a78a8110376e84f9bc9",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "9层东走廊点型感烟(090136)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623049131992,
+        "alarmTime": 1623049120000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "090136",
+        "rawId": 23867,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "8ff02f71f0594abd857e82666808809c",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "9层东走廊手动按钮(090141)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623049134193,
+        "alarmTime": 1623049131000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "090141",
+        "rawId": 23870,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "bf9b3f09a073478db105083861fba43b",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "8层东走廊点型感烟(080092)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623049188212,
+        "alarmTime": 1623049176000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "080092",
+        "rawId": 23874,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "3f711d2805a34a5b8f3e4fda559b7051",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "8层东走廊手动按钮(080084)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623049191141,
+        "alarmTime": 1623049189000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "080084",
+        "rawId": 23877,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "68ba38001b214922857c42de4ad25706",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "7层东走廊点型感烟(070042)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623049246141,
+        "alarmTime": 1623049234000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "070042",
+        "rawId": 23881,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "d22f5ebbb4dd45a387e0c1f8576bfc73",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "7层东走廊手动按钮(070041)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623049247570,
+        "alarmTime": 1623049244000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "070041",
+        "rawId": 23883,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "639efd538e03426592bf798cd9b7f93a",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "6层东走廊手动按钮(060141)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623049294133,
+        "alarmTime": 1623049292000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "060141",
+        "rawId": 23890,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "bc81e7c207b643bea94cd324f8131fd7",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "5层货梯点型感烟(050090)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623049361249,
+        "alarmTime": 1623049349000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "050090",
+        "rawId": 23895,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "96cb962500014eb8857e3553355f50a0",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "5层东走廊手动按钮(050091)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623049361784,
+        "alarmTime": 1623049359000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "050091",
+        "rawId": 23897,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "f08594ab605a4b0ab0a45a2d03d773a5",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "4层东电梯间点型感烟(040040)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623049444047,
+        "alarmTime": 1623049432000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "040040",
+        "rawId": 23904,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "fcee5555e8bc4b7cb8b135a473368ebf",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "4层东走廊东手动按钮(040041)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623049444726,
+        "alarmTime": 1623049442000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "040041",
+        "rawId": 23906,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "3c1a4eb990f04fc4904dac6ec86be5c1",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "2层货梯前室点型感烟(020077)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623049563928,
+        "alarmTime": 1623049552000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "020077",
+        "rawId": 23910,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "3cb7ec64021f4c64b4ab8cbc2d59c601",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "2层东走廊手动按钮(020079)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623049564823,
+        "alarmTime": 1623049562000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "020079",
+        "rawId": 23912,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "456f573ce3b84106ab0acac3207aea36",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "地下室西配电东点型感烟(990215)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623050123817,
+        "alarmTime": 1623050112000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "990215",
+        "rawId": 23947,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "380bc7c055214a518933265d84de1506",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "门1中间通道点型感烟(301067)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623050517738,
+        "alarmTime": 1623050506000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "301067",
+        "rawId": 23969,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "f0b23a8d7ed04beb98cf3594d5dde916",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "门1中间通道点型感烟(301067)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623050834842,
+        "alarmTime": 1623050823000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "301067",
+        "rawId": 23995,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "9ab846f021044dd9bd9733e860d4b6bb",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "地下室西配电东点型感烟(990215)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623051536738,
+        "alarmTime": 1623051525000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "990215",
+        "rawId": 24029,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "156dce7e6ae64a0d8211d6914bea9711",
+        "zoneName": "5号门诊楼1层"
+      },
+      {
+        "alarmContent": "点(510003)报警",
+        "orgName": "郑州市中医院",
+        "addTime": 1623056022101,
+        "alarmTime": 1623056010000,
+        "alarmTypeName": "消控主机",
+        "deviceCode": "202103710003",
+        "sendCount": 0,
+        "deviceId": "2c911eb7775c5389017760c1044c001d",
+        "orgId": 523,
+        "alarmType": "6",
+        "handleState": 0,
+        "deviceChildCode": "510003",
+        "rawId": 24253,
+        "zoneId": "2c911eb7773c3ee001775c070ccd0014",
+        "devicename": "用户传输装置",
+        "alarmLevel": "3",
+        "id": "215309d691f44c59a33c39aef9cef662",
+        "zoneName": "5号门诊楼1层"
+      }
+    ]
+  },
+  "msg": "操作成功",
+  "url": ""
+}

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

@@ -0,0 +1,41 @@
+<template>
+  <section class="app-main">
+<!--    <transition name="fade-transform" mode="out-in">-->
+<!--      <router-view :key="key" />-->
+      <router-view />
+<!--    </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 - 84px);
+  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>

+ 119 - 0
src/layout/components/AppTop.vue

@@ -0,0 +1,119 @@
+<template>
+  <div class="app-top">
+    <div class="top-left">
+      <div class="menus-wrap">
+        <div class="menus-item" :class='index == activeMenuIndex ? "active" : ""'
+             v-for="(item,index) in menusList" :key="item.name"
+              @click="navToMenu(index,item.path)">
+          {{item.name}}
+        </div>
+      </div>
+    </div>
+    <div class="top-center">
+      <h1>{{title}}</h1>
+    </div>
+    <div class="top-right"></div>
+  </div>
+</template>
+
+<script>
+  import {getTopActiveMenuIndex,setTopActiveMenuIndex,setLeftActiveMenuIndex} from '@/utils/auth'
+export default {
+  name: 'AppTop',
+  data(){
+    return {
+      // title: '郑州市中医院',
+      title: '智慧消防',
+      // title: '广仁寺',
+      menusList: [
+        {
+          name: '设备监控',
+          path: '/'
+        },
+        {
+          name: '报警统计',
+          path: '/alarm-statistics'
+        }
+      ],
+      activeMenuIndex: getTopActiveMenuIndex() ? getTopActiveMenuIndex() : 0
+    }
+  },
+  computed: {
+    key() {
+      return this.$route.path
+    }
+  },
+  mounted() {
+  },
+  methods:{
+    navToMenu(index,path){
+      this.activeMenuIndex = index
+      setTopActiveMenuIndex(index)
+      setLeftActiveMenuIndex(0)
+      this.$router.push({
+        path: path
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+</style>
+
+<style lang="scss">
+  .app-top{
+    height: 84px;
+    background: #f90;
+    width: 100%;
+    background: url("../../assets/images/unit_top_bg.png");
+    color: #FFF;
+
+    .top-left,.top-right{
+      z-index: 999;
+      position: relative;
+    }
+
+    .top-left{
+      width: 50%;
+      float: left;
+      .menus-wrap{
+        overflow: hidden;
+        margin: 26px 0 0 60px;
+
+        .menus-item{
+          float: left;
+          width: 130px;
+          height: 31px;
+          line-height: 31px;
+          background: url("../../assets/images/top_btn_bg.png");
+          text-align: center;
+          color: #24e3ff;
+        }
+
+        .menus-item:hover,.active{
+          background: url("../../assets/images/top_btn_bg_active.png");
+          color: #fff;
+        }
+      }
+
+    }
+    .top-center{
+      position: absolute;
+      text-align: center;
+      width: 100%;
+
+      line-height: 75px;
+      h1{
+        font-size: 26px;
+        margin: 0;
+      }
+    }
+
+    .top-right{
+      width: 50%;
+      float: right;
+      text-align: right;
+    }
+  }
+</style>

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

@@ -0,0 +1,2 @@
+export { default as AppTop } from './AppTop'
+export { default as AppMain } from './AppMain'

+ 110 - 0
src/layout/index.vue

@@ -0,0 +1,110 @@
+<template>
+    <div :class="classObj" class="app-wrapper">
+        <app-top/>
+        <app-main/>
+    </div>
+</template>
+
+<script>
+    import {AppTop, AppMain} from './components'
+    import ResizeMixin from './mixin/ResizeHandler'
+    import {setReqParam, getReqParam} from '@/utils/auth'
+
+    export default {
+        name: 'Layout',
+        components: {
+            AppTop, AppMain
+        },
+        mixins: [ResizeMixin],
+        computed: {
+            device() {
+                return this.$store.state.app.device
+            },
+            fixedHeader() {
+                return this.$store.state.settings.fixedHeader
+            },
+            classObj() {
+                return {
+                    mobile: this.device === 'mobile'
+                }
+            }
+        },
+        mounted() {
+            if (this.$route.query.cId && this.$route.query.gId) {
+                setReqParam({
+                    cId: this.$route.query.cId,
+                    gId: this.$route.query.gId,
+                })
+            }
+
+            let {cId, gId} = getReqParam()
+            if (cId && gId) {
+                this.initData();
+            } else {
+                setTimeout(() => {
+                    this.$router.go(0)
+                }, 50)
+
+            }
+        },
+        methods:{
+            async initData(){
+                // equipmentMonitoring
+                await this.$store.dispatch("equipmentMonitoring/wsFireAlarmData");
+                await this.$store.dispatch("equipmentMonitoring/wsFireWaterData");
+                await this.$store.dispatch("equipmentMonitoring/wsEleFireData");
+                await this.$store.dispatch("equipmentMonitoring/wsGasMoData");
+                await this.$store.dispatch("equipmentMonitoring/wsAlarmMoData");
+
+                // alarmStatistics
+                await this.$store.dispatch("alarmStatistics/wsAlarmStatistics");
+            },
+        }
+    }
+</script>
+<style>
+    ::-webkit-scrollbar {
+        width : 5px;
+        height: 5px;
+    }
+    ::-webkit-scrollbar-thumb {
+        /*滚动条里面小方块*/
+        /*border-radius: 10px;*/
+        box-shadow   : inset 0 0 5px rgba(0, 0, 0, 0.2);
+        background   : #535353;
+    }
+    ::-webkit-scrollbar-corner{
+        display: none;
+    }
+    ::-webkit-scrollbar-track {
+        /*滚动条里面轨道*/
+        box-shadow   : inset 0 0 5px rgba(0, 0, 0, 0.2);
+        border-radius: 10px;
+        background   : unset;
+    }
+</style>
+<style lang="scss" scoped>
+    @import "~@/styles/mixin.scss";
+    @import "~@/styles/variables.scss";
+
+    .app-wrapper {
+        //@include clearfix;
+        position: relative;
+        height: 100%;
+        width: 100%;
+        background: url("../assets/images/unit_bg_all.jpg");
+        background-size: 100% 100%;
+        overflow: hidden;
+    }
+
+    .drawer-bg {
+        background: #000;
+        opacity: 0.3;
+        width: 100%;
+        top: 0;
+        height: 100%;
+        position: absolute;
+        z-index: 999;
+    }
+
+</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 })
+        }
+      }
+    }
+  }
+}

+ 53 - 0
src/main.js

@@ -0,0 +1,53 @@
+import Vue from 'vue'
+
+import $ from "jquery";
+
+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
+
+Vue.prototype.$ = $;
+
+// 打包环境去除日志
+if(process.env.NODE_ENV === 'production'){
+  console.log = ()=>{};
+}
+
+
+new Vue({
+  el: '#app',
+  router,
+  store,
+  render: h => h(App)
+})

+ 17 - 0
src/permission.js

@@ -0,0 +1,17 @@
+import router from './router'
+import { Message } from 'element-ui'
+import NProgress from 'nprogress' // progress bar
+import 'nprogress/nprogress.css' // progress bar style
+import getPageTitle from '@/utils/get-page-title'
+
+NProgress.configure({ showSpinner: false }) // NProgress Configuration
+
+
+router.beforeEach(async(to, from, next) => {
+  NProgress.start()
+  next()
+})
+
+router.afterEach(() => {
+  NProgress.done()
+})

+ 116 - 0
src/router/index.js

@@ -0,0 +1,116 @@
+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: '/404',
+    component: () => import('@/views/404'),
+    hidden: true
+  },
+  {
+    path: '/',
+    component: Layout,
+    redirect: '/equipment-monitoring',
+    children: [
+      {
+        path: 'equipment-monitoring',
+        name: 'equipment-monitoring',
+        // redirect: '/equipment-monitoring/fire-alarm',
+        redirect: '/equipment-monitoring/alarm-mo',
+        component: () => import('@/views/equipment-monitoring/index'),
+        meta: { title: '设备监控' },
+        children: [
+          {
+            path: 'fire-alarm',
+            name: 'fire-alarm',
+            component: () => import('@/views/equipment-monitoring/fire-alarm/index'),
+            meta: { title: '火灾自动报警系统' }
+          },
+          {
+            path: 'fire-water',
+            name: 'fire-water',
+            component: () => import('@/views/equipment-monitoring/fire-water/index'),
+            meta: { title: '消防给水监测系统' }
+          },
+          {
+            path: 'ele-fire',
+            name: 'ele-fire',
+            component: () => import('@/views/equipment-monitoring/ele-fire/index'),
+            meta: { title: '电气火灾智能监控系统' }
+          },
+          {
+            path: 'gas-mo',
+            name: 'gas-mo',
+            component: () => import('@/views/equipment-monitoring/gas-mo/index'),
+            meta: { title: '可燃气体检测预警系统' }
+          },
+          {
+            path: 'alarm-mo',
+            name: 'alarm-mo',
+            component: () => import('@/views/equipment-monitoring/alarm-mo/index'),
+            meta: { title: '无线火灾报警监控系统' }
+          },
+          {
+            path: 'video-ai',
+            name: 'video-ai',
+            component: () => import('@/views/equipment-monitoring/video-ai/index'),
+            meta: { title: '视频AI系统' }
+          }
+        ]
+      },
+      {
+        path: 'alarm-statistics',
+        name: 'alarm-statistics',
+        component: () => import('@/views/alarm-statistics/index'),
+        meta: { title: '报警统计' },
+        children: []
+      }
+    ]
+  },
+  { 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: '智慧消防',
+
+  /**
+   * @type {boolean} true | false
+   * @description Whether fix the header
+   */
+  fixedHeader: false,
+
+  /**
+   * @type {boolean} true | false
+   * @description Whether show the logo in sidebar
+   */
+  sidebarLogo: false
+}

+ 19 - 0
src/store/getters.js

@@ -0,0 +1,19 @@
+const getters = {
+  sidebar: state => state.app.sidebar,
+  device: state => state.app.device,
+  freAlarmData: state => state.equipmentMonitoring.freAlarmData,
+  fireWaterData: state => state.equipmentMonitoring.fireWaterData,
+  eleFireData: state => state.equipmentMonitoring.eleFireData,
+  gasMoData: state => state.equipmentMonitoring.gasMoData,
+  alarmMoData: state => state.equipmentMonitoring.alarmMoData,
+
+  freAlarmStatisticsData: state => state.equipmentMonitoring.freAlarmStatisticsData,
+  fireWaterStatisticsData: state => state.equipmentMonitoring.fireWaterStatisticsData,
+  eleFireStatisticsData: state => state.equipmentMonitoring.eleFireStatisticsData,
+  gasMoStatisticsData: state => state.equipmentMonitoring.gasMoStatisticsData,
+  alarmMoStatisticsData: state => state.equipmentMonitoring.alarmMoStatisticsData,
+
+  alarmStatisticsData: state => state.alarmStatistics.alarmStatisticsData,
+
+}
+export default getters

+ 22 - 0
src/store/index.js

@@ -0,0 +1,22 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import getters from './getters'
+
+Vue.use(Vuex)
+
+const modulesFiles = require.context('./modules', true, /\.js$/)
+
+const modules = modulesFiles.keys().reduce((modules, modulePath) => {
+  const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
+  const value = modulesFiles(modulePath)
+  modules[moduleName] = value.default
+  return modules
+}, {})
+
+
+const store = new Vuex.Store({
+  modules,
+  getters
+})
+
+export default store

+ 86 - 0
src/store/modules/alarmStatistics.js

@@ -0,0 +1,86 @@
+// http://127.0.0.1:8899/3rd/fire/alarmAnalysis?gId=133783305191425&cId=133783305191425&dataType=2&alarmMonth=2021-06
+// eslint-disable-next-line no-unused-vars
+import { dateUtils } from 'happy-utils'
+import websocketFn from '@/utils/ws'
+/**
+ * 1:用户信息传输装置     这个就是网站上的:火灾自动报警系统
+ 2:电气火灾监控器                        电气火灾智能监控系统
+ 3:液位
+ 4:水压                 3和4就是网站上的: 消防给水监测系统
+ 5:燃气                 本次用不到
+ 6:无线烟感                                 无线火灾报警监控系统
+ * @param params
+ * @returns {AxiosPromise}
+ */
+
+import { getAlarmStatisticsData } from '@/api/alarm-statistics'
+import { getReqParam } from '@/utils/auth'
+
+const getDefaultState = () => {
+  return {
+    alarmStatisticsData: null // 报警统计数据
+  }
+}
+
+const state = getDefaultState()
+
+const mutations = {
+  RESET_alarmStatisticsData: (state, data) => {
+    state.alarmStatisticsData = data
+  }
+}
+
+const actions = {
+  /** ************     ws start        ****************/
+  /**
+     * 报警统计
+     * @param params
+     * @returns {AxiosPromise}
+     */
+  wsAlarmStatistics({ commit, state }) {
+    const { cId, gId } = getReqParam()
+    websocketFn({ gId: gId, cId: cId, path: 'fd2' }, (response) => {
+      const { data } = response
+      const parseData = JSON.parse(data)
+      if (parseData.fireData) {
+        console.log('wsAlarmStatistics', parseData.fireData[0])
+        commit('RESET_alarmStatisticsData', parseData.fireData[0])
+      }
+    })
+  },
+  /** ************     ws end        ****************/
+
+  /**
+     * 报警统计
+     * @param params
+     * @returns {AxiosPromise}
+     */
+  getAlarmStatistics({ commit, state }, params) {
+    return new Promise((resolve, reject) => {
+      const { cId, gId } = getReqParam()
+      getAlarmStatisticsData({
+        gId: gId,
+        cId: cId,
+        alarmMonth: params.alarmMonth,
+        dataType: params.dataType
+      }).then(response => {
+        const { data } = response
+        if (params.dataType == 2) {
+          commit('RESET_alarmStatisticsData', data)
+        }
+
+        resolve()
+      }).catch(error => {
+        reject(error)
+      })
+    })
+  }
+}
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+}
+

+ 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
+}

+ 458 - 0
src/store/modules/equipmentMonitoring.js

@@ -0,0 +1,458 @@
+// http://gavin0212.imwork.net/3rd/bigScreenData/bigScreenMessage?gId=133783305191425&cId=133783305191425&path=fireData1
+//     http://gavin0212.imwork.net/3rd/bigScreenData/bigScreenMessage?gId=133783305191425&cId=133783305191425&path=fireData2
+import { dateUtils } from 'happy-utils'
+import websocketFn from '@/utils/ws'
+
+/**
+ * 1:用户信息传输装置     这个就是网站上的:火灾自动报警系统
+ 2:电气火灾监控器                        电气火灾智能监控系统
+ 3:液位
+ 4:水压                 3和4就是网站上的: 消防给水监测系统
+ 5:燃气                 本次用不到
+ 6:无线烟感                                 无线火灾报警监控系统
+ * @param params
+ * @returns {AxiosPromise}
+ */
+
+import { getEquipmentMonitoringData } from '@/api/equipment-monitoring'
+import { getReqParam } from '@/utils/auth'
+
+const getDefaultState = () => {
+  return {
+    freAlarmData: null, // 火灾自动报警系统
+    fireWaterData: null, // 消防给水监测系统
+    eleFireData: null, // 电气火灾智能监控系统
+    gasMoData: null, // 可燃气体检测预警系统
+    alarmMoData: null, // 无线火灾报警监控系统
+    freAlarmStatisticsData: null, // 火灾自动报警系统 - 统计数据
+    fireWaterStatisticsData: null, // 消防给水监测系统 - 统计数据
+    eleFireStatisticsData: null, // 电气火灾智能监控系统 - 统计数据
+    gasMoStatisticsData: null, // 可燃气体检测预警系统 - 统计数据
+    alarmMoStatisticsData: null // 无线火灾报警监控系统 - 统计数据
+  }
+}
+
+const state = getDefaultState()
+
+const mutations = {
+  RESET_freAlarmStatisticsData: (state, data) => {
+    state.freAlarmStatisticsData = data
+  },
+  RESET_fireWaterStatisticsData: (state, data) => {
+    state.fireWaterStatisticsData = data
+  },
+  RESET_eleFireStatisticsData: (state, data) => {
+    state.eleFireStatisticsData = data
+  },
+  RESET_gasMoStatisticsData: (state, data) => {
+    state.gasMoStatisticsData = data
+  },
+  RESET_alarmMoStatisticsData: (state, data) => {
+    state.alarmMoStatisticsData = data
+  },
+  RESET_freAlarmData: (state, data) => {
+    state.freAlarmData = data
+  },
+  RESET_fireWaterData: (state, data) => {
+    state.fireWaterData = data
+  },
+  RESET_fireWaterData_fireData3: (state, data) => {
+    const temp = state.fireWaterData
+    if (temp) {
+      state.fireWaterData = {
+        fireData3: data,
+        fireData4: temp.fireData4
+      }
+    } else {
+      state.fireWaterData = {
+        fireData3: data,
+        fireData4: null
+      }
+    }
+  },
+  RESET_fireWaterData_fireData4: (state, data) => {
+    const temp = state.fireWaterData
+    if (temp) {
+      state.fireWaterData = {
+        fireData3: temp.fireData3,
+        fireData4: data
+      }
+    } else {
+      state.fireWaterData = {
+        fireData3: null,
+        fireData4: data
+      }
+    }
+  },
+  RESET_eleFireData: (state, data) => {
+    state.eleFireData = data
+  },
+  RESET_gasMoData: (state, data) => {
+    state.gasModata = data
+  },
+  RESET_alarmMoData: (state, data) => {
+    state.alarmMoData = data
+  }
+}
+
+const actions = {
+  /** ************     ws start        ****************/
+  /**
+     * 火灾自动报警系统
+     * @param params
+     * @returns {AxiosPromise}
+     */
+  wsFireAlarmData({ commit, state }) {
+    const { cId, gId } = getReqParam()
+    websocketFn({ gId: gId, cId: cId, path: 'fireData1' }, (response) => {
+      const { data } = response
+      const parseData = JSON.parse(data)
+      if (parseData.fireData) {
+        parseData.fireData[0].forEach(item => {
+          item.viewInfo = JSON.parse(item.viewInfo)
+        })
+      }
+      console.log('wsFireAlarmData', parseData.fireData[0])
+      commit('RESET_freAlarmData', parseData)
+    })
+  },
+
+  /**
+     * 消防给水监测系统
+     * @param params
+     * @returns {AxiosPromise}
+     */
+  wsFireWaterData({ commit, state }) {
+    const { cId, gId } = getReqParam()
+    websocketFn({ gId: gId, cId: cId, path: 'fireData3' }, (response) => {
+      const { data } = response
+      const parseData = JSON.parse(data)
+      if (parseData.fireData) {
+        parseData.fireData[0].forEach(item => {
+          item.viewInfo = JSON.parse(item.viewInfo)
+          if (item.last7DayDatas) {
+            const names = []; const values = []
+            item.last7DayDatas.forEach((cItem) => {
+              names.push(dateUtils.dateFormat(new Date(cItem.occurTime), 'yyyy-MM-dd'))
+              // values.push(cItem.actVal.replace(item.viewInfo.unit,''))
+              values.push(cItem.actVal.replace(item.viewInfo.unit, ''))
+            })
+            item.names = names
+            item.values = values
+          }
+        })
+      }
+      console.log('wsFireWaterData_fireData3', parseData.fireData[0])
+      commit('RESET_fireWaterData_fireData3', parseData)
+    })
+    websocketFn({ gId: gId, cId: cId, path: 'fireData4' }, (response) => {
+      const { data } = response
+      const parseData = JSON.parse(data)
+      if (parseData.fireData) {
+        parseData.fireData[0].forEach(item => {
+          item.viewInfo = JSON.parse(item.viewInfo)
+          if (item.last7DayDatas) {
+            const names = []; const values = []
+            item.last7DayDatas.forEach((cItem) => {
+              names.push(dateUtils.dateFormat(new Date(cItem.occurTime), 'yyyy-MM-dd'))
+              // values.push(cItem.actVal.replace(item.viewInfo.unit,''))
+              values.push(cItem.actVal.replace(item.viewInfo.unit, ''))
+            })
+            item.names = names
+            item.values = values
+          }
+        })
+      }
+      console.log('wsFireWaterData_fireData4', parseData.fireData[0])
+      commit('RESET_fireWaterData_fireData4', parseData)
+    })
+  },
+
+  /**
+     * 电气火灾智能监控系统
+     * @param params
+     * @returns {AxiosPromise}
+     */
+  wsEleFireData({ commit, state }) {
+    const { cId, gId } = getReqParam()
+    websocketFn({ gId: gId, cId: cId, path: 'fireData2' }, (response) => {
+      const { data } = response
+      const parseData = JSON.parse(data)
+      if (parseData.fireData) {
+        parseData.fireData[0].forEach(item => {
+          item.viewInfo = JSON.parse(item.viewInfo)
+        })
+      }
+      console.log('wsEleFireData', parseData.fireData[0])
+      commit('RESET_eleFireData', parseData)
+    })
+  },
+
+  /**
+     * 可燃气体检测预警系统
+     * @param params
+     * @returns {AxiosPromise}
+     */
+  wsGasMoData({ commit, state }) {
+    const { cId, gId } = getReqParam()
+    websocketFn({ gId: gId, cId: cId, path: 'fireData5' }, (response) => {
+      const { data } = response
+      const parseData = JSON.parse(data)
+      if (parseData.fireData) {
+        parseData.fireData[0].forEach(item => {
+          item.viewInfo = JSON.parse(item.viewInfo)
+        })
+      }
+      console.log('wsGasMoData', parseData.fireData[0])
+      commit('RESET_gasMoData', parseData)
+    })
+  },
+
+  /**
+     * 无线火灾报警监控系统
+     * @param params
+     * @returns {AxiosPromise}
+     */
+  wsAlarmMoData({ commit, state }) {
+    const { cId, gId } = getReqParam()
+    websocketFn({ gId: gId, cId: cId, path: 'fireData6' }, (response) => {
+      const { data } = response
+      const parseData = JSON.parse(data)
+      if (parseData.fireData) {
+        parseData.fireData[0].forEach(item => {
+          item.viewInfo = JSON.parse(item.viewInfo)
+        })
+      }
+      console.log('wsAlarmMoData', parseData.fireData[0])
+      commit('RESET_alarmMoData', parseData)
+    })
+  },
+  /** ************     ws end        ****************/
+
+  /**
+     * 火灾自动报警系统
+     * @param params
+     * @returns {AxiosPromise}
+     */
+  getFireAlarmData({ commit, state }) {
+    return new Promise(async(resolve, reject) => {
+      const { cId, gId } = getReqParam()
+      await getEquipmentMonitoringData({ gId: gId, cId: cId, path: 'fireData1' }).then(response => {
+        const { data } = response
+        const parseData = JSON.parse(data)
+        if (parseData.fireData) {
+          parseData.fireData[0].forEach(item => {
+            item.viewInfo = JSON.parse(item.viewInfo)
+          })
+        }
+        commit('RESET_freAlarmData', parseData)
+      }).catch(error => {
+        reject(error)
+      })
+      await getEquipmentMonitoringData({ gId: gId, cId: cId, path: 'fdc1' }).then(response => {
+        const { data } = response
+        const parseData = JSON.parse(data)
+        if (parseData.fireData) {
+          commit('RESET_freAlarmStatisticsData', parseData.fireData[0])
+        }
+      }).catch(error => {
+        reject(error)
+      })
+      resolve()
+    })
+  },
+
+  /**
+     * 消防给水监测系统
+     * @param params
+     * @returns {AxiosPromise}
+     */
+  getFireWaterData({ commit, state }) {
+    const { cId, gId } = getReqParam()
+    return new Promise(async(resolve, reject) => {
+      const setData = {}
+      await getEquipmentMonitoringData({ gId: gId, cId: cId, path: 'fireData3' }).then(response => {
+        const { data } = response
+        const parseData = JSON.parse(data)
+        if (parseData.fireData) {
+          parseData.fireData[0].forEach(item => {
+            item.viewInfo = JSON.parse(item.viewInfo)
+            if (item.last7DayDatas) {
+              const names = []; const values = []
+              item.last7DayDatas.forEach((cItem) => {
+                names.push(dateUtils.dateFormat(new Date(cItem.occurTime), 'yyyy-MM-dd'))
+                // values.push(cItem.actVal.replace(item.viewInfo.unit,''))
+                values.push(cItem.actVal.replace(item.viewInfo.unit, ''))
+              })
+              item.names = names
+              item.values = values
+            }
+          })
+        }
+        setData.fireData3 = parseData
+      }).catch(error => {
+        reject(error)
+      })
+
+      await getEquipmentMonitoringData({ gId: gId, cId: cId, path: 'fireData4' }).then(response => {
+        const { data } = response
+        const parseData = JSON.parse(data)
+        if (parseData.fireData) {
+          parseData.fireData[0].forEach(item => {
+            item.viewInfo = JSON.parse(item.viewInfo)
+            if (item.last7DayDatas) {
+              const names = []; const values = []
+              item.last7DayDatas.forEach((cItem) => {
+                names.push(dateUtils.dateFormat(new Date(cItem.occurTime), 'yyyy-MM-dd'))
+                // values.push(cItem.actVal.replace(item.viewInfo.unit,''))
+                values.push(cItem.actVal.replace(item.viewInfo.unit, ''))
+              })
+              item.names = names
+              item.values = values
+            }
+          })
+        }
+        setData.fireData4 = parseData
+      }).catch(error => {
+        reject(error)
+      })
+
+      commit('RESET_fireWaterData', setData)
+
+      const temp = []
+
+      await getEquipmentMonitoringData({ gId: gId, cId: cId, path: 'fdc3' }).then(response => {
+        const { data } = response
+        const parseData = JSON.parse(data)
+        if (parseData.fireData) {
+          temp[0] = parseData.fireData[0]
+        }
+      }).catch(error => {
+        reject(error)
+      })
+
+      await getEquipmentMonitoringData({ gId: gId, cId: cId, path: 'fdc4' }).then(response => {
+        const { data } = response
+        const parseData = JSON.parse(data)
+        if (parseData.fireData) {
+          temp[1] = parseData.fireData[0]
+        }
+      }).catch(error => {
+        reject(error)
+      })
+      commit('RESET_fireWaterStatisticsData', temp)
+
+      resolve()
+    })
+  },
+
+  /**
+     * 电气火灾智能监控系统
+     * @param params
+     * @returns {AxiosPromise}
+     */
+  getEleFireData({ commit, state }) {
+    const { cId, gId } = getReqParam()
+    return new Promise(async(resolve, reject) => {
+      await getEquipmentMonitoringData({ gId: gId, cId: cId, path: 'fireData2' }).then(response => {
+        const { data } = response
+        const parseData = JSON.parse(data)
+        if (parseData.fireData) {
+          parseData.fireData[0].forEach(item => {
+            item.viewInfo = JSON.parse(item.viewInfo)
+          })
+        }
+        commit('RESET_eleFireData', parseData)
+      }).catch(error => {
+        reject(error)
+      })
+
+      await getEquipmentMonitoringData({ gId: gId, cId: cId, path: 'fdc2' }).then(response => {
+        const { data } = response
+        const parseData = JSON.parse(data)
+        if (parseData.fireData) {
+          commit('RESET_eleFireStatisticsData', parseData.fireData[0])
+        }
+      }).catch(error => {
+        reject(error)
+      })
+      resolve()
+    })
+  },
+
+  /**
+     * 可燃气体检测预警系统
+     * @param params
+     * @returns {AxiosPromise}
+     */
+  getGasMoData({ commit, state }) {
+    const { cId, gId } = getReqParam()
+    return new Promise(async(resolve, reject) => {
+      await getEquipmentMonitoringData({ gId: gId, cId: cId, path: 'fireData5' }).then(response => {
+        const { data } = response
+        const parseData = JSON.parse(data)
+        if (parseData.fireData) {
+          parseData.fireData[0].forEach(item => {
+            item.viewInfo = JSON.parse(item.viewInfo)
+          })
+        }
+        commit('RESET_gasMoData', parseData)
+      }).catch(error => {
+        reject(error)
+      })
+
+      await getEquipmentMonitoringData({ gId: gId, cId: cId, path: 'fdc5' }).then(response => {
+        const { data } = response
+        const parseData = JSON.parse(data)
+        if (parseData.fireData) {
+          commit('RESET_gasMoStatisticsData', parseData.fireData[0])
+        }
+      }).catch(error => {
+        reject(error)
+      })
+      resolve()
+    })
+  },
+
+  /**
+     * 无线火灾报警监控系统
+     * @param params
+     * @returns {AxiosPromise}
+     */
+  getAlarmMoData({ commit, state }) {
+    const { cId, gId } = getReqParam()
+    return new Promise(async(resolve, reject) => {
+      await getEquipmentMonitoringData({ gId: gId, cId: cId, path: 'fireData6' }).then(response => {
+        const { data } = response
+        const parseData = JSON.parse(data)
+        if (parseData.fireData) {
+          parseData.fireData[0].forEach(item => {
+            item.viewInfo = JSON.parse(item.viewInfo)
+          })
+        }
+        commit('RESET_alarmMoData', parseData)
+      }).catch(error => {
+        reject(error)
+      })
+
+      await getEquipmentMonitoringData({ gId: gId, cId: cId, path: 'fdc6' }).then(response => {
+        const { data } = response
+        const parseData = JSON.parse(data)
+        if (parseData.fireData) {
+          commit('RESET_alarmMoStatisticsData', parseData.fireData[0])
+        }
+      }).catch(error => {
+        reject(error)
+      })
+      resolve()
+    })
+  }
+}
+
+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
+}
+

+ 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;
+}

+ 49 - 0
src/utils/auth.js

@@ -0,0 +1,49 @@
+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)
+}
+
+const ReqParamKey = 'req_param'
+
+export function setReqParam(param) {
+    return Cookies.set(ReqParamKey, JSON.stringify(param))
+}
+
+export function getReqParam() {
+    if (Cookies.get(ReqParamKey)) {
+        return JSON.parse(Cookies.get(ReqParamKey))
+    }
+    return {}
+}
+
+
+const topActiveMenuIndex = 'top_active_menu_index'
+
+export function setTopActiveMenuIndex(param) {
+    return Cookies.set(topActiveMenuIndex, param)
+}
+
+export function getTopActiveMenuIndex() {
+    return Cookies.get(topActiveMenuIndex)
+}
+
+const leftActiveMenuIndex = 'left_active_menu_index'
+
+export function setLeftActiveMenuIndex(param) {
+    return Cookies.set(leftActiveMenuIndex, param)
+}
+
+export function getLeftActiveMenuIndex() {
+    return Cookies.get(leftActiveMenuIndex)
+}

+ 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}`
+}

+ 12 - 0
src/utils/global-config.js

@@ -0,0 +1,12 @@
+// ?gId=133783305191425&cId=133783305191425
+// http://syfv.anquanzhuli.com/fire/#/equipment-monitoring/alarm-mo?gId=186884525522945&cId=186884525522945
+// http://localhost:9528/#/equipment-monitoring/alarm-mo?gId=186884525522945&cId=186884525522945
+const globalConfig = {
+  // ip: 'http://api.anquanzhuli.com/',   // http://aqpt.anquanzhuli.com/fire/#/equipment-monitoring/alarm-mo?gId=133783305191425&cId=133783305191425
+  // ip: 'http://grsapi.anquanzhuli.com/',   // http://grs.anquanzhuli.com/fire/#/equipment-monitoring/alarm-mo?gId=161279123718145&cId=161279123718145
+  ip: 'http://syfa.anquanzhuli.com/',
+  // ip: 'http://gavin0212.imwork.net/',
+  wsip: 'ws://ws.anquanzhuli.com/ws'
+}
+
+export default globalConfig

+ 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
+}

+ 73 - 0
src/utils/request.js

@@ -0,0 +1,73 @@
+import axios from 'axios'
+import { Message } from 'element-ui'
+import globalConfig from '@/utils/global-config'
+// import { getToken } from '@/utils/auth'
+
+const { ip } = globalConfig
+
+// create an axios instance
+const service = axios.create({
+  baseURL: ip, // url = base url + request url
+  // withCredentials: true, // send cookies when cross-domain requests
+  timeout: 1000 * 60 // 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 !== 1) {
+      Message({
+        message: res.message || 'Error',
+        type: 'error',
+        duration: 5 * 1000
+      })
+      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
+}

+ 99 - 0
src/utils/ws.js

@@ -0,0 +1,99 @@
+import globalConfig from './global-config'
+
+let {ip, wsip} = globalConfig;
+
+let tempIp = ip.replace('http://', '').replace('https://', '')
+
+function websocketFn(obj, callBackFn) {
+
+    let lockReconnect = false;//避免重复连接
+    let wsUrl = wsip + '?gId=' + obj.gId + '&cId=' + obj.cId + '&path=' + obj.path + '&from=' + tempIp;
+    let ws;
+    let tt;
+
+    function createWebSocket() {
+        try {
+            ws = new WebSocket(wsUrl);
+            init();
+        } catch (e) {
+            console.log('catch');
+            reconnect(wsUrl);
+        }
+    }
+
+    function init() {
+        ws.onclose = function () {
+            console.log('链接关闭');
+            ws = null;
+            reconnect(wsUrl);
+        };
+        ws.onerror = function () {
+            console.log('发生异常了');
+            reconnect(wsUrl);
+        };
+        // 监听socket连接  是否连接上
+        ws.onopen = function () {
+            console.log('建立连接onopen');
+            //心跳检测重置
+            heartCheck.start();
+        };
+        // 监听socket消息
+        ws.onmessage = function (evt) {
+            //拿到任何消息都说明当前连接是正常的
+            // console.log('接收到消息onmessage', JSON.parse(evt.data));
+            heartCheck.start();
+
+            let data = evt.data;
+            if (data.indexOf('10000') != -1) {
+                return;
+            }
+            let respData = JSON.parse(data);
+            // console.log('ws_data', data)
+            callBackFn(respData);
+        };
+
+    }
+    function reconnect(url) {
+        if(lockReconnect) {
+            return;
+        };
+
+        lockReconnect = true;
+        //没连接上会一直重连,设置延迟避免请求过多  4秒后会重新创建一个新的websocket链接
+        tt && clearTimeout(tt);
+        tt = setTimeout(function () {
+            createWebSocket(url);
+            lockReconnect = false;
+        }, 4000);
+    }
+
+    //心跳检测
+    var heartCheck = {
+        timeout: 10*1000, //此时间不能大于服务器设置的心跳时间
+        timeoutObj: null,
+        serverTimeoutObj: null,
+        start: function(){
+            var self = this;
+            this.timeoutObj && clearTimeout(this.timeoutObj);
+            this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
+            this.timeoutObj = setTimeout(function(){
+                //这里发送一个心跳,后端收到后,返回一个心跳消息,
+                // console.log(self.timeoutObj, self.serverTimeoutObj, obj.path+'-cmd');
+                // console.log('wswsws', ws)
+                if(ws){
+                    ws.send(obj.path+'-cmd');
+                }
+                self.serverTimeoutObj = setTimeout(function() {
+                    ws.close();
+                    // createWebSocket();
+                }, self.timeout);
+
+            }, this.timeout)
+        }
+    }
+    createWebSocket(wsUrl);
+
+
+}
+
+export default websocketFn;

+ 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">请检查您输入的网址是否正确,或单击下面的按钮返回主页。</div>
+        <a href="" class="bullshit__return-home">回到家</a>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+
+export default {
+  name: 'Page404',
+  computed: {
+    message() {
+      return '站长说你进不去这个页面...'
+    }
+  }
+}
+</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>

+ 619 - 0
src/views/alarm-statistics/components/DataChart.vue

@@ -0,0 +1,619 @@
+<template>
+  <div class="data-chart">
+    <div class="chart-line">
+      <div class="chart-group w-50">
+        <div class="chart-title">
+          报警月度统计
+        </div>
+        <div class="chart-content">
+          <div class="chart-box" style="width: 100%;height: 300px" />
+        </div>
+      </div>
+      <div class="chart-group w-50">
+        <div class="chart-title">
+          报警时段分布
+        </div>
+        <div class="chart-content">
+          <div class="chart-box" style="width: 100%;height: 300px" />
+        </div>
+      </div>
+
+      <div class="chart-group w-33">
+        <div class="chart-title">
+          报警区域分布
+        </div>
+        <div class="chart-content">
+          <div class="chart-box" style="width: 100%;height: 300px" />
+        </div>
+      </div>
+      <div class="chart-group w-33">
+        <div class="chart-title">
+          报警设备分布
+        </div>
+        <div class="chart-content">
+          <div class="chart-box" style="width: 100%;height: 300px" />
+        </div>
+      </div>
+      <div class="chart-group w-33">
+        <div class="chart-title">
+          报警原因分布
+        </div>
+        <div class="chart-content">
+          <div class="chart-box" style="width: 100%;height: 300px" />
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import * as echarts from 'echarts'
+
+export default {
+  name: 'DataChart',
+  props: ['data'],
+  data() {
+    return {}
+  },
+  watch: {
+    'data': {
+      handler(item) {
+        this.initChart1()
+        this.initChart2()
+        this.initChart3()
+        this.initChart4()
+        this.initChart5()
+      },
+      deep: true
+    }
+  },
+  mounted() {
+    this.initChart1()
+    this.initChart2()
+    this.initChart3()
+    this.initChart4()
+    this.initChart5()
+  },
+  methods: {
+    initChart5() {
+      const chart = echarts.init(document.getElementsByClassName('chart-box')[4])
+
+      const datas = []; const names = []
+      if (this.data) {
+        this.data.alarmReasonType.forEach((item) => {
+          datas.push(item.value)
+          names.push(item.label)
+        })
+      }
+
+      // 指定图表的配置项和数据
+      const option = {
+        legend: {
+          top: 10,
+          right: 0,
+          textStyle: {
+            color: '#FFFFFF'
+          }
+        },
+        polar: {
+          radius: '60%'
+          // center: ['30%', '30%']
+        },
+        angleAxis: {
+          startAngle: 0,
+          type: 'category',
+          data: names,
+          axisLabel: {
+            color: '#FFF'
+          },
+          splitLine: {
+            show: true,
+            lineStyle: {
+              color: '#FFF'
+            }
+          },
+          axisLine: {
+            lineStyle: {
+              color: '#FFF'
+            }
+          }
+        },
+        radiusAxis: {
+          boundaryGap: ['20%', '20%'],
+          axisLabel: {
+            show: false
+          }
+        },
+        series: [{
+          type: 'bar',
+          data: datas,
+          coordinateSystem: 'polar',
+          name: '范围',
+          stack: '最大最小值'
+        }]
+      }
+      // 使用刚指定的配置项和数据显示图表。
+      chart.setOption(option)
+    },
+    initChart4() {
+      const chart = echarts.init(document.getElementsByClassName('chart-box')[3])
+
+      const data = []
+      let count = 0
+      if (this.data) {
+        this.data.alarmCountByType.forEach((item) => {
+          data.push({
+            value: item.alarmCount,
+            name: item.typeName
+          })
+          count += Number(item.alarmCount)
+        })
+      }
+
+      // 指定图表的配置项和数据
+      const option = {
+        title: {
+          text: `总报警数`,
+          left: '50%',
+          top: '42%',
+          textAlign: 'center',
+          textStyle: {
+            fontWeight: '0',
+            fontSize: '15px',
+            color: '#00BBD2'
+          },
+          subtext: count,
+          subtextStyle: {
+            fontWeight: '600',
+            fontSize: '15px',
+            color: '#00BBD2'
+          }
+        },
+        tooltip: {
+          trigger: 'item'
+        },
+        legend: {
+          top: 10,
+          right: 0,
+          textStyle: {
+            color: '#FFFFFF'
+          }
+        },
+        series: [
+          {
+            name: '报警设备分布',
+            type: 'pie',
+            radius: ['45%', '60%'],
+            labelLine: {
+              length: 30
+            },
+            avoidLabelOverlap: false,
+            itemStyle: {
+              borderRadius: 10,
+              borderColor: '#fff',
+              borderWidth: 1
+            },
+            label: {
+              formatter: '{a|{a}}{abg|}\n{hr|}\n  {b|{b}:}{c}  {per|{d}%}  ',
+              backgroundColor: '#F6F8FC',
+              borderColor: '#8C8D8E',
+              borderWidth: 1,
+              borderRadius: 4,
+              position: 'outside',
+              rich: {
+                a: {
+                  color: '#6E7079',
+                  lineHeight: 22,
+                  align: 'center'
+                },
+                hr: {
+                  borderColor: '#8C8D8E',
+                  width: '100%',
+                  borderWidth: 1,
+                  height: 0
+                },
+                b: {
+                  color: '#4C5058',
+                  fontSize: 12,
+                  fontWeight: 'bold',
+                  lineHeight: 33
+                },
+                per: {
+                  color: '#fff',
+                  backgroundColor: '#4C5058',
+                  padding: [3, 4],
+                  borderRadius: 4
+                }
+              }
+            },
+            color: ['#0069BF', '#D7663E'],
+            data: data
+          }
+        ]
+      }
+
+      // 使用刚指定的配置项和数据显示图表。
+      chart.setOption(option)
+    },
+    initChart3() {
+      const chart = echarts.init(document.getElementsByClassName('chart-box')[2])
+
+      var labelLeft = {
+        position: 'left'
+      }
+
+      var labelRight = {
+        position: 'right'
+      }
+
+      /**
+                 * alarmCount:3
+                 zoneName:"5号门诊楼2层"
+                 * @type {*[]}
+                 */
+      const data1 = []; const data2 = []
+      if (this.data) {
+        //  {value: -0.07, label: labelRight},
+        this.data.alarmCountByNomalAreas.forEach((item) => {
+          data1.push({
+            value: '-' + item.alarmCount,
+            label: labelLeft,
+            name: item.zoneName
+          })
+          data2.push({
+            value: 0,
+            label: labelRight,
+            name: item.zoneName
+          })
+        })
+      }
+
+      // 指定图表的配置项和数据
+      const option = {
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: { // 坐标轴指示器,坐标轴触发有效
+            type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
+          }
+        },
+        legend: {
+          top: 10,
+          right: 0,
+          textStyle: {
+            color: '#FFFFFF'
+          }
+        },
+        grid: {
+          top: '40',
+          bottom: '30'
+        },
+        xAxis: {
+          type: 'value',
+          position: 'top',
+          show: false
+        },
+        yAxis: {
+          type: 'category',
+          nameTextStyle: {
+            color: '#FFF'
+          }
+        },
+        series: [
+          {
+            name: '普通区域',
+            type: 'bar',
+            stack: '总量',
+            label: {
+              show: true,
+              formatter: '{b}',
+              color: '#fff'
+            },
+            itemStyle: {
+              color: '#008DF6'
+            },
+            data: data1
+          },
+          {
+            name: '重点区域',
+            type: 'bar',
+            stack: '总量',
+            label: {
+              show: true,
+              formatter: '{b}',
+              color: '#fff'
+            },
+            itemStyle: {
+              color: '#FFC600'
+            },
+            data: data2
+          }
+        ]
+      }
+
+      // 使用刚指定的配置项和数据显示图表。
+      chart.setOption(option)
+    },
+    initChart2() {
+      const chart = echarts.init(document.getElementsByClassName('chart-box')[1])
+
+      const data = []; const date = []
+      if (this.data) {
+        this.data.alarmCountByHour.forEach((item) => {
+          data.push(item.alarmCount)
+          date.push(item.alarmHour)
+        })
+      }
+
+      // 指定图表的配置项和数据
+      const option = {
+        tooltip: {},
+        grid: {
+          top: '40',
+          bottom: '30'
+        },
+        xAxis: {
+          name: '点位号',
+          nameTextStyle: {
+            color: '#FFF'
+          },
+          axisLabel: {
+            color: '#FFF'
+          },
+          axisLine: {
+            lineStyle: {
+              color: '#0075D2'
+            }
+          },
+          data: date
+        },
+        yAxis: {
+          name: '报警数(个)',
+          nameTextStyle: {
+            color: '#FFF'
+          },
+          axisLabel: {
+            textStyle: {
+              color: '#FFF'
+            }
+          },
+          axisLine: {
+            show: true,
+            lineStyle: {
+              color: '#0075D2'
+            }
+          },
+          splitLine: {
+            show: false
+          }
+        },
+        series: [{
+          name: '报警量',
+          type: 'bar',
+          itemStyle: {
+            color: '#D7241F'
+          },
+          data: data
+        }]
+      }
+
+      // 使用刚指定的配置项和数据显示图表。
+      chart.setOption(option)
+    },
+    initChart1() {
+      const chart = echarts.init(document.getElementsByClassName('chart-box')[0])
+      const lv0 = []; const lv1 = []; const lv2 = []; const lv3 = []; const date = []
+      const dataMap = new Map()
+      if (this.data) {
+        this.data.alarmCountByDateAndLevel.forEach((item) => {
+          date.push(item.alarmDate)
+          let dataItem = dataMap.get(item.alarmDate)
+          if (!dataItem) {
+            dataItem = {
+              lv0: 0,
+              lv1: 0,
+              lv2: 0,
+              lv3: 0
+            }
+          }
+
+          if (item.alarmLevel === '1') {
+            dataItem.lv1 = item.alarmCount
+          }
+
+          if (item.alarmLevel === '2') {
+            dataItem.lv2 = item.alarmCount
+          }
+
+          if (item.alarmLevel === '3') {
+            dataItem.lv3 = item.alarmCount
+          }
+
+          dataItem.lv0 += Number(item.alarmCount)
+
+          dataMap.set(item.alarmDate, dataItem)
+        })
+      }
+
+      for (const [key, val] of dataMap) {
+        date.push(key)
+        lv0.push(val.lv0)
+        lv1.push(val.lv1)
+        lv2.push(val.lv2)
+        lv3.push(val.lv3)
+      }
+
+      // 指定图表的配置项和数据
+      const option = {
+        tooltip: {},
+        legend: {
+          top: 10,
+          right: 0,
+          textStyle: {
+            color: '#FFFFFF'
+          }
+        },
+        grid: {
+          top: '40',
+          bottom: '30'
+        },
+        xAxis: {
+          name: '日期',
+          nameTextStyle: {
+            color: '#FFF'
+          },
+          axisLabel: {
+            color: '#FFF'
+          },
+          axisLine: {
+            lineStyle: {
+              color: '#0075D2'
+            }
+          },
+          data: date
+        },
+        yAxis: {
+          name: '报警数(个)',
+          nameTextStyle: {
+            color: '#FFF'
+          },
+          axisLabel: {
+            textStyle: {
+              color: '#FFF'
+            }
+          },
+          axisLine: {
+            show: true,
+            lineStyle: {
+              color: '#0075D2'
+            }
+          },
+          splitLine: {
+            show: false
+          }
+        },
+        series: [
+          {
+            name: '总报警',
+            type: 'line',
+            stack: '总量',
+            itemStyle: {
+              color: '#00AAC2'
+            },
+            label: {
+              show: true,
+              position: 'top',
+              textStyle: {
+                color: '#fff'
+              }
+            },
+            data: lv0
+          },
+          {
+            name: '一级报警',
+            type: 'line',
+            stack: '总量',
+            itemStyle: {
+              color: '#B0010F'
+            },
+            label: {
+              show: true,
+              position: 'top',
+              textStyle: {
+                color: '#fff'
+              }
+            },
+            data: lv1
+          },
+          {
+            name: '二级报警',
+            type: 'line',
+            stack: '总量',
+            itemStyle: {
+              color: '#EE5107'
+            },
+            label: {
+              show: true,
+              position: 'top',
+              textStyle: {
+                color: '#fff'
+              }
+            },
+            data: lv2
+          },
+          {
+            name: '三级报警',
+            type: 'line',
+            stack: '总量',
+            itemStyle: {
+              color: '#B39740'
+            },
+            label: {
+              show: true,
+              position: 'top',
+              textStyle: {
+                color: '#fff'
+              }
+            },
+            data: lv3
+          }
+        ]
+      }
+
+      // 使用刚指定的配置项和数据显示图表。
+      chart.setOption(option)
+    },
+    handleSizeChange(val) {
+      console.log(`每页 ${val} 条`)
+    },
+    handleCurrentChange(val) {
+      console.log(`当前页: ${val}`)
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+    .data-chart {
+        width: 100%;
+
+        .chart-line {
+            overflow: hidden;
+
+            .chart-group {
+                float: left;
+
+                .chart-title {
+                    background-image: url(../../../assets/images/unit_tibg2.png);
+                    background-size: cover;
+                    overflow: hidden;
+                    padding-left: 15px;
+                    box-sizing: border-box;
+                    font-size: 18px;
+                    height: 37px;
+                    line-height: 37px;
+                    color: #FFF;
+                }
+
+                .chart-content {
+
+                }
+            }
+
+            .w-50 {
+                width: 50%;
+            }
+
+            .w-33 {
+                width: 33.33%;
+            }
+
+        }
+
+    }
+
+</style>
+
+<style scoped>
+
+</style>
+

+ 303 - 0
src/views/alarm-statistics/components/DataList.vue

@@ -0,0 +1,303 @@
+<template>
+  <div class="data-list">
+    <el-table
+      height="600px"
+      :data="tableData"
+    >
+      <el-table-column
+        width="160px"
+        prop="addTime"
+        label="报警时间"
+      >
+        <template slot-scope="scope">
+          <span>{{ scope.row.addTime|dateFilter }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column
+        prop="alarmContent"
+        label="报警内容"
+      >
+        <template slot-scope="scope">
+          <span>{{ scope.row.alarmContent }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column
+        width="120px"
+        prop="alarmTypeName"
+        label="报警类型"
+      >
+        <template slot-scope="scope">
+          <span>{{ scope.row.alarmTypeName }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column
+        width="100px"
+        prop="alarmLevel"
+        label="报警等级"
+      >
+        <template slot-scope="scope">
+          <span v-if="scope.row.alarmLevel == 1">一级预警</span>
+          <span v-if="scope.row.alarmLevel == 2">二级预警</span>
+          <span v-if="scope.row.alarmLevel == 3">三级预警</span>
+        </template>
+      </el-table-column>
+      <el-table-column
+        width="140px"
+        prop="deviceCode"
+        label="设备编码"
+      >
+        <template slot-scope="scope">
+          <span>{{ scope.row.deviceCode }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column
+        width="160px"
+        prop="devicename"
+        label="设备名称"
+      >
+        <template slot-scope="scope">
+          <span>{{ scope.row.devicename }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column
+        width="120px"
+        prop="devicename"
+        label="设备类型"
+      >
+        <template slot-scope="scope">
+          <span>{{ scope.row.equipmentType }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column
+        width="120px"
+        prop="orgName"
+        label="安装位置"
+      >
+        <template slot-scope="scope">
+          <span>{{ scope.row.orgName }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column
+        width="180px"
+        prop="zoneName"
+        label="所在位置"
+      >
+        <template slot-scope="scope">
+          <span>{{ scope.row.zoneName }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column
+        width="100px"
+        prop="handleState"
+        label="处理状态"
+      >
+        <template slot-scope="scope">
+          <span :class="scope.row.handleState == 0 ? 'color-orange' : 'color-blue'">{{ scope.row.handleState|handleStateFilter }}</span>
+        </template>
+      </el-table-column>
+      <!--            <el-table-column-->
+      <!--                    width="120px"-->
+      <!--                    label="操作">-->
+      <!--                <template slot-scope="scope">-->
+      <!--                    <div>-->
+      <!--                        <el-button type="primary" size="mini">推送结果</el-button>-->
+      <!--                    </div>-->
+      <!--                </template>-->
+      <!--            </el-table-column>-->
+    </el-table>
+    <el-pagination
+      :current-page="pageData.limit"
+      :page-sizes="[10, 20, 30, 40, 50]"
+      :page-size="pageData.size"
+      layout="total, sizes, prev, pager, next, jumper"
+      :total="pageData.total"
+      @size-change="handleSizeChange"
+      @current-change="handleCurrentChange"
+    />
+  </div>
+</template>
+
+<script>
+import { dateUtils } from 'happy-utils'
+
+export default {
+  name: 'DataList',
+  filters: {
+    dateFilter(str) {
+      return dateUtils.dateFormat(new Date(str), 'yyyy-MM-dd hh:mm:ss')
+    },
+    handleStateFilter(handleState) {
+      return handleState == 0 ? '未处理' : '已处理'
+    }
+  },
+  props: ['data', 'searchFormData'],
+  data() {
+    return {
+      ops: {
+        bar: {
+          keepShow: true,
+          background: '#006ED0'
+        }
+      },
+      pageData: {
+        limit: 1,
+        size: 10,
+        total: 0
+      },
+      tableData: [],
+      constTableData: []
+    }
+  },
+  mounted() {
+    if (this.data) {
+      this.constTableData = JSON.parse(JSON.stringify(this.data.fireData))
+      this.setConstTableData()
+    }
+  },
+  methods: {
+    filterTableData(item) {
+      let filterData = JSON.parse(JSON.stringify(this.data.fireData))
+      if (item.equipmentType != '') {
+        filterData = filterData.filter((dataItem) => {
+          if (dataItem.equipmentType == item.equipmentType) {
+            return dataItem
+          }
+        })
+      }
+      if (item.alarmLevel != '') {
+        filterData = filterData.filter((dataItem) => {
+          if (dataItem.alarmLevel == item.alarmLevel) {
+            return dataItem
+          }
+        })
+      }
+      if (item.processingProgress != '') {
+        filterData = filterData.filter((dataItem) => {
+          if (dataItem.handleState == item.processingProgress) {
+            return dataItem
+          }
+        })
+      }
+      if (item.alarmArea != '') {
+        filterData = filterData.filter((dataItem) => {
+          if (dataItem.zoneName == item.alarmArea) {
+            return dataItem
+          }
+        })
+      }
+
+      this.constTableData = JSON.parse(JSON.stringify(filterData))
+      this.setConstTableData()
+    },
+    handleSizeChange(val) {
+      this.pageData.size = val
+      this.updatePageData()
+    },
+    handleCurrentChange(val) {
+      this.pageData.limit = val
+      this.updatePageData()
+    },
+    setConstTableData() {
+      this.pageData.total = this.constTableData.length
+      this.pageData.limit = 1
+      this.updatePageData()
+    },
+    updatePageData() {
+      this.tableData = JSON.parse(JSON.stringify(this.constTableData))
+      const limit = (this.pageData.limit - 1) * this.pageData.size
+      const size = this.pageData.limit * this.pageData.size
+      this.tableData = this.tableData.slice(limit, size)
+    }
+  }
+}
+</script>
+
+<style lang="scss">
+    .data-list {
+        width: 100%;
+
+        .color-blue {
+            color: #6783aa;
+        }
+
+        .color-orange {
+            color: orange;
+        }
+
+        .el-table {
+            /*padding-top: 30px;*/
+            background: unset;
+
+            &:before {
+                display: none;
+            }
+
+            td, th.is-leaf {
+                border: none;
+            }
+
+            .el-table__header-wrapper {
+                th, tr {
+                    background: #003058;
+                    color: #FFF;
+                }
+            }
+
+            .el-table__body-wrapper {
+
+                th, tr {
+                    background: unset;
+                    color: #FFF;
+                }
+
+                :hover > td {
+                    background: unset;
+                }
+            }
+        }
+
+        .el-pagination {
+            margin: 15px 0 0 0;
+
+            span {
+                color: #6783aa;
+            }
+
+            .el-input__inner {
+                background: unset;
+                border: 1px solid #126381;
+                color: #6783aa;
+            }
+
+            &:focus {
+                border: 1px solid #6783aa;
+            }
+
+            .btn-prev, .btn-next {
+                background: unset;
+                color: #6783aa;
+            }
+
+            .el-pager {
+                .number {
+                    margin-right: 15px;
+
+                    &:first-child {
+                        margin-left: 15px;
+                    }
+                }
+
+                .active {
+                    background: #00E8FF;
+                    color: #000;
+                }
+            }
+        }
+    }
+
+</style>
+
+<style scoped>
+
+</style>
+

+ 283 - 0
src/views/alarm-statistics/components/LeftStatistics.vue

@@ -0,0 +1,283 @@
+<template>
+    <div class="left-statistics">
+        <div class="statistics-info">
+            <div class="title-line">
+                <h4>监测设备统计</h4>
+                <div class="line">
+                    <img src="../../../assets/images/title_border_line.png" alt="">
+                </div>
+                <p class="sum">报警总数<span>{{totalAlarms}}</span>次</p>
+                <ul>
+                    <li>
+                        <p>一级预警</p>
+                        <p><span class="red-text">{{statistics.lv1}}</span> 个</p>
+                    </li>
+                    <li>
+                        <p>二级预警</p>
+                        <p><span class="yellow-text">{{statistics.lv2}}</span> 个</p>
+                    </li>
+                    <li>
+                        <p>三级预警</p>
+                        <p><span class="blue-text">{{statistics.lv3}}</span> 个</p>
+                    </li>
+                </ul>
+
+                <ul>
+                    <li>
+                        <p>已处理</p>
+                        <p><span class="red-text">0</span> 个</p>
+                    </li>
+                    <li>
+                        <p>真实火警</p>
+                        <p><span class="yellow-text">0</span> 个</p>
+                    </li>
+                    <li>
+                        <p>误报</p>
+                        <p><span class="blue-text">0</span> 个</p>
+                    </li>
+                </ul>
+            </div>
+
+            <div class="title-line bottom-info">
+                <h4>综合分析</h4>
+                <div class="line">
+                    <img src="../../../assets/images/title_border_line.png" alt="">
+                </div>
+                <p class="info-text" v-if="data">{{data.zhfx}}</p>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+    import {mapGetters} from "vuex";
+
+    export default {
+        name: "left-menus",
+        props: ['data'],
+        data() {
+            return {
+                totalAlarms: 0,
+                statistics: {
+                    lv1: 0,
+                    lv2: 0,
+                    lv3: 0
+                }
+            }
+        },
+        // computed: {
+        //     ...mapGetters(['freAlarmStatisticsData', 'fireWaterStatisticsData', 'eleFireStatisticsData', 'gasMoStatisticsData', 'alarmMoStatisticsData'])
+        // },
+        watch: {
+            data: {
+                handler(val, olVal) {
+                    this.setLevelData();
+                },
+                deep: true
+            },
+            // 'fireWaterStatisticsData': {
+            //     handler(item) {
+            //         this.countStatisticsData();
+            //     },
+            //     deep: true,
+            //     immediate: true
+            // },
+            // 'freAlarmStatisticsData': {
+            //     handler(item) {
+            //         this.countStatisticsData();
+            //     },
+            //     deep: true,
+            //     immediate: true
+            // },
+            // 'eleFireStatisticsData': {
+            //     handler(item) {
+            //         this.countStatisticsData();
+            //     },
+            //     deep: true,
+            //     immediate: true
+            // },
+            // 'gasMoStatisticsData': {
+            //     handler(item) {
+            //         this.countStatisticsData();
+            //     },
+            //     deep: true,
+            //     immediate: true
+            // },
+            // 'alarmMoStatisticsData': {
+            //     handler(item) {
+            //         this.countStatisticsData();
+            //     },
+            //     deep: true,
+            //     immediate: true
+            // },
+        },
+        mounted() {
+            if (this.data) {
+                this.setLevelData();
+            }
+        },
+        methods: {
+            countStatisticsData() {
+                let count = 0;
+                if (this.freAlarmStatisticsData) {
+                    count += this.freAlarmStatisticsData['30Day'];
+                }
+                if (this.eleFireStatisticsData) {
+                    count += this.eleFireStatisticsData['30Day'];
+                }
+                if (this.gasMoStatisticsData) {
+                    count += this.gasMoStatisticsData['30Day'];
+                }
+                if (this.alarmMoStatisticsData) {
+                    count += this.alarmMoStatisticsData['30Day'];
+                }
+                if (this.fireWaterStatisticsData) {
+                    this.fireWaterStatisticsData.forEach((dataItem) => {
+                        count += dataItem['30Day'];
+                    })
+                }
+                this.totalAlarms = count;
+            },
+            setLevelData() {
+                if(this.data.fireDataCount){
+                    this.totalAlarms = this.data.fireDataCount;
+                }
+                if (this.data.alarmCountByLevel) {
+                    this.data.alarmCountByLevel.forEach(item => {
+                        if (item.alarmLevel == '3') {
+                            this.statistics.lv3 = item.alarmCount
+                        }
+                        if (item.alarmLevel == '2') {
+                            this.statistics.lv2 = item.alarmCount
+                        }
+                        if (item.alarmLevel == '1') {
+                            this.statistics.lv1 = item.alarmCount
+                        }
+                    })
+                }
+
+            },
+            navToMenu(index, path) {
+                this.activeMenuIndex = index
+                this.$router.push({
+                    path: path
+                })
+                this.$emit("navToMenu", path)
+            }
+        }
+    }
+</script>
+
+<style lang="scss">
+    .left-statistics {
+        width: 100%;
+
+
+        .statistics-info {
+            width: 365px;
+            color: #FFF;
+            height: calc(100vh - 250px);
+            border: 1px solid #116081;
+            background: rgba(10, 40, 74, .5);
+            padding: 15px;
+
+            .title-line {
+                overflow: hidden;
+
+
+                ul, li, h4 {
+                    margin: 0;
+                    padding: 0;
+                    list-style-type: none;
+                    color: #fff;
+                }
+
+                ul {
+                    border-bottom: 1px dashed #116081;
+                    overflow: hidden;
+                    padding: 15px 0 30px 0;
+                }
+
+                h4 {
+                    margin-top: 15px;
+                }
+
+                ul {
+                    margin: 15px 0 30px;
+                }
+
+                li {
+                    float: left;
+                    font-size: 14px;
+                    text-align: center;
+                    width: 33.33%;
+
+                    p {
+                        margin: 0 0 10px;
+                        color: #9faabe;
+
+                        &:last-child {
+                            font-size: 24px;
+                        }
+                    }
+                }
+
+                li:first-child {
+                    border: none;
+                    padding-left: 0;
+                }
+
+                h4 {
+                    font-size: 22px;
+
+                    .line {
+                        padding-top: 5px;
+                    }
+                }
+
+                .blue-text, .red-text, .yellow-text {
+                    font-weight: 600;
+                }
+
+                .blue-text {
+                    color: #24e3ff
+                }
+
+                .red-text {
+                    color: #cf540d;
+                }
+
+                .yellow-text {
+                    color: #fefe50;
+                }
+
+                .sum {
+                    font-size: 14px;
+
+                    span {
+                        margin-top: 28px;
+                        font-size: 26px;
+                        color: #20d5ef;
+                    }
+                }
+            }
+
+            .bottom-info {
+                margin-top: 50px;
+
+                .info-text {
+                    font-size: 14px;
+                    line-height: 2em;
+                }
+            }
+        }
+
+    }
+
+</style>
+
+<style scoped>
+
+
+</style>
+

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