Pan 7 年 前
コミット
c528bd8ad0
共有61 個のファイルを変更した3251 個の追加0 個の削除を含む
  1. 7 0
      .babelrc
  2. 3 0
      .eslintignore
  3. 318 0
      .eslintrc.js
  4. 6 0
      .gitignore
  5. 8 0
      .postcssrc.js
  6. 21 0
      README.md
  7. 35 0
      build/build.js
  8. 48 0
      build/check-versions.js
  9. 9 0
      build/dev-client.js
  10. 89 0
      build/dev-server.js
  11. 71 0
      build/utils.js
  12. 12 0
      build/vue-loader.conf.js
  13. 67 0
      build/webpack.base.conf.js
  14. 35 0
      build/webpack.dev.conf.js
  15. 120 0
      build/webpack.prod.conf.js
  16. 6 0
      config/dev.env.js
  17. 38 0
      config/index.js
  18. 3 0
      config/prod.env.js
  19. 11 0
      index.html
  20. 74 0
      package.json
  21. 12 0
      src/App.vue
  22. 29 0
      src/api/login.js
  23. BIN
      src/assets/404_images/404.png
  24. BIN
      src/assets/404_images/404_cloud.png
  25. 1 0
      src/assets/iconfont/iconfont.js
  26. 36 0
      src/components/Hamburger/index.vue
  27. 11 0
      src/components/Icon-svg/index.js
  28. 52 0
      src/components/Icon-svg/wscn-icon-stack.vue
  29. 26 0
      src/components/Icon-svg/wscn-icon-svg.vue
  30. 66 0
      src/main.js
  31. 1 0
      src/router/_import_development.js
  32. 1 0
      src/router/_import_production.js
  33. 60 0
      src/router/index.js
  34. 14 0
      src/store/getters.js
  35. 19 0
      src/store/index.js
  36. 26 0
      src/store/modules/app.js
  37. 62 0
      src/store/modules/permission.js
  38. 149 0
      src/store/modules/user.js
  39. 103 0
      src/styles/btn.scss
  40. 83 0
      src/styles/element-ui.scss
  41. 266 0
      src/styles/index.scss
  42. 60 0
      src/styles/mixin.scss
  43. 8 0
      src/utils/createUniqueString.js
  44. 66 0
      src/utils/fetch.js
  45. 214 0
      src/utils/index.js
  46. 27 0
      src/utils/openWindow.js
  47. 41 0
      src/utils/validate.js
  48. 228 0
      src/views/404.vue
  49. 5 0
      src/views/dashboard/default/index.vue
  50. 38 0
      src/views/dashboard/index.vue
  51. 18 0
      src/views/layout/AppMain.vue
  52. 80 0
      src/views/layout/Layout.vue
  53. 49 0
      src/views/layout/Levelbar.vue
  54. 105 0
      src/views/layout/Navbar.vue
  55. 24 0
      src/views/layout/Sidebar.vue
  56. 47 0
      src/views/layout/SidebarItem.vue
  57. 45 0
      src/views/layout/TabsView.vue
  58. 7 0
      src/views/layout/index.js
  59. 186 0
      src/views/login/index.vue
  60. 5 0
      src/views/page/index.vue
  61. 0 0
      static/.gitkeep

+ 7 - 0
.babelrc

@@ -0,0 +1,7 @@
1
+{
2
+  "presets": [
3
+    ["env", { "modules": false }],
4
+    "stage-2"
5
+  ],
6
+  "plugins": ["transform-runtime"]
7
+}

+ 3 - 0
.eslintignore

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

+ 318 - 0
.eslintrc.js

@@ -0,0 +1,318 @@
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
+    },
11
+    extends: 'eslint:recommended',
12
+    // required to lint *.vue files
13
+    plugins: [
14
+        'html'
15
+    ],
16
+    // check if imports actually resolve
17
+    'settings': {
18
+        'import/resolver': {
19
+            'webpack': {
20
+                'config': 'build/webpack.base.conf.js'
21
+            }
22
+        }
23
+    },
24
+    // add your custom rules here
25
+    'rules': {
26
+        // don't require .vue extension when importing
27
+        // 'import/extensions': ['error', 'always', {
28
+        //     'js': 'never',
29
+        //     'vue': 'never'
30
+        // }],
31
+        // allow debugger during development
32
+        'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
33
+        /*
34
+         * Possible Errors
35
+         */
36
+
37
+        // disallow unnecessary parentheses
38
+        'no-extra-parens': ['error', 'all', {'nestedBinaryExpressions': false}],
39
+
40
+        // disallow negating the left operand of relational operators
41
+        'no-unsafe-negation': 'error',
42
+
43
+        // enforce valid JSDoc comments
44
+        'valid-jsdoc': 'off',
45
+
46
+        /*
47
+         * Best Practices
48
+         */
49
+
50
+        // enforce return statements in callbacks of array methods
51
+        'array-callback-return': 'error',
52
+
53
+        // enforce consistent brace style for all control statements
54
+        curly: ['error', 'multi-line'],
55
+
56
+        // enforce consistent newlines before and after dots
57
+        'dot-location': ['error', 'property'],
58
+
59
+        // enforce dot notation whenever possible
60
+        'dot-notation': 'error',
61
+
62
+        // require the use of === and !==
63
+        'eqeqeq': ['error', 'smart'],
64
+
65
+        // disallow the use of arguments.caller or arguments.callee
66
+        'no-caller': 'error',
67
+
68
+        // disallow empty functions
69
+        'no-empty-function': 'error',
70
+
71
+        // disallow unnecessary calls to .bind()
72
+        'no-extra-bind': 'error',
73
+
74
+        // disallow unnecessary labels
75
+        'no-extra-label': 'error',
76
+
77
+        // disallow leading or trailing decimal points in numeric literals
78
+        'no-floating-decimal': 'error',
79
+
80
+        // disallow assignments to native objects or read-only global variables
81
+        'no-global-assign': 'error',
82
+
83
+        // disallow the use of eval()-like methods
84
+        'no-implied-eval': 'error',
85
+
86
+        // disallow the use of the __iterator__ property
87
+        'no-iterator': 'error',
88
+
89
+        // disallow unnecessary nested blocks
90
+        'no-lone-blocks': 'error',
91
+
92
+        // disallow multiple spaces
93
+        'no-multi-spaces': 'error',
94
+
95
+        // disallow new operators with the String, Number, and Boolean objects
96
+        'no-new-wrappers': 'error',
97
+
98
+        // disallow octal escape sequences in string literals
99
+        'no-octal-escape': 'error',
100
+
101
+        // disallow the use of the __proto__ property
102
+        'no-proto': 'error',
103
+
104
+        // disallow comparisons where both sides are exactly the same
105
+        'no-self-compare': 'error',
106
+
107
+        // disallow throwing literals as exceptions
108
+        'no-throw-literal': 'error',
109
+
110
+        // disallow unused expressions
111
+        'no-unused-expressions': 'error',
112
+
113
+        // disallow unnecessary calls to .call() and .apply()
114
+        'no-useless-call': 'error',
115
+
116
+        // disallow unnecessary concatenation of literals or template literals
117
+        'no-useless-concat': 'error',
118
+
119
+        // disallow unnecessary escape characters
120
+        'no-useless-escape': 'error',
121
+
122
+        // disallow void operators
123
+        'no-void': 'error',
124
+
125
+        // require parentheses around immediate function invocations
126
+        'wrap-iife': 'error',
127
+
128
+        // require or disallow “Yoda” conditions
129
+        yoda: 'error',
130
+
131
+        /*
132
+         * Variables
133
+         */
134
+
135
+        // disallow labels that share a name with a variable
136
+        'no-label-var': 'error',
137
+
138
+        // disallow initializing variables to undefined
139
+        'no-undef-init': 'error',
140
+        'no-undef': 'off',
141
+        // disallow the use of variables before they are defined
142
+        'no-use-before-define': 'error',
143
+
144
+        /*
145
+         * Node.js and CommonJS
146
+         */
147
+
148
+        // disallow new operators with calls to require
149
+        'no-new-require': 'error',
150
+
151
+        /*
152
+         * Stylistic Issues
153
+         */
154
+
155
+        // enforce consistent spacing inside array brackets
156
+        'array-bracket-spacing': 'error',
157
+
158
+        // enforce consistent spacing inside single-line blocks
159
+        'block-spacing': 'error',
160
+
161
+        // enforce consistent brace style for blocks
162
+        'brace-style': ['error', '1tbs', {'allowSingleLine': true}],
163
+
164
+        // require or disallow trailing commas
165
+        'comma-dangle': 'error',
166
+
167
+        // enforce consistent spacing before and after commas
168
+        'comma-spacing': 'error',
169
+
170
+        // enforce consistent comma style
171
+        'comma-style': 'error',
172
+
173
+        // enforce consistent spacing inside computed property brackets
174
+        'computed-property-spacing': 'error',
175
+
176
+        // require or disallow spacing between function identifiers and their invocations
177
+        'func-call-spacing': 'error',
178
+
179
+        // enforce consistent indentation
180
+        indent: ['error', 2, {SwitchCase: 1}],
181
+
182
+        // enforce the consistent use of either double or single quotes in JSX attributes
183
+        'jsx-quotes': 'error',
184
+
185
+        // enforce consistent spacing between keys and values in object literal properties
186
+        'key-spacing': 'error',
187
+
188
+        // enforce consistent spacing before and after keywords
189
+        'keyword-spacing': 'error',
190
+
191
+        // enforce consistent linebreak style
192
+        'linebreak-style': 'error',
193
+
194
+        // require or disallow newlines around directives
195
+        'lines-around-directive': 'error',
196
+
197
+        // require constructor names to begin with a capital letter
198
+        'new-cap': 'off',
199
+
200
+        // require parentheses when invoking a constructor with no arguments
201
+        'new-parens': 'error',
202
+
203
+        // disallow Array constructors
204
+        'no-array-constructor': 'error',
205
+
206
+        // disallow Object constructors
207
+        'no-new-object': 'error',
208
+
209
+        // disallow trailing whitespace at the end of lines
210
+        'no-trailing-spaces': 'error',
211
+
212
+        // disallow ternary operators when simpler alternatives exist
213
+        'no-unneeded-ternary': 'error',
214
+
215
+        // disallow whitespace before properties
216
+        'no-whitespace-before-property': 'error',
217
+
218
+        // enforce consistent spacing inside braces
219
+        'object-curly-spacing': ['error', 'always'],
220
+
221
+        // require or disallow padding within blocks
222
+        'padded-blocks': ['error', 'never'],
223
+
224
+        // require quotes around object literal property names
225
+        'quote-props': ['error', 'as-needed'],
226
+
227
+        // enforce the consistent use of either backticks, double, or single quotes
228
+        quotes: ['error', 'single'],
229
+
230
+        // enforce consistent spacing before and after semicolons
231
+        'semi-spacing': 'error',
232
+
233
+        // require or disallow semicolons instead of ASI
234
+        // semi: ['error', 'never'],
235
+
236
+        // enforce consistent spacing before blocks
237
+        'space-before-blocks': 'error',
238
+
239
+        'no-console': 'off',
240
+
241
+        // enforce consistent spacing before function definition opening parenthesis
242
+        'space-before-function-paren': ['error', 'never'],
243
+
244
+        // enforce consistent spacing inside parentheses
245
+        'space-in-parens': 'error',
246
+
247
+        // require spacing around infix operators
248
+        'space-infix-ops': 'error',
249
+
250
+        // enforce consistent spacing before or after unary operators
251
+        'space-unary-ops': 'error',
252
+
253
+        // enforce consistent spacing after the // or /* in a comment
254
+        'spaced-comment': 'error',
255
+
256
+        // require or disallow Unicode byte order mark (BOM)
257
+        'unicode-bom': 'error',
258
+
259
+
260
+        /*
261
+         * ECMAScript 6
262
+         */
263
+
264
+        // require braces around arrow function bodies
265
+        'arrow-body-style': 'error',
266
+
267
+        // require parentheses around arrow function arguments
268
+        'arrow-parens': ['error', 'as-needed'],
269
+
270
+        // enforce consistent spacing before and after the arrow in arrow functions
271
+        'arrow-spacing': 'error',
272
+
273
+        // enforce consistent spacing around * operators in generator functions
274
+        'generator-star-spacing': ['error', 'after'],
275
+
276
+        // disallow duplicate module imports
277
+        'no-duplicate-imports': 'error',
278
+
279
+        // disallow unnecessary computed property keys in object literals
280
+        'no-useless-computed-key': 'error',
281
+
282
+        // disallow unnecessary constructors
283
+        'no-useless-constructor': 'error',
284
+
285
+        // disallow renaming import, export, and destructured assignments to the same name
286
+        'no-useless-rename': 'error',
287
+
288
+        // require let or const instead of var
289
+        'no-var': 'error',
290
+
291
+        // require or disallow method and property shorthand syntax for object literals
292
+        'object-shorthand': 'error',
293
+
294
+        // require arrow functions as callbacks
295
+        'prefer-arrow-callback': 'error',
296
+
297
+        // require const declarations for variables that are never reassigned after declared
298
+        'prefer-const': 'error',
299
+
300
+        // disallow parseInt() in favor of binary, octal, and hexadecimal literals
301
+        'prefer-numeric-literals': 'error',
302
+
303
+        // require rest parameters instead of arguments
304
+        'prefer-rest-params': 'error',
305
+
306
+        // require spread operators instead of .apply()
307
+        'prefer-spread': 'error',
308
+
309
+        // enforce spacing between rest and spread operators and their expressions
310
+        'rest-spread-spacing': 'error',
311
+
312
+        // require or disallow spacing around embedded expressions of template strings
313
+        'template-curly-spacing': 'error',
314
+
315
+        // require or disallow spacing around the * in yield* expressions
316
+        'yield-star-spacing': 'error'
317
+    }
318
+}

