Browse Source

项目搭建

luyanan 6 years ago
parent
commit
0577e61790
88 changed files with 3847 additions and 71 deletions
  1. 12 0
      .babelrc
  2. 14 0
      .editorconfig
  3. 3 0
      .eslintignore
  4. 198 0
      .eslintrc.js
  5. 15 0
      .gitignore
  6. 10 0
      .postcssrc.js
  7. 5 0
      .travis.yml
  8. 1 71
      README.md
  9. 41 0
      build/build.js
  10. 54 0
      build/check-versions.js
  11. BIN
      build/logo.png
  12. 101 0
      build/utils.js
  13. 22 0
      build/vue-loader.conf.js
  14. 100 0
      build/webpack.base.conf.js
  15. 87 0
      build/webpack.dev.conf.js
  16. 150 0
      build/webpack.prod.conf.js
  17. 8 0
      config/dev.env.js
  18. 87 0
      config/index.js
  19. 5 0
      config/prod.env.js
  20. BIN
      favicon.ico
  21. 12 0
      index.html
  22. 80 0
      package.json
  23. 11 0
      src/App.vue
  24. 41 0
      src/api/article.js
  25. 27 0
      src/api/login.js
  26. 9 0
      src/api/table.js
  27. BIN
      src/assets/404_images/404.png
  28. BIN
      src/assets/404_images/404_cloud.png
  29. 51 0
      src/components/Breadcrumb/index.vue
  30. 44 0
      src/components/Hamburger/index.vue
  31. 42 0
      src/components/SvgIcon/index.vue
  32. 49 0
      src/directive/clipboard/clipboard.js
  33. 13 0
      src/directive/clipboard/index.js
  34. 77 0
      src/directive/el-dragDialog/drag.js
  35. 13 0
      src/directive/el-dragDialog/index.js
  36. 13 0
      src/directive/permission/index.js
  37. 23 0
      src/directive/permission/permission.js
  38. 91 0
      src/directive/sticky.js
  39. 13 0
      src/directive/waves/index.js
  40. 26 0
      src/directive/waves/waves.css
  41. 42 0
      src/directive/waves/waves.js
  42. 9 0
      src/icons/index.js
  43. 1 0
      src/icons/svg/example.svg
  44. 1 0
      src/icons/svg/eye.svg
  45. 1 0
      src/icons/svg/form.svg
  46. 1 0
      src/icons/svg/list.svg
  47. 1 0
      src/icons/svg/nested.svg
  48. 1 0
      src/icons/svg/password.svg
  49. 1 0
      src/icons/svg/peoples.svg
  50. 1 0
      src/icons/svg/table.svg
  51. 1 0
      src/icons/svg/tree.svg
  52. 1 0
      src/icons/svg/user.svg
  53. 27 0
      src/main.js
  54. 41 0
      src/permission.js
  55. 98 0
      src/router/index.js
  56. 9 0
      src/store/getters.js
  57. 17 0
      src/store/index.js
  58. 43 0
      src/store/modules/app.js
  59. 87 0
      src/store/modules/user.js
  60. 29 0
      src/styles/element-ui.scss
  61. 78 0
      src/styles/index.scss
  62. 27 0
      src/styles/mixin.scss
  63. 120 0
      src/styles/sidebar.scss
  64. 32 0
      src/styles/transition.scss
  65. 4 0
      src/styles/variables.scss
  66. 15 0
      src/utils/auth.js
  67. 58 0
      src/utils/index.js
  68. 66 0
      src/utils/request.js
  69. 33 0
      src/utils/validate.js
  70. 236 0
      src/views/404.vue
  71. 72 0
      src/views/baseInfoManage/boxesConfig/index.vue
  72. 72 0
      src/views/baseInfoManage/bridgesInfo/index.vue
  73. 72 0
      src/views/baseInfoManage/sensorsConfig/index.vue
  74. 72 0
      src/views/baseInfoManage/serversConfig/index.vue
  75. 32 0
      src/views/dashboard/index.vue
  76. 182 0
      src/views/findPwd/index.vue
  77. 71 0
      src/views/layout/Layout.vue
  78. 28 0
      src/views/layout/components/AppMain.vue
  79. 94 0
      src/views/layout/components/Navbar.vue
  80. 78 0
      src/views/layout/components/Sidebar/SidebarItem.vue
  81. 35 0
      src/views/layout/components/Sidebar/index.vue
  82. 92 0
      src/views/layout/components/TopNavbar.vue
  83. 4 0
      src/views/layout/components/index.js
  84. 41 0
      src/views/layout/mixin/ResizeHandler.js
  85. 182 0
      src/views/login/index.vue
  86. 91 0
      src/views/peoplesManage/infoManage/index.vue
  87. 0 0
      static/.gitkeep
  88. BIN
      static/touchwave.png

+ 12 - 0
.babelrc

@ -0,0 +1,12 @@
1
{
2
  "presets": [
3
    ["env", {
4
      "modules": false,
5
      "targets": {
6
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7
      }
8
    }],
9
    "stage-2"
10
  ],
11
  "plugins":["transform-vue-jsx", "transform-runtime"]
12
}

+ 14 - 0
.editorconfig

@ -0,0 +1,14 @@
1
# http://editorconfig.org
2
root = true
3
4
[*]
5
charset = utf-8
6
indent_style = space
7
indent_size = 2
8
end_of_line = lf
9
insert_final_newline = true
10
trim_trailing_whitespace = true
11
12
[*.md]
13
insert_final_newline = false
14
trim_trailing_whitespace = false

+ 3 - 0
.eslintignore

