1c64b1c0 by qingxiao

项目初始化

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