+ 6 - 0
.gitignore

@@ -0,0 +1,6 @@
1
+.DS_Store
2
+node_modules/
3
+dist/
4
+npm-debug.log*
5
+yarn-debug.log*
6
+yarn-error.log*

+ 8 - 0
.postcssrc.js

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

+ 21 - 0
README.md

@@ -0,0 +1,21 @@
1
+# vue-admin
2
+
3
+> A Vue.js project
4
+
5
+## Build Setup
6
+
7
+``` bash
8
+# install dependencies
9
+npm install
10
+
11
+# serve with hot reload at localhost:8080
12
+npm run dev
13
+
14
+# build for production with minification
15
+npm run build
16
+
17
+# build for production and view the bundle analyzer report
18
+npm run build --report
19
+```
20
+
21
+For detailed explanation on how things work, checkout the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).

+ 35 - 0
build/build.js

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

+ 48 - 0
build/check-versions.js

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

+ 9 - 0
build/dev-client.js

@@ -0,0 +1,9 @@
1
+/* eslint-disable */
2
+require('eventsource-polyfill')
3
+var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
4
+
5
+hotClient.subscribe(function (event) {
6
+  if (event.action === 'reload') {
7
+    window.location.reload()
8
+  }
9
+})

+ 89 - 0
build/dev-server.js

@@ -0,0 +1,89 @@
1
+require('./check-versions')()
2
+
3
+var config = require('../config')
4
+if (!process.env.NODE_ENV) {
5
+  process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
6
+}
7
+
8
+var opn = require('opn')
9
+var path = require('path')
10
+var express = require('express')
11
+var webpack = require('webpack')
12
+var proxyMiddleware = require('http-proxy-middleware')
13
+var webpackConfig = require('./webpack.dev.conf')
14
+
15
+// default port where dev server listens for incoming traffic
16
+var port = process.env.PORT || config.dev.port
17
+// automatically open browser, if not set will be false
18
+var autoOpenBrowser = !!config.dev.autoOpenBrowser
19
+// Define HTTP proxies to your custom API backend
20
+// https://github.com/chimurai/http-proxy-middleware
21
+var proxyTable = config.dev.proxyTable
22
+
23
+var app = express()
24
+var compiler = webpack(webpackConfig)
25
+
26
+var devMiddleware = require('webpack-dev-middleware')(compiler, {
27
+  publicPath: webpackConfig.output.publicPath,
28
+  quiet: true
29
+})
30
+
31
+var hotMiddleware = require('webpack-hot-middleware')(compiler, {
32
+  log: () => {}
33
+})
34
+// force page reload when html-webpack-plugin template changes
35
+compiler.plugin('compilation', function (compilation) {
36
+  compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
37
+    hotMiddleware.publish({ action: 'reload' })
38
+    cb()
39
+  })
40
+})
41
+
42
+// proxy api requests
43
+Object.keys(proxyTable).forEach(function (context) {
44
+  var options = proxyTable[context]
45
+  if (typeof options === 'string') {
46
+    options = { target: options }
47
+  }
48
+  app.use(proxyMiddleware(options.filter || context, options))
49
+})
50
+
51
+// handle fallback for HTML5 history API
52
+app.use(require('connect-history-api-fallback')())
53
+
54
+// serve webpack bundle output
55
+app.use(devMiddleware)
56
+
57
+// enable hot-reload and state-preserving
58
+// compilation error display
59
+app.use(hotMiddleware)
60
+
61
+// serve pure static assets
62
+var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
63
+app.use(staticPath, express.static('./static'))
64
+
65
+var uri = 'http://localhost:' + port
66
+
67
+var _resolve
68
+var readyPromise = new Promise(resolve => {
69
+  _resolve = resolve
70
+})
71
+
72
+console.log('> Starting dev server...')
73
+devMiddleware.waitUntilValid(() => {
74
+  console.log('> Listening at ' + uri + '\n')
75
+  // when env is testing, don't need open it
76
+  if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
77
+    opn(uri)
78
+  }
79
+  _resolve()
80
+})
81
+
82
+var server = app.listen(port)
83
+
84
+module.exports = {
85
+  ready: readyPromise,
86
+  close: () => {
87
+    server.close()
88
+  }
89
+}

+ 71 - 0
build/utils.js

@@ -0,0 +1,71 @@
1
+var path = require('path')
2
+var config = require('../config')
3
+var ExtractTextPlugin = require('extract-text-webpack-plugin')
4
+
5
+exports.assetsPath = function (_path) {
6
+  var assetsSubDirectory = process.env.NODE_ENV === 'production'
7
+    ? config.build.assetsSubDirectory
8
+    : config.dev.assetsSubDirectory
9
+  return path.posix.join(assetsSubDirectory, _path)
10
+}
11
+
12
+exports.cssLoaders = function (options) {
13
+  options = options || {}
14
+
15
+  var cssLoader = {
16
+    loader: 'css-loader',
17
+    options: {
18
+      minimize: process.env.NODE_ENV === 'production',
19
+      sourceMap: options.sourceMap
20
+    }
21
+  }
22
+
23
+  // generate loader string to be used with extract text plugin
24
+  function generateLoaders (loader, loaderOptions) {
25
+    var loaders = [cssLoader]
26
+    if (loader) {
27
+      loaders.push({
28
+        loader: loader + '-loader',
29
+        options: Object.assign({}, loaderOptions, {
30
+          sourceMap: options.sourceMap
31
+        })
32
+      })
33
+    }
34
+
35
+    // Extract CSS when that option is specified
36
+    // (which is the case during production build)
37
+    if (options.extract) {
38
+      return ExtractTextPlugin.extract({
39
+        use: loaders,
40
+        fallback: 'vue-style-loader'
41
+      })
42
+    } else {
43
+      return ['vue-style-loader'].concat(loaders)
44
+    }
45
+  }
46
+
47
+  // https://vue-loader.vuejs.org/en/configurations/extract-css.html
48
+  return {
49
+    css: generateLoaders(),
50
+    postcss: generateLoaders(),
51
+    less: generateLoaders('less'),
52
+    sass: generateLoaders('sass', { indentedSyntax: true }),
53
+    scss: generateLoaders('sass'),
54
+    stylus: generateLoaders('stylus'),
55
+    styl: generateLoaders('stylus')
56
+  }
57
+}
58
+
59
+// Generate loaders for standalone style files (outside of .vue)
60
+exports.styleLoaders = function (options) {
61
+  var output = []
62
+  var loaders = exports.cssLoaders(options)
63
+  for (var extension in loaders) {
64
+    var loader = loaders[extension]
65
+    output.push({
66
+      test: new RegExp('\\.' + extension + '$'),
67
+      use: loader
68
+    })
69
+  }
70
+  return output
71
+}

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

@@ -0,0 +1,12 @@
1
+var utils = require('./utils')
2
+var config = require('../config')
3
+var isProduction = process.env.NODE_ENV === 'production'
4
+
5
+module.exports = {
6
+  loaders: utils.cssLoaders({
7
+    sourceMap: isProduction
8
+      ? config.build.productionSourceMap
9
+      : config.dev.cssSourceMap,
10
+    extract: isProduction
11
+  })
12
+}

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

@@ -0,0 +1,67 @@
1
+var path = require('path')
2
+var utils = require('./utils')
3
+var config = require('../config')
4
+var vueLoaderConfig = require('./vue-loader.conf')
5
+
6
+function resolve (dir) {
7
+  return path.join(__dirname, '..', dir)
8
+}
9
+
10
+module.exports = {
11
+  entry: {
12
+    app: './src/main.js'
13
+  },
14
+  output: {
15
+    path: config.build.assetsRoot,
16
+    filename: '[name].js',
17
+    publicPath: process.env.NODE_ENV === 'production'
18
+      ? config.build.assetsPublicPath
19
+      : config.dev.assetsPublicPath
20
+  },
21
+  resolve: {
22
+    extensions: ['.js', '.vue', '.json'],
23
+    alias: {
24
+      'vue$': 'vue/dist/vue.esm.js',
25
+      '@': resolve('src')
26
+    }
27
+  },
28
+  module: {
29
+    rules: [
30
+      // {
31
+      //   test: /\.(js|vue)$/,
32
+      //   loader: 'eslint-loader',
33
+      //   enforce: 'pre',
34
+      //   include: [resolve('src'), resolve('test')],
35
+      //   options: {
36
+      //     formatter: require('eslint-friendly-formatter')
37
+      //   }
38
+      // },
39
+      {
40
+        test: /\.vue$/,
41
+        loader: 'vue-loader',
42
+        options: vueLoaderConfig
43
+      },
44
+      {
45
+        test: /\.js$/,
46
+        loader: 'babel-loader',
47
+        include: [resolve('src'), resolve('test')]
48
+      },
49
+      {
50
+        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
51
+        loader: 'url-loader',
52
+        options: {
53
+          limit: 10000,
54
+          name: utils.assetsPath('img/[name].[hash:7].[ext]')
55
+        }
56
+      },
57
+      {
58
+        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
59
+        loader: 'url-loader',
60
+        options: {
61
+          limit: 10000,
62
+          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
63
+        }
64
+      }
65
+    ]
66
+  }
67
+}

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

