9a64d87d by simon

基础工程

0 parents
Showing 90 changed files with 3838 additions and 0 deletions
1 {
2 "presets": [
3 ["env", {
4 "modules": false,
5 "targets": {
6 "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7 }
8 }],
9 "stage-2"
10 ],
11 "plugins":["transform-vue-jsx", "transform-runtime"]
12 }
1 # http://editorconfig.org
2 root = true
3
4 [*]
5 charset = utf-8
6 indent_style = space
7 indent_size = 2
8 end_of_line = lf
9 insert_final_newline = true
10 trim_trailing_whitespace = true
11
12 [*.md]
13 insert_final_newline = false
14 trim_trailing_whitespace = false
1 build/*.js
2 config/*.js
3 src/assets
4 src/
1 module.exports = {
2 root: true,
3 parserOptions: {
4 parser: 'babel-eslint',
5 sourceType: 'module'
6 },
7 env: {
8 browser: true,
9 node: true,
10 es6: true,
11 },
12 extends: ['plugin:vue/recommended', 'eslint:recommended'],
13
14 // add your custom rules here
15 //it is base on https://github.com/vuejs/eslint-config-vue
16 rules: {
17 "vue/max-attributes-per-line": [2, {
18 "singleline": 10,
19 "multiline": {
20 "max": 1,
21 "allowFirstLine": false
22 }
23 }],
24 "vue/name-property-casing": ["error", "PascalCase"],
25 'accessor-pairs': 2,
26 'arrow-spacing': [2, {
27 'before': true,
28 'after': true
29 }],
30 'block-spacing': [2, 'always'],
31 'brace-style': [2, '1tbs', {
32 'allowSingleLine': true
33 }],
34 'camelcase': [0, {
35 'properties': 'always'
36 }],
37 'comma-dangle': [2, 'never'],
38 'comma-spacing': [2, {
39 'before': false,
40 'after': true
41 }],
42 'comma-style': [2, 'last'],
43 'constructor-super': 2,
44 'curly': [2, 'multi-line'],
45 'dot-location': [2, 'property'],
46 'eol-last': 2,
47 'eqeqeq': [2, 'allow-null'],
48 'generator-star-spacing': [2, {
49 'before': true,
50 'after': true
51 }],
52 'handle-callback-err': [2, '^(err|error)$'],
53 'indent': [2, 2, {
54 'SwitchCase': 1
55 }],
56 'jsx-quotes': [2, 'prefer-single'],
57 'key-spacing': [2, {
58 'beforeColon': false,
59 'afterColon': true
60 }],
61 'keyword-spacing': [2, {
62 'before': true,
63 'after': true
64 }],
65 'new-cap': [2, {
66 'newIsCap': true,
67 'capIsNew': false
68 }],
69 'new-parens': 2,
70 'no-array-constructor': 2,
71 'no-caller': 2,
72 'no-console': 'off',
73 'no-class-assign': 2,
74 'no-cond-assign': 2,
75 'no-const-assign': 2,
76 'no-control-regex': 2,
77 'no-delete-var': 2,
78 'no-dupe-args': 2,
79 'no-dupe-class-members': 2,
80 'no-dupe-keys': 2,
81 'no-duplicate-case': 2,
82 'no-empty-character-class': 2,
83 'no-empty-pattern': 2,
84 'no-eval': 2,
85 'no-ex-assign': 2,
86 'no-extend-native': 2,
87 'no-extra-bind': 2,
88 'no-extra-boolean-cast': 2,
89 'no-extra-parens': [2, 'functions'],
90 'no-fallthrough': 2,
91 'no-floating-decimal': 2,
92 'no-func-assign': 2,
93 'no-implied-eval': 2,
94 'no-inner-declarations': [2, 'functions'],
95 'no-invalid-regexp': 2,
96 'no-irregular-whitespace': 2,
97 'no-iterator': 2,
98 'no-label-var': 2,
99 'no-labels': [2, {
100 'allowLoop': false,
101 'allowSwitch': false
102 }],
103 'no-lone-blocks': 2,
104 'no-mixed-spaces-and-tabs': 2,
105 'no-multi-spaces': 2,
106 'no-multi-str': 2,
107 'no-multiple-empty-lines': [2, {
108 'max': 1
109 }],
110 'no-native-reassign': 2,
111 'no-negated-in-lhs': 2,
112 'no-new-object': 2,
113 'no-new-require': 2,
114 'no-new-symbol': 2,
115 'no-new-wrappers': 2,
116 'no-obj-calls': 2,
117 'no-octal': 2,
118 'no-octal-escape': 2,
119 'no-path-concat': 2,
120 'no-proto': 2,
121 'no-redeclare': 2,
122 'no-regex-spaces': 2,
123 'no-return-assign': [2, 'except-parens'],
124 'no-self-assign': 2,
125 'no-self-compare': 2,
126 'no-sequences': 2,
127 'no-shadow-restricted-names': 2,
128 'no-spaced-func': 2,
129 'no-sparse-arrays': 2,
130 'no-this-before-super': 2,
131 'no-throw-literal': 2,
132 'no-trailing-spaces': 2,
133 'no-undef': 2,
134 'no-undef-init': 2,
135 'no-unexpected-multiline': 2,
136 'no-unmodified-loop-condition': 2,
137 'no-unneeded-ternary': [2, {
138 'defaultAssignment': false
139 }],
140 'no-unreachable': 2,
141 'no-unsafe-finally': 2,
142 'no-unused-vars': [2, {
143 'vars': 'all',
144 'args': 'none'
145 }],
146 'no-useless-call': 2,
147 'no-useless-computed-key': 2,
148 'no-useless-constructor': 2,
149 'no-useless-escape': 0,
150 'no-whitespace-before-property': 2,
151 'no-with': 2,
152 'one-var': [2, {
153 'initialized': 'never'
154 }],
155 'operator-linebreak': [2, 'after', {
156 'overrides': {
157 '?': 'before',
158 ':': 'before'
159 }
160 }],
161 'padded-blocks': [2, 'never'],
162 'quotes': [2, 'single', {
163 'avoidEscape': true,
164 'allowTemplateLiterals': true
165 }],
166 'semi': [2, 'never'],
167 'semi-spacing': [2, {
168 'before': false,
169 'after': true
170 }],
171 'space-before-blocks': [2, 'always'],
172 'space-before-function-paren': [2, 'never'],
173 'space-in-parens': [2, 'never'],
174 'space-infix-ops': 2,
175 'space-unary-ops': [2, {
176 'words': true,
177 'nonwords': false
178 }],
179 'spaced-comment': [2, 'always', {
180 'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
181 }],
182 'template-curly-spacing': [2, 'never'],
183 'use-isnan': 2,
184 'valid-typeof': 2,
185 'wrap-iife': [2, 'any'],
186 'yield-star-spacing': [2, 'both'],
187 'yoda': [2, 'never'],
188 'prefer-const': 2,
189 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
190 'object-curly-spacing': [2, 'always', {
191 objectsInObjects: false
192 }],
193 'array-bracket-spacing': [2, 'never']
194 }
195 }
196
1 .DS_Store
2 node_modules/
3 dist/
4 npm-debug.log*
5 yarn-debug.log*
6 yarn-error.log*
7 package-lock.json
8
9 # Editor directories and files
10 .idea
11 .vscode
12 *.suo
13 *.ntvs*
14 *.njsproj
15 *.sln
1 // https://github.com/michael-ciniawsky/postcss-load-config
2
3 module.exports = {
4 "plugins": {
5 "postcss-import": {},
6 "postcss-url": {},
7 // to edit target browsers: use "browserslist" field in package.json
8 "autoprefixer": {}
9 }
10 }
1 language: node_js
2 node_js: stable
3 script: npm run test
4 notifications:
5 email: false
1 MIT License
2
3 Copyright (c) 2017-present PanJiaChen
4
5 Permission is hereby granted, free of charge, to any person obtaining a copy
6 of this software and associated documentation files (the "Software"), to deal
7 in the Software without restriction, including without limitation the rights
8 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 copies of the Software, and to permit persons to whom the Software is
10 furnished to do so, subject to the following conditions:
11
12 The above copyright notice and this permission notice shall be included in all
13 copies or substantial portions of the Software.
14
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 SOFTWARE.
1 # vue-admin-template
...\ No newline at end of file ...\ No newline at end of file
1 # vue-admin-template
...\ No newline at end of file ...\ No newline at end of file
1 'use strict'
2 require('./check-versions')()
3
4 process.env.NODE_ENV = 'production'
5
6 const ora = require('ora')
7 const rm = require('rimraf')
8 const path = require('path')
9 const chalk = require('chalk')
10 const webpack = require('webpack')
11 const config = require('../config')
12 const webpackConfig = require('./webpack.prod.conf')
13
14 const spinner = ora('building for production...')
15 spinner.start()
16
17 rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
18 if (err) throw err
19 webpack(webpackConfig, (err, stats) => {
20 spinner.stop()
21 if (err) throw err
22 process.stdout.write(
23 stats.toString({
24 colors: true,
25 modules: false,
26 children: false,
27 chunks: false,
28 chunkModules: false
29 }) + '\n\n'
30 )
31
32 if (stats.hasErrors()) {
33 console.log(chalk.red(' Build failed with errors.\n'))
34 process.exit(1)
35 }
36
37 console.log(chalk.cyan(' Build complete.\n'))
38 console.log(
39 chalk.yellow(
40 ' Tip: built files are meant to be served over an HTTP server.\n' +
41 " Opening index.html over file:// won't work.\n"
42 )
43 )
44 })
45 })
1 'use strict'
2 const chalk = require('chalk')
3 const semver = require('semver')
4 const packageConfig = require('../package.json')
5 const shell = require('shelljs')
6
7 function exec(cmd) {
8 return require('child_process')
9 .execSync(cmd)
10 .toString()
11 .trim()
12 }
13
14 const versionRequirements = [
15 {
16 name: 'node',
17 currentVersion: semver.clean(process.version),
18 versionRequirement: packageConfig.engines.node
19 }
20 ]
21
22 if (shell.which('npm')) {
23 versionRequirements.push({
24 name: 'npm',
25 currentVersion: exec('npm --version'),
26 versionRequirement: packageConfig.engines.npm
27 })
28 }
29
30 module.exports = function() {
31 const warnings = []
32
33 for (let i = 0; i < versionRequirements.length; i++) {
34 const mod = versionRequirements[i]
35
36 if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
37 warnings.push(
38 mod.name +
39 ': ' +
40 chalk.red(mod.currentVersion) +
41 ' should be ' +
42 chalk.green(mod.versionRequirement)
43 )
44 }
45 }
46
47 if (warnings.length) {
48 console.log('')
49 console.log(
50 chalk.yellow(
51 'To use this template, you must update following to modules:'
52 )
53 )
54 console.log()
55
56 for (let i = 0; i < warnings.length; i++) {
57 const warning = warnings[i]
58 console.log(' ' + warning)
59 }
60
61 console.log()
62 process.exit(1)
63 }
64 }
1 'use strict'
2 const path = require('path')
3 const config = require('../config')
4 const MiniCssExtractPlugin = require('mini-css-extract-plugin')
5 const packageConfig = require('../package.json')
6
7 exports.assetsPath = function(_path) {
8 const assetsSubDirectory =
9 process.env.NODE_ENV === 'production'
10 ? config.build.assetsSubDirectory
11 : config.dev.assetsSubDirectory
12
13 return path.posix.join(assetsSubDirectory, _path)
14 }
15
16 exports.cssLoaders = function(options) {
17 options = options || {}
18
19 const cssLoader = {
20 loader: 'css-loader',
21 options: {
22 sourceMap: options.sourceMap
23 }
24 }
25
26 const postcssLoader = {
27 loader: 'postcss-loader',
28 options: {
29 sourceMap: options.sourceMap
30 }
31 }
32
33 // generate loader string to be used with extract text plugin
34 function generateLoaders(loader, loaderOptions) {
35 const loaders = []
36
37 // Extract CSS when that option is specified
38 // (which is the case during production build)
39 if (options.extract) {
40 loaders.push(MiniCssExtractPlugin.loader)
41 } else {
42 loaders.push('vue-style-loader')
43 }
44
45 loaders.push(cssLoader)
46
47 if (options.usePostCSS) {
48 loaders.push(postcssLoader)
49 }
50
51 if (loader) {
52 loaders.push({
53 loader: loader + '-loader',
54 options: Object.assign({}, loaderOptions, {
55 sourceMap: options.sourceMap
56 })
57 })
58 }
59
60 return loaders
61 }
62 // https://vue-loader.vuejs.org/en/configurations/extract-css.html
63 return {
64 css: generateLoaders(),
65 postcss: generateLoaders(),
66 less: generateLoaders('less'),
67 sass: generateLoaders('sass', {
68 indentedSyntax: true
69 }),
70 scss: generateLoaders('sass'),
71 stylus: generateLoaders('stylus'),
72 styl: generateLoaders('stylus')
73 }
74 }
75
76 // Generate loaders for standalone style files (outside of .vue)
77 exports.styleLoaders = function(options) {
78 const output = []
79 const loaders = exports.cssLoaders(options)
80
81 for (const extension in loaders) {
82 const loader = loaders[extension]
83 output.push({
84 test: new RegExp('\\.' + extension + '$'),
85 use: loader
86 })
87 }
88
89 return output
90 }
91
92 exports.createNotifierCallback = () => {
93 const notifier = require('node-notifier')
94
95 return (severity, errors) => {
96 if (severity !== 'error') return
97
98 const error = errors[0]
99 const filename = error.file && error.file.split('!').pop()
100
101 notifier.notify({
102 title: packageConfig.name,
103 message: severity + ': ' + error.name,
104 subtitle: filename || '',
105 icon: path.join(__dirname, 'logo.png')
106 })
107 }
108 }
1 'use strict'
2
3 module.exports = {
4 //You can set the vue-loader configuration by yourself.
5 }
1 'use strict'
2 const path = require('path')
3 const utils = require('./utils')
4 const config = require('../config')
5 const { VueLoaderPlugin } = require('vue-loader')
6 const vueLoaderConfig = require('./vue-loader.conf')
7
8 function resolve(dir) {
9 return path.join(__dirname, '..', dir)
10 }
11
12 const createLintingRule = () => ({
13 test: /\.(js|vue)$/,
14 loader: 'eslint-loader',
15 enforce: 'pre',
16 include: [resolve('src'), resolve('test')],
17 options: {
18 formatter: require('eslint-friendly-formatter'),
19 emitWarning: !config.dev.showEslintErrorsInOverlay
20 }
21 })
22
23 module.exports = {
24 context: path.resolve(__dirname, '../'),
25 entry: {
26 app: './src/main.js'
27 },
28 output: {
29 path: config.build.assetsRoot,
30 filename: '[name].js',
31 publicPath:
32 process.env.NODE_ENV === 'production'
33 ? config.build.assetsPublicPath
34 : config.dev.assetsPublicPath
35 },
36 resolve: {
37 extensions: ['.js', '.vue', '.json'],
38 alias: {
39 '@': resolve('src')
40 }
41 },
42 module: {
43 rules: [
44 ...(config.dev.useEslint ? [createLintingRule()] : []),
45 {
46 test: /\.vue$/,
47 loader: 'vue-loader',
48 options: vueLoaderConfig
49 },
50 {
51 test: /\.js$/,
52 loader: 'babel-loader',
53 include: [
54 resolve('src'),
55 resolve('test'),
56 resolve('mock'),
57 resolve('node_modules/webpack-dev-server/client')
58 ]
59 },
60 {
61 test: /\.svg$/,
62 loader: 'svg-sprite-loader',
63 include: [resolve('src/icons')],
64 options: {
65 symbolId: 'icon-[name]'
66 }
67 },
68 {
69 test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
70 loader: 'url-loader',
71 exclude: [resolve('src/icons')],
72 options: {
73 limit: 10000,
74 name: utils.assetsPath('img/[name].[hash:7].[ext]')
75 }
76 },
77 {
78 test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
79 loader: 'url-loader',
80 options: {
81 limit: 10000,
82 name: utils.assetsPath('media/[name].[hash:7].[ext]')
83 }
84 },
85 {
86 test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
87 loader: 'url-loader',
88 options: {
89 limit: 10000,
90 name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
91 }
92 }
93 ]
94 },
95 plugins: [new VueLoaderPlugin()],
96 node: {
97 // prevent webpack from injecting useless setImmediate polyfill because Vue
98 // source contains it (although only uses it if it's native).
99 setImmediate: false,
100 // prevent webpack from injecting mocks to Node native modules
101 // that does not make sense for the client
102 dgram: 'empty',
103 fs: 'empty',
104 net: 'empty',
105 tls: 'empty',
106 child_process: 'empty'
107 }
108 }
1 'use strict'
2 const path = require('path')
3 const utils = require('./utils')
4 const webpack = require('webpack')
5 const config = require('../config')
6 const merge = require('webpack-merge')
7 const baseWebpackConfig = require('./webpack.base.conf')
8 const HtmlWebpackPlugin = require('html-webpack-plugin')
9 const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
10 const portfinder = require('portfinder')
11
12 function resolve(dir) {
13 return path.join(__dirname, '..', dir)
14 }
15
16 const HOST = process.env.HOST
17 const PORT = process.env.PORT && Number(process.env.PORT)
18
19 const devWebpackConfig = merge(baseWebpackConfig, {
20 mode: 'development',
21 module: {
22 rules: utils.styleLoaders({
23 sourceMap: config.dev.cssSourceMap,
24 usePostCSS: true
25 })
26 },
27 // cheap-module-eval-source-map is faster for development
28 devtool: config.dev.devtool,
29
30 // these devServer options should be customized in /config/index.js
31 devServer: {
32 clientLogLevel: 'warning',
33 historyApiFallback: true,
34 hot: true,
35 compress: true,
36 host: HOST || config.dev.host,
37 port: PORT || config.dev.port,
38 open: config.dev.autoOpenBrowser,
39 overlay: config.dev.errorOverlay
40 ? { warnings: false, errors: true }
41 : false,
42 publicPath: config.dev.assetsPublicPath,
43 proxy: config.dev.proxyTable,
44 quiet: true, // necessary for FriendlyErrorsPlugin
45 watchOptions: {
46 poll: config.dev.poll
47 }
48 },
49 plugins: [
50 new webpack.DefinePlugin({
51 'process.env': require('../config/dev.env')
52 }),
53 new webpack.HotModuleReplacementPlugin(),
54 // https://github.com/ampedandwired/html-webpack-plugin
55 new HtmlWebpackPlugin({
56 filename: 'index.html',
57 template: 'index.html',
58 inject: true,
59 favicon: resolve('favicon.ico'),
60 title: 'vue-admin-template'
61 })
62 ]
63 })
64
65 module.exports = new Promise((resolve, reject) => {
66 portfinder.basePort = process.env.PORT || config.dev.port
67 portfinder.getPort((err, port) => {
68 if (err) {
69 reject(err)
70 } else {
71 // publish the new Port, necessary for e2e tests
72 process.env.PORT = port
73 // add port to devServer config
74 devWebpackConfig.devServer.port = port
75
76 // Add FriendlyErrorsPlugin
77 devWebpackConfig.plugins.push(
78 new FriendlyErrorsPlugin({
79 compilationSuccessInfo: {
80 messages: [
81 `Your application is running here: http://${
82 devWebpackConfig.devServer.host
83 }:${port}`
84 ]
85 },
86 onErrors: config.dev.notifyOnErrors
87 ? utils.createNotifierCallback()
88 : undefined
89 })
90 )
91
92 resolve(devWebpackConfig)
93 }
94 })
95 })
1 'use strict'
2 const path = require('path')
3 const utils = require('./utils')
4 const webpack = require('webpack')
5 const config = require('../config')
6 const merge = require('webpack-merge')
7 const baseWebpackConfig = require('./webpack.base.conf')
8 const CopyWebpackPlugin = require('copy-webpack-plugin')
9 const HtmlWebpackPlugin = require('html-webpack-plugin')
10 const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin')
11 const MiniCssExtractPlugin = require('mini-css-extract-plugin')
12 const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
13 const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
14
15 function resolve(dir) {
16 return path.join(__dirname, '..', dir)
17 }
18
19 const env = require('../config/prod.env')
20
21 // For NamedChunksPlugin
22 const seen = new Set()
23 const nameLength = 4
24
25 const webpackConfig = merge(baseWebpackConfig, {
26 mode: 'production',
27 module: {
28 rules: utils.styleLoaders({
29 sourceMap: config.build.productionSourceMap,
30 extract: true,
31 usePostCSS: true
32 })
33 },
34 devtool: config.build.productionSourceMap ? config.build.devtool : false,
35 output: {
36 path: config.build.assetsRoot,
37 filename: utils.assetsPath('js/[name].[chunkhash:8].js'),
38 chunkFilename: utils.assetsPath('js/[name].[chunkhash:8].js')
39 },
40 plugins: [
41 // http://vuejs.github.io/vue-loader/en/workflow/production.html
42 new webpack.DefinePlugin({
43 'process.env': env
44 }),
45 // extract css into its own file
46 new MiniCssExtractPlugin({
47 filename: utils.assetsPath('css/[name].[contenthash:8].css'),
48 chunkFilename: utils.assetsPath('css/[name].[contenthash:8].css')
49 }),
50 // generate dist index.html with correct asset hash for caching.
51 // you can customize output by editing /index.html
52 // see https://github.com/ampedandwired/html-webpack-plugin
53 new HtmlWebpackPlugin({
54 filename: config.build.index,
55 template: 'index.html',
56 inject: true,
57 favicon: resolve('favicon.ico'),
58 title: 'vue-admin-template',
59 minify: {
60 removeComments: true,
61 collapseWhitespace: true,
62 removeAttributeQuotes: true
63 // more options:
64 // https://github.com/kangax/html-minifier#options-quick-reference
65 }
66 // default sort mode uses toposort which cannot handle cyclic deps
67 // in certain cases, and in webpack 4, chunk order in HTML doesn't
68 // matter anyway
69 }),
70 new ScriptExtHtmlWebpackPlugin({
71 //`runtime` must same as runtimeChunk name. default is `runtime`
72 inline: /runtime\..*\.js$/
73 }),
74 // keep chunk.id stable when chunk has no name
75 new webpack.NamedChunksPlugin(chunk => {
76 if (chunk.name) {
77 return chunk.name
78 }
79 const modules = Array.from(chunk.modulesIterable)
80 if (modules.length > 1) {
81 const hash = require('hash-sum')
82 const joinedHash = hash(modules.map(m => m.id).join('_'))
83 let len = nameLength
84 while (seen.has(joinedHash.substr(0, len))) len++
85 seen.add(joinedHash.substr(0, len))
86 return `chunk-${joinedHash.substr(0, len)}`
87 } else {
88 return modules[0].id
89 }
90 }),
91 // keep module.id stable when vender modules does not change
92 new webpack.HashedModuleIdsPlugin(),
93 // copy custom static assets
94 new CopyWebpackPlugin([
95 {
96 from: path.resolve(__dirname, '../static'),
97 to: config.build.assetsSubDirectory,
98 ignore: ['.*']
99 }
100 ])
101 ],
102 optimization: {
103 splitChunks: {
104 chunks: 'all',
105 cacheGroups: {
106 libs: {
107 name: 'chunk-libs',
108 test: /[\\/]node_modules[\\/]/,
109 priority: 10,
110 chunks: 'initial' // 只打包初始时依赖的第三方
111 },
112 elementUI: {
113 name: 'chunk-elementUI', // 单独将 elementUI 拆包
114 priority: 20, // 权重要大于 libs 和 app 不然会被打包进 libs 或者 app
115 test: /[\\/]node_modules[\\/]element-ui[\\/]/
116 }
117 }
118 },
119 runtimeChunk: 'single',
120 minimizer: [
121 new UglifyJsPlugin({
122 uglifyOptions: {
123 mangle: {
124 safari10: true
125 }
126 },
127 sourceMap: config.build.productionSourceMap,
128 cache: true,
129 parallel: true
130 }),
131 // Compress extracted CSS. We are using this plugin so that possible
132 // duplicated CSS from different components can be deduped.
133 new OptimizeCSSAssetsPlugin()
134 ]
135 }
136 })
137
138 if (config.build.productionGzip) {
139 const CompressionWebpackPlugin = require('compression-webpack-plugin')
140
141 webpackConfig.plugins.push(
142 new CompressionWebpackPlugin({
143 algorithm: 'gzip',
144 test: new RegExp(
145 '\\.(' + config.build.productionGzipExtensions.join('|') + ')$'
146 ),
147 threshold: 10240,
148 minRatio: 0.8
149 })
150 )
151 }
152
153 if (config.build.generateAnalyzerReport || config.build.bundleAnalyzerReport) {
154 const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
155 .BundleAnalyzerPlugin
156
157 if (config.build.bundleAnalyzerReport) {
158 webpackConfig.plugins.push(
159 new BundleAnalyzerPlugin({
160 analyzerPort: 8080,
161 generateStatsFile: false
162 })
163 )
164 }
165
166 if (config.build.generateAnalyzerReport) {
167 webpackConfig.plugins.push(
168 new BundleAnalyzerPlugin({
169 analyzerMode: 'static',
170 reportFilename: 'bundle-report.html',
171 openAnalyzer: false
172 })
173 )
174 }
175 }
176
177 module.exports = webpackConfig
1 'use strict'
2 const merge = require('webpack-merge')
3 const prodEnv = require('./prod.env')
4
5 module.exports = merge(prodEnv, {
6 NODE_ENV: '"development"',
7 BASE_API: '"https://easy-mock.com/mock/5950a2419adc231f356a6636/vue-admin"',
8 })
1 'use strict'
2 // Template version: 1.2.6
3 // see http://vuejs-templates.github.io/webpack for documentation.
4
5 const path = require('path')
6
7 module.exports = {
8 dev: {
9 // Paths
10 assetsSubDirectory: 'static',
11 assetsPublicPath: '/',
12 proxyTable: {},
13
14 // Various Dev Server settings
15 host: 'localhost', // can be overwritten by process.env.HOST
16 port: 9528, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
17 autoOpenBrowser: true,
18 errorOverlay: true,
19 notifyOnErrors: false,
20 poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
21
22 // Use Eslint Loader?
23 // If true, your code will be linted during bundling and
24 // linting errors and warnings will be shown in the console.
25 useEslint: true,
26 // If true, eslint errors and warnings will also be shown in the error overlay
27 // in the browser.
28 showEslintErrorsInOverlay: false,
29
30 /**
31 * Source Maps
32 */
33
34 // https://webpack.js.org/configuration/devtool/#development
35 devtool: 'cheap-source-map',
36
37 // CSS Sourcemaps off by default because relative paths are "buggy"
38 // with this option, according to the CSS-Loader README
39 // (https://github.com/webpack/css-loader#sourcemaps)
40 // In our experience, they generally work as expected,
41 // just be aware of this issue when enabling this option.
42 cssSourceMap: false
43 },
44
45 build: {
46 // Template for index.html
47 index: path.resolve(__dirname, '../dist/index.html'),
48
49 // Paths
50 assetsRoot: path.resolve(__dirname, '../dist'),
51 assetsSubDirectory: 'static',
52
53 /**
54 * You can set by youself according to actual condition
55 * You will need to set this if you plan to deploy your site under a sub path,
56 * for example GitHub pages. If you plan to deploy your site to https://foo.github.io/bar/,
57 * then assetsPublicPath should be set to "/bar/".
58 * In most cases please use '/' !!!
59 */
60 assetsPublicPath: './',
61
62 /**
63 * Source Maps
64 */
65
66 productionSourceMap: false,
67 // https://webpack.js.org/configuration/devtool/#production
68 devtool: 'source-map',
69
70 // Gzip off by default as many popular static hosts such as
71 // Surge or Netlify already gzip all static assets for you.
72 // Before setting to `true`, make sure to:
73 // npm install --save-dev compression-webpack-plugin
74 productionGzip: false,
75 productionGzipExtensions: ['js', 'css'],
76
77 // Run the build command with an extra argument to
78 // View the bundle analyzer report after build finishes:
79 // `npm run build --report`
80 // Set to `true` or `false` to always turn it on or off
81 bundleAnalyzerReport: process.env.npm_config_report || false,
82
83 // `npm run build:prod --generate_report`
84 generateAnalyzerReport: process.env.npm_config_generate_report || false
85 }
86 }
1 'use strict'
2 module.exports = {
3 NODE_ENV: '"production"',
4 BASE_API: '"https://easy-mock.com/mock/5950a2419adc231f356a6636/vue-admin"',
5 }
No preview for this file type
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width,initial-scale=1.0">
6 <title>vue-admin-template</title>
7 </head>
8 <body>
9 <div id="app"></div>
10 <!-- built files will be auto injected -->
11 </body>
12 </html>
1 import Mock from 'mockjs'
2 import userAPI from './user'
3 import tableAPI from './table'
4
5 // Fix an issue with setting withCredentials = true, cross-domain request lost cookies
6 // https://github.com/nuysoft/Mock/issues/300
7 Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
8 Mock.XHR.prototype.send = function() {
9 if (this.custom.xhr) {
10 this.custom.xhr.withCredentials = this.withCredentials || false
11 }
12 this.proxy_send(...arguments)
13 }
14 // Mock.setup({
15 // timeout: '350-600'
16 // })
17
18 // User
19 Mock.mock(/\/user\/login/, 'post', userAPI.login)
20 Mock.mock(/\/user\/info/, 'get', userAPI.getInfo)
21 Mock.mock(/\/user\/logout/, 'post', userAPI.logout)
22
23 // Table
24 Mock.mock(/\/table\/list/, 'get', tableAPI.list)
25
26 export default Mock
1 import Mock from 'mockjs'
2
3 export default {
4 list: () => {
5 const items = Mock.mock({
6 'items|30': [{
7 id: '@id',
8 title: '@sentence(10, 20)',
9 'status|1': ['published', 'draft', 'deleted'],
10 author: 'name',
11 display_time: '@datetime',
12 pageviews: '@integer(300, 5000)'
13 }]
14 })
15 return {
16 code: 200,
17 content: items
18 }
19 }
20 }
1 import {
2 param2Obj
3 } from './utils'
4
5 const tokens = {
6 admin: {
7 token: 'admin-token'
8 },
9 editor: {
10 token: 'editor-token'
11 }
12 }
13
14 const users = {
15 'admin-token': {
16 roles: ['admin'],
17 introduction: 'I am a super administrator',
18 avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
19 name: 'Super Admin'
20 },
21 'editor-token': {
22 roles: ['editor'],
23 introduction: 'I am an editor',
24 avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
25 name: 'Normal Editor'
26 }
27 }
28
29 export default {
30 login: res => {
31 const {
32 username
33 } = JSON.parse(res.body)
34 const data = tokens[username]
35 if (data) {
36 return {
37 code: 200,
38 content: data
39 }
40 }
41 return {
42 code: 60204,
43 message: 'Account and password are incorrect.'
44 }
45 },
46 getInfo: res => {
47 const {
48 token
49 } = param2Obj(res.url)
50 const info = users[token]
51
52 if (info) {
53 return {
54 code: 200,
55 content: info
56 }
57 }
58 return {
59 code: 50008,
60 message: 'Login failed, unable to get user details.'
61 }
62 },
63 logout: () => {
64 return {
65 code: 200,
66 data: 'success'
67 }
68 }
69 }
1 export function param2Obj(url) {
2 const search = url.split('?')[1]
3 if (!search) {
4 return {}
5 }
6 return JSON.parse(
7 '{"' +
8 decodeURIComponent(search)
9 .replace(/"/g, '\\"')
10 .replace(/&/g, '","')
11 .replace(/=/g, '":"') +
12 '"}'
13 )
14 }
1 {
2 "name": "vue-admin-template",
3 "version": "3.9.0",
4 "license": "MIT",
5 "description": "A vue admin template with Element UI & axios & iconfont & permission control & lint",
6 "author": "Pan <panfree23@gmail.com>",
7 "scripts": {
8 "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
9 "start": "npm run dev",
10 "build": "node build/build.js",
11 "build:report": "npm_config_report=true npm run build",
12 "lint": "eslint --ext .js,.vue src",
13 "test": "npm run lint",
14 "svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml"
15 },
16 "dependencies": {
17 "axios": "0.18.0",
18 "element-ui": "2.4.6",
19 "js-cookie": "2.2.0",
20 "mockjs": "1.0.1-beta3",
21 "normalize.css": "7.0.0",
22 "nprogress": "0.2.0",
23 "vue": "2.5.17",
24 "vue-router": "3.0.1",
25 "vuex": "3.0.1"
26 },
27 "devDependencies": {
28 "autoprefixer": "8.5.0",
29 "babel-core": "6.26.0",
30 "babel-eslint": "8.2.6",
31 "babel-helper-vue-jsx-merge-props": "2.0.3",
32 "babel-loader": "7.1.5",
33 "babel-plugin-syntax-jsx": "6.18.0",
34 "babel-plugin-transform-runtime": "6.23.0",
35 "babel-plugin-transform-vue-jsx": "3.7.0",
36 "babel-preset-env": "1.7.0",
37 "babel-preset-stage-2": "6.24.1",
38 "chalk": "2.4.1",
39 "compression-webpack-plugin": "2.0.0",
40 "copy-webpack-plugin": "4.5.2",
41 "css-loader": "1.0.0",
42 "eslint": "4.19.1",
43 "eslint-friendly-formatter": "4.0.1",
44 "eslint-loader": "2.0.0",
45 "eslint-plugin-vue": "4.7.1",
46 "eventsource-polyfill": "0.9.6",
47 "file-loader": "1.1.11",
48 "friendly-errors-webpack-plugin": "1.7.0",
49 "html-webpack-plugin": "4.0.0-alpha",
50 "mini-css-extract-plugin": "0.4.1",
51 "node-notifier": "5.2.1",
52 "node-sass": "^4.7.2",
53 "optimize-css-assets-webpack-plugin": "5.0.0",
54 "ora": "3.0.0",
55 "path-to-regexp": "2.4.0",
56 "portfinder": "1.0.16",
57 "postcss-import": "12.0.0",
58 "postcss-loader": "2.1.6",
59 "postcss-url": "7.3.2",
60 "rimraf": "2.6.2",
61 "sass-loader": "7.0.3",
62 "script-ext-html-webpack-plugin": "2.0.1",
63 "semver": "5.5.0",
64 "shelljs": "0.8.2",
65 "svg-sprite-loader": "3.8.0",
66 "svgo": "1.0.5",
67 "uglifyjs-webpack-plugin": "1.2.7",
68 "url-loader": "1.0.1",
69 "vue-loader": "15.3.0",
70 "vue-style-loader": "4.1.2",
71 "vue-template-compiler": "2.5.17",
72 "webpack": "4.16.5",
73 "webpack-bundle-analyzer": "2.13.1",
74 "webpack-cli": "3.1.0",
75 "webpack-dev-server": "3.1.14",
76 "webpack-merge": "4.1.4"
77 },
78 "engines": {
79 "node": ">= 6.0.0",
80 "npm": ">= 3.0.0"
81 },
82 "browserslist": [
83 "> 1%",
84 "last 2 versions",
85 "not ie <= 8"
86 ]
87 }
1 <template>
2 <div id="app">
3 <router-view/>
4 </div>
5 </template>
6
7 <script>
8 export default {
9 name: 'App'
10 }
11 </script>
1 module.exports = {
2 testListGet: '/xxx/xxx/list'
3 }
...\ No newline at end of file ...\ No newline at end of file
1 import axios from 'axios';
2 // import {
3 // Toast
4 // } from 'vant';
5
6 function Toast(msg) {
7 console.log("msg:", msg);
8 }
9
10 // axios的默认url
11 // axios.defaults.baseURL = ""
12
13 // 服务器地址
14 let base = "https://ow.go.qudone.com";
15 if (location.href.indexOf("//k.wxpai.cn") > 0) {
16 base = "https://api.k.wxpai.cn/bizproxy"
17 }
18 // let base = COM.baseUrl;
19
20 // 请求拦截器
21 // axios.interceptors.request.use(
22 // config => {
23 // // 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了
24 // // 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断
25 // const token = "token"; //这里写入token
26 // token && (config.headers.Authorization = token);
27 // return config;
28 // },
29 // error => {
30 // return Promise.error(error);
31 // })
32
33 // 响应拦截器
34 axios.interceptors.response.use(
35 response => {
36 if (response.status === 200) {
37 if (response.data.code === 200) {
38 return Promise.resolve(response);
39 } else {
40 Toast(response.data.bizMsg);
41 return Promise.reject(response);
42 }
43 } else {
44 return Promise.reject(response);
45 }
46 },
47 // 服务器状态码不是200的情况
48 error => {
49 if (error.response.status) {
50 switch (error.response.status) {
51 // 401: 未登录
52 // 未登录则跳转登录页面,并携带当前页面的路径
53 // 在登录成功后返回当前页面,这一步需要在登录页操作。
54 case 401:
55 router.replace({
56 path: '/login',
57 query: {
58 redirect: router.currentRoute.fullPath
59 }
60 });
61 break;
62 // 403 token过期
63 // 登录过期对用户进行提示
64 // 清除本地token和清空vuex中token对象
65 // 跳转登录页面
66 case 403:
67 Toast({
68 message: '登录过期,请重新登录',
69 duration: 1000,
70 forbidClick: true
71 });
72 // 清除token
73 localStorage.removeItem('token');
74 store.commit('loginSuccess', null);
75 // 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面
76 setTimeout(() => {
77 router.replace({
78 path: '/login',
79 query: {
80 redirect: router.currentRoute.fullPath
81 }
82 });
83 }, 1000);
84 break;
85 // 404请求不存在
86 case 404:
87 Toast({
88 message: '网络请求不存在',
89 duration: 1500,
90 forbidClick: true
91 });
92 break;
93 // 其他错误,直接抛出错误提示
94 default:
95 Toast({
96 message: error.response.data.message,
97 duration: 1500,
98 forbidClick: true
99 });
100 }
101 return Promise.reject(error.response);
102 }
103 }
104 );
105
106 //formDataHeaders设置
107 let formDataHeaders = {
108 headers: {
109 "Content-Type": "multipart/form-data"
110 }
111 }
112
113 /**
114 * 封装get方法
115 * @param {*} params
116 */
117 export const httpGet = params => {
118 let {
119 url,
120 data
121 } = params;
122 return axios.get(`${base}${url}`, {
123 params: data
124 }).then(res => res.data.content);
125 }
126
127 /**
128 * 封装post方法
129 * @param {*} params
130 */
131 export const httpPost = params => {
132 let {
133 url,
134 data
135 } = params;
136 return axios.post(`${base}${url}`, data).then(res => res.data.content);
137 }
138
139
140 /**
141 * 封装post方法
142 * @param {*} params
143 * data数据是 formdata格式
144 * 例如:
145 * this.file = file
146 let data = new FormData() //使用formData对象
147 data.append('path', '/pro/mzczcradmin/')
148 data.append('file', file.file)
149 */
150 export const formdata = params => {
151 let {
152 url,
153 data
154 } = params;
155 return axios.post(`${base}${url}`, data, formDataHeaders).then(res => res.data);
156 }
1 import * as api from './api';
2
3 export default api;
1 import request from '@/utils/request'
2
3 export function login(username, password) {
4 return request({
5 url: '/user/login',
6 method: 'post',
7 data: {
8 username,
9 password
10 }
11 })
12 }
13
14 export function getInfo(token) {
15 return request({
16 url: '/user/info',
17 method: 'get',
18 params: { token }
19 })
20 }
21
22 export function logout() {
23 return request({
24 url: '/user/logout',
25 method: 'post'
26 })
27 }
1 import request from '@/utils/request'
2
3 export function getList(params) {
4 return request({
5 url: '/table/list',
6 method: 'get',
7 params
8 })
9 }
1 <template>
2 <el-breadcrumb class="app-breadcrumb" separator="/">
3 <transition-group name="breadcrumb">
4 <el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
5 <span v-if="item.redirect==='noredirect'||index==levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
6 <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
7 </el-breadcrumb-item>
8 </transition-group>
9 </el-breadcrumb>
10 </template>
11
12 <script>
13 import pathToRegexp from 'path-to-regexp'
14
15 export default {
16 data() {
17 return {
18 levelList: null
19 }
20 },
21 watch: {
22 $route() {
23 this.getBreadcrumb()
24 }
25 },
26 created() {
27 this.getBreadcrumb()
28 },
29 methods: {
30 getBreadcrumb() {
31 let matched = this.$route.matched.filter(item => item.name)
32
33 const first = matched[0]
34 if (first && first.name !== 'dashboard') {
35 matched = [{ path: '/dashboard', meta: { title: 'Dashboard' }}].concat(matched)
36 }
37
38 this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
39 },
40 pathCompile(path) {
41 // To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
42 const { params } = this.$route
43 var toPath = pathToRegexp.compile(path)
44 return toPath(params)
45 },
46 handleLink(item) {
47 const { redirect, path } = item
48 if (redirect) {
49 this.$router.push(redirect)
50 return
51 }
52 this.$router.push(this.pathCompile(path))
53 }
54 }
55 }
56 </script>
57
58 <style rel="stylesheet/scss" lang="scss" scoped>
59 .app-breadcrumb.el-breadcrumb {
60 display: inline-block;
61 font-size: 14px;
62 line-height: 50px;
63 margin-left: 10px;
64 .no-redirect {
65 color: #97a8be;
66 cursor: text;
67 }
68 }
69 </style>
1 <template>
2 <div>
3 <svg
4 :class="{'is-active':isActive}"
5 class="hamburger"
6 viewBox="0 0 1024 1024"
7 xmlns="http://www.w3.org/2000/svg"
8 width="64"
9 height="64"
10 @click="toggleClick">
11 <path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
12 </svg>
13 </div>
14 </template>
15
16 <script>
17 export default {
18 name: 'Hamburger',
19 props: {
20 isActive: {
21 type: Boolean,
22 default: false
23 },
24 toggleClick: {
25 type: Function,
26 default: null
27 }
28 }
29 }
30 </script>
31
32 <style scoped>
33 .hamburger {
34 display: inline-block;
35 cursor: pointer;
36 width: 20px;
37 height: 20px;
38 }
39 .hamburger.is-active {
40 transform: rotate(180deg);
41 }
42 </style>
1 <template>
2 <svg :class="svgClass" aria-hidden="true" v-on="$listeners">
3 <use :xlink:href="iconName"/>
4 </svg>
5 </template>
6
7 <script>
8 export default {
9 name: 'SvgIcon',
10 props: {
11 iconClass: {
12 type: String,
13 required: true
14 },
15 className: {
16 type: String,
17 default: ''
18 }
19 },
20 computed: {
21 iconName() {
22 return `#icon-${this.iconClass}`
23 },
24 svgClass() {
25 if (this.className) {
26 return 'svg-icon ' + this.className
27 } else {
28 return 'svg-icon'
29 }
30 }
31 }
32 }
33 </script>
34
35 <style scoped>
36 .svg-icon {
37 width: 1em;
38 height: 1em;
39 vertical-align: -0.15em;
40 fill: currentColor;
41 overflow: hidden;
42 }
43 </style>
1 import Vue from 'vue'
2 import SvgIcon from '@/components/SvgIcon' // svg组件
3
4 // register globally
5 Vue.component('svg-icon', SvgIcon)
6
7 const requireAll = requireContext => requireContext.keys().map(requireContext)
8 const req = require.context('./svg', false, /\.svg$/)
9 requireAll(req)
1 <svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M96.258 57.462h31.421C124.794 27.323 100.426 2.956 70.287.07v31.422a32.856 32.856 0 0 1 25.971 25.97zm-38.796-25.97V.07C27.323 2.956 2.956 27.323.07 57.462h31.422a32.856 32.856 0 0 1 25.97-25.97zm12.825 64.766v31.421c30.46-2.885 54.507-27.253 57.713-57.712H96.579c-2.886 13.466-13.146 23.726-26.292 26.291zM31.492 70.287H.07c2.886 30.46 27.253 54.507 57.713 57.713V96.579c-13.466-2.886-23.726-13.146-26.291-26.292z"/></svg>
...\ No newline at end of file ...\ No newline at end of file
1 <svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><defs><style/></defs><path d="M512 128q69.675 0 135.51 21.163t115.498 54.997 93.483 74.837 73.685 82.006 51.67 74.837 32.17 54.827L1024 512q-2.347 4.992-6.315 13.483T998.87 560.17t-31.658 51.669-44.331 59.99-56.832 64.34-69.504 60.16-82.347 51.5-94.848 34.687T512 896q-69.675 0-135.51-21.163t-115.498-54.826-93.483-74.326-73.685-81.493-51.67-74.496-32.17-54.997L0 513.707q2.347-4.992 6.315-13.483t18.816-34.816 31.658-51.84 44.331-60.33 56.832-64.683 69.504-60.331 82.347-51.84 94.848-34.816T512 128.085zm0 85.333q-46.677 0-91.648 12.331t-81.152 31.83-70.656 47.146-59.648 54.485-48.853 57.686-37.675 52.821-26.325 43.99q12.33 21.674 26.325 43.52t37.675 52.351 48.853 57.003 59.648 53.845T339.2 767.02t81.152 31.488T512 810.667t91.648-12.331 81.152-31.659 70.656-46.848 59.648-54.186 48.853-57.344 37.675-52.651T927.957 512q-12.33-21.675-26.325-43.648t-37.675-52.65-48.853-57.345-59.648-54.186-70.656-46.848-81.152-31.659T512 213.334zm0 128q70.656 0 120.661 50.006T682.667 512 632.66 632.661 512 682.667 391.339 632.66 341.333 512t50.006-120.661T512 341.333zm0 85.334q-35.328 0-60.33 25.002T426.666 512t25.002 60.33T512 597.334t60.33-25.002T597.334 512t-25.002-60.33T512 426.666z"/></svg>
1 <svg width="128" height="64" xmlns="http://www.w3.org/2000/svg"><path d="M127.072 7.994c1.37-2.208.914-5.152-.914-6.87-2.056-1.717-4.797-1.226-6.396.982-.229.245-25.586 32.382-55.74 32.382-29.24 0-55.74-32.382-55.968-32.627-1.6-1.963-4.57-2.208-6.397-.49C-.17 3.086-.399 6.275 1.2 8.238c.457.736 5.94 7.36 14.62 14.72L4.17 35.96c-1.828 1.963-1.6 5.152.228 6.87.457.98 1.6 1.471 2.742 1.471s2.284-.49 3.198-1.472l12.564-13.983c5.94 4.416 13.021 8.587 20.788 11.53l-4.797 17.418c-.685 2.699.686 5.397 3.198 6.133h1.37c2.057 0 3.884-1.472 4.341-3.68L52.6 42.83c3.655.736 7.538 1.227 11.422 1.227 3.883 0 7.767-.49 11.422-1.227l4.797 17.173c.457 2.208 2.513 3.68 4.34 3.68.457 0 .914 0 1.143-.246 2.513-.736 3.883-3.434 3.198-6.133l-4.797-17.172c7.767-2.944 14.848-7.114 20.788-11.53l12.336 13.738c.913.981 2.056 1.472 3.198 1.472s2.284-.49 3.198-1.472c1.828-1.963 1.828-4.906.228-6.87l-11.65-13.001c9.366-7.36 14.849-14.474 14.849-14.474z"/></svg>
...\ No newline at end of file ...\ No newline at end of file
1 <svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M84.068 23.784c-1.02 0-1.877-.32-2.572-.96a8.588 8.588 0 0 1-1.738-2.237 11.524 11.524 0 0 1-1.042-2.621c-.232-.895-.348-1.641-.348-2.238V0h.278c.834 0 1.622.085 2.363.256.742.17 1.645.575 2.711 1.214 1.066.64 2.363 1.535 3.892 2.686 1.53 1.15 3.453 2.664 5.77 4.54 2.502 2.045 4.494 3.771 5.977 5.178 1.483 1.406 2.618 2.6 3.406 3.58.787.98 1.274 1.812 1.46 2.494.185.682.277 1.278.277 1.79v2.046H84.068zM127.3 84.01c.278.682.464 1.535.556 2.558.093 1.023-.37 2.003-1.39 2.94-.463.427-.88.832-1.25 1.215-.372.384-.696.704-.974.96a6.69 6.69 0 0 1-.973.767l-11.816-10.741a44.331 44.331 0 0 0 1.877-1.535 31.028 31.028 0 0 1 1.737-1.406c1.112-.938 2.317-1.343 3.615-1.215 1.297.128 2.363.405 3.197.83.927.427 1.923 1.173 2.989 2.239 1.065 1.065 1.876 2.195 2.432 3.388zM78.23 95.902c2.038 0 3.752-.511 5.143-1.534l-26.969 25.83H18.037c-1.761 0-3.684-.47-5.77-1.407a24.549 24.549 0 0 1-5.838-3.709 21.373 21.373 0 0 1-4.518-5.306c-1.204-2.003-1.807-4.07-1.807-6.202V16.495c0-1.79.44-3.665 1.32-5.626A18.41 18.41 0 0 1 5.04 5.562a21.798 21.798 0 0 1 5.213-3.964C12.198.533 14.237 0 16.37 0h53.24v15.984c0 1.62.278 3.367.834 5.242a16.704 16.704 0 0 0 2.572 5.179c1.159 1.577 2.665 2.898 4.518 3.964 1.853 1.066 4.078 1.598 6.673 1.598h20.295v42.325L85.458 92.45c1.02-1.364 1.529-2.856 1.529-4.476 0-2.216-.857-4.113-2.572-5.69-1.714-1.577-3.776-2.366-6.186-2.366H26.1c-2.409 0-4.448.789-6.116 2.366-1.668 1.577-2.502 3.474-2.502 5.69 0 2.217.834 4.092 2.502 5.626 1.668 1.535 3.707 2.302 6.117 2.302h52.13zM26.1 47.951c-2.41 0-4.449.789-6.117 2.366-1.668 1.577-2.502 3.473-2.502 5.69 0 2.216.834 4.092 2.502 5.626 1.668 1.534 3.707 2.302 6.117 2.302h52.13c2.409 0 4.47-.768 6.185-2.302 1.715-1.534 2.572-3.41 2.572-5.626 0-2.217-.857-4.113-2.572-5.69-1.714-1.577-3.776-2.366-6.186-2.366H26.1zm52.407 64.063l1.807-1.663 3.476-3.196a479.75 479.75 0 0 0 4.587-4.284 500.757 500.757 0 0 1 5.004-4.667c3.985-3.666 8.48-7.758 13.485-12.276l11.677 10.741-13.485 12.404-5.004 4.603-4.587 4.22a179.46 179.46 0 0 0-3.267 3.068c-.88.853-1.367 1.322-1.46 1.407-.463.341-.973.703-1.529 1.087-.556.383-1.112.703-1.668.959-.556.256-1.413.575-2.572.959a83.5 83.5 0 0 1-3.545 1.087 72.2 72.2 0 0 1-3.475.895c-1.112.256-1.946.426-2.502.511-1.112.17-1.854.043-2.224-.383-.371-.426-.464-1.151-.278-2.174.092-.511.278-1.279.556-2.302.278-1.023.602-2.067.973-3.132l1.042-3.005c.325-.938.58-1.577.765-1.918a10.157 10.157 0 0 1 2.224-2.941z"/></svg>
...\ No newline at end of file ...\ No newline at end of file
1 <svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><g><path d="M115.625 127.937H.063V12.375h57.781v12.374H12.438v90.813h90.813V70.156h12.374z"/><path d="M116.426 2.821l8.753 8.753-56.734 56.734-8.753-8.745z"/><path d="M127.893 37.982h-12.375V12.375H88.706V0h39.187z"/></g></svg>
...\ No newline at end of file ...\ No newline at end of file
1 <svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.002 9.2c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-5.043-3.58-9.132-7.997-9.132S.002 4.157.002 9.2zM31.997.066h95.981V18.33H31.997V.066zm0 45.669c0 5.044 3.58 9.132 7.998 9.132 4.417 0 7.997-4.088 7.997-9.132 0-3.263-1.524-6.278-3.998-7.91-2.475-1.63-5.524-1.63-7.998 0-2.475 1.632-4 4.647-4 7.91zM63.992 36.6h63.986v18.265H63.992V36.6zm-31.995 82.2c0 5.043 3.58 9.132 7.998 9.132 4.417 0 7.997-4.089 7.997-9.132 0-5.044-3.58-9.133-7.997-9.133s-7.998 4.089-7.998 9.133zm31.995-9.131h63.986v18.265H63.992V109.67zm0-27.404c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-3.263-1.524-6.277-3.998-7.909-2.475-1.631-5.524-1.631-7.998 0-2.475 1.632-4 4.646-4 7.91zm31.995-9.13h31.991V91.4H95.987V73.135z"/></svg>
...\ No newline at end of file ...\ No newline at end of file
1 <svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M108.8 44.322H89.6v-5.36c0-9.04-3.308-24.163-25.6-24.163-23.145 0-25.6 16.881-25.6 24.162v5.361H19.2v-5.36C19.2 15.281 36.798 0 64 0c27.202 0 44.8 15.281 44.8 38.961v5.361zm-32 39.356c0-5.44-5.763-9.832-12.8-9.832-7.037 0-12.8 4.392-12.8 9.832 0 3.682 2.567 6.808 6.407 8.477v11.205c0 2.718 2.875 4.962 6.4 4.962 3.524 0 6.4-2.244 6.4-4.962V92.155c3.833-1.669 6.393-4.795 6.393-8.477zM128 64v49.201c0 8.158-8.645 14.799-19.2 14.799H19.2C8.651 128 0 121.359 0 113.201V64c0-8.153 8.645-14.799 19.2-14.799h89.6c10.555 0 19.2 6.646 19.2 14.799z"/></svg>
...\ No newline at end of file ...\ No newline at end of file
1 <svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><g><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/></g></svg>
...\ No newline at end of file ...\ No newline at end of file
1 <svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M126.713 90.023c.858.985 1.287 2.134 1.287 3.447v29.553c0 1.423-.429 2.6-1.287 3.53-.858.93-1.907 1.395-3.146 1.395H97.824c-1.145 0-2.146-.465-3.004-1.395-.858-.93-1.287-2.107-1.287-3.53V93.47c0-.875.19-1.696.572-2.462.382-.766.906-1.368 1.573-1.806a3.84 3.84 0 0 1 2.146-.657h9.725V69.007a3.84 3.84 0 0 0-.43-1.806 3.569 3.569 0 0 0-1.143-1.313 2.714 2.714 0 0 0-1.573-.492h-36.47v23.149h9.725c1.144 0 2.145.492 3.004 1.478.858.985 1.287 2.134 1.287 3.447v29.553c0 .876-.191 1.696-.573 2.463-.38.766-.905 1.368-1.573 1.806a3.84 3.84 0 0 1-2.145.656H51.915a3.84 3.84 0 0 1-2.145-.656c-.668-.438-1.216-1.04-1.645-1.806a4.96 4.96 0 0 1-.644-2.463V93.47c0-1.313.43-2.462 1.288-3.447.858-.986 1.907-1.478 3.146-1.478h9.582v-23.15h-37.9c-.953 0-1.74.356-2.359 1.068-.62.711-.93 1.56-.93 2.544v19.538h9.726c1.239 0 2.264.492 3.074 1.478.81.985 1.216 2.134 1.216 3.447v29.553c0 1.423-.405 2.6-1.216 3.53-.81.93-1.835 1.395-3.074 1.395H4.29c-.476 0-.93-.082-1.358-.246a4.1 4.1 0 0 1-1.144-.657 4.658 4.658 0 0 1-.93-1.067 5.186 5.186 0 0 1-.643-1.395 5.566 5.566 0 0 1-.215-1.56V93.47c0-.437.048-.875.143-1.313a3.95 3.95 0 0 1 .429-1.15c.19-.328.429-.656.715-.984.286-.329.572-.602.858-.821.286-.22.62-.383 1.001-.493.382-.11.763-.164 1.144-.164h9.726V61.619c0-.985.31-1.833.93-2.544.619-.712 1.358-1.068 2.216-1.068h44.335V39.62h-9.582c-1.24 0-2.288-.492-3.146-1.477a5.09 5.09 0 0 1-1.287-3.448V5.14c0-1.423.429-2.627 1.287-3.612.858-.985 1.907-1.477 3.146-1.477h25.743c.763 0 1.478.246 2.145.739a5.17 5.17 0 0 1 1.573 1.888c.382.766.573 1.587.573 2.462v29.553c0 1.313-.43 2.463-1.287 3.448-.859.985-1.86 1.477-3.004 1.477h-9.725v18.389h42.762c.954 0 1.74.355 2.36 1.067.62.711.93 1.56.93 2.545v26.925h9.582c1.239 0 2.288.492 3.146 1.478z"/></svg>
...\ No newline at end of file ...\ No newline at end of file
1 <svg width="130" height="130" xmlns="http://www.w3.org/2000/svg"><path d="M63.444 64.996c20.633 0 37.359-14.308 37.359-31.953 0-17.649-16.726-31.952-37.359-31.952-20.631 0-37.36 14.303-37.358 31.952 0 17.645 16.727 31.953 37.359 31.953zM80.57 75.65H49.434c-26.652 0-48.26 18.477-48.26 41.27v2.664c0 9.316 21.608 9.325 48.26 9.325H80.57c26.649 0 48.256-.344 48.256-9.325v-2.663c0-22.794-21.605-41.271-48.256-41.271z" stroke="#979797"/></svg>
...\ No newline at end of file ...\ No newline at end of file
1 # replace default config
2
3 # multipass: true
4 # full: true
5
6 plugins:
7
8 # - name
9 #
10 # or:
11 # - name: false
12 # - name: true
13 #
14 # or:
15 # - name:
16 # param1: 1
17 # param2: 2
18
19 - removeAttrs:
20 attrs:
21 - 'fill'
22 - 'fill-rule'
1 import Vue from 'vue'
2
3 import 'normalize.css/normalize.css' // A modern alternative to CSS resets
4
5 import ElementUI from 'element-ui'
6 import 'element-ui/lib/theme-chalk/index.css'
7 import locale from 'element-ui/lib/locale/lang/en' // lang i18n
8
9 import '@/styles/index.scss' // global css
10
11 import App from './App'
12 import store from './store'
13 import router from './router'
14
15 import '@/icons' // icon
16 import '@/permission' // permission control
17
18 /**
19 * This project originally used easy-mock to simulate data,
20 * but its official service is very unstable,
21 * and you can build your own service if you need it.
22 * So here I use Mock.js for local emulation,
23 * it will intercept your request, so you won't see the request in the network.
24 * If you remove `../mock` it will automatically request easy-mock data.
25 */
26 import '../mock' // simulation data
27
28 Vue.use(ElementUI, { locale })
29
30 Vue.config.productionTip = false
31
32 new Vue({
33 el: '#app',
34 router,
35 store,
36 render: h => h(App)
37 })
1 import router from './router'
2 import store from './store'
3 import NProgress from 'nprogress' // progress bar
4 import 'nprogress/nprogress.css' // progress bar style
5 import { Message } from 'element-ui'
6 import { getToken } from '@/utils/auth' // getToken from cookie
7
8 NProgress.configure({ showSpinner: false })// NProgress configuration
9
10 const whiteList = ['/login'] // 不重定向白名单
11 router.beforeEach((to, from, next) => {
12 NProgress.start()
13 if (getToken()) {
14 if (to.path === '/login') {
15 next({ path: '/' })
16 NProgress.done() // if current page is dashboard will not trigger afterEach hook, so manually handle it
17 } else {
18 if (store.getters.roles.length === 0) {
19 store.dispatch('GetInfo').then(res => { // 拉取用户信息
20 next()
21 }).catch((err) => {
22 store.dispatch('FedLogOut').then(() => {
23 Message.error(err || 'Verification failed, please login again')
24 next({ path: '/' })
25 })
26 })
27 } else {
28 next()
29 }
30 }
31 } else {
32 if (whiteList.indexOf(to.path) !== -1) {
33 next()
34 } else {
35 next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页
36 NProgress.done()
37 }
38 }
39 })
40
41 router.afterEach(() => {
42 NProgress.done() // 结束Progress
43 })
1 import Vue from 'vue'
2 import Router from 'vue-router'
3
4 // in development-env not use lazy-loading, because lazy-loading too many pages will cause webpack hot update too slow. so only in production use lazy-loading;
5 // detail: https://panjiachen.github.io/vue-element-admin-site/#/lazy-loading
6
7 Vue.use(Router)
8
9 /* Layout */
10 import Layout from '../views/layout/Layout'
11
12 /**
13 * hidden: true if `hidden:true` will not show in the sidebar(default is false)
14 * alwaysShow: true if set true, will always show the root menu, whatever its child routes length
15 * if not set alwaysShow, only more than one route under the children
16 * it will becomes nested mode, otherwise not show the root menu
17 * redirect: noredirect if `redirect:noredirect` will no redirect in the breadcrumb
18 * name:'router-name' the name is used by <keep-alive> (must set!!!)
19 * meta : {
20 title: 'title' the name show in subMenu and breadcrumb (recommend set)
21 icon: 'svg-name' the icon show in the sidebar
22 breadcrumb: false if false, the item will hidden in breadcrumb(default is true)
23 }
24 **/
25 export const constantRouterMap = [
26 { path: '/login', component: () => import('@/views/login/index'), hidden: true },
27 { path: '/404', component: () => import('@/views/404'), hidden: true },
28
29 {
30 path: '/',
31 component: Layout,
32 redirect: '/dashboard',
33 name: 'Dashboard',
34 hidden: true,
35 children: [{
36 path: 'dashboard',
37 component: () => import('@/views/dashboard/index')
38 }]
39 },
40
41 {
42 path: '/example',
43 component: Layout,
44 redirect: '/example/table',
45 name: 'Example',
46 meta: { title: 'Example', icon: 'example' },
47 children: [
48 {
49 path: 'table',
50 name: 'Table',
51 component: () => import('@/views/table/index'),
52 meta: { title: 'Table', icon: 'table' }
53 },
54 {
55 path: 'tree',
56 name: 'Tree',
57 component: () => import('@/views/tree/index'),
58 meta: { title: 'Tree', icon: 'tree' }
59 }
60 ]
61 },
62
63 {
64 path: '/form',
65 component: Layout,
66 children: [
67 {
68 path: 'index',
69 name: 'Form',
70 component: () => import('@/views/form/index'),
71 meta: { title: 'Form', icon: 'form' }
72 }
73 ]
74 },
75
76 {
77 path: '/nested',
78 component: Layout,
79 redirect: '/nested/menu1',
80 name: 'Nested',
81 meta: {
82 title: 'Nested',
83 icon: 'nested'
84 },
85 children: [
86 {
87 path: 'menu1',
88 component: () => import('@/views/nested/menu1/index'), // Parent router-view
89 name: 'Menu1',
90 meta: { title: 'Menu1' },
91 children: [
92 {
93 path: 'menu1-1',
94 component: () => import('@/views/nested/menu1/menu1-1'),
95 name: 'Menu1-1',
96 meta: { title: 'Menu1-1' }
97 },
98 {
99 path: 'menu1-2',
100 component: () => import('@/views/nested/menu1/menu1-2'),
101 name: 'Menu1-2',
102 meta: { title: 'Menu1-2' },
103 children: [
104 {
105 path: 'menu1-2-1',
106 component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1'),
107 name: 'Menu1-2-1',
108 meta: { title: 'Menu1-2-1' }
109 },
110 {
111 path: 'menu1-2-2',
112 component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2'),
113 name: 'Menu1-2-2',
114 meta: { title: 'Menu1-2-2' }
115 }
116 ]
117 },
118 {
119 path: 'menu1-3',
120 component: () => import('@/views/nested/menu1/menu1-3'),
121 name: 'Menu1-3',
122 meta: { title: 'Menu1-3' }
123 }
124 ]
125 },
126 {
127 path: 'menu2',
128 component: () => import('@/views/nested/menu2/index'),
129 meta: { title: 'menu2' }
130 }
131 ]
132 },
133
134 // {
135 // path: 'external-link',
136 // component: Layout,
137 // children: [
138 // {
139 // path: 'https://panjiachen.github.io/vue-element-admin-site/#/',
140 // meta: { title: 'External Link', icon: 'link' }
141 // }
142 // ]
143 // },
144
145 { path: '*', redirect: '/404', hidden: true }
146 ]
147
148 export default new Router({
149 // mode: 'history', //后端支持可开
150 scrollBehavior: () => ({ y: 0 }),
151 routes: constantRouterMap
152 })
1 const getters = {
2 sidebar: state => state.app.sidebar,
3 device: state => state.app.device,
4 token: state => state.user.token,
5 avatar: state => state.user.avatar,
6 name: state => state.user.name,
7 roles: state => state.user.roles
8 }
9 export default getters
1 import Vue from 'vue'
2 import Vuex from 'vuex'
3 import app from './modules/app'
4 import user from './modules/user'
5 import getters from './getters'
6
7 Vue.use(Vuex)
8
9 const store = new Vuex.Store({
10 modules: {
11 app,
12 user
13 },
14 getters
15 })
16
17 export default store
1 import Cookies from 'js-cookie'
2
3 const app = {
4 state: {
5 sidebar: {
6 opened: !+Cookies.get('sidebarStatus'),
7 withoutAnimation: false
8 },
9 device: 'desktop'
10 },
11 mutations: {
12 TOGGLE_SIDEBAR: state => {
13 if (state.sidebar.opened) {
14 Cookies.set('sidebarStatus', 1)
15 } else {
16 Cookies.set('sidebarStatus', 0)
17 }
18 state.sidebar.opened = !state.sidebar.opened
19 state.sidebar.withoutAnimation = false
20 },
21 CLOSE_SIDEBAR: (state, withoutAnimation) => {
22 Cookies.set('sidebarStatus', 1)
23 state.sidebar.opened = false
24 state.sidebar.withoutAnimation = withoutAnimation
25 },
26 TOGGLE_DEVICE: (state, device) => {
27 state.device = device
28 }
29 },
30 actions: {
31 ToggleSideBar: ({ commit }) => {
32 commit('TOGGLE_SIDEBAR')
33 },
34 CloseSideBar({ commit }, { withoutAnimation }) {
35 commit('CLOSE_SIDEBAR', withoutAnimation)
36 },
37 ToggleDevice({ commit }, device) {
38 commit('TOGGLE_DEVICE', device)
39 }
40 }
41 }
42
43 export default app
1 import { login, logout, getInfo } from '@/api/login'
2 import { getToken, setToken, removeToken } from '@/utils/auth'
3
4 const user = {
5 state: {
6 token: getToken(),
7 name: '',
8 avatar: '',
9 roles: []
10 },
11
12 mutations: {
13 SET_TOKEN: (state, token) => {
14 state.token = token
15 },
16 SET_NAME: (state, name) => {
17 state.name = name
18 },
19 SET_AVATAR: (state, avatar) => {
20 state.avatar = avatar
21 },
22 SET_ROLES: (state, roles) => {
23 state.roles = roles
24 }
25 },
26
27 actions: {
28 // 登录
29 Login({ commit }, userInfo) {
30 const username = userInfo.username.trim()
31 return new Promise((resolve, reject) => {
32 login(username, userInfo.password).then(response => {
33 const data = response.content
34 setToken(data.token)
35 commit('SET_TOKEN', data.token)
36 resolve()
37 }).catch(error => {
38 reject(error)
39 })
40 })
41 },
42
43 // 获取用户信息
44 GetInfo({ commit, state }) {
45 return new Promise((resolve, reject) => {
46 getInfo(state.token).then(response => {
47 const data = response.content
48 if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组
49 commit('SET_ROLES', data.roles)
50 } else {
51 reject('getInfo: roles must be a non-null array !')
52 }
53 commit('SET_NAME', data.name)
54 commit('SET_AVATAR', data.avatar)
55 resolve(response)
56 }).catch(error => {
57 reject(error)
58 })
59 })
60 },
61
62 // 登出
63 LogOut({ commit, state }) {
64 return new Promise((resolve, reject) => {
65 logout(state.token).then(() => {
66 commit('SET_TOKEN', '')
67 commit('SET_ROLES', [])
68 removeToken()
69 resolve()
70 }).catch(error => {
71 reject(error)
72 })
73 })
74 },
75
76 // 前端 登出
77 FedLogOut({ commit }) {
78 return new Promise(resolve => {
79 commit('SET_TOKEN', '')
80 removeToken()
81 resolve()
82 })
83 }
84 }
85 }
86
87 export default user
1 //to reset element-ui default css
2 .el-upload {
3 input[type="file"] {
4 display: none !important;
5 }
6 }
7
8 .el-upload__input {
9 display: none;
10 }
11
12 //暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461
13 .el-dialog {
14 transform: none;
15 left: 0;
16 position: relative;
17 margin: 0 auto;
18 }
19
20 //element ui upload
21 .upload-container {
22 .el-upload {
23 width: 100%;
24
25 .el-upload-dragger {
26 width: 100%;
27 height: 200px;
28 }
29 }
30 }
31
32 @media only screen and (min-width : 320px) {
33 .el-message {
34 min-width: 100%;
35 }
36
37 .el-message-box {
38 width: 320px;
39 }
40 }
41
42 .el-table .cell {
43 word-break: break-all;
44 display: -webkit-box;
45 -webkit-line-clamp: 3;
46 -webkit-box-orient: vertical;
47 overflow: hidden;
48 }
1 @import './variables.scss';
2 @import './mixin.scss';
3 @import './transition.scss';
4 @import './element-ui.scss';
5 @import './sidebar.scss';
6
7 body {
8 height: 100%;
9 -moz-osx-font-smoothing: grayscale;
10 -webkit-font-smoothing: antialiased;
11 text-rendering: optimizeLegibility;
12 font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
13 }
14
15 label {
16 font-weight: 700;
17 }
18
19 html {
20 height: 100%;
21 box-sizing: border-box;
22 }
23
24 #app {
25 height: 100%;
26 }
27
28 *,
29 *:before,
30 *:after {
31 box-sizing: inherit;
32 }
33
34 a,
35 a:focus,
36 a:hover {
37 cursor: pointer;
38 color: inherit;
39 outline: none;
40 text-decoration: none;
41 }
42
43 div:focus {
44 outline: none;
45 }
46
47 a:focus,
48 a:active {
49 outline: none;
50 }
51
52 a,
53 a:focus,
54 a:hover {
55 cursor: pointer;
56 color: inherit;
57 text-decoration: none;
58 }
59
60 .clearfix {
61 &:after {
62 visibility: hidden;
63 display: block;
64 font-size: 0;
65 content: " ";
66 clear: both;
67 height: 0;
68 }
69 }
70
71 //main-container全局样式
72 .app-main {
73 min-height: 100%
74 }
75
76 .app-container {
77 padding: 20px;
78 }
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
14 &::-webkit-scrollbar {
15 width: 6px;
16 }
17
18 &::-webkit-scrollbar-thumb {
19 background: #99a9bf;
20 border-radius: 20px;
21 }
22 }
23
24 @mixin relative {
25 position: relative;
26 width: 100%;
27 height: 100%;
28 }
1 #app {
2
3 // 主体区域 Main container
4 .main-container {
5 min-height: 100%;
6 transition: margin-left .28s;
7 margin-left: $sideBarWidth;
8 position: relative;
9 }
10
11 // 侧边栏 Sidebar container
12 .sidebar-container {
13 transition: width 0.28s;
14 width: $sideBarWidth !important;
15 height: 100%;
16 position: fixed;
17 font-size: 0px;
18 top: 0;
19 bottom: 0;
20 left: 0;
21 z-index: 1001;
22 overflow: hidden;
23
24 //reset element-ui css
25 .horizontal-collapse-transition {
26 transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
27 }
28
29 .scrollbar-wrapper {
30 overflow-x: hidden !important;
31
32 .el-scrollbar__view {
33 height: 100%;
34 }
35 }
36
37 .el-scrollbar__bar.is-vertical {
38 right: 0px;
39 }
40
41 .is-horizontal {
42 display: none;
43 }
44
45 a {
46 display: inline-block;
47 width: 100%;
48 overflow: hidden;
49 }
50
51 .svg-icon {
52 margin-right: 16px;
53 }
54
55 .el-menu {
56 border: none;
57 height: 100%;
58 width: 100% !important;
59 }
60
61 // menu hover
62 .submenu-title-noDropdown,
63 .el-submenu__title {
64 &:hover {
65 background-color: $menuHover !important;
66 }
67 }
68
69 .is-active>.el-submenu__title {
70 color: $subMenuActiveText !important;
71 }
72
73 & .nest-menu .el-submenu>.el-submenu__title,
74 & .el-submenu .el-menu-item {
75 min-width: $sideBarWidth !important;
76 background-color: $subMenuBg !important;
77
78 &:hover {
79 background-color: $subMenuHover !important;
80 }
81 }
82 }
83
84 .hideSidebar {
85 .sidebar-container {
86 width: 54px !important;
87 }
88
89 .main-container {
90 margin-left: 54px;
91 }
92
93 .svg-icon {
94 margin-right: 0px;
95 }
96
97 .submenu-title-noDropdown {
98 padding: 0 !important;
99 position: relative;
100
101 .el-tooltip {
102 padding: 0 !important;
103 .svg-icon {
104 margin-left: 20px;
105 }
106 }
107 }
108
109 .el-submenu {
110 overflow: hidden;
111
112 &>.el-submenu__title {
113 padding: 0 !important;
114 .svg-icon {
115 margin-left: 20px;
116 }
117
118 .el-submenu__icon-arrow {
119 display: none;
120 }
121 }
122 }
123
124 .el-menu--collapse {
125 .el-submenu {
126 &>.el-submenu__title {
127 &>span {
128 height: 0;
129 width: 0;
130 overflow: hidden;
131 visibility: hidden;
132 display: inline-block;
133 }
134 }
135 }
136 }
137 }
138
139 .el-menu--collapse .el-menu .el-submenu {
140 min-width: $sideBarWidth !important;
141 }
142
143 // 适配移动端, Mobile responsive
144 .mobile {
145 .main-container {
146 margin-left: 0px;
147 }
148
149 .sidebar-container {
150 transition: transform .28s;
151 width: $sideBarWidth !important;
152 }
153
154 &.hideSidebar {
155 .sidebar-container {
156 pointer-events: none;
157 transition-duration: 0.3s;
158 transform: translate3d(-$sideBarWidth, 0, 0);
159 }
160 }
161 }
162
163 .withoutAnimation {
164
165 .main-container,
166 .sidebar-container {
167 transition: none;
168 }
169 }
170 }
171
172 // when menu collapsed
173 .el-menu--vertical {
174 &>.el-menu {
175 .svg-icon {
176 margin-right: 16px;
177 }
178 }
179
180 .nest-menu .el-submenu>.el-submenu__title,
181 .el-menu-item {
182 &:hover {
183 // you can use $subMenuHover
184 background-color: $menuHover !important;
185 }
186 }
187
188 // the scroll bar appears when the subMenu is too long
189 >.el-menu--popup {
190 max-height: 100vh;
191 overflow-y: auto;
192
193 &::-webkit-scrollbar-track-piece {
194 background: #d3dce6;
195 }
196
197 &::-webkit-scrollbar {
198 width: 6px;
199 }
200
201 &::-webkit-scrollbar-thumb {
202 background: #99a9bf;
203 border-radius: 20px;
204 }
205 }
206 }
1 //globl transition css
2
3 /*fade*/
4 .fade-enter-active,
5 .fade-leave-active {
6 transition: opacity 0.28s;
7 }
8
9 .fade-enter,
10 .fade-leave-active {
11 opacity: 0;
12 }
13
14 /*fade-transform*/
15 .fade-transform-leave-active,
16 .fade-transform-enter-active {
17 transition: all .5s;
18 }
19
20 .fade-transform-enter {
21 opacity: 0;
22 transform: translateX(-30px);
23 }
24
25 .fade-transform-leave-to {
26 opacity: 0;
27 transform: translateX(30px);
28 }
29
30 /*fade*/
31 .breadcrumb-enter-active,
32 .breadcrumb-leave-active {
33 transition: all .5s;
34 }
35
36 .breadcrumb-enter,
37 .breadcrumb-leave-active {
38 opacity: 0;
39 transform: translateX(20px);
40 }
41
42 .breadcrumb-move {
43 transition: all .5s;
44 }
45
46 .breadcrumb-leave-active {
47 position: absolute;
48 }
1 //sidebar
2 $menuText:#bfcbd9;
3 $menuActiveText:#409EFF;
4 $subMenuActiveText:#f4f4f5; //https://github.com/ElemeFE/element/issues/12951
5
6 $menuBg:#304156;
7 $menuHover:#263445;
8
9 $subMenuBg:#1f2d3d;
10 $subMenuHover:#001528;
11
12 $sideBarWidth: 210px;
13
14 // the :export directive is the magic sauce for webpack
15 // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
16 :export {
17 menuText: $menuText;
18 menuActiveText: $menuActiveText;
19 subMenuActiveText: $subMenuActiveText;
20 menuBg: $menuBg;
21 menuHover: $menuHover;
22 subMenuBg: $subMenuBg;
23 subMenuHover: $subMenuHover;
24 sideBarWidth: $sideBarWidth;
25 }
1 import Cookies from 'js-cookie'
2
3 const TokenKey = 'kd_admin_token'
4
5 export function getToken() {
6 return Cookies.get(TokenKey)
7 }
8
9 export function setToken(token) {
10 return Cookies.set(TokenKey, token)
11 }
12
13 export function removeToken() {
14 return Cookies.remove(TokenKey)
15 }
1 import axios from 'axios'
2 import { Message, MessageBox } from 'element-ui'
3 import store from '../store'
4 import { getToken } from '@/utils/auth'
5
6 // 创建axios实例
7 const service = axios.create({
8 baseURL: process.env.BASE_API, // api 的 base_url
9 timeout: 5000 // 请求超时时间
10 })
11
12 // request拦截器
13 service.interceptors.request.use(
14 config => {
15 if (store.getters.token) {
16 config.headers['X-Token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
17 }
18 return config
19 },
20 error => {
21 // Do something with request error
22 console.log(error) // for debug
23 Promise.reject(error)
24 }
25 )
26
27 // response 拦截器
28 service.interceptors.response.use(
29 response => {
30 /**
31 * code为非20000是抛错 可结合自己业务进行修改
32 */
33 // 业务数据
34 const res = response.data
35 if (res.code !== 200) {
36 Message({
37 message: res.message,
38 type: 'error',
39 duration: 5 * 1000
40 })
41
42 // 50008:非法的token; 50012:其他客户端登录了; 50014:Token 过期了;
43 if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
44 MessageBox.confirm(
45 '你已被登出,可以取消继续留在该页面,或者重新登录',
46 '确定登出',
47 {
48 confirmButtonText: '重新登录',
49 cancelButtonText: '取消',
50 type: 'warning'
51 }
52 ).then(() => {
53 store.dispatch('FedLogOut').then(() => {
54 location.reload() // 为了重新实例化vue-router对象 避免bug
55 })
56 })
57 }
58 return Promise.reject('error')
59 } else {
60 return response.data
61 }
62 },
63 error => {
64 console.log('err' + error) // for debug
65 Message({
66 message: error.message,
67 type: 'error',
68 duration: 5 * 1000
69 })
70 return Promise.reject(error)
71 }
72 )
73
74 export default service
1 /**
2 * Created by jiachenpan on 16/11/18.
3 */
4
5 export function isvalidUsername(str) {
6 const valid_map = ['admin', 'editor']
7 return valid_map.indexOf(str.trim()) >= 0
8 }
9
10 export function isExternal(path) {
11 return /^(https?:|mailto:|tel:)/.test(path)
12 }
1 <template>
2 <div class="wscn-http404-container">
3 <div class="wscn-http404">
4 <div class="pic-404">
5 <img class="pic-404__parent" src="@/assets/404_images/404.png" alt="404">
6 <img class="pic-404__child left" src="@/assets/404_images/404_cloud.png" alt="404">
7 <img class="pic-404__child mid" src="@/assets/404_images/404_cloud.png" alt="404">
8 <img class="pic-404__child right" src="@/assets/404_images/404_cloud.png" alt="404">
9 </div>
10 <div class="bullshit">
11 <div class="bullshit__oops">OOPS!</div>
12 <div class="bullshit__info">版权所有
13 <a class="link-type" href="https://wallstreetcn.com" target="_blank">华尔街见闻</a>
14 </div>
15 <div class="bullshit__headline">{{ message }}</div>
16 <div class="bullshit__info">请检查您输入的网址是否正确,请点击以下按钮返回主页或者发送错误报告</div>
17 <a href="" class="bullshit__return-home">返回首页</a>
18 </div>
19 </div>
20 </div>
21 </template>
22
23 <script>
24
25 export default {
26 name: 'Page404',
27 computed: {
28 message() {
29 return '网管说这个页面你不能进......'
30 }
31 }
32 }
33 </script>
34
35 <style rel="stylesheet/scss" lang="scss" scoped>
36 .wscn-http404-container{
37 transform: translate(-50%,-50%);
38 position: absolute;
39 top: 40%;
40 left: 50%;
41 }
42 .wscn-http404 {
43 position: relative;
44 width: 1200px;
45 padding: 0 50px;
46 overflow: hidden;
47 .pic-404 {
48 position: relative;
49 float: left;
50 width: 600px;
51 overflow: hidden;
52 &__parent {
53 width: 100%;
54 }
55 &__child {
56 position: absolute;
57 &.left {
58 width: 80px;
59 top: 17px;
60 left: 220px;
61 opacity: 0;
62 animation-name: cloudLeft;
63 animation-duration: 2s;
64 animation-timing-function: linear;
65 animation-fill-mode: forwards;
66 animation-delay: 1s;
67 }
68 &.mid {
69 width: 46px;
70 top: 10px;
71 left: 420px;
72 opacity: 0;
73 animation-name: cloudMid;
74 animation-duration: 2s;
75 animation-timing-function: linear;
76 animation-fill-mode: forwards;
77 animation-delay: 1.2s;
78 }
79 &.right {
80 width: 62px;
81 top: 100px;
82 left: 500px;
83 opacity: 0;
84 animation-name: cloudRight;
85 animation-duration: 2s;
86 animation-timing-function: linear;
87 animation-fill-mode: forwards;
88 animation-delay: 1s;
89 }
90 @keyframes cloudLeft {
91 0% {
92 top: 17px;
93 left: 220px;
94 opacity: 0;
95 }
96 20% {
97 top: 33px;
98 left: 188px;
99 opacity: 1;
100 }
101 80% {
102 top: 81px;
103 left: 92px;
104 opacity: 1;
105 }
106 100% {
107 top: 97px;
108 left: 60px;
109 opacity: 0;
110 }
111 }
112 @keyframes cloudMid {
113 0% {
114 top: 10px;
115 left: 420px;
116 opacity: 0;
117 }
118 20% {
119 top: 40px;
120 left: 360px;
121 opacity: 1;
122 }
123 70% {
124 top: 130px;
125 left: 180px;
126 opacity: 1;
127 }
128 100% {
129 top: 160px;
130 left: 120px;
131 opacity: 0;
132 }
133 }
134 @keyframes cloudRight {
135 0% {
136 top: 100px;
137 left: 500px;
138 opacity: 0;
139 }
140 20% {
141 top: 120px;
142 left: 460px;
143 opacity: 1;
144 }
145 80% {
146 top: 180px;
147 left: 340px;
148 opacity: 1;
149 }
150 100% {
151 top: 200px;
152 left: 300px;
153 opacity: 0;
154 }
155 }
156 }
157 }
158 .bullshit {
159 position: relative;
160 float: left;
161 width: 300px;
162 padding: 30px 0;
163 overflow: hidden;
164 &__oops {
165 font-size: 32px;
166 font-weight: bold;
167 line-height: 40px;
168 color: #1482f0;
169 opacity: 0;
170 margin-bottom: 20px;
171 animation-name: slideUp;
172 animation-duration: 0.5s;
173 animation-fill-mode: forwards;
174 }
175 &__headline {
176 font-size: 20px;
177 line-height: 24px;
178 color: #222;
179 font-weight: bold;
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>
1 <template>
2 <div class="dashboard-container">
3 <div class="dashboard-text">name:{{ name }}</div>
4 <div class="dashboard-text">roles:<span v-for="role in roles" :key="role">{{ role }}</span></div>
5 </div>
6 </template>
7
8 <script>
9 import { mapGetters } from 'vuex'
10
11 export default {
12 name: 'Dashboard',
13 computed: {
14 ...mapGetters([
15 'name',
16 'roles'
17 ])
18 }
19 }
20 </script>
21
22 <style rel="stylesheet/scss" lang="scss" scoped>
23 .dashboard {
24 &-container {
25 margin: 30px;
26 }
27 &-text {
28 font-size: 30px;
29 line-height: 46px;
30 }
31 }
32 </style>
1 <template>
2 <div class="app-container">
3 <el-form ref="form" :model="form" label-width="120px">
4 <el-form-item label="Activity name">
5 <el-input v-model="form.name"/>
6 </el-form-item>
7 <el-form-item label="Activity zone">
8 <el-select v-model="form.region" placeholder="please select your zone">
9 <el-option label="Zone one" value="shanghai"/>
10 <el-option label="Zone two" value="beijing"/>
11 </el-select>
12 </el-form-item>
13 <el-form-item label="Activity time">
14 <el-col :span="11">
15 <el-date-picker v-model="form.date1" type="date" placeholder="Pick a date" style="width: 100%;"/>
16 </el-col>
17 <el-col :span="2" class="line">-</el-col>
18 <el-col :span="11">
19 <el-time-picker v-model="form.date2" type="fixed-time" placeholder="Pick a time" style="width: 100%;"/>
20 </el-col>
21 </el-form-item>
22 <el-form-item label="Instant delivery">
23 <el-switch v-model="form.delivery"/>
24 </el-form-item>
25 <el-form-item label="Activity type">
26 <el-checkbox-group v-model="form.type">
27 <el-checkbox label="Online activities" name="type"/>
28 <el-checkbox label="Promotion activities" name="type"/>
29 <el-checkbox label="Offline activities" name="type"/>
30 <el-checkbox label="Simple brand exposure" name="type"/>
31 </el-checkbox-group>
32 </el-form-item>
33 <el-form-item label="Resources">
34 <el-radio-group v-model="form.resource">
35 <el-radio label="Sponsor"/>
36 <el-radio label="Venue"/>
37 </el-radio-group>
38 </el-form-item>
39 <el-form-item label="Activity form">
40 <el-input v-model="form.desc" type="textarea"/>
41 </el-form-item>
42 <el-form-item>
43 <el-button type="primary" @click="onSubmit">Create</el-button>
44 <el-button @click="onCancel">Cancel</el-button>
45 </el-form-item>
46 </el-form>
47 </div>
48 </template>
49
50 <script>
51 export default {
52 data() {
53 return {
54 form: {
55 name: '',
56 region: '',
57 date1: '',
58 date2: '',
59 delivery: false,
60 type: [],
61 resource: '',
62 desc: ''
63 }
64 }
65 },
66 methods: {
67 onSubmit() {
68 this.$message('submit!')
69 },
70 onCancel() {
71 this.$message({
72 message: 'cancel!',
73 type: 'warning'
74 })
75 }
76 }
77 }
78 </script>
79
80 <style scoped>
81 .line{
82 text-align: center;
83 }
84 </style>
85
1 <template>
2 <div :class="classObj" class="app-wrapper">
3 <div class="nav-wrap">
4 <div v-if="device !=='mobile'" :class="{isCollapse:isCollapse}" class="tit"> {{!isCollapse?'管理系统':''}} </div>
5 <navbar class="nav" />
6 </div>
7 <div class="main-wrap">
8 <div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
9 <sidebar class="sidebar-container" />
10 <div class="main-container">
11 <app-main />
12 </div>
13 </div>
14 </div>
15 </template>
16
17 <script>
18 import { mapGetters } from 'vuex'
19 import { Navbar, Sidebar, AppMain } from './components'
20 import ResizeMixin from './mixin/ResizeHandler'
21
22 export default {
23 name: 'Layout',
24 components: {
25 Navbar,
26 Sidebar,
27 AppMain
28 },
29 mixins: [ResizeMixin],
30 computed: {
31 sidebar() {
32 return this.$store.state.app.sidebar
33 },
34 device() {
35 return this.$store.state.app.device
36 },
37 classObj() {
38 return {
39 hideSidebar: !this.sidebar.opened,
40 openSidebar: this.sidebar.opened,
41 withoutAnimation: this.sidebar.withoutAnimation,
42 mobile: this.device === 'mobile'
43 }
44 },
45 isCollapse() {
46 return !this.sidebar.opened
47 }
48 },
49 methods: {
50 handleClickOutside() {
51 this.$store.dispatch('CloseSideBar', { withoutAnimation: false })
52 }
53 }
54 }
55 </script>
56
57 <style rel="stylesheet/scss" lang="scss" scoped>
58 @import 'src/styles/mixin.scss';
59 @import 'src/styles/variables.scss';
60
61 .nav-wrap {
62 position: fixed;
63 z-index: 999;
64 width: 100%;
65 height: 50px;
66 background-color: #ffffff;
67 display: flex;
68 justify-content: flex-start;
69 .tit {
70 transition: width 0.28s;
71 width: 210px;
72 height: 100%;
73 display: flex;
74 align-items: center;
75 justify-content: center;
76 font-weight: bold;
77 font-size: 24px;
78 color: #555555;
79 // background-color: $menuBg;
80 }
81 .isCollapse {
82 transition: width 0.28s;
83 width: 54px;
84 }
85 .nav {
86 flex: 1;
87 }
88 }
89
90 .main-wrap {
91 padding-top: 50px;
92 }
93 .sidebar-container {
94 margin-top: 50px;
95 }
96
97 .app-wrapper {
98 @include clearfix;
99 position: relative;
100 height: 100%;
101 width: 100%;
102 &.mobile.openSidebar {
103 position: fixed;
104 top: 0;
105 }
106 }
107 .drawer-bg {
108 background: #000;
109 opacity: 0.3;
110 width: 100%;
111 top: 0;
112 height: 100%;
113 position: absolute;
114 z-index: 999;
115 }
116 </style>
1 <template>
2 <section class="app-main">
3 <transition name="fade-transform" mode="out-in">
4 <!-- or name="fade" -->
5 <!-- <router-view :key="key"></router-view> -->
6 <router-view/>
7 </transition>
8 </section>
9 </template>
10
11 <script>
12 export default {
13 name: 'AppMain',
14 computed: {
15 // key() {
16 // return this.$route.name !== undefined ? this.$route.name + +new Date() : this.$route + +new Date()
17 // }
18 }
19 }
20 </script>
21
22 <style scoped>
23 .app-main {
24 /*50 = navbar */
25 min-height: calc(100vh - 50px);
26 position: relative;
27 overflow: hidden;
28 }
29 </style>
1 <template>
2 <div class="navbar">
3 <hamburger :toggle-click="toggleSideBar" :is-active="sidebar.opened" class="hamburger-container"/>
4 <breadcrumb />
5 <el-dropdown class="avatar-container" trigger="click">
6 <div class="avatar-wrapper">
7 <img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">
8 <i class="el-icon-caret-bottom"/>
9 </div>
10 <el-dropdown-menu slot="dropdown" class="user-dropdown">
11 <router-link class="inlineBlock" to="/">
12 <el-dropdown-item>
13 Home
14 </el-dropdown-item>
15 </router-link>
16 <el-dropdown-item divided>
17 <span style="display:block;" @click="logout">LogOut</span>
18 </el-dropdown-item>
19 </el-dropdown-menu>
20 </el-dropdown>
21 </div>
22 </template>
23
24 <script>
25 import { mapGetters } from 'vuex'
26 import Breadcrumb from '@/components/Breadcrumb'
27 import Hamburger from '@/components/Hamburger'
28
29 export default {
30 components: {
31 Breadcrumb,
32 Hamburger
33 },
34 computed: {
35 ...mapGetters([
36 'sidebar',
37 'avatar'
38 ])
39 },
40 methods: {
41 toggleSideBar() {
42 this.$store.dispatch('ToggleSideBar')
43 },
44 logout() {
45 this.$store.dispatch('LogOut').then(() => {
46 location.reload() // 为了重新实例化vue-router对象 避免bug
47 })
48 }
49 }
50 }
51 </script>
52
53 <style rel="stylesheet/scss" lang="scss" scoped>
54 .navbar {
55 height: 50px;
56 line-height: 50px;
57 box-shadow: 0 1px 3px 0 rgba(0,0,0,.12), 0 0 3px 0 rgba(0,0,0,.04);
58 .hamburger-container {
59 line-height: 58px;
60 height: 50px;
61 float: left;
62 padding: 0 10px;
63 }
64 .screenfull {
65 position: absolute;
66 right: 90px;
67 top: 16px;
68 color: red;
69 }
70 .avatar-container {
71 height: 50px;
72 display: inline-block;
73 position: absolute;
74 right: 35px;
75 .avatar-wrapper {
76 cursor: pointer;
77 margin-top: 5px;
78 position: relative;
79 line-height: initial;
80 .user-avatar {
81 width: 40px;
82 height: 40px;
83 border-radius: 10px;
84 }
85 .el-icon-caret-bottom {
86 position: absolute;
87 right: -20px;
88 top: 25px;
89 font-size: 12px;
90 }
91 }
92 }
93 }
94 </style>
95
1 <script>
2 export default {
3 name: 'MenuItem',
4 functional: true,
5 props: {
6 meta: {
7 type: Object,
8 default: () => {
9 return {
10 title: '',
11 icon: ''
12 }
13 }
14 }
15 },
16 render(h, context) {
17 const { icon, title } = context.props.meta
18 const vnodes = []
19
20 if (icon) {
21 vnodes.push(<svg-icon icon-class={icon}/>)
22 }
23
24 if (title) {
25 vnodes.push(<span slot='title'>{(title)}</span>)
26 }
27 return vnodes
28 }
29 }
30 </script>
1
2 <template>
3 <!-- eslint-disable vue/require-component-is -->
4 <component v-bind="linkProps(to)">
5 <slot/>
6 </component>
7 </template>
8
9 <script>
10 import { isExternal } from '@/utils/validate'
11
12 export default {
13 props: {
14 to: {
15 type: String,
16 required: true
17 }
18 },
19 methods: {
20 linkProps(url) {
21 if (isExternal(url)) {
22 return {
23 is: 'a',
24 href: url,
25 target: '_blank',
26 rel: 'noopener'
27 }
28 }
29 return {
30 is: 'router-link',
31 to: url
32 }
33 }
34 }
35 }
36 </script>
1 <template>
2 <div v-if="!item.hidden" class="menu-wrapper">
3 <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
4 <app-link :to="resolvePath(onlyOneChild.path)">
5 <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
6 <item :meta="Object.assign({},item.meta,onlyOneChild.meta)" />
7 </el-menu-item>
8 </app-link>
9 </template>
10
11 <el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
12 <template slot="title">
13 <item :meta="item.meta" />
14 </template>
15 <sidebar-item
16 v-for="child in item.children"
17 :is-nest="true"
18 :item="child"
19 :key="child.path"
20 :base-path="resolvePath(child.path)"
21 class="nest-menu" />
22 </el-submenu>
23
24 </div>
25 </template>
26
27 <script>
28 import path from 'path'
29 import { isExternal } from '@/utils/validate'
30 import Item from './Item'
31 import AppLink from './Link'
32
33 export default {
34 name: 'SidebarItem',
35 components: { Item, AppLink },
36 props: {
37 // route object
38 item: {
39 type: Object,
40 required: true
41 },
42 isNest: {
43 type: Boolean,
44 default: false
45 },
46 basePath: {
47 type: String,
48 default: ''
49 }
50 },
51 data() {
52 // To fix https://github.com/PanJiaChen/vue-admin-template/issues/237
53 // TODO: refactor with render function
54 this.onlyOneChild = null
55 return {}
56 },
57 methods: {
58 hasOneShowingChild(children = [], parent) {
59 const showingChildren = children.filter(item => {
60 if (item.hidden) {
61 return false
62 } else {
63 // Temp set(will be used if only has one showing child)
64 this.onlyOneChild = item
65 return true
66 }
67 })
68
69 // When there is only one child router, the child router is displayed by default
70 if (showingChildren.length === 1) {
71 return true
72 }
73
74 // Show parent if there are no child router to display
75 if (showingChildren.length === 0) {
76 this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }
77 return true
78 }
79
80 return false
81 },
82 resolvePath(routePath) {
83 if (isExternal(routePath)) {
84 return routePath
85 }
86 return path.resolve(this.basePath, routePath)
87 }
88 }
89 }
90 </script>
1 <template>
2 <el-scrollbar wrap-class="scrollbar-wrapper">
3 <el-menu
4 :default-active="$route.path"
5 :collapse="isCollapse"
6 :background-color="variables.menuBg"
7 :text-color="variables.menuText"
8 :active-text-color="variables.menuActiveText"
9 :collapse-transition="false"
10 mode="vertical"
11 >
12 <sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path"/>
13 </el-menu>
14 </el-scrollbar>
15 </template>
16
17 <script>
18 import { mapGetters } from 'vuex'
19 import variables from '@/styles/variables.scss'
20 import SidebarItem from './SidebarItem'
21
22 export default {
23 components: { SidebarItem },
24 computed: {
25 ...mapGetters([
26 'sidebar'
27 ]),
28 routes() {
29 return this.$router.options.routes
30 },
31 variables() {
32 return variables
33 },
34 isCollapse() {
35 return !this.sidebar.opened
36 }
37 }
38 }
39 </script>
1 export { default as Navbar } from './Navbar'
2 export { default as Sidebar } from './Sidebar'
3 export { default as AppMain } from './AppMain'
1 import store from '@/store'
2
3 const { body } = document
4 const WIDTH = 992 // refer to Bootstrap's responsive design
5
6 export default {
7 watch: {
8 $route(route) {
9 if (this.device === 'mobile' && this.sidebar.opened) {
10 store.dispatch('CloseSideBar', { withoutAnimation: false })
11 }
12 }
13 },
14 beforeMount() {
15 window.addEventListener('resize', this.resizeHandler)
16 },
17 mounted() {
18 const isMobile = this.isMobile()
19 if (isMobile) {
20 store.dispatch('ToggleDevice', 'mobile')
21 store.dispatch('CloseSideBar', { withoutAnimation: true })
22 }
23 },
24 methods: {
25 isMobile() {
26 const rect = body.getBoundingClientRect()
27 return rect.width - 1 < WIDTH
28 },
29 resizeHandler() {
30 if (!document.hidden) {
31 const isMobile = this.isMobile()
32 store.dispatch('ToggleDevice', isMobile ? 'mobile' : 'desktop')
33
34 if (isMobile) {
35 store.dispatch('CloseSideBar', { withoutAnimation: true })
36 }
37 }
38 }
39 }
40 }
1 <template>
2 <div class="login-container">
3 <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form" auto-complete="on" label-position="left">
4 <h3 class="title">vue-admin-template</h3>
5 <el-form-item prop="username">
6 <span class="svg-container">
7 <svg-icon icon-class="user" />
8 </span>
9 <el-input v-model="loginForm.username" name="username" type="text" auto-complete="on" placeholder="username" />
10 </el-form-item>
11 <el-form-item prop="password">
12 <span class="svg-container">
13 <svg-icon icon-class="password" />
14 </span>
15 <el-input :type="pwdType" v-model="loginForm.password" name="password" auto-complete="on" placeholder="password" @keyup.enter.native="handleLogin" />
16 <span class="show-pwd" @click="showPwd">
17 <svg-icon :icon-class="pwdType === 'password' ? 'eye' : 'eye-open'" />
18 </span>
19 </el-form-item>
20 <el-form-item>
21 <el-button :loading="loading" type="primary" style="width:100%;" @click.native.prevent="handleLogin">
22 Sign in
23 </el-button>
24 </el-form-item>
25 <div class="tips">
26 <span style="margin-right:20px;">username: admin</span>
27 <span> password: admin</span>
28 </div>
29 </el-form>
30 </div>
31 </template>
32
33 <script>
34 import { isvalidUsername } from '@/utils/validate'
35
36 export default {
37 name: 'Login',
38 data() {
39 const validateUsername = (rule, value, callback) => {
40 if (!isvalidUsername(value)) {
41 callback(new Error('请输入正确的用户名'))
42 } else {
43 callback()
44 }
45 }
46 const validatePass = (rule, value, callback) => {
47 if (value.length < 5) {
48 callback(new Error('密码不能小于5位'))
49 } else {
50 callback()
51 }
52 }
53 return {
54 loginForm: {
55 username: 'admin',
56 password: 'admin'
57 },
58 loginRules: {
59 username: [
60 {
61 required: true,
62 trigger: 'blur',
63 validator: validateUsername
64 }
65 ],
66 password: [
67 { required: true, trigger: 'blur', validator: validatePass }
68 ]
69 },
70 loading: false,
71 pwdType: 'password',
72 redirect: undefined
73 }
74 },
75 watch: {
76 $route: {
77 handler: function(route) {
78 this.redirect = route.query && route.query.redirect
79 },
80 immediate: true
81 }
82 },
83 methods: {
84 showPwd() {
85 if (this.pwdType === 'password') {
86 this.pwdType = ''
87 } else {
88 this.pwdType = 'password'
89 }
90 },
91 handleLogin() {
92 this.$refs.loginForm.validate(valid => {
93 if (valid) {
94 this.loading = true
95 this.$store
96 .dispatch('Login', this.loginForm)
97 .then(() => {
98 this.loading = false
99 this.$router.push({ path: this.redirect || '/' })
100 })
101 .catch(() => {
102 this.loading = false
103 })
104 } else {
105 console.log('error submit!!')
106 return false
107 }
108 })
109 }
110 }
111 }
112 </script>
113
114 <style rel="stylesheet/scss" lang="scss">
115 $bg: #2d3a4b;
116 $light_gray: #eee;
117
118 /* reset element-ui css */
119 .login-container {
120 .el-input {
121 display: inline-block;
122 height: 47px;
123 width: 85%;
124 input {
125 background: transparent;
126 border: 0px;
127 -webkit-appearance: none;
128 border-radius: 0px;
129 padding: 12px 5px 12px 15px;
130 color: $light_gray;
131 height: 47px;
132 &:-webkit-autofill {
133 -webkit-box-shadow: 0 0 0px 1000px $bg inset !important;
134 -webkit-text-fill-color: #fff !important;
135 }
136 }
137 }
138 .el-form-item {
139 border: 1px solid rgba(255, 255, 255, 0.1);
140 background: rgba(0, 0, 0, 0.1);
141 border-radius: 5px;
142 color: #454545;
143 }
144 }
145 </style>
146
147 <style rel="stylesheet/scss" lang="scss" scoped>
148 $bg: #2d3a4b;
149 $dark_gray: #889aa4;
150 $light_gray: #eee;
151 .login-container {
152 position: fixed;
153 height: 100%;
154 width: 100%;
155 background-color: $bg;
156 .login-form {
157 position: absolute;
158 left: 0;
159 right: 0;
160 width: 520px;
161 max-width: 100%;
162 padding: 35px 35px 15px 35px;
163 margin: 120px auto;
164 }
165 .tips {
166 font-size: 14px;
167 color: #fff;
168 margin-bottom: 10px;
169 span {
170 &:first-of-type {
171 margin-right: 16px;
172 }
173 }
174 }
175 .svg-container {
176 padding: 6px 5px 6px 15px;
177 color: $dark_gray;
178 vertical-align: middle;
179 width: 30px;
180 display: inline-block;
181 }
182 .title {
183 font-size: 26px;
184 font-weight: 400;
185 color: $light_gray;
186 margin: 0px auto 40px auto;
187 text-align: center;
188 font-weight: bold;
189 }
190 .show-pwd {
191 position: absolute;
192 right: 10px;
193 top: 7px;
194 font-size: 16px;
195 color: $dark_gray;
196 cursor: pointer;
197 user-select: none;
198 }
199 }
200 </style>
1 <template >
2 <div style="padding:30px;">
3 <el-alert :closable="false" title="menu 1">
4 <router-view />
5 </el-alert>
6 </div>
7 </template>
1 <template >
2 <div style="padding:30px;">
3 <el-alert :closable="false" title="menu 1-1" type="success">
4 <router-view />
5 </el-alert>
6 </div>
7 </template>
1 <template>
2 <div style="padding:30px;">
3 <el-alert :closable="false" title="menu 1-2" type="success">
4 <router-view />
5 </el-alert>
6 </div>
7 </template>
1 <template functional>
2 <div style="padding:30px;">
3 <el-alert :closable="false" title="menu 1-2-1" type="warning" />
4 </div>
5 </template>
1 <template functional>
2 <div style="padding:30px;">
3 <el-alert :closable="false" title="menu 1-2-2" type="warning" />
4 </div>
5 </template>
1 <template functional>
2 <div style="padding:30px;">
3 <el-alert :closable="false" title="menu 1-3" type="success" />
4 </div>
5 </template>
1 <template>
2 <div>131231</div>
3 </template>
4
5 <script>
6 export default {
7 created() {
8 // this.$message({
9 // message: 'Switch Language Success',
10 // type: 'success',
11 // duration:0
12 // })
13 // this.$alert('这是一段内容', '标题名称', {
14 // confirmButtonText: '确定',
15 // callback: action => {
16 // this.$message({
17 // type: 'info',
18 // message: `action: ${action}`
19 // })
20 // }
21 // })
22 // this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
23 // confirmButtonText: '确定',
24 // cancelButtonText: '取消',
25 // type: 'warning'
26 // }).then(() => {
27 // this.$message({
28 // type: 'success',
29 // message: '删除成功!'
30 // });
31 // }).catch(() => {
32 // this.$message({
33 // type: 'info',
34 // message: '已取消删除'
35 // });
36 // });
37 // this.$prompt('请输入邮箱', '提示', {
38 // confirmButtonText: '确定',
39 // cancelButtonText: '取消',
40 // inputPattern: /[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?/,
41 // inputErrorMessage: '邮箱格式不正确'
42 // }).then(({ value }) => {
43 // this.$message({
44 // type: 'success',
45 // message: '你的邮箱是: ' + value
46 // });
47 // }).catch(() => {
48 // this.$message({
49 // type: 'info',
50 // message: '取消输入'
51 // });
52 // });
53 const h = this.$createElement;
54 this.$msgbox({
55 title: '消息',
56 message: h('p', null, [
57 h('span', null, '内容可以是 '),
58 h('i', { style: 'color: teal' }, 'VNode')
59 ]),
60 showCancelButton: true,
61 confirmButtonText: '确定',
62 cancelButtonText: '取消',
63 beforeClose: (action, instance, done) => {
64 if (action === 'confirm') {
65 instance.confirmButtonLoading = true;
66 instance.confirmButtonText = '执行中...';
67 setTimeout(() => {
68 done();
69 setTimeout(() => {
70 instance.confirmButtonLoading = false;
71 }, 300);
72 }, 3000);
73 } else {
74 done();
75 }
76 }
77 }).then(action => {
78 this.$message({
79 type: 'info',
80 message: 'action: ' + action
81 });
82 });
83 }
84 }
85 </script>
86
87 <style lang="scss" scoped>
88 </style>
1 <template>
2 <div class="app-container">
3 <el-table
4 v-loading="listLoading"
5 :data="list"
6 element-loading-text="Loading"
7 border
8 fit
9 highlight-current-row>
10 <el-table-column align="center" label="ID" width="95">
11 <template slot-scope="scope">
12 {{ scope.$index }}
13 </template>
14 </el-table-column>
15 <el-table-column label="Title">
16 <template slot-scope="scope">
17 {{ scope.row.title }}
18 </template>
19 </el-table-column>
20 <el-table-column label="Author" width="110" align="center">
21 <template slot-scope="scope">
22 <span>{{ scope.row.author }}</span>
23 </template>
24 </el-table-column>
25 <el-table-column label="Pageviews" width="110" align="center">
26 <template slot-scope="scope">
27 {{ scope.row.pageviews }}
28 </template>
29 </el-table-column>
30 <el-table-column class-name="status-col" label="Status" width="110" align="center">
31 <template slot-scope="scope">
32 <el-tag :type="scope.row.status | statusFilter">{{ scope.row.status }}</el-tag>
33 </template>
34 </el-table-column>
35 <el-table-column align="center" prop="created_at" label="Display_time" width="200">
36 <template slot-scope="scope">
37 <i class="el-icon-time"/>
38 <span>{{ scope.row.display_time }}</span>
39 </template>
40 </el-table-column>
41 </el-table>
42 </div>
43 </template>
44
45 <script>
46 import { getList } from '@/api/table'
47
48 export default {
49 filters: {
50 statusFilter(status) {
51 const statusMap = {
52 published: 'success',
53 draft: 'gray',
54 deleted: 'danger'
55 }
56 return statusMap[status]
57 }
58 },
59 data() {
60 return {
61 list: null,
62 listLoading: true
63 }
64 },
65 created() {
66 this.fetchData()
67 },
68 methods: {
69 fetchData() {
70 this.listLoading = true
71 getList(this.listQuery).then(response => {
72 console.log("response:",response);
73 this.list = response.content.items
74 this.listLoading = false
75 })
76 }
77 }
78 }
79 </script>
1 <template>
2 <div class="app-container">
3 <el-input v-model="filterText" placeholder="Filter keyword" style="margin-bottom:30px;" />
4
5 <el-tree
6 ref="tree2"
7 :data="data2"
8 :props="defaultProps"
9 :filter-node-method="filterNode"
10 class="filter-tree"
11 default-expand-all
12 />
13
14 </div>
15 </template>
16
17 <script>
18 export default {
19
20 data() {
21 return {
22 filterText: '',
23 data2: [{
24 id: 1,
25 label: 'Level one 1',
26 children: [{
27 id: 4,
28 label: 'Level two 1-1',
29 children: [{
30 id: 9,
31 label: 'Level three 1-1-1'
32 }, {
33 id: 10,
34 label: 'Level three 1-1-2'
35 }]
36 }]
37 }, {
38 id: 2,
39 label: 'Level one 2',
40 children: [{
41 id: 5,
42 label: 'Level two 2-1'
43 }, {
44 id: 6,
45 label: 'Level two 2-2'
46 }]
47 }, {
48 id: 3,
49 label: 'Level one 3',
50 children: [{
51 id: 7,
52 label: 'Level two 3-1'
53 }, {
54 id: 8,
55 label: 'Level two 3-2'
56 }]
57 }],
58 defaultProps: {
59 children: 'children',
60 label: 'label'
61 }
62 }
63 },
64 watch: {
65 filterText(val) {
66 this.$refs.tree2.filter(val)
67 }
68 },
69
70 methods: {
71 filterNode(value, data) {
72 if (!value) return true
73 return data.label.indexOf(value) !== -1
74 }
75 }
76 }
77 </script>
78
File mode changed