@ -0,0 +1,3 @@
1
build/*.js
2
config/*.js
3
src/assets

+ 198 - 0
.eslintrc.js

@ -0,0 +1,198 @@
1
module.exports = {
2
  root: true,
3
  parser: 'babel-eslint',
4
  parserOptions: {
5
    sourceType: 'module'
6
  },
7
  env: {
8
    browser: true,
9
    node: true,
10
    es6: true,
11
  },
12
  extends: 'eslint:recommended',
13
  // required to lint *.vue files
14
  plugins: [
15
    'html'
16
  ],
17
  // check if imports actually resolve
18
  'settings': {
19
    'import/resolver': {
20
      'webpack': {
21
        'config': 'build/webpack.base.conf.js'
22
      }
23
    }
24
  },
25
  // add your custom rules here
26
  //it is base on https://github.com/vuejs/eslint-config-vue
27
  rules: {
28
    'accessor-pairs': 2,
29
    'arrow-spacing': [2, {
30
      'before': true,
31
      'after': true
32
    }],
33
    'block-spacing': [2, 'always'],
34
    'brace-style': [2, '1tbs', {
35
      'allowSingleLine': true
36
    }],
37
    'camelcase': [0, {
38
      'properties': 'always'
39
    }],
40
    'comma-dangle': [2, 'never'],
41
    'comma-spacing': [2, {
42
      'before': false,
43
      'after': true
44
    }],
45
    'comma-style': [2, 'last'],
46
    'constructor-super': 2,
47
    'curly': [2, 'multi-line'],
48
    'dot-location': [2, 'property'],
49
    'eol-last': 2,
50
    'eqeqeq': [2, 'allow-null'],
51
    'generator-star-spacing': [2, {
52
      'before': true,
53
      'after': true
54
    }],
55
    'handle-callback-err': [2, '^(err|error)$'],
56
    'indent': [2, 2, {
57
      'SwitchCase': 1
58
    }],
59
    'jsx-quotes': [2, 'prefer-single'],
60
    'key-spacing': [2, {
61
      'beforeColon': false,
62
      'afterColon': true
63
    }],
64
    'keyword-spacing': [2, {
65
      'before': true,
66
      'after': true
67
    }],
68
    'new-cap': [2, {
69
      'newIsCap': true,
70
      'capIsNew': false
71
    }],
72
    'new-parens': 2,
73
    'no-array-constructor': 2,
74
    'no-caller': 2,
75
    'no-console': 'off',
76
    'no-class-assign': 2,
77
    'no-cond-assign': 2,
78
    'no-const-assign': 2,
79
    'no-control-regex': 2,
80
    'no-delete-var': 2,
81
    'no-dupe-args': 2,
82
    'no-dupe-class-members': 2,
83
    'no-dupe-keys': 2,
84
    'no-duplicate-case': 2,
85
    'no-empty-character-class': 2,
86
    'no-empty-pattern': 2,
87
    'no-eval': 2,
88
    'no-ex-assign': 2,
89
    'no-extend-native': 2,
90
    'no-extra-bind': 2,
91
    'no-extra-boolean-cast': 2,
92
    'no-extra-parens': [2, 'functions'],
93
    'no-fallthrough': 2,
94
    'no-floating-decimal': 2,
95
    'no-func-assign': 2,
96
    'no-implied-eval': 2,
97
    'no-inner-declarations': [2, 'functions'],
98
    'no-invalid-regexp': 2,
99
    'no-irregular-whitespace': 2,
100
    'no-iterator': 2,
101
    'no-label-var': 2,
102
    'no-labels': [2, {
103
      'allowLoop': false,
104
      'allowSwitch': false
105
    }],
106
    'no-lone-blocks': 2,
107
    'no-mixed-spaces-and-tabs': 2,
108
    'no-multi-spaces': 2,
109
    'no-multi-str': 2,
110
    'no-multiple-empty-lines': [2, {
111
      'max': 1
112
    }],
113
    'no-native-reassign': 2,
114
    'no-negated-in-lhs': 2,
115
    'no-new-object': 2,
116
    'no-new-require': 2,
117
    'no-new-symbol': 2,
118
    'no-new-wrappers': 2,
119
    'no-obj-calls': 2,
120
    'no-octal': 2,
121
    'no-octal-escape': 2,
122
    'no-path-concat': 2,
123
    'no-proto': 2,
124
    'no-redeclare': 2,
125
    'no-regex-spaces': 2,
126
    'no-return-assign': [2, 'except-parens'],
127
    'no-self-assign': 2,
128
    'no-self-compare': 2,
129
    'no-sequences': 2,
130
    'no-shadow-restricted-names': 2,
131
    'no-spaced-func': 2,
132
    'no-sparse-arrays': 2,
133
    'no-this-before-super': 2,
134
    'no-throw-literal': 2,
135
    'no-trailing-spaces': 2,
136
    'no-undef': 2,
137
    'no-undef-init': 2,
138
    'no-unexpected-multiline': 2,
139
    'no-unmodified-loop-condition': 2,
140
    'no-unneeded-ternary': [2, {
141
      'defaultAssignment': false
142
    }],
143
    'no-unreachable': 2,
144
    'no-unsafe-finally': 2,
145
    'no-unused-vars': [2, {
146
      'vars': 'all',
147
      'args': 'none'
148
    }],
149
    'no-useless-call': 2,
150
    'no-useless-computed-key': 2,
151
    'no-useless-constructor': 2,
152
    'no-useless-escape': 0,
153
    'no-whitespace-before-property': 2,
154
    'no-with': 2,
155
    'one-var': [2, {
156
      'initialized': 'never'
157
    }],
158
    'operator-linebreak': [2, 'after', {
159
      'overrides': {
160
        '?': 'before',
161
        ':': 'before'
162
      }
163
    }],
164
    'padded-blocks': [2, 'never'],
165
    'quotes': [2, 'single', {
166
      'avoidEscape': true,
167
      'allowTemplateLiterals': true
168
    }],
169
    'semi': [2, 'never'],
170
    'semi-spacing': [2, {
171
      'before': false,
172
      'after': true
173
    }],
174
    'space-before-blocks': [2, 'always'],
175
    'space-before-function-paren': [2, 'never'],
176
    'space-in-parens': [2, 'never'],
177
    'space-infix-ops': 2,
178
    'space-unary-ops': [2, {
179
      'words': true,
180
      'nonwords': false
181
    }],
182
    'spaced-comment': [2, 'always', {
183
      'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
184
    }],
185
    'template-curly-spacing': [2, 'never'],
186
    'use-isnan': 2,
187
    'valid-typeof': 2,
188
    'wrap-iife': [2, 'any'],
189
    'yield-star-spacing': [2, 'both'],
190
    'yoda': [2, 'never'],
191
    'prefer-const': 2,
192
    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
193
    'object-curly-spacing': [2, 'always', {
194
      objectsInObjects: false
195
    }],
196
    'array-bracket-spacing': [2, 'never']
197
  }
198
}

+ 15 - 0
.gitignore

@ -0,0 +1,15 @@
1
.DS_Store
2
node_modules/
3
dist/
4
npm-debug.log*
5
yarn-debug.log*
6
yarn-error.log*
7
package-lock.json
8
9
# Editor directories and files
10
.idea
11
.vscode
12
*.suo
13
*.ntvs*
14
*.njsproj
15
*.sln

+ 10 - 0
.postcssrc.js

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

+ 5 - 0
.travis.yml

@ -0,0 +1,5 @@
1
language: node_js
2
node_js: stable
3
script: npm run test
4
notifications:
5
  email: false

+ 1 - 71
README.md

@ -1,71 +1 @@
1
# vueAdmin-template
2
3
> A minimal vue admin template with Element UI & axios & iconfont & permission control & lint
4
5
**Live demo:** http://panjiachen.github.io/vueAdmin-template
6
7
[中文文档](https://github.com/PanJiaChen/vueAdmin-template/blob/master/README-zh.md)
8
9
## Build Setup
10
11
``` bash
12
13
# Clone project
14
git clone https://github.com/PanJiaChen/vueAdmin-template.git
15
16
# Install dependencies
17
npm install
18
19
# serve with hot reload at localhost:9528
20
npm run dev
21
22
# build for production with minification
23
npm run build
24
25
# build for production and view the bundle analyzer report
26
npm run build --report
27
```
28
29
## Demo
30
![demo](https://github.com/PanJiaChen/PanJiaChen.github.io/blob/master/images/demo.gif)
31
32
## Extra
33
If you want router permission && generate menu by user roles , you can use this branch [permission-control](https://github.com/PanJiaChen/vueAdmin-template/tree/permission-control)
34
35
## Related Project
36
 [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
37
38
 [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
39
40
### Element-Ui using cdn tutorial
41
First find `index.html`([root directory](https://github.com/PanJiaChen/vueAdmin-template/blob/element-ui-cdn/index.html))
42
43
Import css and js of `Element`, and then import vue. Because `Element` is vue-dependent, vue must be import before it.
44
45
Then find [webpack.base.conf.js](https://github.com/PanJiaChen/vueAdmin-template/blob/element-ui-cdn/build/webpack.base.conf.js)
46
Add `externals` to make webpack not package vue and element.
47
48
```
49
externals: {
50
  vue: 'Vue',
51
  'element-ui':'ELEMENT'
52
}
53
```
54
55
Finally there is a small detail to pay attention to that if you import vue in global, you don't need to manually `Vue.use(Vuex)`, it will be automatically mounted, see
56
 [issue](https://github.com/vuejs/vuex/issues/731)
57
58
And you can use `npm run build --report` to see the effect
59
60
Pictured:
61
![demo](https://panjiachen.github.io/images/element-cdn.png)
62
63
**[Detailed code](https://github.com/PanJiaChen/vueAdmin-template/commit/746aff560932704ae821f82f10b8b2a9681d5177)**
64
65
**[Branch](https://github.com/PanJiaChen/vueAdmin-template/tree/element-ui-cdn)**
66
67
68
## License
69
[MIT](https://github.com/PanJiaChen/vueAdmin-template/blob/master/LICENSE) license.
70
71
Copyright (c) 2017-present PanJiaChen
1
# vueAdmin-touchwave

+ 41 - 0
build/build.js

@ -0,0 +1,41 @@
1
'use strict'
2
require('./check-versions')()
3
4
process.env.NODE_ENV = 'production'
5
6
const ora = require('ora')
7
const rm = require('rimraf')
8
const path = require('path')
9
const chalk = require('chalk')
10
const webpack = require('webpack')
11
const config = require('../config')
12
const webpackConfig = require('./webpack.prod.conf')
13
14
const spinner = ora('building for production...')
15
spinner.start()
16
17
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
18
  if (err) throw err
19
  webpack(webpackConfig, (err, stats) => {
20
    spinner.stop()
21
    if (err) throw err
22
    process.stdout.write(stats.toString({
23
      colors: true,
24
      modules: false,
25
      children: false,
26
      chunks: false,
27
      chunkModules: false
28
    }) + '\n\n')
29
30
    if (stats.hasErrors()) {
31
      console.log(chalk.red('  Build failed with errors.\n'))
32
      process.exit(1)
33
    }
34
35
    console.log(chalk.cyan('  Build complete.\n'))
36
    console.log(chalk.yellow(
37
      '  Tip: built files are meant to be served over an HTTP server.\n' +
38
      '  Opening index.html over file:// won\'t work.\n'
39
    ))
40
  })
41
})

+ 54 - 0
build/check-versions.js

@ -0,0 +1,54 @@
1
'use strict'
2
const chalk = require('chalk')
3
const semver = require('semver')
4
const packageConfig = require('../package.json')
5
const shell = require('shelljs')
6
7
function exec (cmd) {
8
  return require('child_process').execSync(cmd).toString().trim()
9
}
10
11
const versionRequirements = [
12
  {
13
    name: 'node',
14
    currentVersion: semver.clean(process.version),
15
    versionRequirement: packageConfig.engines.node
16
  }
17
]
18
19
if (shell.which('npm')) {
20
  versionRequirements.push({
21
    name: 'npm',
22
    currentVersion: exec('npm --version'),
23
    versionRequirement: packageConfig.engines.npm
24
  })
25
}
26
27
module.exports = function () {
28
  const warnings = []
29
30
  for (let i = 0; i < versionRequirements.length; i++) {
31
    const mod = versionRequirements[i]
32
33
    if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
34
      warnings.push(mod.name + ': ' +
35
        chalk.red(mod.currentVersion) + ' should be ' +
36
        chalk.green(mod.versionRequirement)
37
      )
38
    }
39
  }
40
41
  if (warnings.length) {
42
    console.log('')
43
    console.log(chalk.yellow('To use this template, you must update following to modules:'))
44
    console.log()
45
46
    for (let i = 0; i < warnings.length; i++) {
47
      const warning = warnings[i]
48
      console.log('  ' + warning)
49
    }
50
51
    console.log()
52
    process.exit(1)
53
  }
54
}

BIN
build/logo.png


+ 101 - 0
build/utils.js

@ -0,0 +1,101 @@
1
'use strict'
2
const path = require('path')
3
const config = require('../config')
4
const ExtractTextPlugin = require('extract-text-webpack-plugin')
5
const packageConfig = require('../package.json')
6
7
exports.assetsPath = function (_path) {
8
  const assetsSubDirectory = process.env.NODE_ENV === 'production'
9
    ? config.build.assetsSubDirectory
10
    : config.dev.assetsSubDirectory
11
12
  return path.posix.join(assetsSubDirectory, _path)
13
}
14
15
exports.cssLoaders = function (options) {
16
  options = options || {}
17
18
  const cssLoader = {
19
    loader: 'css-loader',
20
    options: {
21
      sourceMap: options.sourceMap
22
    }
23
  }
24
25
  const postcssLoader = {
26
    loader: 'postcss-loader',
27
    options: {
28
      sourceMap: options.sourceMap
29
    }
30
  }
31
32
  // generate loader string to be used with extract text plugin
33
  function generateLoaders (loader, loaderOptions) {
34
    const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
35
36
    if (loader) {
37
      loaders.push({
38
        loader: loader + '-loader',
39
        options: Object.assign({}, loaderOptions, {
40
          sourceMap: options.sourceMap
41
        })
42
      })
43
    }
44
45
    // Extract CSS when that option is specified
46
    // (which is the case during production build)
47
    if (options.extract) {
48
      return ExtractTextPlugin.extract({
49
        use: loaders,
50
        fallback: 'vue-style-loader'
51
      })
52
    } else {
53
      return ['vue-style-loader'].concat(loaders)
54
    }
55
  }
56
57
  // https://vue-loader.vuejs.org/en/configurations/extract-css.html
58
  return {
59
    css: generateLoaders(),
60
    postcss: generateLoaders(),
61
    less: generateLoaders('less'),
62
    sass: generateLoaders('sass', { indentedSyntax: true }),
63
    scss: generateLoaders('sass'),
64
    stylus: generateLoaders('stylus'),
65
    styl: generateLoaders('stylus')
66
  }
67
}
68
69
// Generate loaders for standalone style files (outside of .vue)
70
exports.styleLoaders = function (options) {
71
  const output = []
72
  const loaders = exports.cssLoaders(options)
73
74
  for (const extension in loaders) {
75
    const loader = loaders[extension]
76
    output.push({
77
      test: new RegExp('\\.' + extension + '$'),
78
      use: loader
79
    })
80
  }
81
82
  return output
83
}
84
85
exports.createNotifierCallback = () => {
86
  const notifier = require('node-notifier')
87
88
  return (severity, errors) => {
89
    if (severity !== 'error') return
90
91
    const error = errors[0]
92
    const filename = error.file && error.file.split('!').pop()
93
94
    notifier.notify({
95
      title: packageConfig.name,
96
      message: severity + ': ' + error.name,
97
      subtitle: filename || '',
98
      icon: path.join(__dirname, 'logo.png')
99
    })
100
  }
101
}

+ 22 - 0
build/vue-loader.conf.js

@ -0,0 +1,22 @@
1
'use strict'
2
const utils = require('./utils')
3
const config = require('../config')
4
const isProduction = process.env.NODE_ENV === 'production'
5
const sourceMapEnabled = isProduction
6
  ? config.build.productionSourceMap
7
  : config.dev.cssSourceMap
8
9
module.exports = {
10
  loaders: utils.cssLoaders({
11
    sourceMap: sourceMapEnabled,
12
    extract: isProduction
13
  }),
14
  cssSourceMap: sourceMapEnabled,
15
  cacheBusting: config.dev.cacheBusting,
16
  transformToRequire: {
17
    video: ['src', 'poster'],
18
    source: 'src',
19
    img: 'src',
20
    image: 'xlink:href'
21
  }
22
}

+ 100 - 0
build/webpack.base.conf.js

@ -0,0 +1,100 @@
1
'use strict'
2
const path = require('path')
3
const utils = require('./utils')
4
const config = require('../config')
5
const vueLoaderConfig = require('./vue-loader.conf')
6
7
function resolve (dir) {
8
  return path.join(__dirname, '..', dir)
9
}
10
11
const createLintingRule = () => ({
12
  test: /\.(js|vue)$/,
13
  loader: 'eslint-loader',
14
  enforce: 'pre',
15
  include: [resolve('src'), resolve('test')],
16
  options: {
17
    formatter: require('eslint-friendly-formatter'),
18
    emitWarning: !config.dev.showEslintErrorsInOverlay
19
  }
20
})
21
22
module.exports = {
23
  context: path.resolve(__dirname, '../'),
24
  entry: {
25
    app: './src/main.js'
26
  },
27
  output: {
28
    path: config.build.assetsRoot,
29
    filename: '[name].js',
30
    publicPath: process.env.NODE_ENV === 'production'
31
      ? config.build.assetsPublicPath
32
      : config.dev.assetsPublicPath
33
  },
34
  resolve: {
35
    extensions: ['.js', '.vue', '.json'],
36
    alias: {
37
      '@': resolve('src')
38
    }
39
  },
40
  module: {
41
    rules: [
42
      ...(config.dev.useEslint ? [createLintingRule()] : []),
43
      {
44
        test: /\.vue$/,
45
        loader: 'vue-loader',
46
        options: vueLoaderConfig
47
      },
48
      {
49
        test: /\.js$/,
50
        loader: 'babel-loader',
51
        include: [resolve('src'), resolve('test') ,resolve('node_modules/webpack-dev-server/client')]
52
      },
53
      {
54
        test: /\.svg$/,
55
        loader: 'svg-sprite-loader',
56
        include: [resolve('src/icons')],
57
        options: {
58
          symbolId: 'icon-[name]'
59
        }
60
      },
61
      {
62
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
63
        loader: 'url-loader',
64
        exclude: [resolve('src/icons')],
65
        options: {
66
          limit: 10000,
67
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
68
        }
69
      },
70
      {
71
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
72
        loader: 'url-loader',
73
        options: {
74
          limit: 10000,
75
          name: utils.assetsPath('media/[name].[hash:7].[ext]')
76
        }
77
      },
78
      {
79
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
80
        loader: 'url-loader',
81
        options: {
82
          limit: 10000,
83
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
84
        }
85
      }
86
    ]
87
  },
88
  node: {
89
    // prevent webpack from injecting useless setImmediate polyfill because Vue
90
    // source contains it (although only uses it if it's native).
91
    setImmediate: false,
92
    // prevent webpack from injecting mocks to Node native modules
93
    // that does not make sense for the client
94
    dgram: 'empty',
95
    fs: 'empty',
96
    net: 'empty',
97
    tls: 'empty',
98
    child_process: 'empty'
99
  }
100
}

+ 87 - 0
build/webpack.dev.conf.js

@ -0,0 +1,87 @@
1
'use strict'
2
const path = require('path')
3
const utils = require('./utils')
4
const webpack = require('webpack')
5
const config = require('../config')
6
const merge = require('webpack-merge')
7
const baseWebpackConfig = require('./webpack.base.conf')
8
const HtmlWebpackPlugin = require('html-webpack-plugin')
9
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
10
const portfinder = require('portfinder')
11
12
function resolve (dir) {
13
  return path.join(__dirname, '..', dir)
14
}
15
16
const HOST = process.env.HOST
17
const PORT = process.env.PORT && Number(process.env.PORT)
18
19
const devWebpackConfig = merge(baseWebpackConfig, {
20
  module: {
21
    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
22
  },
23
  // cheap-module-eval-source-map is faster for development
24
  devtool: config.dev.devtool,
25
26
  // these devServer options should be customized in /config/index.js
27
  devServer: {
28
    clientLogLevel: 'warning',
29
    historyApiFallback: true,
30
    hot: true,
31
    compress: true,
32
    host: HOST || config.dev.host,
33
    port: PORT || config.dev.port,
34
    open: config.dev.autoOpenBrowser,
35
    overlay: config.dev.errorOverlay
36
      ? { warnings: false, errors: true }
37
      : false,
38
    publicPath: config.dev.assetsPublicPath,
39
    proxy: config.dev.proxyTable,
40
    quiet: true, // necessary for FriendlyErrorsPlugin
41
    watchOptions: {
42
      poll: config.dev.poll,
43
    }
44
  },
45
  plugins: [
46
    new webpack.DefinePlugin({
47
      'process.env': require('../config/dev.env')
48
    }),
49
    new webpack.HotModuleReplacementPlugin(),
50
    new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
51
    new webpack.NoEmitOnErrorsPlugin(),
52
    // https://github.com/ampedandwired/html-webpack-plugin
53
    new HtmlWebpackPlugin({
54
      filename: 'index.html',
55
      template: 'index.html',
56
      inject: true,
57
      favicon: resolve('favicon.ico'),
58
      title: 'vue-element-admin'
59
    }),
60
  ]
61
})
62
63
module.exports = new Promise((resolve, reject) => {
64
  portfinder.basePort = process.env.PORT || config.dev.port
65
  portfinder.getPort((err, port) => {
66
    if (err) {
67
      reject(err)
68
    } else {
69
      // publish the new Port, necessary for e2e tests
70
      process.env.PORT = port
71
      // add port to devServer config
72
      devWebpackConfig.devServer.port = port
73
74
      // Add FriendlyErrorsPlugin
75
      devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
76
        compilationSuccessInfo: {
77
          messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
78
        },
79
        onErrors: config.dev.notifyOnErrors
80
        ? utils.createNotifierCallback()
81
        : undefined
82
      }))
83
84
      resolve(devWebpackConfig)
85
    }
86
  })
87
})

+ 150 - 0
build/webpack.prod.conf.js

@ -0,0 +1,150 @@
1
'use strict'
2
const path = require('path')
3
const utils = require('./utils')
4
const webpack = require('webpack')
5
const config = require('../config')
6
const merge = require('webpack-merge')
7
const baseWebpackConfig = require('./webpack.base.conf')
8
const CopyWebpackPlugin = require('copy-webpack-plugin')
9
const HtmlWebpackPlugin = require('html-webpack-plugin')
10
const ExtractTextPlugin = require('extract-text-webpack-plugin')
11
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
12
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
13
14
function resolve (dir) {
15
  return path.join(__dirname, '..', dir)
16
}
17
18
const env = require('../config/prod.env')
19
20
const webpackConfig = merge(baseWebpackConfig, {
21
  module: {
22
    rules: utils.styleLoaders({
23
      sourceMap: config.build.productionSourceMap,
24
      extract: true,
25
      usePostCSS: true
26
    })
27
  },
28
  devtool: config.build.productionSourceMap ? config.build.devtool : false,
29
  output: {
30
    path: config.build.assetsRoot,
31
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
32
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
33
  },
34
  plugins: [
35
    // http://vuejs.github.io/vue-loader/en/workflow/production.html
36
    new webpack.DefinePlugin({
37
      'process.env': env
38
    }),
39
    new UglifyJsPlugin({
40
      uglifyOptions: {
41
        compress: {
42
          warnings: false
43
        }
44
      },
45
      sourceMap: config.build.productionSourceMap,
46
      parallel: true
47
    }),
48
    // extract css into its own file
49
    new ExtractTextPlugin({
50
      filename: utils.assetsPath('css/[name].[contenthash].css'),
51
      // Setting the following option to `false` will not extract CSS from codesplit chunks.
52
      // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
53
      // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
54
      allChunks: false,
55
    }),
56
    // Compress extracted CSS. We are using this plugin so that possible
57
    // duplicated CSS from different components can be deduped.
58
    new OptimizeCSSPlugin({
59
      cssProcessorOptions: config.build.productionSourceMap
60
        ? { safe: true, map: { inline: false } }
61
        : { safe: true }
62
    }),
63
    // generate dist index.html with correct asset hash for caching.
64
    // you can customize output by editing /index.html
65
    // see https://github.com/ampedandwired/html-webpack-plugin
66
    new HtmlWebpackPlugin({
67
      filename: config.build.index,
68
      template: 'index.html',
69
      inject: true,
70
      favicon: resolve('favicon.ico'),
71
      title: 'vue-element-admin',
72
      minify: {
73
        removeComments: true,
74
        collapseWhitespace: true,
75
        removeAttributeQuotes: true
76
        // more options:
77
        // https://github.com/kangax/html-minifier#options-quick-reference
78
      },
79
      // necessary to consistently work with multiple chunks via CommonsChunkPlugin
80
      chunksSortMode: 'dependency'
81
    }),
82
    // keep module.id stable when vender modules does not change
83
    new webpack.HashedModuleIdsPlugin(),
84
    // enable scope hoisting
85
    new webpack.optimize.ModuleConcatenationPlugin(),
86
    // split vendor js into its own file
87
    new webpack.optimize.CommonsChunkPlugin({
88
      name: 'vendor',
89
      minChunks (module) {
90
        // any required modules inside node_modules are extracted to vendor
91
        return (
92
          module.resource &&
93
          /\.js$/.test(module.resource) &&
94
          module.resource.indexOf(
95
            path.join(__dirname, '../node_modules')
96
          ) === 0
97
        )
98
      }
99
    }),
100
    // extract webpack runtime and module manifest to its own file in order to
101
    // prevent vendor hash from being updated whenever app bundle is updated
102
    new webpack.optimize.CommonsChunkPlugin({
103
      name: 'manifest',
104
      minChunks: Infinity
105
    }),
106
    // This instance extracts shared chunks from code splitted chunks and bundles them
107
    // in a separate chunk, similar to the vendor chunk
108
    // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
109
    new webpack.optimize.CommonsChunkPlugin({
110
      name: 'app',
111
      async: 'vendor-async',
112
      children: true,
113
      minChunks: 3
114
    }),
115
116
    // copy custom static assets
117
    new CopyWebpackPlugin([
118
      {
119
        from: path.resolve(__dirname, '../static'),
120
        to: config.build.assetsSubDirectory,
121
        ignore: ['.*']
122
      }
123
    ])
124
  ]
125
})
126
127
if (config.build.productionGzip) {
128
  const CompressionWebpackPlugin = require('compression-webpack-plugin')
129
130
  webpackConfig.plugins.push(
131
    new CompressionWebpackPlugin({
132
      asset: '[path].gz[query]',
133
      algorithm: 'gzip',
134
      test: new RegExp(
135
        '\\.(' +
136
        config.build.productionGzipExtensions.join('|') +
137
        ')$'
138
      ),
139
      threshold: 10240,
140
      minRatio: 0.8
141
    })
142
  )
143
}
144
145
if (config.build.bundleAnalyzerReport) {
146
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
147
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
148
}
149
150
module.exports = webpackConfig

+ 8 - 0
config/dev.env.js

@ -0,0 +1,8 @@
1
'use strict'
2
const merge = require('webpack-merge')
3
const prodEnv = require('./prod.env')
4
5
module.exports = merge(prodEnv, {
6
  NODE_ENV: '"development"',
7
  BASE_API: '"https://easy-mock.com/mock/5950a2419adc231f356a6636/vue-admin"',
8
})

+ 87 - 0
config/index.js

@ -0,0 +1,87 @@
1
'use strict'
2
3
const path = require('path')
4
5
module.exports = {
6
  dev: {
7
8
    // Paths
9
    assetsSubDirectory: 'static',
10
    assetsPublicPath: '/',
11
    proxyTable: {},
12
13
    // Various Dev Server settings
14
    host: 'localhost', // can be overwritten by process.env.HOST
15
    port: 9526, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
16
    autoOpenBrowser: false,
17
    errorOverlay: true,
18
    notifyOnErrors: false,
19
    poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
20
21
    // Use Eslint Loader?
22
    // If true, your code will be linted during bundling and
23
    // linting errors and warnings will be shown in the console.
24
    useEslint: true,
25
    // If true, eslint errors and warnings will also be shown in the error overlay
26
    // in the browser.
27
    showEslintErrorsInOverlay: false,
28
29
    /**
30
     * Source Maps
31
     */
32
33
    // https://webpack.js.org/configuration/devtool/#development
34
    devtool: 'cheap-source-map',
35
36
    // If you have problems debugging vue-files in devtools,
37
    // set this to false - it *may* help
38
    // https://vue-loader.vuejs.org/en/options.html#cachebusting
39
    cacheBusting: true,
40
41
    // CSS Sourcemaps off by default because relative paths are "buggy"
42
    // with this option, according to the CSS-Loader README
43
    // (https://github.com/webpack/css-loader#sourcemaps)
44
    // In our experience, they generally work as expected,
45
    // just be aware of this issue when enabling this option.