@@ -0,0 +1,35 @@
1
+var utils = require('./utils')
2
+var webpack = require('webpack')
3
+var config = require('../config')
4
+var merge = require('webpack-merge')
5
+var baseWebpackConfig = require('./webpack.base.conf')
6
+var HtmlWebpackPlugin = require('html-webpack-plugin')
7
+var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
8
+
9
+// add hot-reload related code to entry chunks
10
+Object.keys(baseWebpackConfig.entry).forEach(function (name) {
11
+  baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
12
+})
13
+
14
+module.exports = merge(baseWebpackConfig, {
15
+  module: {
16
+    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
17
+  },
18
+  // cheap-module-eval-source-map is faster for development
19
+  devtool: '#cheap-module-eval-source-map',
20
+  plugins: [
21
+    new webpack.DefinePlugin({
22
+      'process.env': config.dev.env
23
+    }),
24
+    // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
25
+    new webpack.HotModuleReplacementPlugin(),
26
+    new webpack.NoEmitOnErrorsPlugin(),
27
+    // https://github.com/ampedandwired/html-webpack-plugin
28
+    new HtmlWebpackPlugin({
29
+      filename: 'index.html',
30
+      template: 'index.html',
31
+      inject: true
32
+    }),
33
+    new FriendlyErrorsPlugin()
34
+  ]
35
+})

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

@@ -0,0 +1,120 @@
1
+var path = require('path')
2
+var utils = require('./utils')
3
+var webpack = require('webpack')
4
+var config = require('../config')
5
+var merge = require('webpack-merge')
6
+var baseWebpackConfig = require('./webpack.base.conf')
7
+var CopyWebpackPlugin = require('copy-webpack-plugin')
8
+var HtmlWebpackPlugin = require('html-webpack-plugin')
9
+var ExtractTextPlugin = require('extract-text-webpack-plugin')
10
+var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
11
+
12
+var env = config.build.env
13
+
14
+var webpackConfig = merge(baseWebpackConfig, {
15
+  module: {
16
+    rules: utils.styleLoaders({
17
+      sourceMap: config.build.productionSourceMap,
18
+      extract: true
19
+    })
20
+  },
21
+  devtool: config.build.productionSourceMap ? '#source-map' : false,
22
+  output: {
23
+    path: config.build.assetsRoot,
24
+    filename: utils.assetsPath('js/[name].[chunkhash].js'),
25
+    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
26
+  },
27
+  plugins: [
28
+    // http://vuejs.github.io/vue-loader/en/workflow/production.html
29
+    new webpack.DefinePlugin({
30
+      'process.env': env
31
+    }),
32
+    new webpack.optimize.UglifyJsPlugin({
33
+      compress: {
34
+        warnings: false
35
+      },
36
+      sourceMap: true
37
+    }),
38
+    // extract css into its own file
39
+    new ExtractTextPlugin({
40
+      filename: utils.assetsPath('css/[name].[contenthash].css')
41
+    }),
42
+    // Compress extracted CSS. We are using this plugin so that possible
43
+    // duplicated CSS from different components can be deduped.
44
+    new OptimizeCSSPlugin({
45
+      cssProcessorOptions: {
46
+        safe: true
47
+      }
48
+    }),
49
+    // generate dist index.html with correct asset hash for caching.
50
+    // you can customize output by editing /index.html
51
+    // see https://github.com/ampedandwired/html-webpack-plugin
52
+    new HtmlWebpackPlugin({
53
+      filename: config.build.index,
54
+      template: 'index.html',
55
+      inject: true,
56
+      minify: {
57
+        removeComments: true,
58
+        collapseWhitespace: true,
59
+        removeAttributeQuotes: true
60
+        // more options:
61
+        // https://github.com/kangax/html-minifier#options-quick-reference
62
+      },
63
+      // necessary to consistently work with multiple chunks via CommonsChunkPlugin
64
+      chunksSortMode: 'dependency'
65
+    }),
66
+    // split vendor js into its own file
67
+    new webpack.optimize.CommonsChunkPlugin({
68
+      name: 'vendor',
69
+      minChunks: function (module, count) {
70
+        // any required modules inside node_modules are extracted to vendor
71
+        return (
72
+          module.resource &&
73
+          /\.js$/.test(module.resource) &&
74
+          module.resource.indexOf(
75
+            path.join(__dirname, '../node_modules')
76
+          ) === 0
77
+        )
78
+      }
79
+    }),
80
+    // extract webpack runtime and module manifest to its own file in order to
81
+    // prevent vendor hash from being updated whenever app bundle is updated
82
+    new webpack.optimize.CommonsChunkPlugin({
83
+      name: 'manifest',
84
+      chunks: ['vendor']
85
+    }),
86
+    // copy custom static assets
87
+    new CopyWebpackPlugin([
88
+      {
89
+        from: path.resolve(__dirname, '../static'),
90
+        to: config.build.assetsSubDirectory,
91
+        ignore: ['.*']
92
+      }
93
+    ])
94
+  ]
95
+})
96
+
97
+if (config.build.productionGzip) {
98
+  var CompressionWebpackPlugin = require('compression-webpack-plugin')
99
+
100
+  webpackConfig.plugins.push(
101
+    new CompressionWebpackPlugin({
102
+      asset: '[path].gz[query]',
103
+      algorithm: 'gzip',
104
+      test: new RegExp(
105
+        '\\.(' +
106
+        config.build.productionGzipExtensions.join('|') +
107
+        ')$'
108
+      ),
109
+      threshold: 10240,
110
+      minRatio: 0.8
111
+    })
112
+  )
113
+}
114
+
115
+if (config.build.bundleAnalyzerReport) {
116
+  var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
117
+  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
118
+}
119
+
120
+module.exports = webpackConfig

+ 6 - 0
config/dev.env.js

@@ -0,0 +1,6 @@
1
+var merge = require('webpack-merge')
2
+var prodEnv = require('./prod.env')
3
+
4
+module.exports = merge(prodEnv, {
5
+  NODE_ENV: '"development"'
6
+})

+ 38 - 0
config/index.js

@@ -0,0 +1,38 @@
1
+// see http://vuejs-templates.github.io/webpack for documentation.
2
+var path = require('path')
3
+
4
+module.exports = {
5
+  build: {
6
+    env: require('./prod.env'),
7
+    index: path.resolve(__dirname, '../dist/index.html'),
8
+    assetsRoot: path.resolve(__dirname, '../dist'),
9
+    assetsSubDirectory: 'static',
10
+    assetsPublicPath: '/',
11
+    productionSourceMap: true,
12
+    // Gzip off by default as many popular static hosts such as
13
+    // Surge or Netlify already gzip all static assets for you.
14
+    // Before setting to `true`, make sure to:
15
+    // npm install --save-dev compression-webpack-plugin
16
+    productionGzip: false,
17
+    productionGzipExtensions: ['js', 'css'],
18
+    // Run the build command with an extra argument to
19
+    // View the bundle analyzer report after build finishes:
20
+    // `npm run build --report`
21
+    // Set to `true` or `false` to always turn it on or off
22
+    bundleAnalyzerReport: process.env.npm_config_report
23
+  },
24
+  dev: {
25
+    env: require('./dev.env'),
26
+    port: 9528,
27
+    autoOpenBrowser: true,
28
+    assetsSubDirectory: 'static',
29
+    assetsPublicPath: '/',
30
+    proxyTable: {},
31
+    // CSS Sourcemaps off by default because relative paths are "buggy"
32
+    // with this option, according to the CSS-Loader README
33
+    // (https://github.com/webpack/css-loader#sourcemaps)
34
+    // In our experience, they generally work as expected,
35
+    // just be aware of this issue when enabling this option.
36
+    cssSourceMap: false
37
+  }
38
+}

+ 3 - 0
config/prod.env.js

@@ -0,0 +1,3 @@
1
+module.exports = {
2
+  NODE_ENV: '"production"'
3
+}

+ 11 - 0
index.html

@@ -0,0 +1,11 @@
1
+<!DOCTYPE html>
2
+<html>
3
+  <head>
4
+    <meta charset="utf-8">
5
+    <title>vue-admin</title>
6
+  </head>
7
+  <body>
8
+    <div id="app"></div>
9
+    <!-- built files will be auto injected -->
10
+  </body>
11
+</html>

+ 74 - 0
package.json

@@ -0,0 +1,74 @@
1
+{
2
+  "name": "vue-admin",
3
+  "version": "1.0.0",
4
+  "description": "A Vue.js project",
5
+  "author": "Pan <panfree23@gmail.com>",
6
+  "private": true,
7
+  "scripts": {
8
+    "dev": "node build/dev-server.js",
9
+    "start": "node build/dev-server.js",
10
+    "build": "node build/build.js",
11
+    "lint": "eslint --ext .js,.vue src"
12
+  },
13
+  "dependencies": {
14
+    "axios": "0.16.2",
15
+    "element-ui": "1.3.7",
16
+    "js-cookie": "^2.1.4",
17
+    "normalize.css": "3.0.2",
18
+    "nprogress": "^0.2.0",
19
+    "vue": "2.3.3",
20
+    "vue-router": "2.5.3",
21
+    "vuex": "2.3.1"
22
+  },
23
+  "devDependencies": {
24
+    "autoprefixer": "6.7.2",
25
+    "babel-core": "6.22.1",
26
+    "babel-eslint": "7.1.1",
27
+    "babel-loader": "6.2.10",
28
+    "babel-plugin-transform-runtime": "6.22.0",
29
+    "babel-preset-env": "1.3.2",
30
+    "babel-preset-stage-2": "6.22.0",
31
+    "babel-register": "6.22.0",
32
+    "chalk": "1.1.3",
33
+    "connect-history-api-fallback": "1.3.0",
34
+    "copy-webpack-plugin": "4.0.1",
35
+    "css-loader": "0.28.0",
36
+    "eslint": "3.19.0",
37
+    "eslint-friendly-formatter": "2.0.7",
38
+    "eslint-loader": "1.7.1",
39
+    "eslint-plugin-html": "2.0.0",
40
+    "eventsource-polyfill": "0.9.6",
41
+    "express": "4.14.1",
42
+    "extract-text-webpack-plugin": "2.0.0",
43
+    "file-loader": "0.11.1",
44
+    "friendly-errors-webpack-plugin": "1.1.3",
45
+    "html-webpack-plugin": "2.28.0",
46
+    "http-proxy-middleware": "0.17.3",
47
+    "webpack-bundle-analyzer": "2.2.1",
48
+    "semver": "5.3.0",
49
+    "shelljs": "0.7.6",
50
+    "opn": "4.0.2",
51
+    "optimize-css-assets-webpack-plugin": "1.3.0",
52
+    "ora": "1.2.0",
53
+    "rimraf": "2.6.0",
54
+    "node-sass": "^4.5.0",
55
+    "sass-loader": "6.0.5",
56
+    "url-loader": "0.5.8",
57
+    "vue-loader": "12.1.0",
58
+    "vue-style-loader": "3.0.1",
59
+    "vue-template-compiler": "2.3.3",
60
+    "webpack": "2.6.1",
61
+    "webpack-dev-middleware": "1.10.0",
62
+    "webpack-hot-middleware": "2.18.0",
63
+    "webpack-merge": "4.1.0"
64
+  },
65
+  "engines": {
66
+    "node": ">= 4.0.0",
67
+    "npm": ">= 3.0.0"
68
+  },
69
+  "browserslist": [
70
+    "> 1%",
71
+    "last 2 versions",
72
+    "not ie <= 8"
73
+  ]
74
+}

