diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..ea6e20f
--- /dev/null
+++ b/.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
diff --git a/.env.development b/.env.development
new file mode 100644
index 0000000..c50115e
--- /dev/null
+++ b/.env.development
@@ -0,0 +1,5 @@
+# just a flag
+ENV = 'development'
+
+# base api
+VUE_APP_BASE_API = '/dashengda'
diff --git a/.env.production b/.env.production
new file mode 100644
index 0000000..46ac0bb
--- /dev/null
+++ b/.env.production
@@ -0,0 +1,6 @@
+# just a flag
+ENV = 'production'
+
+# base api
+VUE_APP_BASE_API = '/dashengda'
+
diff --git a/.env.staging b/.env.staging
new file mode 100644
index 0000000..a8793a0
--- /dev/null
+++ b/.env.staging
@@ -0,0 +1,8 @@
+NODE_ENV = production
+
+# just a flag
+ENV = 'staging'
+
+# base api
+VUE_APP_BASE_API = '/stage-api'
+
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..e6529fc
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,4 @@
+build/*.js
+src/assets
+public
+dist
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..abf96de
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,274 @@
+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"],
+ },
+};
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9ad28d2
--- /dev/null
+++ b/.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
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..f4be7a0
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,5 @@
+language: node_js
+node_js: 10
+script: npm run test
+notifications:
+ email: false
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..6151575
--- /dev/null
+++ b/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.
diff --git a/README-zh.md b/README-zh.md
new file mode 100644
index 0000000..1beec9b
--- /dev/null
+++ b/README-zh.md
@@ -0,0 +1,111 @@
+# vue-admin-template
+
+> 这是一个极简的 vue admin 管理后台。它只包含了 Element UI & axios & iconfont & permission control & lint,这些搭建后台必要的东西。
+
+[线上地址](http://panjiachen.github.io/vue-admin-template)
+
+[国内访问](https://panjiachen.gitee.io/vue-admin-template)
+
+目前版本为 `v4.0+` 基于 `vue-cli` 进行构建,若你想使用旧版本,可以切换分支到[tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0),它不依赖 `vue-cli`。
+
+
+ SPONSORED BY
+
+
+
+
+
+
+
+## Extra
+
+如果你想要根据用户角色来动态生成侧边栏和 router,你可以使用该分支[permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control)
+
+## 相关项目
+
+- [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
+
+- [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
+
+- [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template)
+
+- [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312)
+
+写了一个系列的教程配套文章,如何从零构建后一个完整的后台项目:
+
+- [手摸手,带你用 vue 撸后台 系列一(基础篇)](https://juejin.im/post/59097cd7a22b9d0065fb61d2)
+- [手摸手,带你用 vue 撸后台 系列二(登录权限篇)](https://juejin.im/post/591aa14f570c35006961acac)
+- [手摸手,带你用 vue 撸后台 系列三 (实战篇)](https://juejin.im/post/593121aa0ce4630057f70d35)
+- [手摸手,带你用 vue 撸后台 系列四(vueAdmin 一个极简的后台基础模板,专门针对本项目的文章,算作是一篇文档)](https://juejin.im/post/595b4d776fb9a06bbe7dba56)
+- [手摸手,带你封装一个 vue component](https://segmentfault.com/a/1190000009090836)
+
+## Build Setup
+
+```bash
+# 克隆项目
+git clone https://github.com/PanJiaChen/vue-admin-template.git
+
+# 进入项目目录
+cd vue-admin-template
+
+# 安装依赖
+npm install
+
+# 建议不要直接使用 cnpm 安装以来,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
+npm install --registry=https://registry.npm.taobao.org
+
+# 启动服务
+npm run dev
+```
+
+浏览器访问 [http://localhost:9528](http://localhost:9528)
+
+## 发布
+
+```bash
+# 构建测试环境
+npm run build:stage
+
+# 构建生产环境
+npm run build:prod
+```
+
+## 其它
+
+```bash
+# 预览发布环境效果
+npm run preview
+
+# 预览发布环境效果 + 静态资源分析
+npm run preview -- --report
+
+# 代码格式检查
+npm run lint
+
+# 代码格式检查并自动修复
+npm run lint -- --fix
+```
+
+更多信息请参考 [使用文档](https://panjiachen.github.io/vue-element-admin-site/zh/)
+
+## 购买贴纸
+
+你也可以通过 购买[官方授权的贴纸](https://smallsticker.com/product/vue-element-admin) 的方式来支持 vue-element-admin - 每售出一张贴纸,我们将获得 2 元的捐赠。
+
+## Demo
+
+
+
+## Browsers support
+
+Modern browsers and Internet Explorer 10+.
+
+| [
](http://godban.github.io/browsers-support-badges/)IE / Edge | [
](http://godban.github.io/browsers-support-badges/)Firefox | [
](http://godban.github.io/browsers-support-badges/)Chrome | [
](http://godban.github.io/browsers-support-badges/)Safari |
+| --------- | --------- | --------- | --------- |
+| IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions
+
+## License
+
+[MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license.
+
+Copyright (c) 2017-present PanJiaChen
diff --git a/README.md b/README.md
index a8a61aa..fa54b78 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,99 @@
-# dashengda
+# vue-admin-template
+English | [简体中文](./README-zh.md)
+
+> A minimal vue admin template with Element UI & axios & iconfont & permission control & lint
+
+**Live demo:** http://panjiachen.github.io/vue-admin-template
+
+
+**The current version is `v4.0+` build on `vue-cli`. If you want to use the old version , you can switch branch to [tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0), it does not rely on `vue-cli`**
+
+
+ SPONSORED BY
+
+
+
+
+
+
+
+## Build Setup
+
+```bash
+# clone the project
+git clone https://github.com/PanJiaChen/vue-admin-template.git
+
+# enter the project directory
+cd vue-admin-template
+
+# install dependency
+npm install
+
+# develop
+npm run dev
+```
+
+This will automatically open http://localhost:9528
+
+## Build
+
+```bash
+# build for test environment
+npm run build:stage
+
+# build for production environment
+npm run build:prod
+```
+
+## Advanced
+
+```bash
+# preview the release environment effect
+npm run preview
+
+# preview the release environment effect + static resource analysis
+npm run preview -- --report
+
+# code format check
+npm run lint
+
+# code format check and auto fix
+npm run lint -- --fix
+```
+
+Refer to [Documentation](https://panjiachen.github.io/vue-element-admin-site/guide/essentials/deploy.html) for more information
+
+## Demo
+
+
+
+## Extra
+
+If you want router permission && generate menu by user roles , you can use this branch [permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control)
+
+For `typescript` version, you can use [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (Credits: [@Armour](https://github.com/Armour))
+
+## Related Project
+
+- [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
+
+- [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
+
+- [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template)
+
+- [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312)
+
+## Browsers support
+
+Modern browsers and Internet Explorer 10+.
+
+| [
](http://godban.github.io/browsers-support-badges/)IE / Edge | [
](http://godban.github.io/browsers-support-badges/)Firefox | [
](http://godban.github.io/browsers-support-badges/)Chrome | [
](http://godban.github.io/browsers-support-badges/)Safari |
+| --------- | --------- | --------- | --------- |
+| IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions
+
+## License
+
+[MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license.
+
+Copyright (c) 2017-present PanJiaChen
diff --git a/babel.config.js b/babel.config.js
new file mode 100644
index 0000000..fb82b27
--- /dev/null
+++ b/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']
+ }
+ }
+}
diff --git a/build/index.js b/build/index.js
new file mode 100644
index 0000000..0c57de2
--- /dev/null
+++ b/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}`)
+}
diff --git a/jest.config.js b/jest.config.js
new file mode 100644
index 0000000..143cdc8
--- /dev/null
+++ b/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: {
+ '^@/(.*)$': '/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: '/tests/unit/coverage',
+ // 'collectCoverage': true,
+ 'coverageReporters': [
+ 'lcov',
+ 'text-summary'
+ ],
+ testURL: 'http://localhost/'
+}
diff --git a/jsconfig.json b/jsconfig.json
new file mode 100644
index 0000000..ed079e2
--- /dev/null
+++ b/jsconfig.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "baseUrl": "./",
+ "paths": {
+ "@/*": ["src/*"]
+ }
+ },
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/mock/index.js b/mock/index.js
new file mode 100644
index 0000000..c514c13
--- /dev/null
+++ b/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
+}
+
diff --git a/mock/mock-server.js b/mock/mock-server.js
new file mode 100644
index 0000000..8941ec0
--- /dev/null
+++ b/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))
+ }
+ }
+ })
+}
diff --git a/mock/table.js b/mock/table.js
new file mode 100644
index 0000000..bd0e013
--- /dev/null
+++ b/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
+ }
+ }
+ }
+ }
+]
diff --git a/mock/user.js b/mock/user.js
new file mode 100644
index 0000000..7555338
--- /dev/null
+++ b/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'
+ }
+ }
+ }
+]
diff --git a/mock/utils.js b/mock/utils.js
new file mode 100644
index 0000000..95cc27d
--- /dev/null
+++ b/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
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..a68aae8
--- /dev/null
+++ b/package.json
@@ -0,0 +1,62 @@
+{
+ "name": "vue-admin-template",
+ "version": "4.4.0",
+ "description": "A vue admin template with Element UI & axios & iconfont & permission control & lint",
+ "author": "Pan ",
+ "scripts": {
+ "dev": "vue-cli-service serve",
+ "build:prod": "vue-cli-service build",
+ "build:stage": "vue-cli-service build --mode staging",
+ "preview": "node build/index.js --preview",
+ "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml",
+ "lint": "eslint --ext .js,.vue src",
+ "test:unit": "jest --clearCache && vue-cli-service test:unit",
+ "test:ci": "npm run lint && npm run test:unit"
+ },
+ "dependencies": {
+ "axios": "0.18.1",
+ "core-js": "3.6.5",
+ "element-ui": "^2.15.8",
+ "js-cookie": "2.2.0",
+ "normalize.css": "7.0.0",
+ "nprogress": "0.2.0",
+ "path-to-regexp": "2.4.0",
+ "vue": "2.6.10",
+ "vue-router": "3.0.6",
+ "vuex": "3.1.0"
+ },
+ "devDependencies": {
+ "@vue/cli-plugin-babel": "4.4.4",
+ "@vue/cli-plugin-eslint": "4.4.4",
+ "@vue/cli-plugin-unit-jest": "4.4.4",
+ "@vue/cli-service": "4.4.4",
+ "@vue/test-utils": "1.0.0-beta.29",
+ "autoprefixer": "9.5.1",
+ "babel-eslint": "10.1.0",
+ "babel-jest": "23.6.0",
+ "babel-plugin-dynamic-import-node": "2.3.3",
+ "chalk": "2.4.2",
+ "connect": "3.6.6",
+ "eslint": "6.7.2",
+ "eslint-plugin-vue": "6.2.2",
+ "html-webpack-plugin": "3.2.0",
+ "mockjs": "1.0.1-beta3",
+ "runjs": "4.3.2",
+ "sass": "1.26.8",
+ "sass-loader": "8.0.2",
+ "script-ext-html-webpack-plugin": "2.1.3",
+ "serve-static": "1.13.2",
+ "svg-sprite-loader": "4.1.3",
+ "svgo": "1.2.2",
+ "vue-template-compiler": "2.6.10"
+ },
+ "browserslist": [
+ "> 1%",
+ "last 2 versions"
+ ],
+ "engines": {
+ "node": ">=8.9",
+ "npm": ">= 3.0.0"
+ },
+ "license": "MIT"
+}
diff --git a/postcss.config.js b/postcss.config.js
new file mode 100644
index 0000000..10473ef
--- /dev/null
+++ b/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': {}
+ }
+}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..34b63ac
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..fa2be91
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+ <%= webpackConfig.name %>
+
+
+
+
+
+
+
diff --git a/src/App.vue b/src/App.vue
new file mode 100644
index 0000000..ec9032c
--- /dev/null
+++ b/src/App.vue
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/src/api/table.js b/src/api/table.js
new file mode 100644
index 0000000..2752f52
--- /dev/null
+++ b/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
+ })
+}
diff --git a/src/api/terminal/parameterBoundary.js b/src/api/terminal/parameterBoundary.js
new file mode 100644
index 0000000..8d59914
--- /dev/null
+++ b/src/api/terminal/parameterBoundary.js
@@ -0,0 +1,25 @@
+import request from '@/utils/request'
+
+export function getList(data) {
+ return request({
+ url: '/device/params/page',
+ method: 'get',
+ params: data || {}
+ })
+}
+
+export function add(data) {
+ return request({
+ url: '/device/params/addDeviceParams',
+ method: 'post',
+ data
+ })
+}
+
+export function update(data) {
+ return request({
+ url: '/device/params/update',
+ method: 'post',
+ data
+ })
+}
diff --git a/src/api/terminal/parameterSetting.js b/src/api/terminal/parameterSetting.js
new file mode 100644
index 0000000..1d32f92
--- /dev/null
+++ b/src/api/terminal/parameterSetting.js
@@ -0,0 +1,9 @@
+import request from '@/utils/request'
+
+export function getList(data) {
+ return request({
+ url: '/device/type/data/page',
+ method: 'get',
+ params: data || {}
+ })
+}
diff --git a/src/api/terminal/physicalInfo.js b/src/api/terminal/physicalInfo.js
new file mode 100644
index 0000000..53146e3
--- /dev/null
+++ b/src/api/terminal/physicalInfo.js
@@ -0,0 +1,20 @@
+import request from '@/utils/request'
+
+export function getList(data) {
+ return request({
+ url: '/device/info/page',
+ method: 'get',
+ params: data || {}
+ })
+}
+
+export function add(data) {
+ return request({
+ url: '/device/info/addDeviceInfo',
+ method: 'post',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ data
+ })
+}
diff --git a/src/api/terminal/sensorType.js b/src/api/terminal/sensorType.js
new file mode 100644
index 0000000..8f182bd
--- /dev/null
+++ b/src/api/terminal/sensorType.js
@@ -0,0 +1,17 @@
+import request from '@/utils/request'
+
+export function getList(data) {
+ return request({
+ url: '/device/data/basePage',
+ method: 'get',
+ params: data || {}
+ })
+}
+
+export function getParamList(data) {
+ return request({
+ url: '/device/data/allParams',
+ method: 'get',
+ params: data || {}
+ })
+}
diff --git a/src/api/terminal/structure.js b/src/api/terminal/structure.js
new file mode 100644
index 0000000..0c6501f
--- /dev/null
+++ b/src/api/terminal/structure.js
@@ -0,0 +1,37 @@
+import request from '@/utils/request'
+
+export function getList(data) {
+ return request({
+ url: '/device/type/page',
+ method: 'get',
+ params: data || {}
+ })
+}
+
+export function getParamList(data) {
+ return request({
+ url: '/device/type/data/page',
+ method: 'get',
+ params: data || {}
+ })
+}
+
+export function add(data) {
+ return request({
+ url: '/device/type/addDeviceType',
+ method: 'post',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ data
+ })
+}
+
+export function update(data) {
+ return request({
+ url: '/device/type/update',
+ method: 'post',
+ data
+ })
+}
+
diff --git a/src/api/user/index.js b/src/api/user/index.js
new file mode 100644
index 0000000..f4f7e1b
--- /dev/null
+++ b/src/api/user/index.js
@@ -0,0 +1,59 @@
+import request from '@/utils/request'
+
+export function login(data) {
+ return request({
+ url: '/login',
+ method: 'post',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ data
+ })
+}
+
+export function getGifCode(data) {
+ return request({
+ url: '/getGifCode',
+ method: 'get',
+ responseType: 'blob',
+ params: {}
+ })
+}
+
+export function getInfo(token) {
+ return request({
+ url: '/vue-admin-template/user/info',
+ method: 'get',
+ params: { token }
+ })
+}
+
+export function getList(data) {
+ return request({
+ url: '/user/page',
+ method: 'get',
+ params: data || {}
+ })
+}
+
+export function add(data) {
+ return request({
+ url: '/user/add',
+ method: 'post',
+ data
+ })
+}
+
+export function update(data) {
+ return request({
+ url: '/user/update',
+ method: 'post',
+ data
+ })
+}
+export function logout() {
+ return request({
+ url: '/vue-admin-template/user/logout',
+ method: 'post'
+ })
+}
diff --git a/src/assets/404_images/404.png b/src/assets/404_images/404.png
new file mode 100644
index 0000000..3d8e230
Binary files /dev/null and b/src/assets/404_images/404.png differ
diff --git a/src/assets/404_images/404_cloud.png b/src/assets/404_images/404_cloud.png
new file mode 100644
index 0000000..c6281d0
Binary files /dev/null and b/src/assets/404_images/404_cloud.png differ
diff --git a/src/assets/images/avatar.png b/src/assets/images/avatar.png
new file mode 100644
index 0000000..84d546b
Binary files /dev/null and b/src/assets/images/avatar.png differ
diff --git a/src/assets/images/background.png b/src/assets/images/background.png
new file mode 100644
index 0000000..e7381e7
Binary files /dev/null and b/src/assets/images/background.png differ
diff --git a/src/assets/images/logo.png b/src/assets/images/logo.png
new file mode 100644
index 0000000..b9fb142
Binary files /dev/null and b/src/assets/images/logo.png differ
diff --git a/src/assets/images/sidebarLogo.png b/src/assets/images/sidebarLogo.png
new file mode 100644
index 0000000..3fc6610
Binary files /dev/null and b/src/assets/images/sidebarLogo.png differ
diff --git a/src/components/Breadcrumb/index.vue b/src/components/Breadcrumb/index.vue
new file mode 100644
index 0000000..b276c01
--- /dev/null
+++ b/src/components/Breadcrumb/index.vue
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+ {{ item.meta.title }}
+
+
+
+
{{ levelList[levelList.length-1].meta.title }}
+
+
+
+
+
+
diff --git a/src/components/Hamburger/index.vue b/src/components/Hamburger/index.vue
new file mode 100644
index 0000000..368b002
--- /dev/null
+++ b/src/components/Hamburger/index.vue
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
diff --git a/src/components/Pagination/index.vue b/src/components/Pagination/index.vue
new file mode 100644
index 0000000..6d5d753
--- /dev/null
+++ b/src/components/Pagination/index.vue
@@ -0,0 +1,55 @@
+
+
+
+
+
+
diff --git a/src/components/SvgIcon/index.vue b/src/components/SvgIcon/index.vue
new file mode 100644
index 0000000..b07ded2
--- /dev/null
+++ b/src/components/SvgIcon/index.vue
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
diff --git a/src/icons/index.js b/src/icons/index.js
new file mode 100644
index 0000000..2c6b309
--- /dev/null
+++ b/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)
diff --git a/src/icons/svg/Group 3 Copy.svg b/src/icons/svg/Group 3 Copy.svg
new file mode 100644
index 0000000..3d4633b
--- /dev/null
+++ b/src/icons/svg/Group 3 Copy.svg
@@ -0,0 +1,26 @@
+
diff --git a/src/icons/svg/dashboard.svg b/src/icons/svg/dashboard.svg
new file mode 100644
index 0000000..5317d37
--- /dev/null
+++ b/src/icons/svg/dashboard.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/enviroment.svg b/src/icons/svg/enviroment.svg
new file mode 100644
index 0000000..a45d2f0
--- /dev/null
+++ b/src/icons/svg/enviroment.svg
@@ -0,0 +1,21 @@
+
diff --git a/src/icons/svg/example.svg b/src/icons/svg/example.svg
new file mode 100644
index 0000000..46f42b5
--- /dev/null
+++ b/src/icons/svg/example.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/eye-open.svg b/src/icons/svg/eye-open.svg
new file mode 100644
index 0000000..88dcc98
--- /dev/null
+++ b/src/icons/svg/eye-open.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/eye.svg b/src/icons/svg/eye.svg
new file mode 100644
index 0000000..16ed2d8
--- /dev/null
+++ b/src/icons/svg/eye.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/form.svg b/src/icons/svg/form.svg
new file mode 100644
index 0000000..dcbaa18
--- /dev/null
+++ b/src/icons/svg/form.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/link.svg b/src/icons/svg/link.svg
new file mode 100644
index 0000000..48197ba
--- /dev/null
+++ b/src/icons/svg/link.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/lock.svg b/src/icons/svg/lock.svg
new file mode 100644
index 0000000..f340af6
--- /dev/null
+++ b/src/icons/svg/lock.svg
@@ -0,0 +1,15 @@
+
diff --git a/src/icons/svg/nested.svg b/src/icons/svg/nested.svg
new file mode 100644
index 0000000..06713a8
--- /dev/null
+++ b/src/icons/svg/nested.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/password.svg b/src/icons/svg/password.svg
new file mode 100644
index 0000000..e291d85
--- /dev/null
+++ b/src/icons/svg/password.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/table.svg b/src/icons/svg/table.svg
new file mode 100644
index 0000000..0e3dc9d
--- /dev/null
+++ b/src/icons/svg/table.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/terminal.svg b/src/icons/svg/terminal.svg
new file mode 100644
index 0000000..924a25a
--- /dev/null
+++ b/src/icons/svg/terminal.svg
@@ -0,0 +1,11 @@
+
diff --git a/src/icons/svg/tree.svg b/src/icons/svg/tree.svg
new file mode 100644
index 0000000..dd4b7dd
--- /dev/null
+++ b/src/icons/svg/tree.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/user.svg b/src/icons/svg/user.svg
new file mode 100644
index 0000000..0ba0716
--- /dev/null
+++ b/src/icons/svg/user.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/icons/svg/userGroup.svg b/src/icons/svg/userGroup.svg
new file mode 100644
index 0000000..5cd831f
--- /dev/null
+++ b/src/icons/svg/userGroup.svg
@@ -0,0 +1,13 @@
+
diff --git a/src/icons/svgo.yml b/src/icons/svgo.yml
new file mode 100644
index 0000000..d11906a
--- /dev/null
+++ b/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'
diff --git a/src/layout/components/AppMain.vue b/src/layout/components/AppMain.vue
new file mode 100644
index 0000000..f6a3286
--- /dev/null
+++ b/src/layout/components/AppMain.vue
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/Navbar.vue b/src/layout/components/Navbar.vue
new file mode 100644
index 0000000..c6785ac
--- /dev/null
+++ b/src/layout/components/Navbar.vue
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/Sidebar/FixiOSBug.js b/src/layout/components/Sidebar/FixiOSBug.js
new file mode 100644
index 0000000..bc14856
--- /dev/null
+++ b/src/layout/components/Sidebar/FixiOSBug.js
@@ -0,0 +1,26 @@
+export default {
+ computed: {
+ device() {
+ return this.$store.state.app.device
+ }
+ },
+ mounted() {
+ // In order to fix the click on menu on the ios device will trigger the mouseleave bug
+ // https://github.com/PanJiaChen/vue-element-admin/issues/1135
+ this.fixBugIniOS()
+ },
+ methods: {
+ fixBugIniOS() {
+ const $subMenu = this.$refs.subMenu
+ if ($subMenu) {
+ const handleMouseleave = $subMenu.handleMouseleave
+ $subMenu.handleMouseleave = (e) => {
+ if (this.device === 'mobile') {
+ return
+ }
+ handleMouseleave(e)
+ }
+ }
+ }
+ }
+}
diff --git a/src/layout/components/Sidebar/Item.vue b/src/layout/components/Sidebar/Item.vue
new file mode 100644
index 0000000..aa1f5da
--- /dev/null
+++ b/src/layout/components/Sidebar/Item.vue
@@ -0,0 +1,41 @@
+
+
+
diff --git a/src/layout/components/Sidebar/Link.vue b/src/layout/components/Sidebar/Link.vue
new file mode 100644
index 0000000..530b3d5
--- /dev/null
+++ b/src/layout/components/Sidebar/Link.vue
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
diff --git a/src/layout/components/Sidebar/Logo.vue b/src/layout/components/Sidebar/Logo.vue
new file mode 100644
index 0000000..bc5e498
--- /dev/null
+++ b/src/layout/components/Sidebar/Logo.vue
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
diff --git a/src/layout/components/Sidebar/SidebarItem.vue b/src/layout/components/Sidebar/SidebarItem.vue
new file mode 100644
index 0000000..728265d
--- /dev/null
+++ b/src/layout/components/Sidebar/SidebarItem.vue
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/Sidebar/index.vue b/src/layout/components/Sidebar/index.vue
new file mode 100644
index 0000000..702fdb0
--- /dev/null
+++ b/src/layout/components/Sidebar/index.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layout/components/index.js b/src/layout/components/index.js
new file mode 100644
index 0000000..97ee3cd
--- /dev/null
+++ b/src/layout/components/index.js
@@ -0,0 +1,3 @@
+export { default as Navbar } from './Navbar'
+export { default as Sidebar } from './Sidebar'
+export { default as AppMain } from './AppMain'
diff --git a/src/layout/index.vue b/src/layout/index.vue
new file mode 100644
index 0000000..5261a09
--- /dev/null
+++ b/src/layout/index.vue
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
diff --git a/src/layout/mixin/ResizeHandler.js b/src/layout/mixin/ResizeHandler.js
new file mode 100644
index 0000000..b43c1d9
--- /dev/null
+++ b/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 })
+ }
+ }
+ }
+ }
+}
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 0000000..dc36cd0
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,39 @@
+import Vue from 'vue'
+
+import 'normalize.css/normalize.css' // A modern alternative to CSS resets
+
+import ElementUI from 'element-ui'
+import 'element-ui/lib/theme-chalk/index.css'
+
+import '@/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)
+
+Vue.config.productionTip = false
+
+new Vue({
+ el: '#app',
+ router,
+ store,
+ render: h => h(App)
+})
diff --git a/src/permission.js b/src/permission.js
new file mode 100644
index 0000000..3c04a4e
--- /dev/null
+++ b/src/permission.js
@@ -0,0 +1,56 @@
+/*
+ * @Author: 龚祖望 573413756@qq.com
+ * @Date: 2022-05-16 09:16:41
+ * @LastEditors: 龚祖望 573413756@qq.com
+ * @LastEditTime: 2022-05-18 15:34:47
+ * @FilePath: \dashengda\src\permission.js
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+import router from './router'
+import store from './store'
+import { Message } from 'element-ui'
+import NProgress from 'nprogress' // progress bar
+import 'nprogress/nprogress.css' // progress bar style
+import { getToken } from '@/utils/auth' // get token from cookie
+import getPageTitle from '@/utils/get-page-title'
+
+NProgress.configure({ showSpinner: false }) // NProgress Configuration
+
+const whiteList = ['/login'] // no redirect whitelist
+
+router.beforeEach(async(to, from, next) => {
+ // start progress bar
+ NProgress.start()
+
+ // set page title
+ document.title = getPageTitle(to.meta.title)
+ // determine whether the user has logged in
+ const hasToken = getToken()
+
+ if (hasToken) {
+ if (to.path === '/login') {
+ // if is logged in, redirect to the home page
+ next({ path: '/' })
+ NProgress.done()
+ } else {
+ next()
+ NProgress.done()
+ }
+ } else {
+ /* has no token*/
+
+ if (whiteList.indexOf(to.path) !== -1) {
+ // in the free login whitelist, go directly
+ next()
+ } else {
+ // other pages that do not have permission to access are redirected to the login page.
+ next(`/login?redirect=${to.path}`)
+ NProgress.done()
+ }
+ }
+})
+
+router.afterEach(() => {
+ // finish progress bar
+ NProgress.done()
+})
diff --git a/src/router/index.js b/src/router/index.js
new file mode 100644
index 0000000..9e7465b
--- /dev/null
+++ b/src/router/index.js
@@ -0,0 +1,190 @@
+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 (must set!!!)
+ * meta : {
+ roles: ['admin','editor'] control the page roles (you can set multiple roles)
+ title: 'title' the name show in sidebar and breadcrumb (recommend set)
+ icon: 'svg-name'/'el-icon-x' the icon show in the sidebar
+ breadcrumb: false if set false, the item will hidden in breadcrumb(default is true)
+ activeMenu: '/example/list' if set path, the sidebar will highlight the path you set
+ }
+ */
+
+/**
+ * constantRoutes
+ * a base page that does not have permission requirements
+ * all roles can be accessed
+ */
+export const constantRoutes = [
+ {
+ path: '/login',
+ component: () => import('@/views/login/index'),
+ hidden: true
+ },
+ {
+ path: '/404',
+ component: () => import('@/views/404'),
+ hidden: true
+ },
+
+ {
+ path: '/',
+ redirect: '/home',
+ component: Layout,
+ children: [{
+ path: 'home',
+ name: 'Home',
+ component: () => import('@/views/home/index'),
+ meta: { title: '首页', icon: 'dashboard' }
+ }]
+ },
+ {
+ path: '/user',
+ component: Layout,
+ name: 'User',
+ meta: { title: '用户管理', icon: 'userGroup' },
+ children: [
+ {
+ path: 'list',
+ name: 'List',
+ component: () => import('@/views/user/index'),
+ meta: { title: '用户列表' }
+ }
+ ]
+ },
+ {
+ path: '/terminal',
+ component: Layout,
+ name: 'Terminal',
+ meta: { title: '终端管理', icon: 'terminal' },
+ children: [
+ {
+ path: 'structure',
+ name: 'Structure',
+ component: () => import('@/views/terminal/structure/index'),
+ meta: { title: '终端体系结构' }
+ },
+ {
+ path: 'physicalInfo',
+ name: 'PhysicalInfo',
+ component: () => import('@/views/terminal/physicalInfo/index'),
+ meta: { title: '终端物理信息' }
+ },
+ {
+ path: 'parameterBoundary',
+ name: 'ParameterBoundary',
+ component: () => import('@/views/terminal/parameterBoundary/index'),
+ meta: { title: '环境参数界限' }
+ },
+ {
+ path: 'sensorType',
+ name: 'SensorType',
+ component: () => import('@/views/terminal/sensorType/index'),
+ meta: { title: '终端传感种类' }
+ },
+ {
+ path: 'parameterSetting',
+ name: 'ParameterSetting',
+ component: () => import('@/views/terminal/parameterSetting/index'),
+ meta: { title: '环境参数配置' }
+ }
+ ]
+ },
+ {
+ path: '/enviromentData',
+ component: Layout,
+ name: 'EnviromentData',
+ meta: { title: '环境数据', icon: 'enviroment' },
+ children: [
+ {
+ path: 'realTime',
+ name: 'RealTime',
+ component: () => import('@/views/enviromentData/realTime/index'),
+ meta: { title: '实时数据' }
+ },
+ {
+ path: 'dailyAverage',
+ name: 'DailyAverage',
+ component: () => import('@/views/enviromentData/dailyAverage/index'),
+ meta: { title: '日平均数据' }
+ },
+ {
+ path: 'monthlyAverage',
+ name: 'MonthlyAverage',
+ component: () => import('@/views/enviromentData/monthlyAverage/index'),
+ meta: { title: '月平均数据' }
+ }
+ ]
+ },
+ {
+ path: '/ammeterData',
+ component: Layout,
+ name: 'AmmeterData',
+ meta: { title: '电表数据', icon: 'Group 3 Copy' }
+ // children: [
+ // {
+ // path: 'realTime',
+ // name: 'RealTime',
+ // component: () => import('@/views/enviromentData/realTime/index'),
+ // meta: { title: '实时数据', icon: 'table' }
+ // },
+ // {
+ // path: 'dailyAverage',
+ // name: 'DailyAverage',
+ // component: () => import('@/views/enviromentData/dailyAverage/index'),
+ // meta: { title: '日平均数据', icon: 'table' }
+ // },
+ // {
+ // path: 'monthlyAverage',
+ // name: 'MonthlyAverage',
+ // component: () => import('@/views/enviromentData/monthlyAverage/index'),
+ // meta: { title: '月平均数据', icon: 'table' }
+ // }
+ // ]
+ },
+
+ // {
+ // path: 'external-link',
+ // component: Layout,
+ // children: [
+ // {
+ // path: 'https://panjiachen.github.io/vue-element-admin-site/#/',
+ // meta: { title: 'External Link', icon: 'link' }
+ // }
+ // ]
+ // },
+
+ // 404 page must be placed at the end !!!
+ { path: '*', redirect: '/404', hidden: true }
+]
+
+const createRouter = () => new Router({
+ mode: 'history', // require service support
+ scrollBehavior: () => ({ y: 0 }),
+ routes: constantRoutes
+})
+
+const router = createRouter()
+
+// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
+export function resetRouter() {
+ const newRouter = createRouter()
+ router.matcher = newRouter.matcher // reset router
+}
+
+export default router
diff --git a/src/settings.js b/src/settings.js
new file mode 100644
index 0000000..ae3c494
--- /dev/null
+++ b/src/settings.js
@@ -0,0 +1,16 @@
+module.exports = {
+
+ title: 'Vue Admin Template',
+
+ /**
+ * @type {boolean} true | false
+ * @description Whether fix the header
+ */
+ fixedHeader: false,
+
+ /**
+ * @type {boolean} true | false
+ * @description Whether show the logo in sidebar
+ */
+ sidebarLogo: false
+}
diff --git a/src/store/getters.js b/src/store/getters.js
new file mode 100644
index 0000000..5ab7b4c
--- /dev/null
+++ b/src/store/getters.js
@@ -0,0 +1,8 @@
+const getters = {
+ sidebar: state => state.app.sidebar,
+ device: state => state.app.device,
+ token: state => state.user.token,
+ avatar: state => state.user.avatar,
+ name: state => state.user.name
+}
+export default getters
diff --git a/src/store/index.js b/src/store/index.js
new file mode 100644
index 0000000..6be466a
--- /dev/null
+++ b/src/store/index.js
@@ -0,0 +1,19 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import getters from './getters'
+import app from './modules/app'
+import settings from './modules/settings'
+import user from './modules/user'
+
+Vue.use(Vuex)
+
+const store = new Vuex.Store({
+ modules: {
+ app,
+ settings,
+ user
+ },
+ getters
+})
+
+export default store
diff --git a/src/store/modules/app.js b/src/store/modules/app.js
new file mode 100644
index 0000000..24d29cd
--- /dev/null
+++ b/src/store/modules/app.js
@@ -0,0 +1,48 @@
+import Cookies from 'js-cookie'
+
+const state = {
+ sidebar: {
+ opened: 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
+}
diff --git a/src/store/modules/settings.js b/src/store/modules/settings.js
new file mode 100644
index 0000000..b3f33f8
--- /dev/null
+++ b/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
+}
+
diff --git a/src/store/modules/user.js b/src/store/modules/user.js
new file mode 100644
index 0000000..5db429c
--- /dev/null
+++ b/src/store/modules/user.js
@@ -0,0 +1,108 @@
+/*
+ * @Author: 龚祖望 573413756@qq.com
+ * @Date: 2022-05-16 09:16:41
+ * @LastEditors: 龚祖望 573413756@qq.com
+ * @LastEditTime: 2022-05-30 15:42:18
+ * @FilePath: \dashengda\src\store\modules\user.js
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+import { login, logout } from '@/api/user'
+import { getToken, setToken, removeToken } from '@/utils/auth'
+import { resetRouter } from '@/router'
+
+const getDefaultState = () => {
+ return {
+ token: getToken(),
+ name: '',
+ avatar: ''
+ }
+}
+
+const state = getDefaultState()
+
+const mutations = {
+ RESET_STATE: (state) => {
+ Object.assign(state, getDefaultState())
+ },
+ SET_TOKEN: (state, name) => {
+ state.name = name
+ },
+ SET_NAME: (state, token) => {
+ state.token = token
+ },
+ SET_AVATAR: (state, avatar) => {
+ state.avatar = avatar
+ }
+}
+
+const actions = {
+ // user login
+ login({ commit }, userInfo) {
+ const { username, password, verifycode } = userInfo
+ const data = new FormData()
+ data.append('loginName', username.trim())
+ data.append('password', password)
+ data.append('security_code', verifycode)
+ return new Promise((resolve, reject) => {
+ login(data).then(response => {
+ commit('SET_TOKEN', 'success')
+ setToken('success')
+ resolve()
+ }).catch(error => {
+ reject(error)
+ })
+ })
+ },
+
+ // // get user info
+ // getInfo({ commit, state }) {
+ // return new Promise((resolve, reject) => {
+ // getInfo(state.token).then(response => {
+ // const { data } = response
+
+ // if (!data) {
+ // return reject('Verification failed, please Login again.')
+ // }
+
+ // const { name, avatar } = data
+
+ // commit('SET_NAME', name)
+ // commit('SET_AVATAR', avatar)
+ // resolve(data)
+ // }).catch(error => {
+ // reject(error)
+ // })
+ // })
+ // },
+
+ // user logout
+ logout({ commit, state }) {
+ return new Promise((resolve, reject) => {
+ logout(state.token).then(() => {
+ removeToken() // must remove token first
+ resetRouter()
+ commit('RESET_STATE')
+ resolve()
+ }).catch(error => {
+ reject(error)
+ })
+ })
+ },
+
+ // remove token
+ resetToken({ commit }) {
+ return new Promise(resolve => {
+ removeToken() // must remove token first
+ commit('RESET_STATE')
+ resolve()
+ })
+ }
+}
+
+export default {
+ namespaced: true,
+ state,
+ mutations,
+ actions
+}
+
diff --git a/src/styles/element-ui.scss b/src/styles/element-ui.scss
new file mode 100644
index 0000000..be6bdd3
--- /dev/null
+++ b/src/styles/element-ui.scss
@@ -0,0 +1,68 @@
+// 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;
+}
+
+.el-form-item__content .el-input {
+ width: 80%;
+}
+
+.el-form-item__content .el-date-editor--daterange.el-input__inner {
+ width: 80%;
+}
+
+.el-card {
+ margin-bottom: 16px;
+}
+
+.el-dialog__footer {
+ text-align: center;
+}
+
+.el-dialog__header {
+ background-color: #fafafa;
+}
diff --git a/src/styles/index.scss b/src/styles/index.scss
new file mode 100644
index 0000000..ce42254
--- /dev/null
+++ b/src/styles/index.scss
@@ -0,0 +1,71 @@
+@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;
+ }
+}
+
+input::-ms-clear,
+input::-ms-reveal {
+ display: none;
+}
+
+// main-container global css
+.app-container {
+ padding: 20px;
+}
diff --git a/src/styles/mixin.scss b/src/styles/mixin.scss
new file mode 100644
index 0000000..36b74bb
--- /dev/null
+++ b/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%;
+}
diff --git a/src/styles/sidebar.scss b/src/styles/sidebar.scss
new file mode 100644
index 0000000..94760cc
--- /dev/null
+++ b/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;
+ }
+ }
+}
diff --git a/src/styles/transition.scss b/src/styles/transition.scss
new file mode 100644
index 0000000..4cb27cc
--- /dev/null
+++ b/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;
+}
diff --git a/src/styles/variables.scss b/src/styles/variables.scss
new file mode 100644
index 0000000..f73a07d
--- /dev/null
+++ b/src/styles/variables.scss
@@ -0,0 +1,25 @@
+// sidebar
+$menuText: #bfcbd9;
+$menuActiveText: #409eff;
+$subMenuActiveText: #f4f4f5; //https://github.com/ElemeFE/element/issues/12951
+
+$menuBg: #030653;
+$menuHover: #1890ff;
+
+$subMenuBg: #030653;
+$subMenuHover: #1890ff;
+
+$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;
+}
diff --git a/src/utils/auth.js b/src/utils/auth.js
new file mode 100644
index 0000000..059af18
--- /dev/null
+++ b/src/utils/auth.js
@@ -0,0 +1,15 @@
+import Cookies from 'js-cookie'
+
+const TokenKey = 'vue_admin_template_token'
+
+export function getToken() {
+ return Cookies.get(TokenKey)
+}
+
+export function setToken(token) {
+ return Cookies.set(TokenKey, token)
+}
+
+export function removeToken() {
+ return Cookies.remove(TokenKey)
+}
diff --git a/src/utils/get-page-title.js b/src/utils/get-page-title.js
new file mode 100644
index 0000000..a6de99d
--- /dev/null
+++ b/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}`
+}
diff --git a/src/utils/index.js b/src/utils/index.js
new file mode 100644
index 0000000..4830c04
--- /dev/null
+++ b/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
+}
diff --git a/src/utils/request.js b/src/utils/request.js
new file mode 100644
index 0000000..cda96f7
--- /dev/null
+++ b/src/utils/request.js
@@ -0,0 +1,93 @@
+/*
+ * @Author: 龚祖望 573413756@qq.com
+ * @Date: 2022-05-16 09:16:41
+ * @LastEditors: 龚祖望 573413756@qq.com
+ * @LastEditTime: 2022-05-30 15:43:18
+ * @FilePath: \dashengda\src\utils\request.js
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+ */
+import axios from 'axios'
+import { Message } from 'element-ui'
+import store from '@/store'
+import { getToken } from '@/utils/auth'
+
+// create an axios instance
+const service = axios.create({
+ baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
+ // withCredentials: true, // send cookies when cross-domain requests
+ timeout: 5000 // request timeout
+})
+
+// request interceptor
+service.interceptors.request.use(
+ config => {
+ // do something before request is sent
+
+ if (store.getters.token) {
+ // let each request carry token
+ // ['X-Token'] is a custom headers key
+ // please modify it according to the actual situation
+ config.headers['X-Token'] = getToken()
+ }
+ return config
+ },
+ error => {
+ // do something with request error
+ console.log(error) // for debug
+ return Promise.reject(error)
+ }
+)
+
+// response interceptor
+service.interceptors.response.use(
+ /**
+ * If you want to get http information such as headers or status
+ * Please return response => response
+ */
+
+ /**
+ * Determine the request status by custom code
+ * Here is just an example
+ * You can also judge the status by HTTP Status Code
+ */
+ response => {
+ const res = response.data
+
+ // if the custom code is not 20000, it is judged as an error.
+ // if (res.code !== 20000) {
+ // Message({
+ // message: res.message || 'Error',
+ // type: 'error',
+ // duration: 5 * 1000
+ // })
+
+ // // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
+ // if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
+ // // to re-login
+ // MessageBox.confirm('You have been logged out, you can cancel to stay on this page, or log in again', 'Confirm logout', {
+ // confirmButtonText: 'Re-Login',
+ // cancelButtonText: 'Cancel',
+ // type: 'warning'
+ // }).then(() => {
+ // store.dispatch('user/resetToken').then(() => {
+ // location.reload()
+ // })
+ // })
+ // }
+ // return Promise.reject(new Error(res.message || 'Error'))
+ // } else {
+ return res
+ // }
+ },
+ error => {
+ console.log('err' + error) // for debug
+ Message({
+ message: error.message,
+ type: 'error',
+ duration: 5 * 1000
+ })
+ return Promise.reject(error)
+ }
+)
+
+export default service
diff --git a/src/utils/validate.js b/src/utils/validate.js
new file mode 100644
index 0000000..8d962ad
--- /dev/null
+++ b/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
+}
diff --git a/src/views/404.vue b/src/views/404.vue
new file mode 100644
index 0000000..1791f55
--- /dev/null
+++ b/src/views/404.vue
@@ -0,0 +1,228 @@
+
+
+
+
+
+
OOPS!
+
+
{{ message }}
+
Please check that the URL you entered is correct, or click the button below to return to the homepage.
+
Back to home
+
+
+
+
+
+
+
+
diff --git a/src/views/enviromentData/dailyAverage/index.vue b/src/views/enviromentData/dailyAverage/index.vue
new file mode 100644
index 0000000..e69de29
diff --git a/src/views/enviromentData/monthlyAverage/index.vue b/src/views/enviromentData/monthlyAverage/index.vue
new file mode 100644
index 0000000..e69de29
diff --git a/src/views/enviromentData/realTime/index.vue b/src/views/enviromentData/realTime/index.vue
new file mode 100644
index 0000000..e69de29
diff --git a/src/views/form/index.vue b/src/views/form/index.vue
new file mode 100644
index 0000000..f4d66d3
--- /dev/null
+++ b/src/views/form/index.vue
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Create
+ Cancel
+
+
+
+
+
+
+
+
+
diff --git a/src/views/home/index.vue b/src/views/home/index.vue
new file mode 100644
index 0000000..b913d5b
--- /dev/null
+++ b/src/views/home/index.vue
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
diff --git a/src/views/login/index.vue b/src/views/login/index.vue
new file mode 100644
index 0000000..e4eced5
--- /dev/null
+++ b/src/views/login/index.vue
@@ -0,0 +1,250 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/views/nested/menu1/index.vue b/src/views/nested/menu1/index.vue
new file mode 100644
index 0000000..30cb670
--- /dev/null
+++ b/src/views/nested/menu1/index.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/src/views/nested/menu1/menu1-1/index.vue b/src/views/nested/menu1/menu1-1/index.vue
new file mode 100644
index 0000000..27e173a
--- /dev/null
+++ b/src/views/nested/menu1/menu1-1/index.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/src/views/nested/menu1/menu1-2/index.vue b/src/views/nested/menu1/menu1-2/index.vue
new file mode 100644
index 0000000..0c86276
--- /dev/null
+++ b/src/views/nested/menu1/menu1-2/index.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue b/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue
new file mode 100644
index 0000000..f87d88f
--- /dev/null
+++ b/src/views/nested/menu1/menu1-2/menu1-2-1/index.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue b/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue
new file mode 100644
index 0000000..d88789f
--- /dev/null
+++ b/src/views/nested/menu1/menu1-2/menu1-2-2/index.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/views/nested/menu1/menu1-3/index.vue b/src/views/nested/menu1/menu1-3/index.vue
new file mode 100644
index 0000000..f7cd073
--- /dev/null
+++ b/src/views/nested/menu1/menu1-3/index.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/views/nested/menu2/index.vue b/src/views/nested/menu2/index.vue
new file mode 100644
index 0000000..19dd48f
--- /dev/null
+++ b/src/views/nested/menu2/index.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/views/terminal/parameterBoundary/index.vue b/src/views/terminal/parameterBoundary/index.vue
new file mode 100644
index 0000000..e1b414e
--- /dev/null
+++ b/src/views/terminal/parameterBoundary/index.vue
@@ -0,0 +1,297 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查询
+ 新增
+
+
+
+
+
+
+
+
+ {{ scope.row.name }}
+
+
+
+
+ {{ scope.row.minValue + scope.row.unit }}
+
+
+
+
+ {{ scope.row.maxValue + scope.row.unit }}
+
+
+
+
+ {{ scope.row.createTime }}
+
+
+
+
+ {{ scope.row.creator }}
+
+
+
+
+ {{ scope.row.lastUpdTime }}
+
+
+
+
+ {{ scope.row.modifier }}
+
+
+
+
+ 编辑
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/terminal/parameterSetting/index.vue b/src/views/terminal/parameterSetting/index.vue
new file mode 100644
index 0000000..b3795fb
--- /dev/null
+++ b/src/views/terminal/parameterSetting/index.vue
@@ -0,0 +1,249 @@
+
+
+
+
+
+
+
+
+
+
+
+ 查询
+ 新增
+
+
+
+
+
+
+
+
+
+ {{ scope.row.id }}
+
+
+
+
+ {{ scope.row.name }}
+
+
+
+
+ {{ scope.row.unit }}
+
+
+
+
+ {{ scope.row.description }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/terminal/physicalInfo/index.vue b/src/views/terminal/physicalInfo/index.vue
new file mode 100644
index 0000000..63c3676
--- /dev/null
+++ b/src/views/terminal/physicalInfo/index.vue
@@ -0,0 +1,540 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查询
+ 新增
+
+
+
+
+
+ {{ "终端编号:" + (device.no || "") }}
+
+
+
+ {{
+ device.typeName
+ }}
+ {{
+ device.name
+ }}
+ {{
+ device.cpu_type
+ }}
+ {{
+ device.cpu_arch
+ }}
+ {{
+ device.cpu_freq
+ }}
+ {{
+ device.location
+ }}
+ {{
+ device.warnStatus == "0" ? "否" : "是"
+ }}
+ {{
+ device.online | bol2str
+ }}
+ {{
+ device.no_of_GPIO
+ }}
+ {{
+ device.interrupt_controller
+ }}
+ {{
+ device.smp_support | bol2str
+ }}
+ {{
+ device.sram
+ }}
+ {{
+ device.mem_SRAM
+ }}
+ {{
+ device.ext_SRAM
+ }}
+ {{
+ device.mem_extSRAM
+ }}
+ {{
+ device.flash
+ }}
+ {{
+ device.mem_FLASH
+ }}
+ {{
+ device.ext_FLASH
+ }}
+ {{
+ device.mem_extFLASH
+ }}
+ {{
+ device.touch_chip
+ }}
+ {{
+ device.screen
+ }}
+ {{
+ device.reslv_ratio
+ }}
+
+
+
+
+ {{
+ device.narrow_commun
+ }}
+ {{
+ device.narrow_commun_freq
+ }}
+ {{
+ device.loRa
+ }}
+ {{
+ device.loRa_freq
+ }}
+ {{
+ device.wiFi
+ }}
+ {{
+ device.wiFi_freq
+ }}
+ {{
+ device.wiFi_IP
+ }}
+ {{
+ device.wiFi_MAC
+ }}
+ {{
+ device.wiFi_mask
+ }}
+ {{
+ device.wiFi_gateway
+ }}
+ {{
+ device.zigBee
+ }}
+ {{
+ device.zigBee_freq
+ }}
+ {{
+ device.nb_IOT
+ }}
+ {{
+ device.nb_IOT_downfreq
+ }}
+ {{
+ device.nb_IOT_upfreq
+ }}
+ {{
+ device.fourG
+ }}
+ {{
+ device.fourG_downfreq
+ }}
+ {{
+ device.fourG_upfreq
+ }}
+ {{
+ device.fiveG
+ }}
+ {{
+ device.fiveG_downfreq
+ }}
+ {{
+ device.fiveG_upfreq
+ }}
+ {{
+ device.bluetooth
+ }}
+ {{
+ device.bluetooth_freq
+ }}
+ {{
+ device.bluetooth_addr
+ }}
+ {{
+ device.ether
+ }}
+ {{
+ device.ether_MAC
+ }}
+ {{
+ device.ether_mapping_IP
+ }}
+ {{
+ device.ether_port
+ }}
+ {{
+ device.ether_mask
+ }}
+ {{
+ device.ether_gateway
+ }}
+ {{
+ device.rs485
+ }}
+ {{
+ device.rs485_transm_speed
+ }}
+ {{
+ device.can
+ }}
+ {{
+ device.can_transm_speed
+ }}
+
+
+
+
+ {{
+ device.dma | bol2str
+ }}
+ {{
+ device.dac | bol2str
+ }}
+ {{
+ device.dcmi | bol2str
+ }}
+ {{
+ device.iwdg | bol2str
+ }}
+ {{
+ device.wwdg | bol2str
+ }}
+ {{
+ device.cryp | bol2str
+ }}
+ {{
+ device.rng | bol2str
+ }}
+ {{
+ device.hash | bol2str
+ }}
+ {{
+ device.rtc | bol2str
+ }}
+ {{
+ device.sdio | bol2str
+ }}
+ {{
+ device.ots_FS | bol2str
+ }}
+ {{
+ device.fsmc | bol2str
+ }}
+ {{
+ device.rcc | bol2str
+ }}
+ {{
+ device.pwr | bol2str
+ }}
+ {{
+ device.crc | bol2str
+ }}
+ {{
+ device.ether_support | bol2str
+ }}
+ {{
+ device.no_of_CAN
+ }}
+ {{
+ device.no_of_I2C
+ }}
+ {{
+ device.no_of_SPI
+ }}
+ {{
+ device.no_of_UART
+ }}
+ {{
+ device.usb_OTG_FS | bol2str
+ }}
+ {{
+ device.usb_OTG_HS | bol2str
+ }}
+ {{
+ device.nvic | bol2str
+ }}
+ {{
+ device.sys | bol2str
+ }}
+ {{
+ device.no_of_ADC
+ }}
+ {{
+ device.no_of_TIM
+ }}
+ {{
+ device.no_of_I2S
+ }}
+ {{
+ device.syscfg | bol2str
+ }}
+ {{
+ device.usart | bol2str
+ }}
+ {{
+ device.dbg | bol2str
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 告警中
+ 未告警
+
+
+
+
+ 在线
+ 离线
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/terminal/sensorType/index.vue b/src/views/terminal/sensorType/index.vue
new file mode 100644
index 0000000..a71ac24
--- /dev/null
+++ b/src/views/terminal/sensorType/index.vue
@@ -0,0 +1,324 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查询
+ 新增
+
+
+
+
+
+
+
+
+
+ {{ scope.row.cpu_arch }}
+
+
+
+
+ {{ scope.row.typeName }}
+
+
+
+
+ {{ scope.row.no }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ '终端编号:'+currentNo }}
+
+
+
+
+
+
+
+ {{ scope.row.name }}
+
+
+
+
+ {{ scope.row.unit }}
+
+
+
+
+ {{ '串口'+scope.row.port }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/terminal/structure/index.vue b/src/views/terminal/structure/index.vue
new file mode 100644
index 0000000..6bbfc03
--- /dev/null
+++ b/src/views/terminal/structure/index.vue
@@ -0,0 +1,339 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查询
+ 新增
+
+
+
+
+
+
+
+
+
+ {{ scope.row.parentName }}
+
+
+
+
+ {{ scope.row.name }}
+
+
+
+
+ {{ scope.row.isValid ? '有效' : '无效' }}
+
+
+
+
+ {{ scope.row.createTime }}
+
+
+
+
+ {{ scope.row.creator }}
+
+
+
+
+ {{ scope.row.lastUpdTime }}
+
+
+
+
+ {{ scope.row.modifier }}
+
+
+
+
+ 参数
+ 编辑
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 是
+ 否
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查询
+
+
+
+
+
+
+
+ {{ scope.row.id }}
+
+
+
+
+ {{ scope.row.name }}
+
+
+
+
+ {{ scope.row.unit }}
+
+
+
+
+ {{ scope.row.description }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/user/index.vue b/src/views/user/index.vue
new file mode 100644
index 0000000..4f41712
--- /dev/null
+++ b/src/views/user/index.vue
@@ -0,0 +1,291 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查询
+ 新增
+
+
+
+
+
+
+
+
+
+ {{ scope.row.realName }}
+
+
+
+
+ {{ scope.row.loginName }}
+
+
+
+
+ {{ scope.row.mobile }}
+
+
+
+
+
+ {{ scope.row.locked ? "锁定" : "正常" }}
+
+
+
+
+ {{ scope.row.createTime }}
+
+
+
+
+ {{ scope.row.lastLoginTime }}
+
+
+
+
+ 编辑
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 男
+ 女
+
+
+
+
+ 正常
+ 锁定
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/unit/.eslintrc.js b/tests/unit/.eslintrc.js
new file mode 100644
index 0000000..958d51b
--- /dev/null
+++ b/tests/unit/.eslintrc.js
@@ -0,0 +1,5 @@
+module.exports = {
+ env: {
+ jest: true
+ }
+}
diff --git a/tests/unit/components/Breadcrumb.spec.js b/tests/unit/components/Breadcrumb.spec.js
new file mode 100644
index 0000000..1d94c8f
--- /dev/null
+++ b/tests/unit/components/Breadcrumb.spec.js
@@ -0,0 +1,98 @@
+import { mount, createLocalVue } from '@vue/test-utils'
+import VueRouter from 'vue-router'
+import ElementUI from 'element-ui'
+import Breadcrumb from '@/components/Breadcrumb/index.vue'
+
+const localVue = createLocalVue()
+localVue.use(VueRouter)
+localVue.use(ElementUI)
+
+const routes = [
+ {
+ path: '/',
+ name: 'home',
+ children: [{
+ path: 'dashboard',
+ name: 'dashboard'
+ }]
+ },
+ {
+ path: '/menu',
+ name: 'menu',
+ children: [{
+ path: 'menu1',
+ name: 'menu1',
+ meta: { title: 'menu1' },
+ children: [{
+ path: 'menu1-1',
+ name: 'menu1-1',
+ meta: { title: 'menu1-1' }
+ },
+ {
+ path: 'menu1-2',
+ name: 'menu1-2',
+ redirect: 'noredirect',
+ meta: { title: 'menu1-2' },
+ children: [{
+ path: 'menu1-2-1',
+ name: 'menu1-2-1',
+ meta: { title: 'menu1-2-1' }
+ },
+ {
+ path: 'menu1-2-2',
+ name: 'menu1-2-2'
+ }]
+ }]
+ }]
+ }]
+
+const router = new VueRouter({
+ routes
+})
+
+describe('Breadcrumb.vue', () => {
+ const wrapper = mount(Breadcrumb, {
+ localVue,
+ router
+ })
+ it('dashboard', () => {
+ router.push('/dashboard')
+ const len = wrapper.findAll('.el-breadcrumb__inner').length
+ expect(len).toBe(1)
+ })
+ it('normal route', () => {
+ router.push('/menu/menu1')
+ const len = wrapper.findAll('.el-breadcrumb__inner').length
+ expect(len).toBe(2)
+ })
+ it('nested route', () => {
+ router.push('/menu/menu1/menu1-2/menu1-2-1')
+ const len = wrapper.findAll('.el-breadcrumb__inner').length
+ expect(len).toBe(4)
+ })
+ it('no meta.title', () => {
+ router.push('/menu/menu1/menu1-2/menu1-2-2')
+ const len = wrapper.findAll('.el-breadcrumb__inner').length
+ expect(len).toBe(3)
+ })
+ // it('click link', () => {
+ // router.push('/menu/menu1/menu1-2/menu1-2-2')
+ // const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner')
+ // const second = breadcrumbArray.at(1)
+ // console.log(breadcrumbArray)
+ // const href = second.find('a').attributes().href
+ // expect(href).toBe('#/menu/menu1')
+ // })
+ // it('noRedirect', () => {
+ // router.push('/menu/menu1/menu1-2/menu1-2-1')
+ // const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner')
+ // const redirectBreadcrumb = breadcrumbArray.at(2)
+ // expect(redirectBreadcrumb.contains('a')).toBe(false)
+ // })
+ it('last breadcrumb', () => {
+ router.push('/menu/menu1/menu1-2/menu1-2-1')
+ const breadcrumbArray = wrapper.findAll('.el-breadcrumb__inner')
+ const redirectBreadcrumb = breadcrumbArray.at(3)
+ expect(redirectBreadcrumb.contains('a')).toBe(false)
+ })
+})
diff --git a/tests/unit/components/Hamburger.spec.js b/tests/unit/components/Hamburger.spec.js
new file mode 100644
index 0000000..01ea303
--- /dev/null
+++ b/tests/unit/components/Hamburger.spec.js
@@ -0,0 +1,18 @@
+import { shallowMount } from '@vue/test-utils'
+import Hamburger from '@/components/Hamburger/index.vue'
+describe('Hamburger.vue', () => {
+ it('toggle click', () => {
+ const wrapper = shallowMount(Hamburger)
+ const mockFn = jest.fn()
+ wrapper.vm.$on('toggleClick', mockFn)
+ wrapper.find('.hamburger').trigger('click')
+ expect(mockFn).toBeCalled()
+ })
+ it('prop isActive', () => {
+ const wrapper = shallowMount(Hamburger)
+ wrapper.setProps({ isActive: true })
+ expect(wrapper.contains('.is-active')).toBe(true)
+ wrapper.setProps({ isActive: false })
+ expect(wrapper.contains('.is-active')).toBe(false)
+ })
+})
diff --git a/tests/unit/components/SvgIcon.spec.js b/tests/unit/components/SvgIcon.spec.js
new file mode 100644
index 0000000..31467a9
--- /dev/null
+++ b/tests/unit/components/SvgIcon.spec.js
@@ -0,0 +1,22 @@
+import { shallowMount } from '@vue/test-utils'
+import SvgIcon from '@/components/SvgIcon/index.vue'
+describe('SvgIcon.vue', () => {
+ it('iconClass', () => {
+ const wrapper = shallowMount(SvgIcon, {
+ propsData: {
+ iconClass: 'test'
+ }
+ })
+ expect(wrapper.find('use').attributes().href).toBe('#icon-test')
+ })
+ it('className', () => {
+ const wrapper = shallowMount(SvgIcon, {
+ propsData: {
+ iconClass: 'test'
+ }
+ })
+ expect(wrapper.classes().length).toBe(1)
+ wrapper.setProps({ className: 'test' })
+ expect(wrapper.classes().includes('test')).toBe(true)
+ })
+})
diff --git a/tests/unit/utils/formatTime.spec.js b/tests/unit/utils/formatTime.spec.js
new file mode 100644
index 0000000..24e165b
--- /dev/null
+++ b/tests/unit/utils/formatTime.spec.js
@@ -0,0 +1,30 @@
+import { formatTime } from '@/utils/index.js'
+
+describe('Utils:formatTime', () => {
+ const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01"
+ const retrofit = 5 * 1000
+
+ it('ten digits timestamp', () => {
+ expect(formatTime((d / 1000).toFixed(0))).toBe('7月13日17时54分')
+ })
+ it('test now', () => {
+ expect(formatTime(+new Date() - 1)).toBe('刚刚')
+ })
+ it('less two minute', () => {
+ expect(formatTime(+new Date() - 60 * 2 * 1000 + retrofit)).toBe('2分钟前')
+ })
+ it('less two hour', () => {
+ expect(formatTime(+new Date() - 60 * 60 * 2 * 1000 + retrofit)).toBe('2小时前')
+ })
+ it('less one day', () => {
+ expect(formatTime(+new Date() - 60 * 60 * 24 * 1 * 1000)).toBe('1天前')
+ })
+ it('more than one day', () => {
+ expect(formatTime(d)).toBe('7月13日17时54分')
+ })
+ it('format', () => {
+ expect(formatTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54')
+ expect(formatTime(d, '{y}-{m}-{d}')).toBe('2018-07-13')
+ expect(formatTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54')
+ })
+})
diff --git a/tests/unit/utils/param2Obj.spec.js b/tests/unit/utils/param2Obj.spec.js
new file mode 100644
index 0000000..e106ed8
--- /dev/null
+++ b/tests/unit/utils/param2Obj.spec.js
@@ -0,0 +1,14 @@
+import { param2Obj } from '@/utils/index.js'
+describe('Utils:param2Obj', () => {
+ const url = 'https://github.com/PanJiaChen/vue-element-admin?name=bill&age=29&sex=1&field=dGVzdA==&key=%E6%B5%8B%E8%AF%95'
+
+ it('param2Obj test', () => {
+ expect(param2Obj(url)).toEqual({
+ name: 'bill',
+ age: '29',
+ sex: '1',
+ field: window.btoa('test'),
+ key: '测试'
+ })
+ })
+})
diff --git a/tests/unit/utils/parseTime.spec.js b/tests/unit/utils/parseTime.spec.js
new file mode 100644
index 0000000..56045af
--- /dev/null
+++ b/tests/unit/utils/parseTime.spec.js
@@ -0,0 +1,35 @@
+import { parseTime } from '@/utils/index.js'
+
+describe('Utils:parseTime', () => {
+ const d = new Date('2018-07-13 17:54:01') // "2018-07-13 17:54:01"
+ it('timestamp', () => {
+ expect(parseTime(d)).toBe('2018-07-13 17:54:01')
+ })
+ it('timestamp string', () => {
+ expect(parseTime((d + ''))).toBe('2018-07-13 17:54:01')
+ })
+ it('ten digits timestamp', () => {
+ expect(parseTime((d / 1000).toFixed(0))).toBe('2018-07-13 17:54:01')
+ })
+ it('new Date', () => {
+ expect(parseTime(new Date(d))).toBe('2018-07-13 17:54:01')
+ })
+ it('format', () => {
+ expect(parseTime(d, '{y}-{m}-{d} {h}:{i}')).toBe('2018-07-13 17:54')
+ expect(parseTime(d, '{y}-{m}-{d}')).toBe('2018-07-13')
+ expect(parseTime(d, '{y}/{m}/{d} {h}-{i}')).toBe('2018/07/13 17-54')
+ })
+ it('get the day of the week', () => {
+ expect(parseTime(d, '{a}')).toBe('五') // 星期五
+ })
+ it('get the day of the week', () => {
+ expect(parseTime(+d + 1000 * 60 * 60 * 24 * 2, '{a}')).toBe('日') // 星期日
+ })
+ it('empty argument', () => {
+ expect(parseTime()).toBeNull()
+ })
+
+ it('null', () => {
+ expect(parseTime(null)).toBeNull()
+ })
+})
diff --git a/tests/unit/utils/validate.spec.js b/tests/unit/utils/validate.spec.js
new file mode 100644
index 0000000..f774905
--- /dev/null
+++ b/tests/unit/utils/validate.spec.js
@@ -0,0 +1,17 @@
+import { validUsername, isExternal } from '@/utils/validate.js'
+
+describe('Utils:validate', () => {
+ it('validUsername', () => {
+ expect(validUsername('admin')).toBe(true)
+ expect(validUsername('editor')).toBe(true)
+ expect(validUsername('xxxx')).toBe(false)
+ })
+ it('isExternal', () => {
+ expect(isExternal('https://github.com/PanJiaChen/vue-element-admin')).toBe(true)
+ expect(isExternal('http://github.com/PanJiaChen/vue-element-admin')).toBe(true)
+ expect(isExternal('github.com/PanJiaChen/vue-element-admin')).toBe(false)
+ expect(isExternal('/dashboard')).toBe(false)
+ expect(isExternal('./dashboard')).toBe(false)
+ expect(isExternal('dashboard')).toBe(false)
+ })
+})
diff --git a/vue.config.js b/vue.config.js
new file mode 100644
index 0000000..e3e6d00
--- /dev/null
+++ b/vue.config.js
@@ -0,0 +1,130 @@
+'use strict'
+const path = require('path')
+const defaultSettings = require('./src/settings.js')
+
+function resolve(dir) {
+ return path.join(__dirname, dir)
+}
+
+const name = defaultSettings.title || 'vue Admin Template' // page title
+
+// If your port is set to 80,
+// use administrator privileges to execute the command line.
+// For example, Mac: sudo npm run
+// You can change the port by the following methods:
+// port = 9528 npm run dev OR npm run dev --port = 9528
+const port = process.env.port || process.env.npm_config_port || 9528 // dev port
+
+// All configuration item explanations can be find in https://cli.vuejs.org/config/
+module.exports = {
+ /**
+ * You will need to set publicPath if you plan to deploy your site under a sub path,
+ * for example GitHub Pages. If you plan to deploy your site to https://foo.github.io/bar/,
+ * then publicPath should be set to "/bar/".
+ * In most cases please use '/' !!!
+ * Detail: https://cli.vuejs.org/config/#publicpath
+ */
+ publicPath: '/',
+ outputDir: 'dist',
+ assetsDir: 'static',
+ lintOnSave: process.env.NODE_ENV === 'development',
+ productionSourceMap: false,
+ devServer: {
+ port: port,
+ proxy: {
+ '/dashengda': {
+ target: 'http://115.238.53.60:33333/', // 大胜达
+ // target: 'http://192.168.23.102:8888/',
+ // target: 'http://10.0.30.23:8080',
+ changeOrigin: true,
+ pathRewrite: {
+ '^/dashengda': ''
+ }
+ }
+ },
+ open: true,
+ overlay: {
+ warnings: false,
+ errors: true
+ },
+ before: require('./mock/mock-server.js')
+ },
+ configureWebpack: {
+ // provide the app's title in webpack's name field, so that
+ // it can be accessed in index.html to inject the correct title.
+ name: name,
+ devtool: 'source-map',
+ resolve: {
+ alias: {
+ '@': resolve('src')
+ }
+ }
+ },
+ chainWebpack(config) {
+ // it can improve the speed of the first screen, it is recommended to turn on preload
+ config.plugin('preload').tap(() => [
+ {
+ rel: 'preload',
+ // to ignore runtime.js
+ // https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171
+ fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
+ include: 'initial'
+ }
+ ])
+
+ // when there are many pages, it will cause too many meaningless requests
+ config.plugins.delete('prefetch')
+
+ // set svg-sprite-loader
+ config.module.rule('svg').exclude.add(resolve('src/icons')).end()
+ config.module
+ .rule('icons')
+ .test(/\.svg$/)
+ .include.add(resolve('src/icons'))
+ .end()
+ .use('svg-sprite-loader')
+ .loader('svg-sprite-loader')
+ .options({
+ symbolId: 'icon-[name]'
+ })
+ .end()
+
+ config.when(process.env.NODE_ENV !== 'development', (config) => {
+ config
+ .plugin('ScriptExtHtmlWebpackPlugin')
+ .after('html')
+ .use('script-ext-html-webpack-plugin', [
+ {
+ // `runtime` must same as runtimeChunk name. default is `runtime`
+ inline: /runtime\..*\.js$/
+ }
+ ])
+ .end()
+ config.optimization.splitChunks({
+ chunks: 'all',
+ cacheGroups: {
+ libs: {
+ name: 'chunk-libs',
+ test: /[\\/]node_modules[\\/]/,
+ priority: 10,
+ chunks: 'initial' // only package third parties that are initially dependent
+ },
+ elementUI: {
+ name: 'chunk-elementUI', // split elementUI into a single package
+ priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app
+ test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // in order to adapt to cnpm
+ },
+ commons: {
+ name: 'chunk-commons',
+ test: resolve('src/components'), // can customize your rules
+ minChunks: 3, // minimum common number
+ priority: 5,
+ reuseExistingChunk: true
+ }
+ }
+ })
+ // https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
+ config.optimization.runtimeChunk('single')
+ })
+ }
+}