基础工程
0 parents
Showing
90 changed files
with
3838 additions
and
0 deletions
.babelrc
0 → 100755
.editorconfig
0 → 100755
.eslintignore
0 → 100755
.eslintrc.js
0 → 100755
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 |
.gitignore
0 → 100755
.postcssrc.js
0 → 100755
.travis.yml
0 → 100755
LICENSE
0 → 100755
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. |
README-zh.md
0 → 100755
1 | # vue-admin-template | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
README.md
0 → 100755
1 | # vue-admin-template | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
build/build.js
0 → 100755
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 | }) |
build/check-versions.js
0 → 100755
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 | } |
build/logo.png
0 → 100755
6.69 KB
build/utils.js
0 → 100755
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 | } |
build/vue-loader.conf.js
0 → 100755
build/webpack.base.conf.js
0 → 100755
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 | } |
build/webpack.dev.conf.js
0 → 100755
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 | }) |
build/webpack.prod.conf.js
0 → 100755
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 |
config/dev.env.js
0 → 100755
config/index.js
0 → 100755
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 | } |
config/prod.env.js
0 → 100755
favicon.ico
0 → 100755
No preview for this file type
index.html
0 → 100755
mock/index.js
0 → 100755
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 |
mock/table.js
0 → 100755
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 | } |
mock/user.js
0 → 100755
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 | } |
mock/utils.js
0 → 100755
package.json
0 → 100755
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 | } |
src/App.vue
0 → 100755
src/api/api.js
0 → 100644
src/api/fetch-api.js
0 → 100644
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 | } |
src/api/index.js
0 → 100644
src/api/login.js
0 → 100755
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 | } |
src/api/table.js
0 → 100755
src/assets/404_images/404.png
0 → 100755
95.8 KB
src/assets/404_images/404_cloud.png
0 → 100755
4.65 KB
src/components/Breadcrumb/index.vue
0 → 100755
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> |
src/components/Hamburger/index.vue
0 → 100755
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> |
src/components/SvgIcon/index.vue
0 → 100755
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> |
src/icons/index.js
0 → 100755
src/icons/svg/example.svg
0 → 100755
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 |
src/icons/svg/eye-open.svg
0 → 100755
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> |
src/icons/svg/eye.svg
0 → 100755
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 |
src/icons/svg/form.svg
0 → 100755
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 |
src/icons/svg/link.svg
0 → 100755
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 |
src/icons/svg/nested.svg
0 → 100755
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 |
src/icons/svg/password.svg
0 → 100755
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 |
src/icons/svg/table.svg
0 → 100755
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 |
src/icons/svg/tree.svg
0 → 100755
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 |
src/icons/svg/user.svg
0 → 100755
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 |
src/icons/svgo.yml
0 → 100755
src/main.js
0 → 100755
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 | }) |
src/permission.js
0 → 100755
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 | }) |
src/router/index.js
0 → 100755
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 | }) |
src/store/getters.js
0 → 100755
src/store/index.js
0 → 100755
src/store/modules/app.js
0 → 100755
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 |
src/store/modules/user.js
0 → 100755
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 |
src/styles/element-ui.scss
0 → 100755
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 | } |
src/styles/index.scss
0 → 100755
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 | } |
src/styles/mixin.scss
0 → 100755
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 | } |
src/styles/sidebar.scss
0 → 100755
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 | } |
src/styles/transition.scss
0 → 100755
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 | } |
src/styles/variables.scss
0 → 100755
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 | } |
src/utils/auth.js
0 → 100755
src/utils/request.js
0 → 100755
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 |
src/utils/validate.js
0 → 100755
src/views/404.vue
0 → 100755
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> |
src/views/dashboard/index.vue
0 → 100755
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> |
src/views/form/index.vue
0 → 100755
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 |
src/views/layout/Layout.vue
0 → 100755
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> |
src/views/layout/components/AppMain.vue
0 → 100755
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> |
src/views/layout/components/Navbar.vue
0 → 100755
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 |
src/views/layout/components/Sidebar/Item.vue
0 → 100755
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> |
src/views/layout/components/Sidebar/Link.vue
0 → 100755
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> |
src/views/layout/components/index.js
0 → 100755
src/views/layout/mixin/ResizeHandler.js
0 → 100755
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 | } |
src/views/login/index.vue
0 → 100755
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> |
src/views/nested/menu1/index.vue
0 → 100755
src/views/nested/menu1/menu1-1/index.vue
0 → 100755
src/views/nested/menu1/menu1-2/index.vue
0 → 100755
src/views/nested/menu1/menu1-3/index.vue
0 → 100755
src/views/nested/menu2/index.vue
0 → 100755
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> |
src/views/table/index.vue
0 → 100755
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> |
src/views/tree/index.vue
0 → 100755
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 |
static/.gitkeep
0 → 100755
File mode changed
-
Please register or sign in to post a comment