+ 12 - 0
src/App.vue

@@ -0,0 +1,12 @@
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>
12
+

+ 29 - 0
src/api/login.js

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

BIN
src/assets/404_images/404.png


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


File diff suppressed because it is too large
+ 1 - 0
src/assets/iconfont/iconfont.js


File diff suppressed because it is too large
+ 36 - 0
src/components/Hamburger/index.vue


+ 11 - 0
src/components/Icon-svg/index.js

@@ -0,0 +1,11 @@
1
+import Vue from 'vue'
2
+
3
+function registerAllComponents(requireContext) {
4
+  return requireContext.keys().forEach(comp => {
5
+    const vueComp = requireContext(comp)
6
+    const compName = vueComp.name ? vueComp.name.toLowerCase() : /\/([\w-]+)\.vue$/.exec(comp)[1]
7
+    Vue.component(compName, vueComp)
8
+  })
9
+}
10
+
11
+registerAllComponents(require.context('./', false, /\.vue$/))

+ 52 - 0
src/components/Icon-svg/wscn-icon-stack.vue

@@ -0,0 +1,52 @@
1
+<template>
2
+  <div class="icon-container" :style="containerStyle">
3
+    <slot class="icon"></slot>
4
+  </div>
5
+</template>
6
+
7
+<script>
8
+  export default {
9
+    name: 'wscn-icon-stack',
10
+    props: {
11
+      width: {
12
+        type: Number,
13
+        default: 20
14
+      },
15
+      shape: {
16
+        type: String,
17
+        default: 'circle',
18
+        validator: val => {
19
+          const validShapes = ['circle', 'square']
20
+          return validShapes.indexOf(val) > -1
21
+        }
22
+      }
23
+    },
24
+    computed: {
25
+      containerStyle() {
26
+        return {
27
+          width: `${this.width}px`,
28
+          height: `${this.width}px`,
29
+          fontSize: `${this.width * 0.6}px`,
30
+          borderRadius: `${this.shape === 'circle' && '50%'}`
31
+        }
32
+      }
33
+    }
34
+  }
35
+</script>
36
+
37
+<style lang="scss" scoped>
38
+  .icon-container {
39
+    display: inline-block;
40
+    position: relative;
41
+    overflow: hidden;
42
+    background: #1482F0;
43
+
44
+    .icon {
45
+      position: absolute;
46
+      color: #ffffff;
47
+      top: 50%;
48
+      left: 50%;
49
+      transform: translate(-50%, -50%);
50
+    }
51
+  }
52
+</style>

+ 26 - 0
src/components/Icon-svg/wscn-icon-svg.vue

@@ -0,0 +1,26 @@
1
+<template>
2
+  <svg class="wscn-icon" aria-hidden="true">
3
+    <use :xlink:href="iconName"></use>
4
+  </svg>
5
+</template>
6
+
7
+<script>
8
+  export default {
9
+    name: 'wscn-icon-svg',
10
+    props: {
11
+      iconClass: {
12
+        type: String,
13
+        required: true
14
+      }
15
+    },
16
+    computed: {
17
+      iconName() {
18
+        return `#icon-${this.iconClass}`
19
+      }
20
+    }
21
+  }
22
+</script>
23
+
24
+<style lang="scss" scoped>
25
+
26
+</style>

+ 66 - 0
src/main.js

@@ -0,0 +1,66 @@
1
+// The Vue build version to load with the `import` command
2
+// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
3
+import Vue from 'vue'
4
+import App from './App'
5
+import router from './router'
6
+import store from './store'
7
+import ElementUI from 'element-ui';
8
+import 'element-ui/lib/theme-default/index.css'
9
+import NProgress from 'nprogress'
10
+import 'normalize.css/normalize.css';// normalize.css 样式格式化
11
+import '@/styles/index.scss'; // 全局自定义的css样式
12
+import '@/components/Icon-svg/index'; // 封装的svg组件
13
+
14
+Vue.config.productionTip = false
15
+
16
+Vue.use(ElementUI);
17
+
18
+router.afterEach(() => {
19
+  NProgress.done(); // 结束Progress
20
+});
21
+
22
+
23
+// const whiteList = ['/login', '/authredirect', '/reset', '/sendpwd'];// 不重定向白名单
24
+// router.beforeEach((to, from, next) => {
25
+//   NProgress.start(); // 开启Progress
26
+//   if (store.getters.token) { // 判断是否有token
27
+//     if (to.path === '/login') {
28
+//       next({ path: '/' });
29
+//     } else {
30
+//       if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息
31
+//         store.dispatch('GetInfo').then(res => { // 拉取user_info
32
+//           const roles = res.data.role;
33
+//           store.dispatch('GenerateRoutes', { roles }).then(() => { // 生成可访问的路由表
34
+//             router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
35
+//             next(to.path); // hack方法 确保addRoutes已完成
36
+//           })
37
+//         }).catch(err => {
38
+//           console.log(err);
39
+//         });
40
+//       } else {
41
+//         // 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓
42
+//         if (hasPermission(store.getters.roles, to.meta.role)) {
43
+//           next();//
44
+//         } else {
45
+//           next({ path: '/401', query: { noGoBack: true } });
46
+//         }
47
+//         // 可删 ↑
48
+//       }
49
+//     }
50
+//   } else {
51
+//     if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
52
+//       next()
53
+//     } else {
54
+//       next('/login'); // 否则全部重定向到登录页
55
+//       NProgress.done(); // 在hash模式下 改变手动改变hash 重定向回来 不会触发afterEach 暂时hack方案 ps:history模式下无问题,可删除该行!
56
+//     }
57
+//   }
58
+// });
59
+
60
+new Vue({
61
+  el: '#app',
62
+  router,
63
+  store,
64
+  template: '<App/>',
65
+  components: { App }
66
+})

+ 1 - 0
src/router/_import_development.js

@@ -0,0 +1 @@
1
+module.exports = file => require('@/views/' + file + '.vue')

+ 1 - 0
src/router/_import_production.js

@@ -0,0 +1 @@
1
+module.exports = file => () => import('@/views/' + file + '.vue')

+ 60 - 0
src/router/index.js

@@ -0,0 +1,60 @@
1
+import Vue from 'vue';
2
+import Router from 'vue-router';
3
+const _import = require('./_import_' + process.env.NODE_ENV);
4
+// in development env not use Lazy Loading,because Lazy Loading large page will cause webpack hot update too slow
5
+// so only in production use Lazy Loading
6
+
7
+/* layout */
8
+import Layout from '../views/layout/Layout';
9
+
10
+/* login */
11
+const Login = _import('login/index');
12
+
13
+/* dashboard */
14
+const dashboard = _import('dashboard/index');
15
+
16
+/* error page */
17
+const Err404 = _import('404');
18
+
19
+const Page = _import('page/index');
20
+
21
+Vue.use(Router);
22
+
23
+ /**
24
+  * icon : the icon show in the sidebar
25
+  * hidden : if hidden:true will not show in the sidebar
26
+  * redirect : if redirect:noredirect will not redirct in the levelbar
27
+  * noDropdown : if noDropdown:true will not has submenu
28
+  * meta : { role: ['admin'] }  will control the page role
29
+  **/
30
+export const constantRouterMap = [
31
+  { path: '/404', component: Err404, hidden: true },
32
+  {
33
+    path: '/',
34
+    component: Layout,
35
+    redirect: '/dashboard',
36
+    name: '首页',
37
+    hidden: true,
38
+    children: [{ path: 'dashboard', component: dashboard }]
39
+  }
40
+]
41
+
42
+export default new Router({
43
+  // mode: 'history', //后端支持可开
44
+  scrollBehavior: () => ({ y: 0 }),
45
+  routes: constantRouterMap
46
+});
47
+
48
+export const asyncRouterMap = [
49
+  {
50
+    path: '/example',
51
+    component: Layout,
52
+    redirect: 'noredirect',
53
+    name: 'page',
54
+    icon: 'zonghe',
55
+    children: [
56
+      { path: 'index', component: Page, name: 'page' }
57
+    ]
58
+  },
59
+  { path: '*', redirect: '/404', hidden: true }
60
+];

+ 14 - 0
src/store/getters.js

@@ -0,0 +1,14 @@
1
+const getters = {
2
+  sidebar: state => state.app.sidebar,
3
+  visitedViews: state => state.app.visitedViews,
4
+  token: state => state.user.token,
5
+  avatar: state => state.user.avatar,
6
+  name: state => state.user.name,
7
+  uid: state => state.user.uid,
8
+  email: state => state.user.email,
9
+  introduction: state => state.user.introduction,
10
+  roles: state => state.user.roles,
11
+  permission_routers: state => state.permission.routers,
12
+  addRouters: state => state.permission.addRouters
13
+};
14
+export default getters

+ 19 - 0
src/store/index.js

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

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

@@ -0,0 +1,26 @@
1
+import Cookies from 'js-cookie';
2
+
3
+const app = {
4
+  state: {
5
+    sidebar: {
6
+      opened: !+Cookies.get('sidebarStatus')
7
+    }
8
+  },
9
+  mutations: {
10
+    TOGGLE_SIDEBAR: state => {
11
+      if (state.sidebar.opened) {
12
+        Cookies.set('sidebarStatus', 1);
13
+      } else {
14
+        Cookies.set('sidebarStatus', 0);
15
+      }
16
+      state.sidebar.opened = !state.sidebar.opened;
17
+    }
18
+  },
19
+  actions: {
20
+    ToggleSideBar: ({ commit }) => {
21
+      commit('TOGGLE_SIDEBAR')
22
+    }
23
+  }
24
+};
25
+
26
+export default app;

+ 62 - 0
src/store/modules/permission.js