46
    cssSourceMap: false,
47
  },
48
49
  build: {
50
    // Template for index.html
51
    index: path.resolve(__dirname, '../dist/index.html'),
52
53
    // Paths
54
    assetsRoot: path.resolve(__dirname, '../dist'),
55
    assetsSubDirectory: 'static',
56
57
    /**
58
     * You can set by youself according to actual condition
59
     * You will need to set this if you plan to deploy your site under a sub path,
60
     * for example GitHub pages. If you plan to deploy your site to https://foo.github.io/bar/,
61
     * then assetsPublicPath should be set to "/bar/".
62
     * In most cases please use '/' !!!
63
     */
64
    assetsPublicPath: '/vueAdmin-template/', // If you are deployed on the root path, please use '/'
65
66
    /**
67
     * Source Maps
68
     */
69
70
    productionSourceMap: false,
71
    // https://webpack.js.org/configuration/devtool/#production
72
    devtool: '#source-map',
73
74
    // Gzip off by default as many popular static hosts such as
75
    // Surge or Netlify already gzip all static assets for you.
76
    // Before setting to `true`, make sure to:
77
    // npm install --save-dev compression-webpack-plugin
78
    productionGzip: false,
79
    productionGzipExtensions: ['js', 'css'],
80
81
    // Run the build command with an extra argument to
82
    // View the bundle analyzer report after build finishes:
83
    // `npm run build --report`
84
    // Set to `true` or `false` to always turn it on or off
85
    bundleAnalyzerReport: process.env.npm_config_report
86
  }
87
}

+ 5 - 0
config/prod.env.js

@ -0,0 +1,5 @@
1
'use strict'
2
module.exports = {
3
  NODE_ENV: '"production"',
4
  BASE_API: '"https://easy-mock.com/mock/5950a2419adc231f356a6636/vue-admin"',
5
}

BIN
favicon.ico


+ 12 - 0
index.html

@ -0,0 +1,12 @@
1
<!DOCTYPE html>
2
<html>
3
  <head>
4
    <meta charset="utf-8">
5
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
6
    <title>声脉桥梁云监控平台</title>
7
  </head>
8
  <body>
9
    <div id="app"></div>
10
    <!-- built files will be auto injected -->
11
  </body>
12
</html>

+ 80 - 0
package.json

@ -0,0 +1,80 @@
1
{
2
  "name": "vueAdmin-touchwave",
3
  "version": "1.0.0",
4
  "description": "A vue admin template with Element UI & axios & iconfont & permission control & lint",
5
  "author": "luyanan <1271778060@qq.com>",
6
  "private": true,
7
  "scripts": {
8
    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
9
    "start": "npm run dev",
10
    "build": "node build/build.js",
11
    "build:report": "npm_config_report=true node build/build.js",
12
    "lint": "eslint --ext .js,.vue src",
13
    "test": "npm run lint"
14
  },
15
  "dependencies": {
16
    "axios": "0.17.1",
17
    "element-ui": "2.3.4",
18
    "js-cookie": "2.2.0",
19
    "normalize.css": "7.0.0",
20
    "nprogress": "0.2.0",
21
    "vue": "2.5.10",
22
    "vue-router": "3.0.1",
23
    "vuex": "3.0.1"
24
  },
25
  "devDependencies": {
26
    "autoprefixer": "7.2.3",
27
    "babel-core": "6.26.0",
28
    "babel-eslint": "8.0.3",
29
    "babel-helper-vue-jsx-merge-props": "2.0.3",
30
    "babel-loader": "7.1.2",
31
    "babel-plugin-syntax-jsx": "6.18.0",
32
    "babel-plugin-transform-runtime": "6.23.0",
33
    "babel-plugin-transform-vue-jsx": "3.5.0",
34
    "babel-preset-env": "1.6.1",
35
    "babel-preset-stage-2": "6.24.1",
36
    "chalk": "2.3.0",
37
    "copy-webpack-plugin": "4.2.3",
38
    "css-loader": "0.28.7",
39
    "eslint": "4.13.1",
40
    "eslint-friendly-formatter": "3.0.0",
41
    "eslint-loader": "1.9.0",
42
    "eslint-plugin-html": "4.0.1",
43
    "eventsource-polyfill": "0.9.6",
44
    "extract-text-webpack-plugin": "3.0.2",
45
    "file-loader": "1.1.5",
46
    "friendly-errors-webpack-plugin": "1.6.1",
47
    "html-webpack-plugin": "2.30.1",
48
    "node-notifier": "5.1.2",
49
    "node-sass": "^4.7.2",
50
    "optimize-css-assets-webpack-plugin": "3.2.0",
51
    "ora": "1.3.0",
52
    "portfinder": "1.0.13",
53
    "postcss-import": "11.0.0",
54
    "postcss-loader": "2.0.9",
55
    "postcss-url": "7.3.0",
56
    "rimraf": "2.6.2",
57
    "sass-loader": "6.0.6",
58
    "semver": "5.4.1",
59
    "shelljs": "0.7.8",
60
    "svg-sprite-loader": "3.5.2",
61
    "uglifyjs-webpack-plugin": "1.1.3",
62
    "url-loader": "0.6.2",
63
    "vue-loader": "13.7.2",
64
    "vue-style-loader": "3.0.3",
65
    "vue-template-compiler": "2.5.10",
66
    "webpack": "3.10.0",
67
    "webpack-bundle-analyzer": "2.9.1",
68
    "webpack-dev-server": "2.9.7",
69
    "webpack-merge": "4.1.1"
70
  },
71
  "engines": {
72
    "node": ">= 4.0.0",
73
    "npm": ">= 3.0.0"
74
  },
75
  "browserslist": [
76
    "> 1%",
77
    "last 2 versions",
78
    "not ie <= 8"
79
  ]
80
}

+ 11 - 0
src/App.vue

@ -0,0 +1,11 @@
1
<template>
2
  <div id="app">
3
    <router-view></router-view>
4
  </div>
5
</template>
6
7
<script>
8
export default {
9
  name: 'App'
10
}
11
</script>

+ 41 - 0
src/api/article.js

@ -0,0 +1,41 @@
1
import request from '@/utils/request'
2
3
export function fetchList(query) {
4
  return request({
5
    url: '/article/list',
6
    method: 'get',
7
    params: query
8
  })
9
}
10
11
export function fetchArticle(id) {
12
  return request({
13
    url: '/article/detail',
14
    method: 'get',
15
    params: { id }
16
  })
17
}
18
19
export function fetchPv(pv) {
20
  return request({
21
    url: '/article/pv',
22
    method: 'get',
23
    params: { pv }
24
  })
25
}
26
27
export function createArticle(data) {
28
  return request({
29
    url: '/article/create',
30
    method: 'post',
31
    data
32
  })
33
}
34
35
export function updateArticle(data) {
36
  return request({
37
    url: '/article/update',
38
    method: 'post',
39
    data
40
  })
41
}

+ 27 - 0
src/api/login.js

@ -0,0 +1,27 @@
1
import request from '@/utils/request'
2
3
export function login(username, password) {
4
  return request({
5
    url: '/user/login',
6
    method: 'post',
7
    data: {
8
      username,
9
      password
10
    }
11
  })
12
}
13
14
export function getInfo(token) {
15
  return request({
16
    url: '/user/info',
17
    method: 'get',
18
    params: { token }
19
  })
20
}
21
22
export function logout() {
23
  return request({
24
    url: '/user/logout',
25
    method: 'post'
26
  })
27
}

+ 9 - 0
src/api/table.js

@ -0,0 +1,9 @@
1
import request from '@/utils/request'
2
3
export function getList(params) {
4
  return request({
5
    url: '/table/list',
6
    method: 'get',
7
    params
8
  })
9
}

BIN
src/assets/404_images/404.png


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


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

@ -0,0 +1,51 @@
1
<template>
2
  <el-breadcrumb class="app-breadcrumb" separator="/">
3
    <transition-group name="breadcrumb">
4
      <el-breadcrumb-item v-for="(item,index)  in levelList" :key="item.path" v-if="item.meta.title">
5
        <span v-if="item.redirect==='noredirect'||index==levelList.length-1" class="no-redirect">{{item.meta.title}}</span>
6
        <router-link v-else :to="item.redirect||item.path">{{item.meta.title}}</router-link>
7
      </el-breadcrumb-item>
8
    </transition-group>
9
  </el-breadcrumb>
10
</template>
11
12
<script>
13
export default {
14
  created() {
15
    this.getBreadcrumb()
16
  },
17
  data() {
18
    return {
19
      levelList: null
20
    }
21
  },
22
  watch: {
23
    $route() {
24
      this.getBreadcrumb()
25
    }
26
  },
27
  methods: {
28
    getBreadcrumb() {
29
      let matched = this.$route.matched.filter(item => item.name)
30
      const first = matched[0]
31
      if (first && first.name !== 'dashboard') {
32
        matched = [{ path: '/dashboard', meta: { title: '主页' }}].concat(matched)
33
      }
34
      this.levelList = matched
35
    }
36
  }
37
}
38
</script>
39
40
<style rel="stylesheet/scss" lang="scss" scoped>
41
  .app-breadcrumb.el-breadcrumb {
42
    display: inline-block;
43
    font-size: 14px;
44
    line-height: 50px;
45
    margin-left: 10px;
46
    .no-redirect {
47
      color: #97a8be;
48
      cursor: text;
49
    }
50
  }
51
</style>

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

@ -0,0 +1,44 @@
1
<template>
2
  <div>
3
    <svg t="1492500959545" @click="toggleClick" class="hamburger" :class="{'is-active':isActive}" style="" viewBox="0 0 1024 1024"
4
      version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1691" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64">
5
      <path d="M966.8023 568.849776 57.196677 568.849776c-31.397081 0-56.850799-25.452695-56.850799-56.850799l0 0c0-31.397081 25.452695-56.849776 56.850799-56.849776l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.849776l0 0C1023.653099 543.397081 998.200404 568.849776 966.8023 568.849776z"
6
        p-id="1692"></path>
7
      <path d="M966.8023 881.527125 57.196677 881.527125c-31.397081 0-56.850799-25.452695-56.850799-56.849776l0 0c0-31.397081 25.452695-56.849776 56.850799-56.849776l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.849776l0 0C1023.653099 856.07443 998.200404 881.527125 966.8023 881.527125z"
8
        p-id="1693"></path>
9
      <path d="M966.8023 256.17345 57.196677 256.17345c-31.397081 0-56.850799-25.452695-56.850799-56.849776l0 0c0-31.397081 25.452695-56.850799 56.850799-56.850799l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.850799l0 0C1023.653099 230.720755 998.200404 256.17345 966.8023 256.17345z"
10
        p-id="1694"></path>
11
    </svg>
12
  </div>
13
</template>
14
15
<script>
16
export default {
17
  name: 'hamburger',
18
  props: {
19
    isActive: {
20
      type: Boolean,
21
      default: false
22
    },
23
    toggleClick: {
24
      type: Function,
25
      default: null
26
    }
27
  }
28
}
29
</script>
30
31
<style scoped>
32
.hamburger {
33
	display: inline-block;
34
	cursor: pointer;
35
	width: 20px;
36
	height: 20px;
37
	transform: rotate(90deg);
38
	transition: .38s;
39
	transform-origin: 50% 50%;
40
}
41
.hamburger.is-active {
42
	transform: rotate(0deg);
43
}
44
</style>

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

@ -0,0 +1,42 @@
1
<template>
2
  <svg :class="svgClass" aria-hidden="true">
3
    <use :xlink:href="iconName"></use>
4
  </svg>
5
</template>
6
7
<script>
8
export default {
9
  name: 'svg-icon',
10
  props: {
11
    iconClass: {
12
      type: String,
13
      required: true
14
    },
15
    className: {
16
      type: String
17
    }
18
  },
19
  computed: {
20
    iconName() {
21
      return `#icon-${this.iconClass}`
22
    },
23
    svgClass() {
24
      if (this.className) {
25
        return 'svg-icon ' + this.className
26
      } else {
27
        return 'svg-icon'
28
      }
29
    }
30
  }
31
}
32
</script>
33
34
<style scoped>
35
.svg-icon {
36
  width: 1em;
37
  height: 1em;
38
  vertical-align: -0.15em;
39
  fill: currentColor;
40
  overflow: hidden;
41
}
42
</style>

+ 49 - 0
src/directive/clipboard/clipboard.js

@ -0,0 +1,49 @@
1
// Inspired by https://github.com/Inndy/vue-clipboard2
2
const Clipboard = require('clipboard')
3
if (!Clipboard) {
4
  throw new Error('you shold npm install `clipboard` --save at first ')
5
}
6
7
export default {
8
  bind(el, binding) {
9
    if (binding.arg === 'success') {
10
      el._v_clipboard_success = binding.value
11
    } else if (binding.arg === 'error') {
12
      el._v_clipboard_error = binding.value
13
    } else {
14
      const clipboard = new Clipboard(el, {
15
        text() { return binding.value },
16
        action() { return binding.arg === 'cut' ? 'cut' : 'copy' }
17
      })
18
      clipboard.on('success', e => {
19
        const callback = el._v_clipboard_success
20
        callback && callback(e) // eslint-disable-line
21
      })
22
      clipboard.on('error', e => {
23
        const callback = el._v_clipboard_error
24
        callback && callback(e) // eslint-disable-line
25
      })
26
      el._v_clipboard = clipboard
27
    }
28
  },
29
  update(el, binding) {
30
    if (binding.arg === 'success') {
31
      el._v_clipboard_success = binding.value
32
    } else if (binding.arg === 'error') {
33
      el._v_clipboard_error = binding.value
34
    } else {
35
      el._v_clipboard.text = function() { return binding.value }
36
      el._v_clipboard.action = function() { return binding.arg === 'cut' ? 'cut' : 'copy' }
37
    }
38
  },
39
  unbind(el, binding) {
40
    if (binding.arg === 'success') {
41
      delete el._v_clipboard_success
42
    } else if (binding.arg === 'error') {
43
      delete el._v_clipboard_error
44
    } else {
45
      el._v_clipboard.destroy()
46
      delete el._v_clipboard
47
    }
48
  }
49
}

+ 13 - 0
src/directive/clipboard/index.js

@ -0,0 +1,13 @@
1
import Clipboard from './clipboard'
2
3
const install = function(Vue) {
4
  Vue.directive('Clipboard', Clipboard)
5
}
6
7
if (window.Vue) {
8
  window.clipboard = Clipboard
9
  Vue.use(install); // eslint-disable-line
10
}
11
12
Clipboard.install = install
13
export default Clipboard

+ 77 - 0
src/directive/el-dragDialog/drag.js