@@ -0,0 +1,62 @@
1
+import { asyncRouterMap, constantRouterMap } from '@/router/index';
2
+
3
+/**
4
+ * 通过meta.role判断是否与当前用户权限匹配
5
+ * @param roles
6
+ * @param route
7
+ */
8
+function hasPermission(roles, route) {
9
+  if (route.meta && route.meta.role) {
10
+    return roles.some(role => route.meta.role.indexOf(role) >= 0)
11
+  } else {
12
+    return true
13
+  }
14
+}
15
+
16
+/**
17
+ * 递归过滤异步路由表,返回符合用户角色权限的路由表
18
+ * @param asyncRouterMap
19
+ * @param roles
20
+ */
21
+function filterAsyncRouter(asyncRouterMap, roles) {
22
+  const accessedRouters = asyncRouterMap.filter(route => {
23
+    if (hasPermission(roles, route)) {
24
+      if (route.children && route.children.length) {
25
+        route.children = filterAsyncRouter(route.children, roles)
26
+      }
27
+      return true
28
+    }
29
+    return false
30
+  })
31
+  return accessedRouters
32
+}
33
+
34
+const permission = {
35
+  state: {
36
+    routers: constantRouterMap,
37
+    addRouters: []
38
+  },
39
+  mutations: {
40
+    SET_ROUTERS: (state, routers) => {
41
+      state.addRouters = routers;
42
+      state.routers = constantRouterMap.concat(routers);
43
+    }
44
+  },
45
+  actions: {
46
+    GenerateRoutes({ commit }, data) {
47
+      return new Promise(resolve => {
48
+        const { roles } = data
49
+        let accessedRouters
50
+        if (roles.indexOf('admin') >= 0) {
51
+          accessedRouters = asyncRouterMap
52
+        } else {
53
+          accessedRouters = filterAsyncRouter(asyncRouterMap, roles)
54
+        }
55
+        commit('SET_ROUTERS', accessedRouters);
56
+        resolve();
57
+      })
58
+    }
59
+  }
60
+};
61
+
62
+export default permission;

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

@@ -0,0 +1,149 @@
1
+import { loginByEmail, logout, getInfo } from '@/api/login';
2
+import Cookies from 'js-cookie';
3
+
4
+const user = {
5
+  state: {
6
+    user: '',
7
+    status: '',
8
+    email: '',
9
+    code: '',
10
+    uid: undefined,
11
+    auth_type: '',
12
+    token: Cookies.get('Admin-Token'),
13
+    name: '',
14
+    avatar: '',
15
+    introduction: '',
16
+    roles: [],
17
+    setting: {
18
+      articlePlatform: []
19
+    }
20
+  },
21
+
22
+  mutations: {
23
+    SET_AUTH_TYPE: (state, type) => {
24
+      state.auth_type = type;
25
+    },
26
+    SET_CODE: (state, code) => {
27
+      state.code = code;
28
+    },
29
+    SET_TOKEN: (state, token) => {
30
+      state.token = token;
31
+    },
32
+    SET_UID: (state, uid) => {
33
+      state.uid = uid;
34
+    },
35
+    SET_EMAIL: (state, email) => {
36
+      state.email = email;
37
+    },
38
+    SET_INTRODUCTION: (state, introduction) => {
39
+      state.introduction = introduction;
40
+    },
41
+    SET_SETTING: (state, setting) => {
42
+      state.setting = setting;
43
+    },
44
+    SET_STATUS: (state, status) => {
45
+      state.status = status;
46
+    },
47
+    SET_NAME: (state, name) => {
48
+      state.name = name;
49
+    },
50
+    SET_AVATAR: (state, avatar) => {
51
+      state.avatar = avatar;
52
+    },
53
+    SET_ROLES: (state, roles) => {
54
+      state.roles = roles;
55
+    },
56
+    LOGIN_SUCCESS: () => {
57
+      console.log('login success')
58
+    },
59
+    LOGOUT_USER: state => {
60
+      state.user = '';
61
+    }
62
+  },
63
+
64
+  actions: {
65
+    // 邮箱登录
66
+    LoginByEmail({ commit }, userInfo) {
67
+      const email = userInfo.email.trim();
68
+      return new Promise((resolve, reject) => {
69
+        loginByEmail(email, userInfo.password).then(response => {
70
+          const data = response.data;
71
+          Cookies.set('Admin-Token', response.data.token);
72
+          commit('SET_TOKEN', data.token);
73
+          commit('SET_EMAIL', email);
74
+          resolve();
75
+        }).catch(error => {
76
+          reject(error);
77
+        });
78
+      });
79
+    },
80
+
81
+
82
+    // 获取用户信息
83
+    GetInfo({ commit, state }) {
84
+      return new Promise((resolve, reject) => {
85
+        getInfo(state.token).then(response => {
86
+          const data = response.data;
87
+          commit('SET_ROLES', data.role);
88
+          commit('SET_NAME', data.name);
89
+          commit('SET_AVATAR', data.avatar);
90
+          commit('SET_UID', data.uid);
91
+          commit('SET_INTRODUCTION', data.introduction);
92
+          resolve(response);
93
+        }).catch(error => {
94
+          reject(error);
95
+        });
96
+      });
97
+    },
98
+
99
+    // 第三方验证登录
100
+    LoginByThirdparty({ commit, state }, code) {
101
+      return new Promise((resolve, reject) => {
102
+        commit('SET_CODE', code);
103
+        loginByThirdparty(state.status, state.email, state.code, state.auth_type).then(response => {
104
+          commit('SET_TOKEN', response.data.token);
105
+          Cookies.set('Admin-Token', response.data.token);
106
+          resolve();
107
+        }).catch(error => {
108
+          reject(error);
109
+        });
110
+      });
111
+    },
112
+
113
+
114
+    // 登出
115
+    LogOut({ commit, state }) {
116
+      return new Promise((resolve, reject) => {
117
+        logout(state.token).then(() => {
118
+          commit('SET_TOKEN', '');
119
+          commit('SET_ROLES', []);
120
+          Cookies.remove('Admin-Token');
121
+          resolve();
122
+        }).catch(error => {
123
+          reject(error);
124
+        });
125
+      });
126
+    },
127
+
128
+    // 前端 登出
129
+    FedLogOut({ commit }) {
130
+      return new Promise(resolve => {
131
+        commit('SET_TOKEN', '');
132
+        Cookies.remove('Admin-Token');
133
+        resolve();
134
+      });
135
+    },
136
+
137
+    // 动态修改权限
138
+    ChangeRole({ commit }, role) {
139
+      return new Promise(resolve => {
140
+        commit('SET_ROLES', [role]);
141
+        commit('SET_TOKEN', role);
142
+        Cookies.set('Admin-Token', role);
143
+        resolve();
144
+      })
145
+    }
146
+  }
147
+};
148
+
149
+export default user;

+ 103 - 0
src/styles/btn.scss

@@ -0,0 +1,103 @@
1
+$blue:#324157;
2
+$light-blue:#3A71A8;
3
+$red:#C03639;
4
+$pink: #E65D6E;
5
+$green: #30B08F;
6
+$tiffany: #4AB7BD;
7
+$yellow:#FEC171;
8
+
9
+$panGreen: #30B08F;
10
+
11
+@mixin colorBtn($color) {
12
+  background: $color;
13
+  &:hover {
14
+    color: $color;
15
+    &:before, &:after {
16
+      background: $color;
17
+    }
18
+  }
19
+}
20
+
21
+
22
+.blue-btn {
23
+  @include colorBtn($blue)
24
+}
25
+
26
+.light-blue-btn{
27
+  @include colorBtn($light-blue)
28
+}
29
+
30
+
31
+.red-btn {
32
+  @include colorBtn($red)
33
+}
34
+
35
+.pink-btn {
36
+  @include colorBtn($pink)
37
+}
38
+
39
+.green-btn {
40
+  @include colorBtn($green)
41
+}
42
+
43
+
44
+.tiffany-btn {
45
+  @include colorBtn($tiffany)
46
+}
47
+
48
+
49
+.yellow-btn {
50
+  @include colorBtn($yellow)
51
+}
52
+
53
+.pan-btn {
54
+  font-size: 14px;
55
+  color: #fff;
56
+  padding: 14px 36px;
57
+  border-radius: 8px;
58
+  border: none;
59
+  outline: none;
60
+  margin-right: 25px;
61
+  transition: 600ms ease all;
62
+  position: relative;
63
+  display: inline-block;
64
+  &:hover {
65
+    background: #fff;
66
+    &:before, &:after {
67
+      width: 100%;
68
+      transition: 600ms ease all;
69
+    }
70
+  }
71
+  &:before, &:after {
72
+    content: '';
73
+    position: absolute;
74
+    top: 0;
75
+    right: 0;
76
+    height: 2px;
77
+    width: 0;
78
+    transition: 400ms ease all;
79
+  }
80
+  &::after {
81
+    right: inherit;
82
+    top: inherit;
83
+    left: 0;
84
+    bottom: 0;
85
+  }
86
+}
87
+
88
+.custom-button{
89
+    display: inline-block;
90
+    line-height: 1;
91
+    white-space: nowrap;
92
+    cursor: pointer;
93
+    background: #fff;
94
+    color: #fff;
95
+    -webkit-appearance: none;
96
+    text-align: center;
97
+    box-sizing: border-box;
98
+    outline: 0;
99
+    margin: 0;
100
+    padding: 10px 15px;
101
+    font-size: 14px;
102
+    border-radius: 4px;
103
+}

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

@@ -0,0 +1,83 @@
1
+ //覆盖一些element-ui样式
2
+.block-checkbox {
3
+  display: block;
4
+}
5
+
6
+.operation-container {
7
+  .cell {
8
+    padding: 10px !important;
9
+  }
10
+  .el-button {
11
+    &:nth-child(3) {
12
+      margin-top: 10px;
13
+      margin-left: 0px;
14
+    }
15
+    &:nth-child(4) {
16
+      margin-top: 10px;
17
+    }
18
+  }
19
+}
20
+
21
+.el-upload {
22
+  input[type="file"] {
23
+    display: none !important;
24
+  }
25
+}
26
+
27
+.el-upload__input {
28
+  display: none;
29
+}
30
+
31
+.cell {
32
+  .el-tag {
33
+    margin-right: 8px;
34
+  }
35
+}
36
+
37
+.small-padding {
38
+  .cell {
39
+    padding-left: 8px;
40
+    padding-right: 8px;
41
+  }
42
+}
43
+
44
+.status-col {
45
+  .cell {
46
+    padding: 0 10px;
47
+    text-align: center;
48
+    .el-tag {
49
+      margin-right: 0px;
50
+    }
51
+  }
52
+}
53
+
54
+//暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461
55
+.el-dialog {
56
+  transform: none;
57
+  left: 0;
58
+  position: relative;
59
+  margin: 0 auto;
60
+}
61
+
62
+
63
+//文章页textarea修改样式
64
+.article-textarea {
65
+  textarea {
66
+    padding-right: 40px;
67
+    resize: none;
68
+    border: none;
69
+    border-radius: 0px;
70
+    border-bottom: 1px solid #bfcbd9;
71
+  }
72
+}
73
+
74
+//element ui upload
75
+.upload-container {
76
+  .el-upload {
77
+    width: 100%;
78
+    .el-upload-dragger {
79
+      width: 100%;
80
+      height: 200px;
81
+    }
82
+  }
83
+}

+ 266 - 0
src/styles/index.scss

@@ -0,0 +1,266 @@
1
+@import './btn.scss';
2
+@import './element-ui.scss';
3
+@import "./mixin.scss";
4
+body {
5
+  -moz-osx-font-smoothing: grayscale;
6
+  -webkit-font-smoothing: antialiased;
7
+  text-rendering: optimizeLegibility;
8
+  font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
9
+}
10
+
11
+label {
12
+  font-weight: 700;
13
+}
14
+
15
+html {
16
+  box-sizing: border-box;
17
+}
18
+
19
+*,
20
+*:before,
21
+*:after {
22
+  box-sizing: inherit;
23
+}
24
+
25
+.no-padding {
26
+  padding: 0px !important;
27
+}
28
+
29
+.padding-content {
30
+  padding: 4px 0;
31
+}
32
+
33
+a:focus,
34
+a:active {
35
+  outline: none;
36
+}
37
+
38
+a,
39
+a:focus,
40
+a:hover {
41
+  cursor: pointer;
42
+  color: inherit;
43
+  text-decoration: none;
44
+}
45
+
46
+.fr {
47
+  float: right;
48
+}
49
+
50
+.fl {
51
+  float: left;
52
+}
53
+
54
+.pr-5 {
55
+  padding-right: 5px;
56
+}
57
+
58
+.pl-5 {
59
+  padding-left: 5px;
60
+}
61
+
62
+.block {
63
+  display: block;
64
+}
65
+
66
+.pointer {
67
+  cursor: pointer;
68
+}
69
+
70
+.inlineBlock {
71
+  display: block;
72
+}
73
+
74
+code {
75
+  background: #eef1f6;
76
+  padding: 15px 10px;
77
+  margin-bottom: 20px;
78
+  display: block;
79
+  line-height: 36px;
80
+  a {
81
+    color: #337ab7;
82
+    cursor: pointer;
83
+    &:hover {
84
+      color: rgb(32, 160, 255);
85
+    }
86
+  }
87
+}
88
+
89
+.fade-enter-active,
90
+.fade-leave-active {
91
+  transition: all .2s ease
92
+}
93
+
94
+.fade-enter,
95
+.fade-leave-active {
96
+  opacity: 0;
97
+}
98
+
99
+//main-container全局样式
100
+.app-container {
101
+  padding: 20px;
102
+}
103
+.components-container {
104
+  margin: 30px 50px;
105
+  position: relative;
106
+}
107
+.pagination-container {
108
+  margin-top: 30px;
109
+}
110
+
111
+
112
+.editor-container .CodeMirror {
113
+  height: 100%!important;
114
+}
115
+
116
+.wscn-icon {
117
+  width: 1em;
118
+  height: 1em;
119
+  vertical-align: -0.15em;
120
+  fill: currentColor;
121
+  overflow: hidden;
122
+}
123
+
124
+.sub-navbar {
125
+  height: 50px;
126
+  line-height: 50px;
127
+  position: relative;
128
+  width: 100%;
129
+  text-align: right;
130
+  padding-right: 20px;
131
+  transition: 600ms ease position;
132
+  background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%);
133
+  .subtitle {
134
+    font-size: 20px;
135
+    color: #fff;
136
+  }
137
+  &.draft {
138
+    background: #d0d0d0;
139
+  }
140
+  &.deleted {
141
+    background: #d0d0d0;
142
+  }
143
+}
144
+
145
+.link-type,
146
+.link-type:focus {
147
+  color: #337ab7;
148
+  cursor: pointer;
149
+  &:hover {
150
+    color: rgb(32, 160, 255);
151
+  }
152
+}
153
+
154
+.publishedTag,
155
+.draftTag,
156
+.deletedTag {
157
+  color: #fff;
158
+  background-color: $panGreen;
159
+  line-height: 1;
160
+  text-align: center;
161
+  margin: 0;
162
+  padding: 8px 12px;
163
+  font-size: 14px;
164
+  border-radius: 4px;
165
+  position: absolute;
166
+  left: 20px;
167
+  top: 10px;
168
+}
169
+
170
+.draftTag {
171
+  background-color: $yellow;
172
+}
173
+
174
+.deletedTag {
175
+  background-color: $red;
176
+}
177
+
178
+.input-label {
179
+  font-size: 14px;
180
+  color: #48576a;
181
+  line-height: 1;
182
+  padding: 11px 5px 11px 0;
183
+}
184
+
185
+.clearfix {
186
+  &:after {
187
+    visibility: hidden;
188
+    display: block;
189
+    font-size: 0;
190
+    content: " ";
191
+    clear: both;
192
+    height: 0;
193
+  }
194
+}
195
+
196
+.no-marginLeft {
197
+  .el-checkbox {
198
+    margin: 0 20px 15px 0;
199
+  }
200
+  .el-checkbox+.el-checkbox {
201
+    margin-left: 0px;
202
+  }
203
+}
204
+
205
+.filter-container {
206
+  padding-bottom: 10px;
207
+  .filter-item {
208
+    display: inline-block;
209
+    vertical-align: middle;
210
+    margin-bottom: 10px;
211
+  }
212
+}
213
+
214
+
215
+//refine vue-multiselect plugin
216
+.multiselect {
217
+  line-height: 16px;
218
+}
219
+
220
+.multiselect--active {
221
+  z-index: 1000 !important;
222
+}
223
+
224
+//refine simplemde
225
+.simplemde-container{
226
+  .editor-toolbar.fullscreen,.CodeMirror-fullscreen{
227
+    z-index: 1003;
228
+  }
229
+}
230
+
231
+//暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461
232
+.el-dialog {
233
+  transform: none;
234
+  left: 0;
235
+  position: relative;
236
+  margin: 0 auto;
237
+}
238
+
239
+//github-corner
240
+.github-corner:hover .octo-arm {
241
+  animation: octocat-wave 560ms ease-in-out
242
+}
243
+
244
+@keyframes octocat-wave {
245
+  0%,
246
+  100% {
247
+    transform: rotate(0)
248
+  }
249
+  20%,
250
+  60% {
251
+    transform: rotate(-25deg)
252
+  }
253
+  40%,
254
+  80% {
255
+    transform: rotate(10deg)
256
+  }
257
+}
258
+
259
+@media (max-width:500px) {
260
+  .github-corner:hover .octo-arm {
261
+    animation: none
262
+  }
263
+  .github-corner .octo-arm {
264
+    animation: octocat-wave 560ms ease-in-out
265
+  }
266
+}

+ 60 - 0
src/styles/mixin.scss

@@ -0,0 +1,60 @@
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
+
28
+@mixin pct($pct) {
29
+  width: #{$pct};
30
+  position: relative;
31
+  margin: 0 auto;
32
+}
33
+
34
+@mixin triangle($width, $height, $color, $direction) {
35
+  $width: $width/2;
36
+  $color-border-style: $height solid $color;
37
+  $transparent-border-style: $width solid transparent;
38
+  height: 0;
39
+  width: 0;
40
+  @if $direction==up {
41
+    border-bottom: $color-border-style;
42
+    border-left: $transparent-border-style;
43
+    border-right: $transparent-border-style;
44
+  }
45
+  @else if $direction==right {
46
+    border-left: $color-border-style;
47
+    border-top: $transparent-border-style;
48
+    border-bottom: $transparent-border-style;
49
+  }
50
+  @else if $direction==down {
51
+    border-top: $color-border-style;
52
+    border-left: $transparent-border-style;
53
+    border-right: $transparent-border-style;
54
+  }
55
+  @else if $direction==left {
56
+    border-right: $color-border-style;
57
+    border-top: $transparent-border-style;
58
+    border-bottom: $transparent-border-style;
59
+  }
60
+}

+ 8 - 0
src/utils/createUniqueString.js

@@ -0,0 +1,8 @@
1
+/**
2
+ * Created by jiachenpan on 17/3/8.
3
+ */
4
+export default function createUniqueString() {
5
+  const timestamp = +new Date() + '';
6
+  const randomNum = parseInt((1 + Math.random()) * 65536) + '';
7
+  return (+(randomNum + timestamp)).toString(32);
8
+}

+ 66 - 0
src/utils/fetch.js