@ -0,0 +1,77 @@
1
export default{
2
  bind(el, binding, vnode) {
3
    const dialogHeaderEl = el.querySelector('.el-dialog__header')
4
    const dragDom = el.querySelector('.el-dialog')
5
    dialogHeaderEl.style.cssText += ';cursor:move;'
6
    dragDom.style.cssText += ';top:0px;'
7
8
    // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
9
    const getStyle = (function() {
10
      if (window.document.currentStyle) {
11
        return (dom, attr) => dom.currentStyle[attr]
12
      } else {
13
        return (dom, attr) => getComputedStyle(dom, false)[attr]
14
      }
15
    })()
16
17
    dialogHeaderEl.onmousedown = (e) => {
18
      // 鼠标按下,计算当前元素距离可视区的距离
19
      const disX = e.clientX - dialogHeaderEl.offsetLeft
20
      const disY = e.clientY - dialogHeaderEl.offsetTop
21
22
      const dragDomWidth = dragDom.offsetWidth
23
      const dragDomheight = dragDom.offsetHeight
24
25
      const screenWidth = document.body.clientWidth
26
      const screenHeight = document.body.clientHeight
27
28
      const minDragDomLeft = dragDom.offsetLeft
29
      const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth
30
31
      const minDragDomTop = dragDom.offsetTop
32
      const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight
33
34
      // 获取到的值带px 正则匹配替换
35
      let styL = getStyle(dragDom, 'left')
36
      let styT = getStyle(dragDom, 'top')
37
38
      if (styL.includes('%')) {
39
        styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100)
40
        styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100)
41
      } else {
42
        styL = +styL.replace(/\px/g, '')
43
        styT = +styT.replace(/\px/g, '')
44
      }
45
46
      document.onmousemove = function(e) {
47
        // 通过事件委托,计算移动的距离
48
        let left = e.clientX - disX
49
        let top = e.clientY - disY
50
51
        // 边界处理
52
        if (-(left) > minDragDomLeft) {
53
          left = -minDragDomLeft
54
        } else if (left > maxDragDomLeft) {
55
          left = maxDragDomLeft
56
        }
57
58
        if (-(top) > minDragDomTop) {
59
          top = -minDragDomTop
60
        } else if (top > maxDragDomTop) {
61
          top = maxDragDomTop
62
        }
63
64
        // 移动当前元素
65
        dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`
66
67
        // emit onDrag event
68
        vnode.child.$emit('dragDialog')
69
      }
70
71
      document.onmouseup = function(e) {
72
        document.onmousemove = null
73
        document.onmouseup = null
74
      }
75
    }
76
  }
77
}

+ 13 - 0
src/directive/el-dragDialog/index.js

@ -0,0 +1,13 @@
1
import drag from './drag'
2
3
const install = function(Vue) {
4
  Vue.directive('el-drag-dialog', drag)
5
}
6
7
if (window.Vue) {
8
  window['el-drag-dialog'] = drag
9
  Vue.use(install); // eslint-disable-line
10
}
11
12
drag.install = install
13
export default drag

+ 13 - 0
src/directive/permission/index.js

@ -0,0 +1,13 @@
1
import permission from './permission'
2
3
const install = function(Vue) {
4
  Vue.directive('permission', permission)
5
}
6
7
if (window.Vue) {
8
  window['permission'] = permission
9
  Vue.use(install); // eslint-disable-line
10
}
11
12
permission.install = install
13
export default permission

+ 23 - 0
src/directive/permission/permission.js

@ -0,0 +1,23 @@
1
2
import store from '@/store'
3
4
export default{
5
  inserted(el, binding, vnode) {
6
    const { value } = binding
7
    const roles = store.getters && store.getters.roles
8
9
    if (value && value instanceof Array && value.length > 0) {
10
      const permissionRoles = value
11
12
      const hasPermission = roles.some(role => {
13
        return permissionRoles.includes(role)
14
      })
15
16
      if (!hasPermission) {
17
        el.parentNode && el.parentNode.removeChild(el)
18
      }
19
    } else {
20
      throw new Error(`need roles! Like v-permission="['admin','editor']"`)
21
    }
22
  }
23
}

+ 91 - 0
src/directive/sticky.js

@ -0,0 +1,91 @@
1
const vueSticky = {}
2
let listenAction
3
vueSticky.install = Vue => {
4
  Vue.directive('sticky', {
5
    inserted(el, binding) {
6
      const params = binding.value || {}
7
      const stickyTop = params.stickyTop || 0
8
      const zIndex = params.zIndex || 1000
9
      const elStyle = el.style
10
11
      elStyle.position = '-webkit-sticky'
12
      elStyle.position = 'sticky'
13
      // if the browser support css sticky(Currently Safari, Firefox and Chrome Canary)
14
      // if (~elStyle.position.indexOf('sticky')) {
15
      //     elStyle.top = `${stickyTop}px`;
16
      //     elStyle.zIndex = zIndex;
17
      //     return
18
      // }
19
      const elHeight = el.getBoundingClientRect().height
20
      const elWidth = el.getBoundingClientRect().width
21
      elStyle.cssText = `top: ${stickyTop}px; z-index: ${zIndex}`
22
23
      const parentElm = el.parentNode || document.documentElement
24
      const placeholder = document.createElement('div')
25
      placeholder.style.display = 'none'
26
      placeholder.style.width = `${elWidth}px`
27
      placeholder.style.height = `${elHeight}px`
28
      parentElm.insertBefore(placeholder, el)
29
30
      let active = false
31
32
      const getScroll = (target, top) => {
33
        const prop = top ? 'pageYOffset' : 'pageXOffset'
34
        const method = top ? 'scrollTop' : 'scrollLeft'
35
        let ret = target[prop]
36
        if (typeof ret !== 'number') {
37
          ret = window.document.documentElement[method]
38
        }
39
        return ret
40
      }
41
42
      const sticky = () => {
43
        if (active) {
44
          return
45
        }
46
        if (!elStyle.height) {
47
          elStyle.height = `${el.offsetHeight}px`
48
        }
49
50
        elStyle.position = 'fixed'
51
        elStyle.width = `${elWidth}px`
52
        placeholder.style.display = 'inline-block'
53
        active = true
54
      }
55
56
      const reset = () => {
57
        if (!active) {
58
          return
59
        }
60
61
        elStyle.position = ''
62
        placeholder.style.display = 'none'
63
        active = false
64
      }
65
66
      const check = () => {
67
        const scrollTop = getScroll(window, true)
68
        const offsetTop = el.getBoundingClientRect().top
69
        if (offsetTop < stickyTop) {
70
          sticky()
71
        } else {
72
          if (scrollTop < elHeight + stickyTop) {
73
            reset()
74
          }
75
        }
76
      }
77
      listenAction = () => {
78
        check()
79
      }
80
81
      window.addEventListener('scroll', listenAction)
82
    },
83
84
    unbind() {
85
      window.removeEventListener('scroll', listenAction)
86
    }
87
  })
88
}
89
90
export default vueSticky
91

+ 13 - 0
src/directive/waves/index.js

@ -0,0 +1,13 @@
1
import waves from './waves'
2
3
const install = function(Vue) {
4
  Vue.directive('waves', waves)
5
}
6
7
if (window.Vue) {
8
  window.waves = waves
9
  Vue.use(install); // eslint-disable-line
10
}
11
12
waves.install = install
13
export default waves

+ 26 - 0
src/directive/waves/waves.css

@ -0,0 +1,26 @@
1
.waves-ripple {
2
    position: absolute;
3
    border-radius: 100%;
4
    background-color: rgba(0, 0, 0, 0.15);
5
    background-clip: padding-box;
6
    pointer-events: none;
7
    -webkit-user-select: none;
8
    -moz-user-select: none;
9
    -ms-user-select: none;
10
    user-select: none;
11
    -webkit-transform: scale(0);
12
    -ms-transform: scale(0);
13
    transform: scale(0);
14
    opacity: 1;
15
}
16
17
.waves-ripple.z-active {
18
    opacity: 0;
19
    -webkit-transform: scale(2);
20
    -ms-transform: scale(2);
21
    transform: scale(2);
22
    -webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
23
    transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
24
    transition: opacity 1.2s ease-out, transform 0.6s ease-out;
25
    transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
26
}

+ 42 - 0
src/directive/waves/waves.js

@ -0,0 +1,42 @@
1
import './waves.css'
2
3
export default{
4
  bind(el, binding) {
5
    el.addEventListener('click', e => {
6
      const customOpts = Object.assign({}, binding.value)
7
      const opts = Object.assign({
8
        ele: el, // 波纹作用元素
9
        type: 'hit', // hit点击位置扩散center中心点扩展
10
        color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
11
      }, customOpts)
12
      const target = opts.ele
13
      if (target) {
14
        target.style.position = 'relative'
15
        target.style.overflow = 'hidden'
16
        const rect = target.getBoundingClientRect()
17
        let ripple = target.querySelector('.waves-ripple')
18
        if (!ripple) {
19
          ripple = document.createElement('span')
20
          ripple.className = 'waves-ripple'
21
          ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px'
22
          target.appendChild(ripple)
23
        } else {
24
          ripple.className = 'waves-ripple'
25
        }
26
        switch (opts.type) {
27
          case 'center':
28
            ripple.style.top = (rect.height / 2 - ripple.offsetHeight / 2) + 'px'
29
            ripple.style.left = (rect.width / 2 - ripple.offsetWidth / 2) + 'px'
30
            break
31
          default:
32
            ripple.style.top = (e.pageY - rect.top - ripple.offsetHeight / 2 - document.body.scrollTop) + 'px'
33
            ripple.style.left = (e.pageX - rect.left - ripple.offsetWidth / 2 - document.body.scrollLeft) + 'px'
34
        }
35
        ripple.style.backgroundColor = opts.color
36
        ripple.className = 'waves-ripple z-active'
37
        return false
38
      }
39
    }, false)
40
  }
41
}
42

+ 9 - 0
src/icons/index.js

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

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

@ -0,0 +1 @@
1
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1511504199105" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1815" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M770.56 460.8h250.88C998.4 220.16 803.84 25.6 563.2 2.56v250.88c104.96 20.48 186.88 102.4 207.36 207.36z m0 0M460.8 253.44V2.56C220.16 25.6 25.6 220.16 2.56 460.8h250.88c20.48-104.96 102.4-186.88 207.36-207.36z m0 0M563.2 770.56v250.88c243.2-23.04 435.2-217.6 460.8-460.8H773.12C750.08 668.16 668.16 750.08 563.2 770.56z m0 0M253.44 563.2H2.56c23.04 243.2 217.6 435.2 460.8 460.8V773.12C355.84 750.08 273.92 668.16 253.44 563.2z m0 0" fill="" p-id="1816"></path></svg>

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

@ -0,0 +1 @@
1
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1503993826520" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7878" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M941.677063 391.710356c9.337669-14.005992 6.224772-32.68133-6.224772-43.575447-14.005992-10.894118-32.68133-7.78122-43.575447 6.224771-1.556449 1.556449-174.300768 205.426673-379.727441 205.426673-199.200878 0-379.727441-205.426673-381.28389-206.982098-10.894118-12.450567-31.124881-14.005992-43.575448-3.112898-12.450567 10.894118-14.005992 31.124881-3.112897 43.575448 3.112897 4.668323 40.46255 46.687322 99.600439 93.375667l-79.369676 82.48155c-12.450567 12.450567-10.894118 32.68133 1.556449 43.575448 3.112897 6.224772 10.894118 9.337669 18.675338 9.337669 7.78122 0 15.562441-3.112897 21.787213-9.337669l85.594447-88.706321c40.46255 28.013007 88.706321 54.469566 141.619438 73.14388L340.959485 707.631586c-4.668323 17.118889 4.669346 34.237779 21.787213 38.906101h9.337669c14.005992 0 26.456558-9.337669 29.568432-23.343661l32.68133-110.494556c24.90011 4.668323 51.356668 7.78122 77.813227 7.78122s52.913117-3.112897 77.813227-7.78122l32.68133 108.938108c3.112897 14.005992 17.118889 23.343661 29.569456 23.343661 3.112897 0 6.224772 0 7.78122-1.556449 17.118889-4.669346 26.456558-21.787212 21.788236-38.906102l-32.68133-108.938108c52.913117-18.675338 101.156888-45.131897 141.619438-73.14388l84.037998 87.150896c6.224772 6.224772 14.005992 9.337669 21.787212 9.337669 7.78122 0 15.562441-3.112897 21.787212-9.337669 12.450567-12.450567 12.450567-31.124881 1.556449-43.575448l-79.369675-82.48155c63.808258-46.688345 101.158934-91.820242 101.158934-91.820242z" p-id="7879"></path></svg>

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

@ -0,0 +1 @@
1
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1511504319223" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3230" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M942.827259 80.3367c-11.42419-11.406794-26.41051-17.117866-41.377386-17.117866-14.985296 0-29.952172 5.711072-41.358967 17.117866L719.392444 221.014696l-19.441794 19.441794L681.577187 258.832 569.516971 370.909611 375.99749 564.411697l0 0.019443 0 84.372619 81.145112 0 0.010233 0 95.418186-95.435583 213.398228-213.400275 3.14155-3.14155-0.019443 0 9.979282-9.977235 0 0L942.827259 163.073052C965.697129 140.259464 965.697129 103.186104 942.827259 80.3367z" p-id="3231"></path><path d="M793.542234 367.521444 580.14196 580.939115 484.72582 676.376745 473.299583 687.800935 457.152834 687.800935 375.99749 687.800935 337.000314 687.800935 337.000314 648.803759 337.000314 564.411697 337.000314 548.264948 348.424504 536.838711 541.943986 343.338672 654.004201 231.259014 665.428392 219.834824 64.020082 219.834824 64.020082 960.781166 804.966425 960.781166 804.966425 356.116697 796.607036 364.475062Z" p-id="3232"></path></svg>

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

@ -0,0 +1 @@
1
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1525761666409" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10880" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M107.2 212.8m-67.2 0a4.2 4.2 0 1 0 134.4 0 4.2 4.2 0 1 0-134.4 0Z" p-id="10881"></path><path d="M980.8 145.6 297.6 145.6c-9.6 0-16 8-16 16l0 102.4c0 9.6 8 16 16 16l683.2 0c9.6 0 16-8 16-16l0-102.4C996.8 152 988.8 145.6 980.8 145.6z" p-id="10882"></path><path d="M96 497.6m-67.2 0a4.2 4.2 0 1 0 134.4 0 4.2 4.2 0 1 0-134.4 0Z" p-id="10883"></path><path d="M968 430.4 284.8 430.4c-9.6 0-16 8-16 16l0 102.4c0 9.6 8 16 16 16l683.2 0c9.6 0 16-8 16-16l0-102.4C984 438.4 977.6 430.4 968 430.4z" p-id="10884"></path><path d="M96 795.2m-67.2 0a4.2 4.2 0 1 0 134.4 0 4.2 4.2 0 1 0-134.4 0Z" p-id="10885"></path><path d="M968 728 284.8 728c-9.6 0-16 8-16 16l0 102.4c0 9.6 8 16 16 16l683.2 0c9.6 0 16-8 16-16l0-102.4C984 736 977.6 728 968 728z" p-id="10886"></path></svg>

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

@ -0,0 +1 @@
1
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1529559567446" class="icon" style="" viewBox="0 0 1167 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1767" xmlns:xlink="http://www.w3.org/1999/xlink" width="227.9296875" height="200"><defs><style type="text/css"></style></defs><path d="M0.015952 74.459413A2.286 2.286 1440 1 0 145.85218 74.459413 2.286 2.286 1440 1 0 0.015952 74.459413zM291.720312 1.525347 1166.801488 1.525347 1166.801488 147.361574 291.720312 147.361574zM291.720312 366.163773A2.286 2.286 1440 1 0 437.55654 366.163773 2.286 2.286 1440 1 0 291.720312 366.163773zM583.424672 293.229707 1166.801488 293.229707 1166.801488 439.065934 583.424672 439.065934zM291.720312 949.540588A2.286 2.286 1440 1 0 437.55654 949.540588 2.286 2.286 1440 1 0 291.720312 949.540588zM583.424672 876.638427 1166.801488 876.638427 1166.801488 1022.474654 583.424672 1022.474654zM583.424672 657.836228A2.286 2.286 1440 1 0 729.2609 657.836228 2.286 2.286 1440 1 0 583.424672 657.836228zM875.129032 584.934067 1166.801488 584.934067 1166.801488 730.770294 875.129032 730.770294z" p-id="1768"></path></svg>

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

@ -0,0 +1 @@
1
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1503994678729" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9229" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M780.8 354.579692 665.6 354.579692 665.6 311.689846c0-72.310154-19.849846-193.299692-153.6-193.299692-138.870154 0-153.6 135.049846-153.6 193.299692l0 42.889846L243.2 354.579692 243.2 311.689846C243.2 122.249846 348.790154 0 512 0s268.8 122.249846 268.8 311.689846L780.8 354.579692zM588.8 669.420308C588.8 625.900308 554.220308 590.769231 512 590.769231s-76.8 35.131077-76.8 78.651077c0 29.459692 15.399385 54.468923 38.439385 67.820308l0 89.639385c0 21.740308 17.250462 39.699692 38.4 39.699692s38.4-17.959385 38.4-39.699692l0-89.639385C573.44 723.889231 588.8 698.88 588.8 669.420308zM896 512l0 393.609846c0 65.260308-51.869538 118.390154-115.2 118.390154L243.2 1024c-63.291077 0-115.2-53.129846-115.2-118.390154L128 512c0-65.220923 51.869538-118.390154 115.2-118.390154l537.6 0C844.130462 393.609846 896 446.779077 896 512z" p-id="9230"></path></svg>

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

@ -0,0 +1 @@
1
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1510727502091" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1640" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><defs><style type="text/css"></style></defs><path d="M765.184 873.941333c0 33.28-28.501333 60.288-63.829333 60.288L63.829333 934.229333C28.501333 934.229333 0 907.221333 0 873.941333c0-120.576 123.264-233.258667 249.216-277.674667-72.789333-42.496-121.728-118.058667-121.728-204.8L127.488 331.136c0-133.248 114.346667-241.365333 255.146667-241.365333s255.146667 108.117333 255.146667 241.365333l0 60.288c0 86.826667-48.981333 162.304-121.728 204.842667C641.962667 640.725333 765.184 753.365333 765.184 873.941333L765.184 873.941333z" p-id="1641"></path><path d="M848.256 870.570667l126.933333 0c27.008 0 48.810667-20.650667 48.810667-46.08 0-92.245333-94.293333-178.346667-190.549333-212.309333 55.637333-32.512 93.098667-90.282667 93.098667-156.672L926.549333 409.344c0-101.888-87.424-184.576-195.114667-184.576-13.397333 0-26.453333 1.28-39.125333 3.712 15.488 31.146667 24.149333 65.92 24.149333 102.613333l0 60.288c0 86.826667-24.448 152.746667-88.533333 204.842667C746.666667 625.365333 846.421333 751.018667 848.256 870.570667z" p-id="1642"></path></svg>

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

@ -0,0 +1 @@
1
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1511504440567" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5070" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M568.6 0h454.9v454.9H568.6V0z m0 568.6h454.9v454.9H568.6V568.6zM0 568.6h454.9v454.9H0V568.6zM0 0h454.9v454.9H0V0z" fill="" p-id="5071"></path></svg>

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

@ -0,0 +1 @@
1
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1511512690058" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3507" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M1013.703 693.345c6.865 6.865 10.297 14.874 10.297 24.027l0 205.944c0 9.916-3.432 18.115-10.297 24.599-6.865 6.483-15.255 9.725-25.171 9.725L782.588 957.64c-9.153 0-17.162-3.242-24.027-9.725-6.865-6.483-10.297-14.683-10.297-24.599L748.264 717.372c0-6.102 1.526-11.823 4.577-17.162s7.246-9.534 12.586-12.586 11.06-4.577 17.162-4.577l77.801 0L860.39 546.896c0-4.577-1.144-8.772-3.432-12.586s-5.339-6.865-9.153-9.153-8.009-3.432-12.585-3.432L543.464 521.725l0 161.323 77.801 0c9.153 0 17.162 3.432 24.027 10.297s10.297 14.874 10.297 24.027l0 205.944c0 6.102-1.526 11.823-4.577 17.162s-7.246 9.534-12.585 12.585-11.06 4.577-17.162 4.577L415.321 957.64c-6.102 0-11.823-1.526-17.162-4.577s-9.725-7.246-13.158-12.585-5.149-11.06-5.149-17.162L379.852 717.372c0-9.153 3.432-17.162 10.297-24.027s15.255-10.297 25.171-10.297l76.657 0L491.977 521.725 188.782 521.725c-7.628 0-13.92 2.479-18.878 7.437-4.958 4.958-7.437 10.869-7.437 17.734l0 136.152 77.801 0c9.916 0 18.115 3.432 24.599 10.297s9.725 14.874 9.725 24.027l0 205.944c0 9.916-3.242 18.115-9.725 24.599-6.483 6.483-14.683 9.725-24.599 9.725L34.324 957.64c-3.814 0-7.437-0.572-10.869-1.716-3.432-1.144-6.483-2.67-9.153-4.577-2.67-1.907-5.149-4.386-7.437-7.437-2.288-3.051-4.004-6.293-5.149-9.725C0.572 930.753 0 927.13 0 923.316L0 717.372c0-3.051 0.381-6.102 1.144-9.153s1.907-5.721 3.432-8.009 3.432-4.577 5.721-6.865 4.577-4.195 6.865-5.721 4.958-2.67 8.009-3.432 6.102-1.144 9.153-1.144l77.801 0L112.125 495.41c0-6.865 2.479-12.776 7.437-17.734s10.869-7.437 17.734-7.437l354.682 0L491.978 342.096l-76.657 0c-9.916 0-18.306-3.432-25.171-10.297s-10.297-14.874-10.297-24.027L379.853 101.828c0-9.916 3.432-18.306 10.297-25.171s15.255-10.297 25.171-10.297l205.944 0c6.102 0 11.823 1.716 17.162 5.149 5.339 3.432 9.534 7.818 12.585 13.158 3.051 5.339 4.577 11.06 4.577 17.162l0 205.944c0 9.153-3.432 17.162-10.297 24.027s-14.874 10.297-24.027 10.297l-77.801 0 0 128.143L885.56 470.24c7.628 0 13.92 2.479 18.878 7.437s7.437 10.869 7.437 17.734l0 187.638 76.657 0C998.448 683.048 1006.838 686.48 1013.703 693.345z" p-id="3508"></path></svg>

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

@ -0,0 +1 @@
1
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1503993891882" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7986" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><defs><style type="text/css"></style></defs><path d="M504.951 511.98c93.49 0 169.28-74.002 169.28-165.26 0-91.276-75.79-165.248-169.28-165.248-93.486 0-169.287 73.972-169.279 165.248-0.001 91.258 75.793 165.26 169.28 165.26z m77.6 55.098H441.466c-120.767 0-218.678 95.564-218.678 213.45V794.3c0 48.183 97.911 48.229 218.678 48.229H582.55c120.754 0 218.66-1.78 218.66-48.229v-13.77c0-117.887-97.898-213.45-218.66-213.45z" p-id="7987"></path></svg>

+ 27 - 0
src/main.js

@ -0,0 +1,27 @@
1
import Vue from 'vue'
2
3
import 'normalize.css/normalize.css'// A modern alternative to CSS resets
4
5
import ElementUI from 'element-ui'
6
import 'element-ui/lib/theme-chalk/index.css'
7
import locale from 'element-ui/lib/locale/lang/en' // lang i18n
8
9
import '@/styles/index.scss' // global css
10
11
import App from './App'
12
import router from './router'
13
import store from './store'
14
15
import '@/icons' // icon
16
import '@/permission' // permission control
17
18
Vue.use(ElementUI, { locale })
19
20
Vue.config.productionTip = false
21
22
new Vue({
23
  el: '#app',
24
  router,
25
  store,
26
  render: h => h(App)
27
})

+ 41 - 0
src/permission.js

@ -0,0 +1,41 @@
1
import router from './router'
2
import store from './store'
3
import NProgress from 'nprogress' // Progress 进度条
4
import 'nprogress/nprogress.css'// Progress 进度条样式
5
import { Message } from 'element-ui'
6
import { getToken } from '@/utils/auth' // 验权
7
8
const whiteList = ['/login'] // 不重定向白名单
9
router.beforeEach((to, from, next) => {
10
  NProgress.start()
11
  if (getToken()) {
12
    if (to.path === '/login') {
13
      next({ path: '/' })
14
      NProgress.done() // if current page is dashboard will not trigger	afterEach hook, so manually handle it
15
    } else {
16
      if (store.getters.roles.length === 0) {
17
        store.dispatch('GetInfo').then(res => { // 拉取用户信息
18
          next()
19
        }).catch((err) => {
20
          store.dispatch('FedLogOut').then(() => {
21
            Message.error(err || 'Verification failed, please login again')
22
            next({ path: '/' })
23
          })
24
        })
25
      } else {
26
        next()
27
      }
28
    }
29
  } else {
30
    if (whiteList.indexOf(to.path) !== -1) {
31
      next()
32
    } else {
33
      next('/login')
34
      NProgress.done()
35
    }
36
  }
37
})
38
39
router.afterEach(() => {
40
  NProgress.done() // 结束Progress
41
})

+ 98 - 0
src/router/index.js

@ -0,0 +1,98 @@
1
import Vue from 'vue'
2
import Router from 'vue-router'
3
4
// in development-env not use lazy-loading, because lazy-loading too many pages will cause webpack hot update too slow. so only in production use lazy-loading;
5
// detail: https://panjiachen.github.io/vue-element-admin-site/#/lazy-loading
6
7
Vue.use(Router)
8
9
/* Layout */
10
import Layout from '../views/layout/Layout'
11
12
/**
13
* hidden: true                   if `hidden:true` will not show in the sidebar(default is false)
14
* alwaysShow: true               if set true, will always show the root menu, whatever its child routes length
15
*                                if not set alwaysShow, only more than one route under the children
16
*                                it will becomes nested mode, otherwise not show the root menu
17
* redirect: noredirect           if `redirect:noredirect` will no redirct in the breadcrumb
18
* name:'router-name'             the name is used by <keep-alive> (must set!!!)
19
* meta : {
20
    title: 'title'               the name show in submenu and breadcrumb (recommend set)
21
    icon: 'svg-name'             the icon show in the sidebar,
22
  }
23
**/
24
export const constantRouterMap = [
25
  { path: '/login', component: () => import('@/views/login/index'), hidden: true },
26
  { path: '/findPwd', component: () => import('@/views/findPwd/index'), hidden: true },
27
  { path: '/404', component: () => import('@/views/404'), hidden: true },
28
29
  {
30
    path: '/',
31
    component: Layout,
32
    redirect: '/dashboard',
33
    name: 'Dashboard',
34
    hidden: true,
35
    children: [{
36
      path: 'dashboard',
37
      component: () => import('@/views/dashboard/index')
38
    }]
39
  },
40
41
  {
42
    path: '/peoplesManage',
43
    component: Layout,
44
    redirect: '/peoplesManage/infoManage',
45
    name: 'peoplesManage',
46
    meta: { title: '用户系统管理', icon: 'peoples' },
47
    children: [
48
      {
49
        path: 'infoManage',
50
        name: 'infoManage',
51
        component: () => import('@/views/peoplesManage/infoManage/index'),
52
        meta: { title: '用户信息管理', icon: 'peoples' }
53
      }
54
    ]
55
  },
56
  {
57
    path: '/baseInfoManage',
58
    component: Layout,
59
    redirect: '/baseInfoManage/bridgesInfo',
60
    name: 'BaseInfoManage',
61
    meta: { title: '基础信息管理', icon: 'list' },
62
    children: [
63
      {
64
        path: 'bridgesInfo',
65
        name: 'BridgesInfo',
66
        component: () => import('@/views/baseInfoManage/bridgesInfo/index'),
67
        meta: { title: '桥梁信息管理' }
68
      },
69
      {
70
        path: 'serversConfig',
71
        name: 'ServersConfig',
72
        component: () => import('@/views/baseInfoManage/serversConfig/index'),
73
        meta: { title: '采集服务器配置' }
74
      },
75
      {
76
        path: 'boxesConfig',
77
        name: 'BoxesConfig',
78
        component: () => import('@/views/baseInfoManage/boxesConfig/index'),
79
        meta: { title: '采集盒配置' }
80
      },
81
      {
82
        path: 'sensorsConfig',
83
        name: 'SensorsConfig',
84
        component: () => import('@/views/baseInfoManage/sensorsConfig/index'),
85
        meta: { title: '传感器配置' }
86
      }
87
    ]
88
  },
89
90
  { path: '*', redirect: '/404', hidden: true }
91
]
92
93
export default new Router({
94
  // mode: 'history', //后端支持可开
95
  scrollBehavior: () => ({ y: 0 }),
96
  routes: constantRouterMap
97
})
98

+ 9 - 0
src/store/getters.js

@ -0,0 +1,9 @@
1
const getters = {
2
  sidebar: state => state.app.sidebar,
3
  device: state => state.app.device,
4
  token: state => state.user.token,
5
  avatar: state => state.user.avatar,
6
  name: state => state.user.name,
7
  roles: state => state.user.roles
8
}
9
export default getters

+ 17 - 0
src/store/index.js

@ -0,0 +1,17 @@
1
import Vue from 'vue'
2
import Vuex from 'vuex'
3
import app from './modules/app'
4
import user from './modules/user'
5
import getters from './getters'
6
7
Vue.use(Vuex)
8
9
const store = new Vuex.Store({
10
  modules: {
11
    app,
12
    user
13
  },
14
  getters
15
})
16
17
export default store

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

@ -0,0 +1,43 @@
1
import Cookies from 'js-cookie'
2
3
const app = {
4
  state: {
5
    sidebar: {
6
      opened: !+Cookies.get('sidebarStatus'),
7
      withoutAnimation: false
8
    },
9
    device: 'desktop'
10
  },
11
  mutations: {
12
    TOGGLE_SIDEBAR: state => {
13
      if (state.sidebar.opened) {
14
        Cookies.set('sidebarStatus', 1)
15
      } else {
16
        Cookies.set('sidebarStatus', 0)
17
      }
18
      state.sidebar.opened = !state.sidebar.opened
19
      state.sidebar.withoutAnimation = false
20
    },
21
    CLOSE_SIDEBAR: (state, withoutAnimation) => {
22
      Cookies.set('sidebarStatus', 1)
23
      state.sidebar.opened = false
24
      state.sidebar.withoutAnimation = withoutAnimation
25
    },
26
    TOGGLE_DEVICE: (state, device) => {
27
      state.device = device
28
    }
29
  },
30
  actions: {
31
    ToggleSideBar: ({ commit }) => {
32
      commit('TOGGLE_SIDEBAR')
33
    },
34
    CloseSideBar({ commit }, { withoutAnimation }) {
35
      commit('CLOSE_SIDEBAR', withoutAnimation)
36
    },
37
    ToggleDevice({ commit }, device) {
38
      commit('TOGGLE_DEVICE', device)
39
    }
40
  }
41
}
42
43
export default app

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

@ -0,0 +1,87 @@
1
import { login, logout, getInfo } from '@/api/login'
2
import { getToken, setToken, removeToken } from '@/utils/auth'
3
4
const user = {
5
  state: {
6
    token: getToken(),
7
    name: '',
8
    avatar: '',
9
    roles: []
10
  },
11
12
  mutations: {
13
    SET_TOKEN: (state, token) => {
14
      state.token = token
15
    },
16
    SET_NAME: (state, name) => {
17
      state.name = name
18
    },
19
    SET_AVATAR: (state, avatar) => {
20
      state.avatar = avatar
21
    },
22
    SET_ROLES: (state, roles) => {
23
      state.roles = roles
24
    }
25
  },
26
27
  actions: {
28
    // 登录
29
    Login({ commit }, userInfo) {
30
      const username = userInfo.username.trim()
31
      return new Promise((resolve, reject) => {
32
        login(username, userInfo.password).then(response => {
33
          const data = response.data
34
          setToken(data.token)
35
          commit('SET_TOKEN', data.token)
36
          resolve()
37
        }).catch(error => {
38
          reject(error)
39
        })
40
      })
41
    },
42
43
    // 获取用户信息
44
    GetInfo({ commit, state }) {
45
      return new Promise((resolve, reject) => {
46
        getInfo(state.token).then(response => {
47
          const data = response.data
48
          if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组
49
            commit('SET_ROLES', data.roles)
50
          } else {
51
            reject('getInfo: roles must be a non-null array !')
52
          }
53
          commit('SET_NAME', data.name)
54
          commit('SET_AVATAR', data.avatar)
55
          resolve(response)
56
        }).catch(error => {
57
          reject(error)
58
        })
59
      })
60
    },
61
62
    // 登出
63
    LogOut({ commit, state }) {
64
      return new Promise((resolve, reject) => {
65
        logout(state.token).then(() => {
66
          commit('SET_TOKEN', '')
67
          commit('SET_ROLES', [])
68
          removeToken()
69
          resolve()
70
        }).catch(error => {
71
          reject(error)
72
        })
73
      })
74
    },
75
76
    // 前端 登出
77
    FedLogOut({ commit }) {
78
      return new Promise(resolve => {
79
        commit('SET_TOKEN', '')
80
        removeToken()
81
        resolve()
82
      })
83
    }
84
  }
85
}
86
87
export default user

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

@ -0,0 +1,29 @@
1
 //to reset element-ui default css
2
.el-upload {
3
  input[type="file"] {
4
    display: none !important;
5
  }
6
}
7
8
.el-upload__input {
9
  display: none;
10
}
11
12
//暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461
13
.el-dialog {
14
  transform: none;
15
  left: 0;
16
  position: relative;
17
  margin: 0 auto;
18
}
19
20
//element ui upload
21
.upload-container {
22
  .el-upload {
23
    width: 100%;
24
    .el-upload-dragger {
25
      width: 100%;
26
      height: 200px;
27
    }
28
  }
29
}

+ 78 - 0
src/styles/index.scss

@ -0,0 +1,78 @@
1
@import './variables.scss';
2
@import './mixin.scss';
3
@import './transition.scss';
4
@import './element-ui.scss';
5
@import './sidebar.scss';
6
7
body {
8
  height: 100%;
9
  -moz-osx-font-smoothing: grayscale;
10
  -webkit-font-smoothing: antialiased;
11
  text-rendering: optimizeLegibility;
12
  font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
13
}
14
15
label {
16
  font-weight: 700;
17
}
18
19
html {
20
  height: 100%;
21
  box-sizing: border-box;
22
}
23
24
#app{
25
  height: 100%;
26
}
27
28
*,
29
*:before,
30
*:after {
31
  box-sizing: inherit;
32
}
33
34
a,
35
a:focus,
36
a:hover {
37
  cursor: pointer;
38
  color: inherit;
39
  outline: none;
40
  text-decoration: none;
41
}
42
43
div:focus{
44
  outline: none;
45
 }
46
47
a:focus,
48
a:active {
49
  outline: none;
50
}
51
52
a,
53
a:focus,
54
a:hover {
55
  cursor: pointer;
56
  color: inherit;
57
  text-decoration: none;
58
}
59
60
.clearfix {
61
  &:after {
62
    visibility: hidden;
63
    display: block;
64
    font-size: 0;
65
    content: " ";
66
    clear: both;
67
    height: 0;
68
  }
69
}
70
71
//main-container全局样式
72
.app-main{
73
  min-height: 100%
74
}
75
76
.app-container {
77
  padding: 20px;
78
}

+ 27 - 0
src/styles/mixin.scss

@ -0,0 +1,27 @@
1
@mixin clearfix {
2
  &:after {
3
    content: "";
4
    display: table;
5
    clear: both;
6
  }
7
}
8
9
@mixin scrollBar {
10
  &::-webkit-scrollbar-track-piece {
11
    background: #d3dce6;
12
  }
13
  &::-webkit-scrollbar {
14
    width: 6px;
15
  }
16
  &::-webkit-scrollbar-thumb {
17
    background: #99a9bf;
18
    border-radius: 20px;
19
  }
20
}
21
22
@mixin relative {
23
  position: relative;
24
  width: 100%;
25
  height: 100%;
26
}
27

+ 120 - 0
src/styles/sidebar.scss

@ -0,0 +1,120 @@
1
#app {
2
  // 主体区域
3
  .main-container {
4
    min-height: 100%;
5
    top:50px;
6
    transition: margin-left .28s;
7
    margin-left: 240px;
8
    position: relative;
9
  }
10
  // 侧边栏
11
  .sidebar-container {
12
    transition: width 0.28s;
13
    width: 240px !important;
14
    height: 100%;
15
    position: fixed;
16
    font-size: 0px;
17
    top: 50px;
18
    bottom: 0;
19
    left: 0;
20
    z-index: 1001;
21
    overflow: hidden;
22
    //reset element-ui css
23
    .horizontal-collapse-transition {
24
      transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
25
    }
26
    .scrollbar-wrapper {
27
      height: 100%;
28
      .el-scrollbar__view {
29
        height: 100%;
30
      }
31
    }
32
    .is-horizontal {
33
      display: none;
34
    }
35
    a {
36
      display: inline-block;
37
      width: 100%;
38
      overflow: hidden;
39
    }
40
    .svg-icon {
41
      margin-right: 16px;
42
    }
43
    .el-menu {
44
      border: none;
45
      height: 100%;
46
      width: 100% !important;
47
    }
48
  }
49
  .hideSidebar {
50
    .sidebar-container {
51
      width: 36px !important;
52
    }
53
    .main-container {
54
      margin-left: 36px;
55
    }
56
    .submenu-title-noDropdown {
57
      padding-left: 10px !important;
58
      position: relative;
59
      .el-tooltip {
60
        padding: 0 10px !important;
61
      }
62
    }
63
    .el-submenu {
64
      overflow: hidden;
65
      &>.el-submenu__title {
66
        padding-left: 10px !important;
67
        .el-submenu__icon-arrow {
68
          display: none;
69
        }
70
      }
71
    }
72
    .el-menu--collapse {
73
      .el-submenu {
74
        &>.el-submenu__title {
75
          &>span {
76
            height: 0;
77
            width: 0;
78
            overflow: hidden;
79
            visibility: hidden;
80
            display: inline-block;
81
          }
82
        }
83
      }
84
    }
85
  }
86
  .sidebar-container .nest-menu .el-submenu>.el-submenu__title,
87
  .sidebar-container .el-submenu .el-menu-item {
88
    min-width: 180px !important;
89
    background-color: $subMenuBg !important;
90
    &:hover {
91
      background-color: $menuHover !important;
92
    }
93
  }
94
  .el-menu--collapse .el-menu .el-submenu {
95
    min-width: 180px !important;
96
  }
97
98
  //适配移动端
99
  .mobile {
100
    .main-container {
101
      margin-left: 0px;
102
    }
103
    .sidebar-container {
104
      transition: transform .28s;
105
      width: 180px !important;
106
    }
107
    &.hideSidebar {
108
      .sidebar-container {
109
        transition-duration: 0.3s;
110
        transform: translate3d(-180px, 0, 0);
111
      }
112
    }
113
  }
114
  .withoutAnimation {
115
    .main-container,
116
    .sidebar-container {
117
      transition: none;
118
    }
119
  }
120
}

+ 32 - 0
src/styles/transition.scss

@ -0,0 +1,32 @@
1
//globl transition css
2
3
/*fade*/
4
.fade-enter-active,
5
.fade-leave-active {
6
  transition: opacity 0.28s;
7
}
8
9
.fade-enter,
10
.fade-leave-active {
11
  opacity: 0;
12
}
13
14
/*fade*/
15
.breadcrumb-enter-active,
16
.breadcrumb-leave-active {
17
  transition: all .5s;
18
}
19
20
.breadcrumb-enter,
21
.breadcrumb-leave-active {
22
  opacity: 0;
23
  transform: translateX(20px);
24
}
25
26
.breadcrumb-move {
27
  transition: all .5s;
28
}
29
30
.breadcrumb-leave-active {
31
  position: absolute;
32
}

+ 4 - 0
src/styles/variables.scss

@ -0,0 +1,4 @@
1
//sidebar
2
$menuBg:#304156;
3
$subMenuBg:#1f2d3d;
4
$menuHover:#001528;

+ 15 - 0
src/utils/auth.js

@ -0,0 +1,15 @@
1
import Cookies from 'js-cookie'
2
3
const TokenKey = 'Admin-Token'
4
5
export function getToken() {
6
  return Cookies.get(TokenKey)
7
}
8
9
export function setToken(token) {
10
  return Cookies.set(TokenKey, token)
11
}
12
13
export function removeToken() {
14
  return Cookies.remove(TokenKey)
15
}

+ 58 - 0
src/utils/index.js

@ -0,0 +1,58 @@
1
/**
2
 * Created by jiachenpan on 16/11/18.
3
 */
4
5
export function parseTime(time, cFormat) {
6
  if (arguments.length === 0) {
7
    return null
8
  }
9
  const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
10
  let date
11
  if (typeof time === 'object') {
12
    date = time
13
  } else {
14
    if (('' + time).length === 10) time = parseInt(time) * 1000
15
    date = new Date(time)
16
  }
17
  const formatObj = {
18
    y: date.getFullYear(),
19
    m: date.getMonth() + 1,
20
    d: date.getDate(),
21
    h: date.getHours(),
22
    i: date.getMinutes(),
23
    s: date.getSeconds(),
24
    a: date.getDay()
25
  }
26
  const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
27
    let value = formatObj[key]
28
    if (key === 'a') return ['一', '二', '三', '四', '五', '六', '日'][value - 1]
29
    if (result.length > 0 && value < 10) {
30
      value = '0' + value
31
    }
32
    return value || 0
33
  })
34
  return time_str
35
}
36
37
export function formatTime(time, option) {
38
  time = +time * 1000
39
  const d = new Date(time)
40
  const now = Date.now()
41
42
  const diff = (now - d) / 1000
43
44
  if (diff < 30) {
45
    return '刚刚'
46
  } else if (diff < 3600) { // less 1 hour
47
    return Math.ceil(diff / 60) + '分钟前'
48
  } else if (diff < 3600 * 24) {
49
    return Math.ceil(diff / 3600) + '小时前'
50
  } else if (diff < 3600 * 24 * 2) {
51
    return '1天前'
52
  }
53
  if (option) {
54
    return parseTime(time, option)
55
  } else {
56
    return d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分'
57
  }
58
}

+ 66 - 0
src/utils/request.js

@ -0,0 +1,66 @@
1
import axios from 'axios'
2
import { Message, MessageBox } from 'element-ui'
3
import store from '../store'
4
import { getToken } from '@/utils/auth'
5
6
// 创建axios实例
7
const service = axios.create({
8
  baseURL: process.env.BASE_API, // api的base_url
9
  timeout: 5000 // 请求超时时间
10
})
11
12
// request拦截器
13
service.interceptors.request.use(config => {
14
  if (store.getters.token) {
15
    config.headers['X-Token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
16
  }
17
  return config
18
}, error => {
19
  // Do something with request error
20
  console.log(error) // for debug
21
  Promise.reject(error)
22
})
23
24
// respone拦截器
25
service.interceptors.response.use(
26
  response => {
27
  /**
28
  * code为非20000是抛错 可结合自己业务进行修改
29
  */
30
    const res = response.data
31
    if (res.code !== 20000) {
32
      Message({
33
        message: res.message,
34
        type: 'error',
35
        duration: 5 * 1000
36
      })
37
38
      // 50008:非法的token; 50012:其他客户端登录了;  50014:Token 过期了;
39
      if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
40
        MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', {
41
          confirmButtonText: '重新登录',
42
          cancelButtonText: '取消',
43
          type: 'warning'
44
        }).then(() => {
45
          store.dispatch('FedLogOut').then(() => {
46
            location.reload()// 为了重新实例化vue-router对象 避免bug
47
          })
48
        })
49
      }
50
      return Promise.reject('error')
51
    } else {
52
      return response.data
53
    }
54
  },
55
  error => {
56
    console.log('err' + error)// for debug
57
    Message({
58
      message: error.message,
59
      type: 'error',
60
      duration: 5 * 1000
61
    })
62
    return Promise.reject(error)
63
  }
64
)
65
66
export default service

+ 33 - 0
src/utils/validate.js

@ -0,0 +1,33 @@
1
/**
2
 * Created by jiachenpan on 16/11/18.
3
 */
4
5
export function isvalidUsername(str) {
6
  const valid_map = ['admin', 'editor']
7
  return valid_map.indexOf(str.trim()) >= 0
8
}
9
10
/* 合法uri*/
11
export function validateURL(textval) {
12
  const urlregex = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
13
  return urlregex.test(textval)
14
}
15
16
/* 小写字母*/
17
export function validateLowerCase(str) {
18
  const reg = /^[a-z]+$/
19
  return reg.test(str)
20
}
21
22
/* 大写字母*/
23
export function validateUpperCase(str) {
24
  const reg = /^[A-Z]+$/
25
  return reg.test(str)
26
}
27
28
/* 大小写字母*/
29
export function validatAlphabets(str) {
30
  const reg = /^[A-Za-z]+$/
31
  return reg.test(str)
32
}
33

+ 236 - 0
src/views/404.vue

@ -0,0 +1,236 @@
1
<template>
2
  <div class="wscn-http404-container">
3
    <div class="wscn-http404">
4
      <div class="pic-404">
5
        <img class="pic-404__parent" :src="img_404" alt="404">
6
        <img class="pic-404__child left" :src="img_404_cloud" alt="404">
7
        <img class="pic-404__child mid" :src="img_404_cloud" alt="404">
8
        <img class="pic-404__child right" :src="img_404_cloud" alt="404">
9
      </div>
10
      <div class="bullshit">
11
        <div class="bullshit__oops">OOPS!</div>
12
        <div class="bullshit__info">版权所有
13
          <a class='link-type' href='https://wallstreetcn.com' target='_blank'>华尔街见闻</a>
14
        </div>
15
        <div class="bullshit__headline">{{ message }}</div>
16
        <div class="bullshit__info">请检查您输入的网址是否正确,请点击以下按钮返回主页或者发送错误报告</div>
17
        <a href="" class="bullshit__return-home">返回首页</a>
18
      </div>
19
    </div>
20
  </div>
21
</template>
22
23
<script>
24
import img_404 from '@/assets/404_images/404.png'
25
import img_404_cloud from '@/assets/404_images/404_cloud.png'
26
27
export default {
28
  name: 'page404',
29
  data() {
30
    return {
31
      img_404,
32
      img_404_cloud
33
    }
34
  },
35
  computed: {
36
    message() {
37
      return '网管说这个页面你不能进......'
38
    }
39
  }
40
}
41
</script>
42
43
<style rel="stylesheet/scss" lang="scss" scoped>
44
.wscn-http404-container{
45
  transform: translate(-50%,-50%);
46
  position: absolute;
47
  top: 40%;
48
  left: 50%;
49
}
50
.wscn-http404 {
51
  position: relative;
52
  width: 1200px;
53
  padding: 0 50px;
54
  overflow: hidden;
55
  .pic-404 {
56
    position: relative;
57
    float: left;
58
    width: 600px;
59
    overflow: hidden;
60
    &__parent {
61
      width: 100%;
62
    }
63
    &__child {
64
      position: absolute;
65
      &.left {
66
        width: 80px;
67
        top: 17px;
68
        left: 220px;
69
        opacity: 0;
70
        animation-name: cloudLeft;
71
        animation-duration: 2s;
72
        animation-timing-function: linear;
73
        animation-fill-mode: forwards;
74
        animation-delay: 1s;
75
      }
76
      &.mid {
77
        width: 46px;
78
        top: 10px;
79
        left: 420px;
80
        opacity: 0;
81
        animation-name: cloudMid;
82
        animation-duration: 2s;
83
        animation-timing-function: linear;
84
        animation-fill-mode: forwards;
85
        animation-delay: 1.2s;
86
      }
87
      &.right {
88
        width: 62px;
89
        top: 100px;
90
        left: 500px;
91
        opacity: 0;
92
        animation-name: cloudRight;
93
        animation-duration: 2s;
94
        animation-timing-function: linear;
95
        animation-fill-mode: forwards;
96
        animation-delay: 1s;
97
      }
98
      @keyframes cloudLeft {
99
        0% {
100
          top: 17px;
101
          left: 220px;
102
          opacity: 0;
103
        }
104
        20% {
105
          top: 33px;
106
          left: 188px;
107
          opacity: 1;
108
        }
109
        80% {
110
          top: 81px;
111
          left: 92px;
112
          opacity: 1;
113
        }
114
        100% {
115
          top: 97px;
116
          left: 60px;
117
          opacity: 0;
118
        }
119
      }
120
      @keyframes cloudMid {
121
        0% {
122
          top: 10px;
123
          left: 420px;
124
          opacity: 0;
125
        }
126
        20% {
127
          top: 40px;
128
          left: 360px;
129
          opacity: 1;
130
        }
131
        70% {
132
          top: 130px;
133
          left: 180px;
134
          opacity: 1;
135
        }
136
        100% {
137
          top: 160px;
138
          left: 120px;
139
          opacity: 0;
140
        }
141
      }
142
      @keyframes cloudRight {
143
        0% {
144
          top: 100px;
145
          left: 500px;
146
          opacity: 0;
147
        }
148
        20% {
149
          top: 120px;
150
          left: 460px;
151
          opacity: 1;
152
        }
153
        80% {
154
          top: 180px;
155
          left: 340px;
156
          opacity: 1;
157
        }
158
        100% {
159
          top: 200px;
160
          left: 300px;
161
          opacity: 0;
162
        }
163
      }
164
    }
165
  }
166
  .bullshit {
167
    position: relative;
168
    float: left;
169
    width: 300px;
170
    padding: 30px 0;
171
    overflow: hidden;
172
    &__oops {
173
      font-size: 32px;
174
      font-weight: bold;
175
      line-height: 40px;
176
      color: #1482f0;
177
      opacity: 0;
178
      margin-bottom: 20px;
179
      animation-name: slideUp;
180
      animation-duration: 0.5s;
181
      animation-fill-mode: forwards;
182
    }
183
    &__headline {
184
      font-size: 20px;
185
      line-height: 24px;
186
      color: #222;
187
      font-weight: bold;
188
      opacity: 0;
189
      margin-bottom: 10px;
190
      animation-name: slideUp;
191
      animation-duration: 0.5s;
192
      animation-delay: 0.1s;
193
      animation-fill-mode: forwards;
194
    }
195
    &__info {
196
      font-size: 13px;
197
      line-height: 21px;
198
      color: grey;
199
      opacity: 0;
200
      margin-bottom: 30px;
201
      animation-name: slideUp;
202
      animation-duration: 0.5s;
203
      animation-delay: 0.2s;
204
      animation-fill-mode: forwards;
205
    }
206
    &__return-home {
207
      display: block;
208
      float: left;
209
      width: 110px;
210
      height: 36px;
211
      background: #1482f0;
212
      border-radius: 100px;
213
      text-align: center;
214
      color: #ffffff;
215
      opacity: 0;
216
      font-size: 14px;
217
      line-height: 36px;
218
      cursor: pointer;
219
      animation-name: slideUp;
220
      animation-duration: 0.5s;
221
      animation-delay: 0.3s;
222
      animation-fill-mode: forwards;
223
    }
224
    @keyframes slideUp {
225
      0% {
226
        transform: translateY(60px);
227
        opacity: 0;
228
      }
229
      100% {
230
        transform: translateY(0);
231
        opacity: 1;
232
      }
233
    }
234
  }
235
}
236
</style>

+ 72 - 0
src/views/baseInfoManage/boxesConfig/index.vue

@ -0,0 +1,72 @@
1
<template>
2
  <div class="app-container">
3
    <el-table :data="list" v-loading="listLoading" element-loading-text="Loading" border fit highlight-current-row>
4
      <el-table-column align="center" label='ID' width="95">
5
        <template slot-scope="scope">
6
          {{scope.$index}}
7
        </template>
8
      </el-table-column>
9
      <el-table-column label="Title">
10
        <template slot-scope="scope">
11
          {{scope.row.title}}
12
        </template>
13
      </el-table-column>
14
      <el-table-column label="Author" width="110" align="center">
15
        <template slot-scope="scope">
16
          <span>{{scope.row.author}}</span>
17
        </template>
18
      </el-table-column>
19
      <el-table-column label="Pageviews" width="110" align="center">
20
        <template slot-scope="scope">
21
          {{scope.row.pageviews}}
22
        </template>
23
      </el-table-column>
24
      <el-table-column class-name="status-col" label="Status" width="110" align="center">
25
        <template slot-scope="scope">
26
          <el-tag :type="scope.row.status | statusFilter">{{scope.row.status}}</el-tag>
27
        </template>
28
      </el-table-column>
29
      <el-table-column align="center" prop="created_at" label="Display_time" width="200">
30
        <template slot-scope="scope">
31
          <i class="el-icon-time"></i>
32
          <span>{{scope.row.display_time}}</span>
33
        </template>
34
      </el-table-column>
35
    </el-table>
36
  </div>
37
</template>
38
39
<script>
40
import { getList } from '@/api/table'
41
42
export default {
43
  data() {
44
    return {
45
      list: null,
46
      listLoading: true
47
    }
48
  },
49
  filters: {
50
    statusFilter(status) {
51
      const statusMap = {
52
        published: 'success',
53
        draft: 'gray',
54
        deleted: 'danger'
55
      }
56
      return statusMap[status]
57
    }
58
  },
59
  created() {
60
    this.fetchData()
61
  },
62
  methods: {
63
    fetchData() {
64
      this.listLoading = true
65
      getList(this.listQuery).then(response => {
66
        this.list = response.data.items
67
        this.listLoading = false
68
      })
69
    }
70
  }
71
}
72
</script>

+ 72 - 0
src/views/baseInfoManage/bridgesInfo/index.vue

@ -0,0 +1,72 @@
1
<template>
2
  <div class="app-container">
3
    <el-table :data="list" v-loading="listLoading" element-loading-text="Loading" border fit highlight-current-row>
4
      <el-table-column align="center" label='ID' width="95">
5
        <template slot-scope="scope">
6
          {{scope.$index}}
7
        </template>
8
      </el-table-column>
9
      <el-table-column label="Title">
10
        <template slot-scope="scope">
11
          {{scope.row.title}}
12
        </template>
13
      </el-table-column>
14
      <el-table-column label="Author" width="110" align="center">
15
        <template slot-scope="scope">
16
          <span>{{scope.row.author}}</span>
17
        </template>
18
      </el-table-column>
19
      <el-table-column label="Pageviews" width="110" align="center">
20
        <template slot-scope="scope">
21
          {{scope.row.pageviews}}
22
        </template>
23
      </el-table-column>
24
      <el-table-column class-name="status-col" label="Status" width="110" align="center">
25
        <template slot-scope="scope">
26
          <el-tag :type="scope.row.status | statusFilter">{{scope.row.status}}</el-tag>
27
        </template>
28
      </el-table-column>
29
      <el-table-column align="center" prop="created_at" label="Display_time" width="200">
30
        <template slot-scope="scope">
31
          <i class="el-icon-time"></i>
32
          <span>{{scope.row.display_time}}</span>
33
        </template>
34
      </el-table-column>
35
    </el-table>
36
  </div>
37
</template>
38
39
<script>
40
import { getList } from '@/api/table'
41
42
export default {
43
  data() {
44
    return {
45
      list: null,
46
      listLoading: true
47
    }
48
  },
49
  filters: {
50
    statusFilter(status) {
51
      const statusMap = {
52
        published: 'success',
53
        draft: 'gray',
54
        deleted: 'danger'
55
      }
56
      return statusMap[status]
57
    }
58
  },
59
  created() {
60
    this.fetchData()
61
  },
62
  methods: {
63
    fetchData() {
64
      this.listLoading = true
65
      getList(this.listQuery).then(response => {
66
        this.list = response.data.items
67
        this.listLoading = false
68
      })
69
    }
70
  }
71
}
72
</script>

+ 72 - 0
src/views/baseInfoManage/sensorsConfig/index.vue

@ -0,0 +1,72 @@
1
<template>
2
  <div class="app-container">
3
    <el-table :data="list" v-loading="listLoading" element-loading-text="Loading" border fit highlight-current-row>
4
      <el-table-column align="center" label='ID' width="95">
5
        <template slot-scope="scope">
6
          {{scope.$index}}
7
        </template>
8
      </el-table-column>
9
      <el-table-column label="Title">
10
        <template slot-scope="scope">
11
          {{scope.row.title}}
12
        </template>
13
      </el-table-column>
14
      <el-table-column label="Author" width="110" align="center">
15
        <template slot-scope="scope">
16
          <span>{{scope.row.author}}</span>
17
        </template>
18
      </el-table-column>
19
      <el-table-column label="Pageviews" width="110" align="center">
20
        <template slot-scope="scope">
21
          {{scope.row.pageviews}}
22
        </template>
23
      </el-table-column>
24
      <el-table-column class-name="status-col" label="Status" width="110" align="center">
25
        <template slot-scope="scope">
26
          <el-tag :type="scope.row.status | statusFilter">{{scope.row.status}}</el-tag>
27
        </template>
28
      </el-table-column>
29
      <el-table-column align="center" prop="created_at" label="Display_time" width="200">
30
        <template slot-scope="scope">
31
          <i class="el-icon-time"></i>
32
          <span>{{scope.row.display_time}}</span>
33
        </template>
34
      </el-table-column>
35
    </el-table>
36
  </div>
37
</template>
38
39
<script>
40
import { getList } from '@/api/table'
41
42
export default {
43
  data() {
44
    return {
45
      list: null,
46
      listLoading: true
47
    }
48
  },
49
  filters: {
50
    statusFilter(status) {
51
      const statusMap = {
52
        published: 'success',
53
        draft: 'gray',
54
        deleted: 'danger'
55
      }
56
      return statusMap[status]
57
    }
58
  },
59
  created() {
60
    this.fetchData()
61
  },
62
  methods: {
63
    fetchData() {
64
      this.listLoading = true
65
      getList(this.listQuery).then(response => {
66
        this.list = response.data.items
67
        this.listLoading = false
68
      })
69
    }
70
  }
71
}
72
</script>

+ 72 - 0
src/views/baseInfoManage/serversConfig/index.vue

@ -0,0 +1,72 @@
1
<template>
2
  <div class="app-container">
3
    <el-table :data="list" v-loading="listLoading" element-loading-text="Loading" border fit highlight-current-row>
4
      <el-table-column align="center" label='ID' width="95">
5
        <template slot-scope="scope">
6
          {{scope.$index}}
7
        </template>
8
      </el-table-column>
9
      <el-table-column label="Title">
10
        <template slot-scope="scope">
11
          {{scope.row.title}}
12
        </template>
13
      </el-table-column>
14
      <el-table-column label="Author" width="110" align="center">
15
        <template slot-scope="scope">
16
          <span>{{scope.row.author}}</span>
17
        </template>
18
      </el-table-column>
19
      <el-table-column label="Pageviews" width="110" align="center">
20
        <template slot-scope="scope">
21
          {{scope.row.pageviews}}
22
        </template>
23
      </el-table-column>
24
      <el-table-column class-name="status-col" label="Status" width="110" align="center">
25
        <template slot-scope="scope">
26
          <el-tag :type="scope.row.status | statusFilter">{{scope.row.status}}</el-tag>
27
        </template>
28
      </el-table-column>
29
      <el-table-column align="center" prop="created_at" label="Display_time" width="200">
30
        <template slot-scope="scope">
31
          <i class="el-icon-time"></i>
32
          <span>{{scope.row.display_time}}</span>
33
        </template>
34
      </el-table-column>
35
    </el-table>
36
  </div>
37
</template>
38
39
<script>
40
import { getList } from '@/api/table'
41
42
export default {
43
  data() {
44
    return {
45
      list: null,
46
      listLoading: true
47
    }
48
  },
49
  filters: {
50
    statusFilter(status) {
51
      const statusMap = {
52
        published: 'success',
53
        draft: 'gray',
54
        deleted: 'danger'
55
      }
56
      return statusMap[status]
57
    }
58
  },
59
  created() {
60
    this.fetchData()
61
  },
62
  methods: {
63
    fetchData() {
64
      this.listLoading = true
65
      getList(this.listQuery).then(response => {
66
        this.list = response.data.items
67
        this.listLoading = false
68
      })
69
    }
70
  }
71
}
72
</script>

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

@ -0,0 +1,32 @@
1
<template>
2
  <div class="dashboard-container">
3
    <div class="dashboard-text">name:{{name}}</div>
4
    <div class="dashboard-text">roles:<span v-for='role in roles' :key='role'>{{role}}</span></div>
5
  </div>
6
</template>
7
8
<script>
9
import { mapGetters } from 'vuex'
10
11
export default {
12
  name: 'dashboard',
13
  computed: {
14
    ...mapGetters([
15
      'name',
16
      'roles'
17
    ])
18
  }
19
}
20
</script>
21
22
<style rel="stylesheet/scss" lang="scss" scoped>
23
.dashboard {
24
  &-container {
25
    margin: 30px;
26
  }
27
  &-text {
28
    font-size: 30px;
29
    line-height: 46px;
30
  }
31
}
32
</style>

+ 182 - 0
src/views/findPwd/index.vue

@ -0,0 +1,182 @@
1
<template>
2
  <div class="login-container">
3
    <el-form class="login-form" autoComplete="on" :model="loginForm" :rules="loginRules" ref="loginForm" label-position="left">
4
      <h3 class="title">vue-element-admin</h3>
5
      <el-form-item prop="username">
6
        <span class="svg-container svg-container_login">
7
          <svg-icon icon-class="user" />
8
        </span>
9
        <el-input name="username" type="text" v-model="loginForm.username" autoComplete="on" placeholder="username" />
10
      </el-form-item>
11
      <el-form-item prop="password">
12
        <span class="svg-container">
13
          <svg-icon icon-class="password"></svg-icon>
14
        </span>
15
        <el-input name="password" :type="pwdType" @keyup.enter.native="handleLogin" v-model="loginForm.password" autoComplete="on"
16
          placeholder="password"></el-input>
17
          <span class="show-pwd" @click="showPwd"><svg-icon icon-class="eye" /></span>
18
      </el-form-item>
19
      <el-form-item>
20
        <el-button type="primary" style="width:100%;" :loading="loading" @click.native.prevent="handleLogin">
21
          Sign in
22
        </el-button>
23
      </el-form-item>
24
      <div class="tips">
25
        <span style="margin-right:20px;">username: admin</span>
26
        <span> password: admin</span>
27
      </div>
28
    </el-form>
29
  </div>
30
</template>
31
32
<script>
33
import { isvalidUsername } from '@/utils/validate'
34
35
export default {
36
  name: 'login',
37
  data() {
38
    const validateUsername = (rule, value, callback) => {
39
      if (!isvalidUsername(value)) {
40
        callback(new Error('请输入正确的用户名'))
41
      } else {
42
        callback()
43
      }
44
    }
45
    const validatePass = (rule, value, callback) => {
46
      if (value.length < 5) {
47
        callback(new Error('密码不能小于5位'))
48
      } else {
49
        callback()
50
      }
51
    }
52
    return {
53
      loginForm: {
54
        username: 'admin',
55
        password: 'admin'
56
      },
57
      loginRules: {
58
        username: [{ required: true, trigger: 'blur', validator: validateUsername }],
59
        password: [{ required: true, trigger: 'blur', validator: validatePass }]
60
      },
61
      loading: false,
62
      pwdType: 'password'
63
    }
64
  },
65
  methods: {
66
    showPwd() {
67
      if (this.pwdType === 'password') {
68
        this.pwdType = ''
69
      } else {
70
        this.pwdType = 'password'
71
      }
72
    },
73
    handleLogin() {
74
      this.$refs.loginForm.validate(valid => {
75
        if (valid) {
76
          this.loading = true
77
          this.$store.dispatch('Login', this.loginForm).then(() => {
78
            this.loading = false
79
            this.$router.push({ path: '/' })
80
          }).catch(() => {
81
            this.loading = false
82
          })
83
        } else {
84
          console.log('error submit!!')
85
          return false
86
        }
87
      })
88
    }
89
  }
90
}
91
</script>
92
93
<style rel="stylesheet/scss" lang="scss">
94
$bg:#2d3a4b;
95
$light_gray:#eee;
96
97
/* reset element-ui css */
98
.login-container {
99
  .el-input {
100
    display: inline-block;
101
    height: 47px;
102
    width: 85%;
103
    input {
104
      background: transparent;
105
      border: 0px;
106
      -webkit-appearance: none;
107
      border-radius: 0px;
108
      padding: 12px 5px 12px 15px;
109
      color: $light_gray;
110
      height: 47px;
111
      &:-webkit-autofill {
112
        -webkit-box-shadow: 0 0 0px 1000px $bg inset !important;
113
        -webkit-text-fill-color: #fff !important;
114
      }
115
    }
116
  }
117
  .el-form-item {
118
    border: 1px solid rgba(255, 255, 255, 0.1);
119
    background: rgba(0, 0, 0, 0.1);
120
    border-radius: 5px;
121
    color: #454545;
122
  }
123
}
124
125
</style>
126
127
<style rel="stylesheet/scss" lang="scss" scoped>
128
$bg:#2d3a4b;
129
$dark_gray:#889aa4;
130
$light_gray:#eee;
131
.login-container {
132
  position: fixed;
133
  height: 100%;
134
  width: 100%;
135
  background-color: $bg;
136
  .login-form {
137
    position: absolute;
138
    left: 0;
139
    right: 0;
140
    width: 520px;
141
    padding: 35px 35px 15px 35px;
142
    margin: 120px auto;
143
  }
144
  .tips {
145
    font-size: 14px;
146
    color: #fff;
147
    margin-bottom: 10px;
148
    span {
149
      &:first-of-type {
150
        margin-right: 16px;
151
      }
152
    }
153
  }
154
  .svg-container {
155
    padding: 6px 5px 6px 15px;
156
    color: $dark_gray;
157
    vertical-align: middle;
158
    width: 30px;
159
    display: inline-block;
160
    &_login {
161
      font-size: 20px;
162
    }
163
  }
164
  .title {
165
    font-size: 26px;
166
    font-weight: 400;
167
    color: $light_gray;
168
    margin: 0px auto 40px auto;
169
    text-align: center;
170
    font-weight: bold;
171
  }
172
  .show-pwd {
173
    position: absolute;
174
    right: 10px;
175
    top: 7px;
176
    font-size: 16px;
177
    color: $dark_gray;
178
    cursor: pointer;
179
    user-select: none;
180
  }
181
}
182
</style>

+ 71 - 0
src/views/layout/Layout.vue

@ -0,0 +1,71 @@
1
<template>
2
  <div class="app-wrapper" :class="classObj">
3
    <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside"></div>
4
    <TopNavbar></TopNavbar>
5
    <sidebar class="sidebar-container"></sidebar>
6
    <div class="main-container">
7
      <navbar></navbar>
8
      <app-main></app-main>
9
    </div>
10
  </div>
11
</template>
12
13
<script>
14
import { TopNavbar, Navbar, Sidebar, AppMain } from './components'
15
import ResizeMixin from './mixin/ResizeHandler'
16
17
export default {
18
  name: 'layout',
19
  components: {
20
    TopNavbar,
21
    Navbar,
22
    Sidebar,
23
    AppMain
24
  },
25
  mixins: [ResizeMixin],
26
  computed: {
27
    sidebar() {
28
      return this.$store.state.app.sidebar
29
    },
30
    device() {
31
      return this.$store.state.app.device
32
    },
33
    classObj() {
34
      return {
35
        hideSidebar: !this.sidebar.opened,
36
        openSidebar: this.sidebar.opened,
37
        withoutAnimation: this.sidebar.withoutAnimation,
38
        mobile: this.device === 'mobile'
39
      }
40
    }
41
  },
42
  methods: {
43
    handleClickOutside() {
44
      this.$store.dispatch('CloseSideBar', { withoutAnimation: false })
45
    }
46
  }
47
}
48
</script>
49
50
<style rel="stylesheet/scss" lang="scss" scoped>
51
  @import "src/styles/mixin.scss";
52
  .app-wrapper {
53
    @include clearfix;
54
    position: relative;
55
    height: 100%;
56
    width: 100%;
57
    &.mobile.openSidebar{
58
      position: fixed;
59
      top: 0;
60
    }
61
  }
62
  .drawer-bg {
63
    background: #000;
64
    opacity: 0.3;
65
    width: 100%;
66
    top: 0;
67
    height: 100%;
68
    position: absolute;
69
    z-index: 999;
70
  }
71
</style>

+ 28 - 0
src/views/layout/components/AppMain.vue

@ -0,0 +1,28 @@
1
<template>
2
  <section class="app-main">
3
    <transition name="fade" mode="out-in">
4
      <!-- <router-view :key="key"></router-view> -->
5
      <router-view></router-view>
6
    </transition>
7
  </section>
8
</template>
9
10
<script>
11
export default {
12
  name: 'AppMain',
13
  computed: {
14
    // key() {
15
    //   return this.$route.name !== undefined ? this.$route.name + +new Date() : this.$route + +new Date()
16
    // }
17
  }
18
}
19
</script>
20
21
<style scoped>
22
.app-main {
23
  /*50 = navbar  */
24
  min-height: 100%;
25
  position: relative;
26
  overflow: hidden;
27
}
28
</style>

+ 94 - 0
src/views/layout/components/Navbar.vue

@ -0,0 +1,94 @@
1
<template>
2
  <el-menu class="navbar" mode="horizontal">
3
    <hamburger class="hamburger-container" :toggleClick="toggleSideBar" :isActive="sidebar.opened"></hamburger>
4
    <breadcrumb></breadcrumb>
5
    <!-- <el-dropdown class="avatar-container" trigger="click">
6
      <div class="avatar-wrapper">
7
        <img class="user-avatar" :src="avatar+'?imageView2/1/w/80/h/80'">
8
        <i class="el-icon-caret-bottom"></i>
9
      </div>
10
      <el-dropdown-menu class="user-dropdown" slot="dropdown">
11
        <router-link class="inlineBlock" to="/">
12
          <el-dropdown-item>
13
            Home
14
          </el-dropdown-item>
15
        </router-link>
16
        <el-dropdown-item divided>
17
          <span @click="logout" style="display:block;">LogOut</span>
18
        </el-dropdown-item>
19
      </el-dropdown-menu>
20
    </el-dropdown> -->
21
  </el-menu>
22
</template>
23
24
<script>
25
import { mapGetters } from 'vuex'
26
import Breadcrumb from '@/components/Breadcrumb'
27
import Hamburger from '@/components/Hamburger'
28
29
export default {
30
  components: {
31
    Breadcrumb,
32
    Hamburger
33
  },
34
  computed: {
35
    ...mapGetters([
36
      'sidebar',
37
      'avatar'
38
    ])
39
  },
40
  methods: {
41
    toggleSideBar() {
42
      this.$store.dispatch('ToggleSideBar')
43
    },
44
    logout() {
45
      this.$store.dispatch('LogOut').then(() => {
46
        location.reload() // 为了重新实例化vue-router对象 避免bug
47
      })
48
    }
49
  }
50
}
51
</script>
52
53
<style rel="stylesheet/scss" lang="scss" scoped>
54
.navbar {
55
  height: 50px;
56
  line-height: 50px;
57
  border-radius: 0px !important;
58
  .hamburger-container {
59
    line-height: 58px;
60
    height: 50px;
61
    float: left;
62
    padding: 0 10px;
63
  }
64
  .screenfull {
65
    position: absolute;
66
    right: 90px;
67
    top: 16px;
68
    color: red;
69
  }
70
  .avatar-container {
71
    height: 50px;
72
    display: inline-block;
73
    position: absolute;
74
    right: 35px;
75
    .avatar-wrapper {
76
      cursor: pointer;
77
      margin-top: 5px;
78
      position: relative;
79
      .user-avatar {
80
        width: 40px;
81
        height: 40px;
82
        border-radius: 10px;
83
      }
84
      .el-icon-caret-bottom {
85
        position: absolute;
86
        right: -20px;
87
        top: 25px;
88
        font-size: 12px;
89
      }
90
    }
91
  }
92
}
93
</style>
94

+ 78 - 0
src/views/layout/components/Sidebar/SidebarItem.vue

@ -0,0 +1,78 @@
1
<template>
2
  <div v-if="!item.hidden&&item.children" class="menu-wrapper">
3
4
      <router-link v-if="hasOneShowingChild(item.children) && !onlyOneChild.children&&!item.alwaysShow" :to="resolvePath(onlyOneChild.path)">
5
        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
6
          <svg-icon v-if="onlyOneChild.meta&&onlyOneChild.meta.icon" :icon-class="onlyOneChild.meta.icon"></svg-icon>
7
          <span v-if="onlyOneChild.meta&&onlyOneChild.meta.title" slot="title">{{onlyOneChild.meta.title}}</span>
8
        </el-menu-item>
9
      </router-link>
10
11
      <el-submenu v-else :index="item.name||item.path">
12
        <template slot="title">
13
          <svg-icon v-if="item.meta&&item.meta.icon" :icon-class="item.meta.icon"></svg-icon>
14
          <span v-if="item.meta&&item.meta.title" slot="title">{{item.meta.title}}</span>
15
        </template>
16
17
        <template v-for="child in item.children" v-if="!child.hidden">
18
          <sidebar-item :is-nest="true" class="nest-menu" v-if="child.children&&child.children.length>0" :item="child" :key="child.path" :base-path="resolvePath(child.path)"></sidebar-item>
19
20
          <router-link v-else :to="resolvePath(child.path)" :key="child.name">
21
            <el-menu-item :index="resolvePath(child.path)">
22
              <svg-icon v-if="child.meta&&child.meta.icon" :icon-class="child.meta.icon"></svg-icon>
23
              <span v-if="child.meta&&child.meta.title" slot="title">{{child.meta.title}}</span>
24
            </el-menu-item>
25
          </router-link>
26
        </template>
27
      </el-submenu>
28
29
  </div>
30
</template>
31
32
<script>
33
import path from 'path'
34
35
export default {
36
  name: 'SidebarItem',
37
  props: {
38
    // route配置json
39
    item: {
40
      type: Object,
41
      required: true
42
    },
43
    isNest: {
44
      type: Boolean,
45
      default: false
46
    },
47
    basePath: {
48
      type: String,
49
      default: ''
50
    }
51
  },
52
  data() {
53
    return {
54
      onlyOneChild: null
55
    }
56
  },
57
  methods: {
58
    hasOneShowingChild(children) {
59
      const showingChildren = children.filter(item => {
60
        if (item.hidden) {
61
          return false
62
        } else {
63
          // temp set(will be used if only has one showing child )
64
          this.onlyOneChild = item
65
          return true
66
        }
67
      })
68
      if (showingChildren.length === 1) {
69
        return true
70
      }
71
      return false
72
    },
73
    resolvePath(...paths) {
74
      return path.resolve(this.basePath, ...paths)
75
    }
76
  }
77
}
78
</script>

+ 35 - 0
src/views/layout/components/Sidebar/index.vue

@ -0,0 +1,35 @@
1
<template>
2
  <el-scrollbar wrapClass="scrollbar-wrapper">
3
    <el-menu
4
      mode="vertical"
5
      :show-timeout="200"
6
      :default-active="$route.path"
7
      :collapse="isCollapse"
8
      background-color="#304156"
9
      text-color="#bfcbd9"
10
      active-text-color="#409EFF"
11
    >
12
      <sidebar-item v-for="route in routes" :key="route.name" :item="route" :base-path="route.path"></sidebar-item>
13
    </el-menu>
14
  </el-scrollbar>
15
</template>
16
17
<script>
18
import { mapGetters } from 'vuex'
19
import SidebarItem from './SidebarItem'
20
21
export default {
22
  components: { SidebarItem },
23
  computed: {
24
    ...mapGetters([
25
      'sidebar'
26
    ]),
27
    routes() {
28
      return this.$router.options.routes
29
    },
30
    isCollapse() {
31
      return !this.sidebar.opened
32
    }
33
  }
34
}
35
</script>

+ 92 - 0
src/views/layout/components/TopNavbar.vue

@ -0,0 +1,92 @@
1
<template>
2
  <el-menu class="topnavbar" mode="horizontal">
3
    <div class="logo-container">
4
      <img class="logo-wrapper" src="/static/touchwave.png" alt="">
5
    </div>
6
    <el-dropdown class="avatar-container" trigger="click">
7
      <div class="avatar-wrapper">
8
        <img class="user-avatar" :src="avatar+'?imageView2/1/w/80/h/80'">
9
        <i class="el-icon-caret-bottom"></i>
10
      </div>
11
      <el-dropdown-menu class="user-dropdown" slot="dropdown">
12
        <router-link class="inlineBlock" to="/">
13
          <el-dropdown-item>
14
            Home
15
          </el-dropdown-item>
16
        </router-link>
17
        <el-dropdown-item divided>
18
          <span @click="logout" style="display:block;">LogOut</span>
19
        </el-dropdown-item>
20
      </el-dropdown-menu>
21
    </el-dropdown>
22
  </el-menu>
23
</template>
24
25
<script>
26
import { mapGetters } from 'vuex'
27
28
export default {
29
  computed: {
30
    ...mapGetters([
31
      'sidebar',
32
      'avatar'
33
    ])
34
  },
35
  methods: {
36
    toggleSideBar() {
37
      this.$store.dispatch('ToggleSideBar')
38
    },
39
    logout() {
40
      this.$store.dispatch('LogOut').then(() => {
41
        location.reload() // 为了重新实例化vue-router对象 避免bug
42
      })
43
    }
44
  }
45
}
46
</script>
47
48
<style rel="stylesheet/scss" lang="scss" scoped>
49
.topnavbar {
50
  position: fixed;
51
  font-size: 0px;
52
  top: 0;
53
  left: 0;
54
  right: 0;
55
  height: 50px;
56
  line-height: 50px;
57
  z-index:1002;
58
  border-radius: 0px !important;
59
  .logo-container {
60
    display: inline-block;
61
    position: absolute;
62
    left:10px;
63
    .logo-wrapper {
64
      width:260px;
65
      margin:14px 20px;
66
    }
67
  }
68
  .avatar-container {
69
    height: 50px;
70
    display: inline-block;
71
    position: absolute;
72
    right: 35px;
73
    .avatar-wrapper {
74
      cursor: pointer;
75
      margin-top: 5px;
76
      position: relative;
77
      .user-avatar {
78
        width: 40px;
79
        height: 40px;
80
        border-radius: 10px;
81
      }
82
      .el-icon-caret-bottom {
83
        position: absolute;
84
        right: -20px;
85
        top: 25px;
86
        font-size: 12px;
87
      }
88
    }
89
  }
90
}
91
</style>
92

+ 4 - 0
src/views/layout/components/index.js

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

+ 41 - 0
src/views/layout/mixin/ResizeHandler.js

@ -0,0 +1,41 @@
1
import store from '@/store'
2
3
const { body } = document
4
const WIDTH = 1024
5
const RATIO = 3
6
7
export default {
8
  watch: {
9
    $route(route) {
10
      if (this.device === 'mobile' && this.sidebar.opened) {
11
        store.dispatch('CloseSideBar', { withoutAnimation: false })
12
      }
13
    }
14
  },
15
  beforeMount() {
16
    window.addEventListener('resize', this.resizeHandler)
17
  },
18
  mounted() {
19
    const isMobile = this.isMobile()
20
    if (isMobile) {
21
      store.dispatch('ToggleDevice', 'mobile')
22
      store.dispatch('CloseSideBar', { withoutAnimation: true })
23
    }
24
  },
25
  methods: {
26
    isMobile() {
27
      const rect = body.getBoundingClientRect()
28
      return rect.width - RATIO < WIDTH
29
    },
30
    resizeHandler() {
31
      if (!document.hidden) {
32
        const isMobile = this.isMobile()
33
        store.dispatch('ToggleDevice', isMobile ? 'mobile' : 'desktop')
34
35
        if (isMobile) {
36
          store.dispatch('CloseSideBar', { withoutAnimation: true })
37
        }
38
      }
39
    }
40
  }
41
}

+ 182 - 0
src/views/login/index.vue

@ -0,0 +1,182 @@
1
<template>
2
  <div class="login-container">
3
    <el-form class="login-form" autoComplete="on" :model="loginForm" :rules="loginRules" ref="loginForm" label-position="left">
4
      <h3 class="title">vue-element-admin</h3>
5
      <el-form-item prop="username">
6
        <span class="svg-container svg-container_login">
7
          <svg-icon icon-class="user" />
8
        </span>
9
        <el-input name="username" type="text" v-model="loginForm.username" autoComplete="on" placeholder="username" />
10
      </el-form-item>
11
      <el-form-item prop="password">
12
        <span class="svg-container">
13
          <svg-icon icon-class="password"></svg-icon>
14
        </span>
15
        <el-input name="password" :type="pwdType" @keyup.enter.native="handleLogin" v-model="loginForm.password" autoComplete="on"
16
          placeholder="password"></el-input>
17
          <span class="show-pwd" @click="showPwd"><svg-icon icon-class="eye" /></span>
18
      </el-form-item>
19
      <el-form-item>
20
        <el-button type="primary" style="width:100%;" :loading="loading" @click.native.prevent="handleLogin">
21
          Sign in
22
        </el-button>
23
      </el-form-item>
24
      <div class="tips">
25
        <span style="margin-right:20px;">username: admin</span>
26
        <span> password: admin</span>
27
      </div>
28
    </el-form>
29
  </div>
30
</template>
31
32
<script>
33
import { isvalidUsername } from '@/utils/validate'
34
35
export default {
36
  name: 'login',
37
  data() {
38
    const validateUsername = (rule, value, callback) => {
39
      if (!isvalidUsername(value)) {
40
        callback(new Error('请输入正确的用户名'))
41
      } else {
42
        callback()
43
      }
44
    }
45
    const validatePass = (rule, value, callback) => {
46
      if (value.length < 5) {
47
        callback(new Error('密码不能小于5位'))
48
      } else {
49
        callback()
50
      }
51
    }
52
    return {
53
      loginForm: {
54
        username: 'admin',
55
        password: 'admin'
56
      },
57
      loginRules: {
58
        username: [{ required: true, trigger: 'blur', validator: validateUsername }],
59
        password: [{ required: true, trigger: 'blur', validator: validatePass }]
60
      },
61
      loading: false,
62
      pwdType: 'password'
63
    }
64
  },
65
  methods: {
66
    showPwd() {
67
      if (this.pwdType === 'password') {
68
        this.pwdType = ''
69
      } else {
70
        this.pwdType = 'password'
71
      }
72
    },
73
    handleLogin() {
74
      this.$refs.loginForm.validate(valid => {
75
        if (valid) {
76
          this.loading = true
77
          this.$store.dispatch('Login', this.loginForm).then(() => {
78
            this.loading = false
79
            this.$router.push({ path: '/' })
80
          }).catch(() => {
81
            this.loading = false
82
          })
83
        } else {
84
          console.log('error submit!!')
85
          return false
86
        }
87
      })
88
    }
89
  }
90
}
91
</script>
92
93
<style rel="stylesheet/scss" lang="scss">
94
$bg:#2d3a4b;
95
$light_gray:#eee;
96
97
/* reset element-ui css */
98
.login-container {
99
  .el-input {
100
    display: inline-block;
101
    height: 47px;
102
    width: 85%;
103
    input {
104
      background: transparent;
105
      border: 0px;
106
      -webkit-appearance: none;
107
      border-radius: 0px;
108
      padding: 12px 5px 12px 15px;
109
      color: $light_gray;
110
      height: 47px;
111
      &:-webkit-autofill {
112
        -webkit-box-shadow: 0 0 0px 1000px $bg inset !important;
113
        -webkit-text-fill-color: #fff !important;
114
      }
115
    }
116
  }
117
  .el-form-item {
118
    border: 1px solid rgba(255, 255, 255, 0.1);
119
    background: rgba(0, 0, 0, 0.1);
120
    border-radius: 5px;
121
    color: #454545;
122
  }
123
}
124
125
</style>
126
127
<style rel="stylesheet/scss" lang="scss" scoped>
128
$bg:#2d3a4b;
129
$dark_gray:#889aa4;
130
$light_gray:#eee;
131
.login-container {
132
  position: fixed;
133
  height: 100%;
134
  width: 100%;
135
  background-color: $bg;
136
  .login-form {
137
    position: absolute;
138
    left: 0;
139
    right: 0;
140
    width: 520px;
141
    padding: 35px 35px 15px 35px;
142
    margin: 120px auto;
143
  }
144
  .tips {
145
    font-size: 14px;
146
    color: #fff;
147
    margin-bottom: 10px;
148
    span {
149
      &:first-of-type {
150
        margin-right: 16px;
151
      }
152
    }
153
  }
154
  .svg-container {
155
    padding: 6px 5px 6px 15px;
156
    color: $dark_gray;
157
    vertical-align: middle;
158
    width: 30px;
159
    display: inline-block;
160
    &_login {
161
      font-size: 20px;
162
    }
163
  }
164
  .title {
165
    font-size: 26px;
166
    font-weight: 400;
167
    color: $light_gray;
168
    margin: 0px auto 40px auto;
169
    text-align: center;
170
    font-weight: bold;
171
  }
172
  .show-pwd {
173
    position: absolute;
174
    right: 10px;
175
    top: 7px;
176
    font-size: 16px;
177
    color: $dark_gray;
178
    cursor: pointer;
179
    user-select: none;
180
  }
181
}
182
</style>

+ 91 - 0
src/views/peoplesManage/infoManage/index.vue

@ -0,0 +1,91 @@
1
<template>
2
  <div class="app-container">
3
    <div class="filter-container" style="margin-bottom:20px">
4
      <el-input @keyup.enter.native="handleFilter" style="width: 200px;" class="filter-item" placeholder="标题">
5
      </el-input>
6
      <el-input @keyup.enter.native="handleFilter" style="width: 200px;" class="filter-item" placeholder="标题">
7
      </el-input>
8
      <el-input @keyup.enter.native="handleFilter" style="width: 200px;" class="filter-item" placeholder="标题">
9
      </el-input>
10
      <el-button class="filter-item" type="primary" v-waves icon="el-icon-search" @click="handleFilter">搜索</el-button>
11
      <el-button class="filter-item" style="margin-left: 10px;" @click="handleCreate" type="primary" icon="el-icon-edit">添加</el-button>
12
    </div>
13
14
    <el-table :data="list" v-loading="listLoading" element-loading-text="Loading" border fit highlight-current-row>
15
      <el-table-column align="center" label='ID' width="95">
16
        <template slot-scope="scope">
17
          {{scope.$index}}
18
        </template>
19
      </el-table-column>
20
      <el-table-column label="Title">
21
        <template slot-scope="scope">
22
          {{scope.row.title}}
23
        </template>
24
      </el-table-column>
25
      <el-table-column label="Author" width="110" align="center">
26
        <template slot-scope="scope">
27
          <span>{{scope.row.author}}</span>
28
        </template>
29
      </el-table-column>
30
      <el-table-column label="Pageviews" width="110" align="center">
31
        <template slot-scope="scope">
32
          {{scope.row.pageviews}}
33
        </template>
34
      </el-table-column>
35
      <el-table-column class-name="status-col" label="Status" width="110" align="center">
36
        <template slot-scope="scope">
37
          <el-tag :type="scope.row.status | statusFilter">{{scope.row.status}}</el-tag>
38
        </template>
39
      </el-table-column>
40
      <el-table-column align="center" prop="created_at" label="Display_time" width="200">
41
        <template slot-scope="scope">
42
          <i class="el-icon-time"></i>
43
          <span>{{scope.row.display_time}}</span>
44
        </template>
45
      </el-table-column>
46
    </el-table>
47
48
<!--     <div class="pagination-container">
49
      <el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="listQuery.page" :page-sizes="[10,20,30, 50]" :page-size="listQuery.limit" layout="total, sizes, prev, pager, next, jumper" :total="total">
50
      </el-pagination>
51
    </div> -->
52
  </div>
53
</template>
54
55
<script>
56
import { getList } from '@/api/table'
57
58
export default {
59
  data() {
60
    return {
61
      list: null,
62
      listLoading: true
63
    }
64
  },
65
  filters: {
66
    statusFilter(status) {
67
      const statusMap = {
68
        published: 'success',
69
        draft: 'gray',
70
        deleted: 'danger'
71
      }
72
      return statusMap[status]
73
    }
74
  },
75
  created() {
76
    this.fetchData()
77
  },
78
  methods: {
79
    fetchData() {
80
      this.listLoading = true
81
      getList(this.listQuery).then(response => {
82
        this.list = response.data.items
83
        this.listLoading = false
84
      })
85
    },
86
    handleFilter() {
87
      this.fetchData()
88
    }
89
  }
90
}
91
</script>

+ 0 - 0
static/.gitkeep


BIN
static/touchwave.png