@@ -0,0 +1,66 @@
1
+import axios from 'axios';
2
+import { Message } from 'element-ui';
3
+import store from '../store';
4
+// import router from '../router';
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
+  // Do something before request is sent
15
+  if (store.getters.token) {
16
+    config.headers['X-Token'] = store.getters.token; // 让每个请求携带token--['X-Token']为自定义key 请根据实际情况自行修改
17
+  }
18
+  return config;
19
+}, error => {
20
+  // Do something with request error
21
+  console.log(error); // for debug
22
+  Promise.reject(error);
23
+})
24
+
25
+// respone拦截器
26
+service.interceptors.response.use(
27
+  response => response,
28
+  /**
29
+  * 下面的注释为通过response自定义code来标示请求状态,当code返回如下情况为权限有问题,登出并返回到登录页
30
+  * 如通过xmlhttprequest 状态码标识 逻辑可写在下面error中
31
+  */
32
+//  const res = response.data;
33
+//     if (res.code !== 20000) {
34
+//       Message({
35
+//         message: res.message,
36
+//         type: 'error',
37
+//         duration: 5 * 1000
38
+//       });
39
+//       // 50008:非法的token; 50012:其他客户端登录了;  50014:Token 过期了;
40
+//       if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
41
+//         MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', {
42
+//           confirmButtonText: '重新登录',
43
+//           cancelButtonText: '取消',
44
+//           type: 'warning'
45
+//         }).then(() => {
46
+//           store.dispatch('FedLogOut').then(() => {
47
+//             location.reload();// 为了重新实例化vue-router对象 避免bug
48
+//           });
49
+//         })
50
+//       }
51
+//       return Promise.reject(error);
52
+//     } else {
53
+//       return response.data;
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;

+ 214 - 0
src/utils/index.js

@@ -0,0 +1,214 @@
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
+ }
59
+
60
+// 格式化时间
61
+ export function getQueryObject(url) {
62
+   url = url == null ? window.location.href : url;
63
+   const search = url.substring(url.lastIndexOf('?') + 1);
64
+   const obj = {};
65
+   const reg = /([^?&=]+)=([^?&=]*)/g;
66
+   search.replace(reg, (rs, $1, $2) => {
67
+     const name = decodeURIComponent($1);
68
+     let val = decodeURIComponent($2);
69
+     val = String(val);
70
+     obj[name] = val;
71
+     return rs;
72
+   });
73
+   return obj;
74
+ }
75
+
76
+
77
+/**
78
+ *get getByteLen
79
+ * @param {Sting} val input value
80
+ * @returns {number} output value
81
+ */
82
+ export function getByteLen(val) {
83
+   let len = 0;
84
+   for (let i = 0; i < val.length; i++) {
85
+     if (val[i].match(/[^\x00-\xff]/ig) != null) {
86
+       len += 1;
87
+     } else { len += 0.5; }
88
+   }
89
+   return Math.floor(len);
90
+ }
91
+
92
+ export function cleanArray(actual) {
93
+   const newArray = [];
94
+   for (let i = 0; i < actual.length; i++) {
95
+     if (actual[i]) {
96
+       newArray.push(actual[i]);
97
+     }
98
+   }
99
+   return newArray;
100
+ }
101
+
102
+ export function param(json) {
103
+   if (!json) return '';
104
+   return cleanArray(Object.keys(json).map(key => {
105
+     if (json[key] === undefined) return '';
106
+     return encodeURIComponent(key) + '=' +
107
+            encodeURIComponent(json[key]);
108
+   })).join('&');
109
+ }
110
+
111
+ export function param2Obj(url) {
112
+   const search = url.split('?')[1];
113
+   return JSON.parse('{"' + decodeURIComponent(search).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"') + '"}')
114
+ }
115
+
116
+ export function html2Text(val) {
117
+   const div = document.createElement('div');
118
+   div.innerHTML = val;
119
+   return div.textContent || div.innerText;
120
+ }
121
+
122
+ export function objectMerge(target, source) {
123
+    /* Merges two  objects,
124
+     giving the last one precedence */
125
+
126
+   if (typeof target !== 'object') {
127
+     target = {};
128
+   }
129
+   if (Array.isArray(source)) {
130
+     return source.slice();
131
+   }
132
+   for (const property in source) {
133
+     if (source.hasOwnProperty(property)) {
134
+       const sourceProperty = source[property];
135
+       if (typeof sourceProperty === 'object') {
136
+         target[property] = objectMerge(target[property], sourceProperty);
137
+         continue;
138
+       }
139
+       target[property] = sourceProperty;
140
+     }
141
+   }
142
+   return target;
143
+ }
144
+
145
+
146
+ export function scrollTo(element, to, duration) {
147
+   if (duration <= 0) return;
148
+   const difference = to - element.scrollTop;
149
+   const perTick = difference / duration * 10;
150
+   setTimeout(() => {
151
+     console.log(new Date())
152
+     element.scrollTop = element.scrollTop + perTick;
153
+     if (element.scrollTop === to) return;
154
+     scrollTo(element, to, duration - 10);
155
+   }, 10);
156
+ }
157
+
158
+ export function toggleClass(element, className) {
159
+   if (!element || !className) {
160
+     return;
161
+   }
162
+   let classString = element.className;
163
+   const nameIndex = classString.indexOf(className);
164
+   if (nameIndex === -1) {
165
+     classString += '' + className;
166
+   } else {
167
+     classString = classString.substr(0, nameIndex) + classString.substr(nameIndex + className.length);
168
+   }
169
+   element.className = classString;
170
+ }
171
+
172
+ export const pickerOptions = [
173
+   {
174
+     text: '今天',
175
+     onClick(picker) {
176
+       const end = new Date();
177
+       const start = new Date(new Date().toDateString());
178
+       end.setTime(start.getTime());
179
+       picker.$emit('pick', [start, end]);
180
+     }
181
+   }, {
182
+     text: '最近一周',
183
+     onClick(picker) {
184
+       const end = new Date(new Date().toDateString());
185
+       const start = new Date();
186
+       start.setTime(end.getTime() - 3600 * 1000 * 24 * 7);
187
+       picker.$emit('pick', [start, end]);
188
+     }
189
+   }, {
190
+     text: '最近一个月',
191
+     onClick(picker) {
192
+       const end = new Date(new Date().toDateString());
193
+       const start = new Date();
194
+       start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
195
+       picker.$emit('pick', [start, end]);
196
+     }
197
+   }, {
198
+     text: '最近三个月',
199
+     onClick(picker) {
200
+       const end = new Date(new Date().toDateString());
201
+       const start = new Date();
202
+       start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
203
+       picker.$emit('pick', [start, end]);
204
+     }
205
+   }]
206
+
207
+ export function getTime(type) {
208
+   if (type === 'start') {
209
+     return new Date().getTime() - 3600 * 1000 * 24 * 90
210
+   } else {
211
+     return new Date(new Date().toDateString())
212
+   }
213
+ }
214
+

+ 27 - 0
src/utils/openWindow.js

@@ -0,0 +1,27 @@
1
+/**
2
+ *Created by jiachenpan on 16/11/29.
3
+ * @param {Sting} url
4
+ * @param {Sting} title
5
+ * @param {Number} w
6
+ * @param {Number} h
7
+ */
8
+
9
+export default function openWindow(url, title, w, h) {
10
+      // Fixes dual-screen position                         Most browsers      Firefox
11
+  const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left;
12
+  const dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top;
13
+
14
+  const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
15
+  const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height;
16
+
17
+  const left = ((width / 2) - (w / 2)) + dualScreenLeft;
18
+  const top = ((height / 2) - (h / 2)) + dualScreenTop;
19
+  const newWindow = window.open(url, title, 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left);
20
+
21
+  // Puts focus on the newWindow
22
+  if (window.focus) {
23
+    newWindow.focus();
24
+  }
25
+}
26
+
27
+

+ 41 - 0
src/utils/validate.js

@@ -0,0 +1,41 @@
1
+/**
2
+ * Created by jiachenpan on 16/11/18.
3
+ */
4
+
5
+/* 是否是公司邮箱*/
6
+export function isWscnEmail(str) {
7
+  const reg = /^[a-z0-9](?:[-_.+]?[a-z0-9]+)*@wallstreetcn\.com$/i;
8
+  return reg.test(str.trim());
9
+}
10
+
11
+/* 合法uri*/
12
+export function validateURL(textval) {
13
+  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.,?'\\+&%$#=~_-]+))*$/;
14
+  return urlregex.test(textval);
15
+}
16
+
17
+/* 小写字母*/
18
+export function validateLowerCase(str) {
19
+  const reg = /^[a-z]+$/;
20
+  return reg.test(str);
21
+}
22
+
23
+/* 验证key*/
24
+// export function validateKey(str) {
25
+//     var reg = /^[a-z_\-:]+$/;
26
+//     return reg.test(str);
27
+// }
28
+
29
+/* 大写字母*/
30
+export function validateUpperCase(str) {
31
+  const reg = /^[A-Z]+$/;
32
+  return reg.test(str);
33
+}
34
+
35
+/* 大小写字母*/
36
+export function validatAlphabets(str) {
37
+  const reg = /^[A-Za-z]+$/;
38
+  return reg.test(str);
39
+}
40
+
41
+

+ 228 - 0
src/views/404.vue

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

+ 5 - 0
src/views/dashboard/default/index.vue

@@ -0,0 +1,5 @@
1
+<template>
2
+    <div class="dashboard-editor-container">
3
+        dashboard
4
+    </div>
5
+</template>

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

@@ -0,0 +1,38 @@
1
+<template>
2
+    <div class="dashboard-container">
3
+        <component v-bind:is="currentRole"> </component>
4
+    </div>
5
+</template>
6
+
7
+<script>
8
+    import { mapGetters } from 'vuex';
9
+    import DefaultDashboard from './default/index';
10
+    export default {
11
+      name: 'dashboard',
12
+      components: { DefaultDashboard },
13
+      data() {
14
+        return {
15
+          currentRole: 'DefaultDashboard'
16
+        }
17
+      },
18
+      computed: {
19
+        ...mapGetters([
20
+          'name',
21
+          'avatar',
22
+          'email',
23
+          'introduction',
24
+          'roles'
25
+        ])
26
+      },
27
+      created() {
28
+        if (this.roles.indexOf('admin') >= 0) {
29
+          return;
30
+        }
31
+        // const isEditor = this.roles.some(v => v.indexOf('editor') >= 0)
32
+        // if (!isEditor) {
33
+        //   this.currentRole = 'DefaultDashboard';
34
+        // }
35
+        this.currentRole = 'DefaultDashboard';
36
+      }
37
+    }
38
+</script>

+ 18 - 0
src/views/layout/AppMain.vue

@@ -0,0 +1,18 @@
1
+<template>
2
+    <section class="app-main" style="min-height: 100%">
3
+        <transition name="fade" mode="out-in">
4
+            <router-view :key="key"></router-view>
5
+        </transition>
6
+    </section>
7
+</template>
8
+
9
+<script>
10
+    export default {
11
+      name: 'AppMain',
12
+      computed: {
13
+        key() {
14
+          return this.$route.name !== undefined ? this.$route.name + +new Date() : this.$route + +new Date()
15
+        }
16
+      }
17
+    }
18
+</script>

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

@@ -0,0 +1,80 @@
1
+<template>
2
+    <div class="app-wrapper" :class="{hideSidebar:!sidebar.opened}">
3
+        <div class="sidebar-wrapper">
4
+            <Sidebar class="sidebar-container" />
5
+        </div>
6
+        <div class="main-container">
7
+            <Navbar/>
8
+            <App-main/>
9
+        </div>
10
+    </div>
11
+</template>
12
+
13
+<script>
14
+    import { Navbar, Sidebar, AppMain } from '@/views/layout';
15
+
16
+    export default {
17
+      name: 'layout',
18
+      components: {
19
+        Navbar,
20
+        Sidebar,
21
+        AppMain
22
+      },
23
+      computed: {
24
+        sidebar() {
25
+          return this.$store.state.app.sidebar;
26
+        }
27
+      }
28
+    }
29
+</script>
30
+
31
+<style rel="stylesheet/scss" lang="scss" scoped>
32
+    @import "src/styles/mixin.scss";
33
+    .app-wrapper {
34
+        @include clearfix;
35
+        position: relative;
36
+        height: 100%;
37
+        width: 100%;
38
+        &.hideSidebar {
39
+            .sidebar-wrapper {
40
+                transform: translate(-140px, 0);
41
+                .sidebar-container {
42
+                    transform: translate(132px, 0);
43
+                }
44
+                &:hover {
45
+                    transform: translate(0, 0);
46
+                    .sidebar-container {
47
+                        transform: translate(0, 0);
48
+                    }
49
+                }
50
+            }
51
+            .main-container{
52
+                margin-left: 40px;
53
+            }
54
+        }
55
+        .sidebar-wrapper {
56
+            width: 180px;
57
+            position: fixed;
58
+            top: 0;
59
+            bottom: 0;
60
+            left: 0;
61
+            z-index: 1001;
62
+            overflow: hidden;
63
+            transition: all .28s ease-out;
64
+        }
65
+        .sidebar-container {
66
+            transition: all .28s ease-out;
67
+            position: absolute;
68
+            top: 0;
69
+            bottom: 0;
70
+            left: 0;
71
+            right: -17px;
72
+            overflow-y: scroll;
73
+        }
74
+        .main-container {
75
+            min-height: 100%;
76
+            transition: all .28s ease-out;
77
+            margin-left: 180px;
78
+        }
79
+    }
80
+</style>

+ 49 - 0
src/views/layout/Levelbar.vue

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

+ 105 - 0
src/views/layout/Navbar.vue

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

+ 24 - 0
src/views/layout/Sidebar.vue

@@ -0,0 +1,24 @@
1
+<template>
2
+    <el-menu mode="vertical" theme="dark" :default-active="$route.path">
3
+        <sidebar-item :routes='permission_routers'></sidebar-item>
4
+    </el-menu>
5
+</template>
6
+
7
+<script>
8
+    import { mapGetters } from 'vuex';
9
+    import SidebarItem from './SidebarItem';
10
+    export default {
11
+      components: { SidebarItem },
12
+      computed: {
13
+        ...mapGetters([
14
+          'permission_routers'
15
+        ])
16
+      }
17
+    }
18
+</script>
19
+
20
+<style rel="stylesheet/scss" lang="scss" scoped>
21
+    .el-menu {
22
+        min-height: 100%;
23
+    }
24
+</style>

+ 47 - 0
src/views/layout/SidebarItem.vue

@@ -0,0 +1,47 @@
1
+<template>
2
+    <div>
3
+        <template v-for="item in routes">
4
+            <router-link v-if="!item.hidden&&item.noDropdown&&item.children.length>0" :to="item.path+'/'+item.children[0].path">
5
+                <el-menu-item :index="item.path+'/'+item.children[0].path">
6
+                    <wscn-icon-svg v-if='item.icon' :icon-class="item.icon" /> {{item.children[0].name}}
7
+                </el-menu-item>
8
+            </router-link>
9
+            <el-submenu :index="item.name" v-if="!item.noDropdown&&!item.hidden">
10
+                <template slot="title">
11
+                    <wscn-icon-svg v-if='item.icon' :icon-class="item.icon" /> {{item.name}}
12
+                </template>
13
+                <template v-for="child in item.children" v-if='!child.hidden'>
14
+                    <sidebar-item class='menu-indent' v-if='child.children&&child.children.length>0' :routes='[child]'> </sidebar-item>
15
+                    <router-link v-else class="menu-indent" :to="item.path+'/'+child.path">
16
+                        <el-menu-item :index="item.path+'/'+child.path">
17
+                            {{child.name}}
18
+                        </el-menu-item>
19
+                    </router-link>
20
+                </template>
21
+            </el-submenu>
22
+        </template>
23
+    </div>
24
+</template>
25
+
26
+<script>
27
+
28
+    export default {
29
+      name: 'SidebarItem',
30
+      props: {
31
+        routes: {
32
+          type: Array
33
+        }
34
+      }
35
+    }
36
+</script>
37
+
38
+<style rel="stylesheet/scss" lang="scss" scoped>
39
+    .wscn-icon {
40
+        margin-right: 10px;
41
+    }
42
+    .hideSidebar .menu-indent{
43
+        display: block;
44
+        text-indent: 10px;
45
+    }
46
+</style>
47
+

+ 45 - 0
src/views/layout/TabsView.vue

@@ -0,0 +1,45 @@
1
+<template>
2
+  <div class='tabs-view-container'>
3
+    <router-link class="tabs-view" v-for="tag in Array.from(visitedViews)" :to="tag.path" :key="tag.path">
4
+      <el-tag :closable="true" @close='closeViewTabs(tag,$event)'>
5
+        {{tag.name}}
6
+      </el-tag>
7
+    </router-link>
8
+    </div>
9
+</template>
10
+
11
+<script>
12
+    export default {
13
+      computed: {
14
+        visitedViews() {
15
+          return this.$store.state.app.visitedViews.slice(-6)
16
+        }
17
+      },
18
+      methods: {
19
+        closeViewTabs(view, $event) {
20
+          this.$store.dispatch('delVisitedViews', view)
21
+          $event.preventDefault()
22
+        },
23
+        addViewTabs() {
24
+          this.$store.dispatch('addVisitedViews', this.$route.matched[this.$route.matched.length - 1])
25
+        }
26
+      },
27
+      watch: {
28
+        $route() {
29
+          this.addViewTabs()
30
+        }
31
+      }
32
+    }
33
+</script>
34
+
35
+<style rel="stylesheet/scss" lang="scss" scoped>
36
+  .tabs-view-container{
37
+    display: inline-block;
38
+    vertical-align: top;
39
+    margin-left: 10px;
40
+    .tabs-view{
41
+      margin-left: 10px;
42
+    }
43
+  }
44
+
45
+</style>

+ 7 - 0
src/views/layout/index.js

@@ -0,0 +1,7 @@
1
+export { default as Navbar } from './Navbar';
2
+
3
+export { default as Sidebar } from './Sidebar';
4
+
5
+export { default as Levelbar } from './Levelbar';
6
+
7
+export { default as AppMain } from './AppMain';

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

@@ -0,0 +1,186 @@
1
+<template>
2
+    <div class="login-container">
3
+        <el-form autoComplete="on" :model="loginForm" :rules="loginRules" ref="loginForm" label-position="left"
4
+                 label-width="0px"
5
+                 class="card-box login-form">
6
+            <h3 class="title">系统登录</h3>
7
+            <el-form-item prop="email">
8
+                <span class="svg-container"><wscn-icon-svg icon-class="jiedianyoujian"/></span>
9
+                <el-input name="email" type="text" v-model="loginForm.email" autoComplete="on"
10
+                          placeholder="邮箱"></el-input>
11
+            </el-form-item>
12
+            <el-form-item prop="password">
13
+                <span class="svg-container"><wscn-icon-svg icon-class="mima"/></span>
14
+                <el-input name="password" type="password" @keyup.enter.native="handleLogin" v-model="loginForm.password"
15
+                          autoComplete="on" placeholder="密码"></el-input>
16
+            </el-form-item>
17
+            <el-form-item>
18
+                <el-button type="primary" style="width:100%;" :loading="loading" @click.native.prevent="handleLogin">
19
+                    登录
20
+                </el-button>
21
+            </el-form-item>
22
+            <div class='tips'>admin账号为:admin@wallstreetcn.com 密码随便填</div>
23
+            <div class='tips'>editor账号:editor@wallstreetcn.com 密码随便填</div>
24
+            <router-link to="/sendpwd" class="forget-pwd">
25
+                忘记密码?(或首次登录)
26
+            </router-link>
27
+        </el-form>
28
+    </div>
29
+</template>
30
+
31
+<script>
32
+    import { mapGetters } from 'vuex';
33
+    import { isWscnEmail } from '@/utils/validate';
34
+
35
+    export default {
36
+      name: 'login',
37
+      data() {
38
+        const validateEmail = (rule, value, callback) => {
39
+          if (!isWscnEmail(value)) {
40
+            callback(new Error('请输入正确的合法邮箱'));
41
+          } else {
42
+            callback();
43
+          }
44
+        };
45
+        const validatePass = (rule, value, callback) => {
46
+          if (value.length < 6) {
47
+            callback(new Error('密码不能小于6位'));
48
+          } else {
49
+            callback();
50
+          }
51
+        };
52
+        return {
53
+          loginForm: {
54
+            email: 'admin@wallstreetcn.com',
55
+            password: ''
56
+          },
57
+          loginRules: {
58
+            email: [
59
+                { required: true, trigger: 'blur', validator: validateEmail }
60
+            ],
61
+            password: [
62
+                { required: true, trigger: 'blur', validator: validatePass }
63
+            ]
64
+          },
65
+          loading: false,
66
+          showDialog: false
67
+        }
68
+      },
69
+      computed: {
70
+        ...mapGetters([
71
+          'auth_type'
72
+        ])
73
+      },
74
+      methods: {
75
+        handleLogin() {
76
+          this.$refs.loginForm.validate(valid => {
77
+            if (valid) {
78
+              this.loading = true;
79
+              this.$store.dispatch('LoginByEmail', this.loginForm).then(() => {
80
+                this.loading = false;
81
+                this.$router.push({ path: '/' });
82
+                // this.showDialog = true;
83
+              }).catch(err => {
84
+                this.$message.error(err);
85
+                this.loading = false;
86
+              });
87
+            } else {
88
+              console.log('error submit!!');
89
+              return false;
90
+            }
91
+          });
92
+        },
93
+        afterQRScan() {
94
+          // const hash = window.location.hash.slice(1);
95
+          // const hashObj = getQueryObject(hash);
96
+          // const originUrl = window.location.origin;
97
+          // history.replaceState({}, '', originUrl);
98
+          // const codeMap = {
99
+          //   wechat: 'code',
100
+          //   tencent: 'code'
101
+          // };
102
+          // const codeName = hashObj[codeMap[this.auth_type]];
103
+          // if (!codeName) {
104
+          //   alert('第三方登录失败');
105
+          // } else {
106
+          //   this.$store.dispatch('LoginByThirdparty', codeName).then(() => {
107
+          //     this.$router.push({ path: '/' });
108
+          //   });
109
+          // }
110
+        }
111
+      },
112
+      created() {
113
+        // window.addEventListener('hashchange', this.afterQRScan);
114
+      },
115
+      destroyed() {
116
+        // window.removeEventListener('hashchange', this.afterQRScan);
117
+      }
118
+    }
119
+</script>
120
+
121
+<style rel="stylesheet/scss" lang="scss">
122
+    @import "src/styles/mixin.scss";
123
+    .tips{
124
+      font-size: 14px;
125
+      color: #fff;
126
+      margin-bottom: 5px;
127
+    }
128
+    .login-container {
129
+        @include relative;
130
+        height: 100vh;
131
+        background-color: #2d3a4b;
132
+
133
+        input:-webkit-autofill {
134
+            -webkit-box-shadow: 0 0 0px 1000px #293444 inset !important;
135
+            -webkit-text-fill-color: #fff !important;
136
+        }
137
+        input {
138
+            background: transparent;
139
+            border: 0px;
140
+            -webkit-appearance: none;
141
+            border-radius: 0px;
142
+            padding: 12px 5px 12px 15px;
143
+            color: #eeeeee;
144
+            height: 47px;
145
+        }
146
+        .el-input {
147
+            display: inline-block;
148
+            height: 47px;
149
+            width: 85%;
150
+        }
151
+        .svg-container {
152
+            padding: 6px 5px 6px 15px;
153
+            color: #889aa4;
154
+        }
155
+
156
+        .title {
157
+            font-size: 26px;
158
+            font-weight: 400;
159
+            color: #eeeeee;
160
+            margin: 0px auto 40px auto;
161
+            text-align: center;
162
+            font-weight: bold;
163
+        }
164
+
165
+        .login-form {
166
+            position: absolute;
167
+            left: 0;
168
+            right: 0;
169
+            width: 400px;
170
+            padding: 35px 35px 15px 35px;
171
+            margin: 120px auto;
172
+        }
173
+
174
+        .el-form-item {
175
+            border: 1px solid rgba(255, 255, 255, 0.1);
176
+            background: rgba(0, 0, 0, 0.1);
177
+            border-radius: 5px;
178
+            color: #454545;
179
+        }
180
+
181
+        .forget-pwd {
182
+            color: #fff;
183
+        }
184
+    }
185
+
186
+</style>

+ 5 - 0
src/views/page/index.vue

@@ -0,0 +1,5 @@
1
+<template>
2
+    <div class="login-container">
3
+        a
4
+    </div>
5
+</template>

+ 0 - 0
static/.gitkeep