├── .babelrc ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .npmrc ├── .postcssrc.js ├── README.md ├── build ├── build-next.js ├── build.js ├── check-versions.js ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js └── webpack.prod.conf.js ├── config ├── build.config.js ├── build │ ├── beta.env.js │ ├── dev.env.js │ ├── prod.env.js │ └── test.env.js ├── index.js └── local │ ├── beta.env.js │ ├── dev.env.js │ ├── prod.env.js │ └── test.env.js ├── git.code.lines.js ├── git.push.js ├── index.html ├── package.json ├── progress ├── readme ├── chrome_48x48.png ├── edge_48x48.png ├── element-ui.svg ├── firefox_48x48.png ├── node.jpeg ├── nodejs_48_48.png ├── safari_48x48.png └── vue.png ├── script ├── ossConfig │ └── example.config.js └── putOss.js ├── server.js ├── src ├── api │ ├── api.js │ ├── http.js │ ├── module │ │ └── demo.js │ └── utils.js ├── assets │ ├── images │ │ └── avatar.jpeg │ └── scss │ │ ├── app.scss │ │ ├── base-layout.component.scss │ │ ├── card.component.scss │ │ ├── common.scss │ │ └── plus.scss ├── components │ ├── global.components.js │ └── service.components.js ├── config.js ├── directive │ └── permission.directive.js ├── element-ui │ └── element-ui.js ├── filter │ └── global.filter.js ├── font-awesome │ └── font-awesome.js ├── main.js ├── mixins │ └── __mixins.js ├── remote-menu.data.js ├── route │ └── route.js ├── store.config.js ├── store │ ├── __module │ │ ├── base.store.js │ │ ├── menu.store.js │ │ └── tags.store.js │ ├── store.js │ └── todo.store.module.js ├── utils │ └── utils.js ├── views │ ├── App.vue │ ├── default │ │ ├── layout │ │ │ ├── header.component.vue │ │ │ ├── menu.component.vue │ │ │ ├── menu.scroll.component.vue │ │ │ └── page-tags.component.vue │ │ ├── page │ │ │ ├── 401.page.vue │ │ │ ├── 404.page.vue │ │ │ └── reload.page.vue │ │ └── route │ │ │ ├── base.route.component.vue │ │ │ └── default.route.component.vue │ ├── general │ │ ├── antv-g2 │ │ │ ├── g2-area │ │ │ │ └── area-stacked.component.vue │ │ │ ├── g2-funnel │ │ │ │ └── funnel-basic.componeent.vue │ │ │ ├── g2-line │ │ │ │ ├── line-curved.component.vue │ │ │ │ ├── line-double-y-axes.component.vue │ │ │ │ └── line-step-series.component.vue │ │ │ └── g2-pie │ │ │ │ └── pie-basic.componeent.vue │ │ ├── dashboard-card │ │ │ └── dashboard-card.component.vue │ │ └── markdown │ │ │ └── markdown.component.vue │ ├── global │ │ ├── va-container.component.vue │ │ └── va-table.component.vue │ ├── pages │ │ ├── 404 │ │ │ └── 404.page.vue │ │ ├── antv │ │ │ └── antv-g2 │ │ │ │ ├── area-stacked.page.vue │ │ │ │ ├── basic-pie.page.vue │ │ │ │ ├── basic-pie2.page.vue │ │ │ │ ├── funnel-basic.page.vue │ │ │ │ ├── line-curved.page.vue │ │ │ │ ├── line-double-y-axes.page.vue │ │ │ │ └── line-step-series.page.vue │ │ ├── base-layout │ │ │ └── base-layout.page.vue │ │ ├── card-layout │ │ │ └── card-layout.page.vue │ │ ├── component │ │ │ ├── component-va-table-filter.page.vue │ │ │ └── component-va-table-filter.page │ │ │ │ ├── example.1.vue │ │ │ │ └── example.2.vue │ │ ├── copy-text │ │ │ └── copy-text.page.vue │ │ ├── doc │ │ │ ├── build.page.vue │ │ │ ├── folder.page.vue │ │ │ ├── font-awesome.page.vue │ │ │ ├── general-component.page.vue │ │ │ ├── global-component │ │ │ │ ├── global-component.page.vue │ │ │ │ ├── va-container.page.vue │ │ │ │ └── va-table.page.vue │ │ │ ├── markdown │ │ │ │ ├── build.md │ │ │ │ ├── folder.md │ │ │ │ ├── font-awesome.md │ │ │ │ ├── general-component.md │ │ │ │ ├── global-component │ │ │ │ │ ├── global-component.md │ │ │ │ │ ├── va-container.md │ │ │ │ │ └── va-table.md │ │ │ │ ├── permission.md │ │ │ │ ├── service-component │ │ │ │ │ └── va-table-filter-service.md │ │ │ │ └── store-data.md │ │ │ ├── permission.page.vue │ │ │ ├── readme.page.vue │ │ │ ├── service-component │ │ │ │ └── va-table-filter-service.page.vue │ │ │ └── store-data.page.vue │ │ ├── editor │ │ │ ├── editor-ueditor.page.vue │ │ │ └── wangEditor.page.vue │ │ ├── font-awesome │ │ │ └── font-awesome.page.vue │ │ ├── home │ │ │ └── home.page.vue │ │ ├── js-big-decimal │ │ │ └── js-big-decimal.page.vue │ │ ├── login │ │ │ └── login.page.vue │ │ ├── markdown │ │ │ └── markdown.page.vue │ │ ├── menu │ │ │ └── menu.page.vue │ │ ├── permission │ │ │ └── permission.page.vue │ │ ├── personal │ │ │ └── personal.page.vue │ │ ├── store-data │ │ │ ├── fixed-header.page.vue │ │ │ ├── logo.page.vue │ │ │ ├── menu-collapse.page.vue │ │ │ └── menu-list.page.vue │ │ ├── store │ │ │ └── store.page.vue │ │ ├── system │ │ │ ├── system-menu │ │ │ │ └── system-menu.page.vue │ │ │ ├── system-role │ │ │ │ └── system-role.page.vue │ │ │ └── system-user │ │ │ │ └── system-user.page.vue │ │ ├── table │ │ │ └── table-example-1.page.vue │ │ └── test │ │ │ └── test.page.vue │ └── service │ │ └── va-list-filter.service.vue └── vm.vue.js ├── static ├── favicon.ico └── logo │ ├── eleme.png │ └── element-ui.svg ├── test.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "modules": false, 7 | "targets": { 8 | "browsers": [ 9 | "> 1%", 10 | "last 2 versions", 11 | "not ie <= 8" 12 | ] 13 | } 14 | } 15 | ], 16 | "stage-2" 17 | ], 18 | "plugins": [ 19 | "transform-vue-jsx", 20 | "transform-runtime", 21 | [ 22 | "component", 23 | { 24 | "libraryName": "element-ui", 25 | "styleLibraryName": "theme-chalk" 26 | } 27 | ] 28 | ] 29 | } 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=JavaScript 2 | *.vue linguist-language=Vue -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | 15 | *.sln 16 | /script/ossConfig/config.js 17 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmmirror.com/ 2 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 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 | }; 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |   4 |   5 |   6 |   7 | 8 |
9 |

vue-admin-template

10 |
11 | 12 | ### 介绍 13 | 14 | `vue-admin-template` 是基于 `vue2.x`+`element-ui` 为基础搭建的后台管理系统模板,可以满足大部分中小应用 15 | 16 | ### 预览 17 | 18 | [深圳节点](http://vue-admin.icode.link) 19 | 20 | ### 文档 21 | 22 | [深圳节点](http://vue-admin.icode.link/#/doc/readme) 23 | 24 | ### 快速开始 25 | 26 | ##### 开发 27 | 28 | > git clone https://github.com/8696/vue-admin-template.git 29 | > 30 | > cd vue-admin-template 31 | > 32 | > yarn 33 | > 34 | > yarn run local:dev 35 | 36 | ##### 打包 37 | 38 | [文档](http://vue-admin.icode.link/#/doc/build) 39 | 40 | 41 | ### 环境支持 42 | 43 | | Chrome|Firefox |Edge| Safari | nodeJs | 44 | | :----:| :----: | :----: |:----: |:------------------------------------------------------------------------------:| 45 | | | | | | | 46 | | * | * | * | * | ^14.0.0 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('./check-versions')(); 3 | 4 | process.env.NODE_ENV = 'production'; 5 | const config = require('../config'); 6 | const {assetsPublicPath, buildNext} = require('./build-next'); 7 | config.build.assetsPublicPath = assetsPublicPath; 8 | const ora = require('ora'); 9 | const rm = require('rimraf'); 10 | const path = require('path'); 11 | const chalk = require('chalk'); 12 | const webpack = require('webpack'); 13 | const webpackConfig = require('./webpack.prod.conf'); 14 | 15 | // const spinner = ora(''); 16 | // spinner.start(); 17 | 18 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 19 | if (err) throw err; 20 | webpack(webpackConfig, (err, stats) => { 21 | // spinner.stop(); 22 | if (err) throw err; 23 | process.stdout.write(stats.toString({ 24 | colors: true, 25 | modules: false, 26 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. 27 | chunks: false, 28 | chunkModules: false 29 | }) + '\n\n'); 30 | 31 | if (stats.hasErrors()) { 32 | console.log(chalk.red(' Build failed with errors.\n')); 33 | process.exit(1); 34 | } 35 | 36 | buildNext(); 37 | 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const chalk = require('chalk'); 3 | const semver = require('semver'); 4 | const packageConfig = require('../package.json'); 5 | const shell = require('shelljs'); 6 | 7 | function exec (cmd) { 8 | return require('child_process').execSync(cmd).toString().trim(); 9 | } 10 | 11 | const versionRequirements = [ 12 | { 13 | name: 'node', 14 | currentVersion: semver.clean(process.version), 15 | versionRequirement: packageConfig.engines.node 16 | } 17 | ]; 18 | 19 | if (shell.which('npm')) { 20 | versionRequirements.push({ 21 | name: 'npm', 22 | currentVersion: exec('npm --version'), 23 | versionRequirement: packageConfig.engines.npm 24 | }); 25 | } 26 | 27 | module.exports = function () { 28 | const warnings = []; 29 | 30 | for (let i = 0; i < versionRequirements.length; i++) { 31 | const mod = versionRequirements[i]; 32 | 33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 34 | warnings.push(mod.name + ': ' + 35 | chalk.red(mod.currentVersion) + ' should be ' + 36 | chalk.green(mod.versionRequirement) 37 | ); 38 | } 39 | } 40 | 41 | if (warnings.length) { 42 | console.log(''); 43 | console.log(chalk.yellow('To use this template, you must update following to modules:')); 44 | console.log(); 45 | 46 | for (let i = 0; i < warnings.length; i++) { 47 | const warning = warnings[i]; 48 | console.log(' ' + warning); 49 | } 50 | 51 | console.log(); 52 | process.exit(1); 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('path'); 3 | const config = require('../config'); 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | const packageConfig = require('../package.json'); 6 | 7 | exports.assetsPath = function (_path) { 8 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 9 | ? config.build.assetsSubDirectory 10 | : config.dev.assetsSubDirectory; 11 | 12 | return path.posix.join(assetsSubDirectory, _path); 13 | }; 14 | 15 | exports.cssLoaders = function (options) { 16 | options = options || {}; 17 | 18 | const cssLoader = { 19 | loader: 'css-loader', 20 | options: { 21 | sourceMap: options.sourceMap 22 | } 23 | }; 24 | 25 | const postcssLoader = { 26 | loader: 'postcss-loader', 27 | options: { 28 | sourceMap: options.sourceMap 29 | } 30 | }; 31 | 32 | // generate loader string to be used with extract text plugin 33 | function generateLoaders (loader, loaderOptions) { 34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]; 35 | 36 | if (loader) { 37 | loaders.push({ 38 | loader: loader + '-loader', 39 | options: Object.assign({}, loaderOptions, { 40 | sourceMap: options.sourceMap 41 | }) 42 | }); 43 | } 44 | 45 | // Extract CSS when that option is specified 46 | // (which is the case during production build) 47 | if (options.extract) { 48 | return ExtractTextPlugin.extract({ 49 | use: loaders, 50 | fallback: 'vue-style-loader' 51 | }); 52 | } else { 53 | return ['vue-style-loader'].concat(loaders); 54 | } 55 | } 56 | 57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 58 | return { 59 | css: generateLoaders(), 60 | postcss: generateLoaders(), 61 | less: generateLoaders('less'), 62 | sass: generateLoaders('sass', {indentedSyntax: true}), 63 | scss: generateLoaders('sass'), 64 | stylus: generateLoaders('stylus'), 65 | styl: generateLoaders('stylus') 66 | }; 67 | }; 68 | 69 | // Generate loaders for standalone style files (outside of .vue) 70 | exports.styleLoaders = function (options) { 71 | const output = []; 72 | const loaders = exports.cssLoaders(options); 73 | 74 | for (const extension in loaders) { 75 | const loader = loaders[extension]; 76 | output.push({ 77 | test: new RegExp('\\.' + extension + '$'), 78 | use: loader 79 | }); 80 | } 81 | 82 | return output; 83 | }; 84 | 85 | exports.createNotifierCallback = () => { 86 | const notifier = require('node-notifier'); 87 | 88 | return (severity, errors) => { 89 | if (severity !== 'error') return; 90 | 91 | const error = errors[0]; 92 | const filename = error.file && error.file.split('!').pop(); 93 | 94 | notifier.notify({ 95 | title: packageConfig.name, 96 | message: severity + ': ' + error.name, 97 | subtitle: filename || '', 98 | icon: path.join(__dirname, 'logo.png') 99 | }); 100 | }; 101 | }; 102 | -------------------------------------------------------------------------------- /build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const utils = require('./utils'); 3 | const config = require('../config'); 4 | const isProduction = process.env.NODE_ENV === 'production'; 5 | const sourceMapEnabled = isProduction 6 | ? config.build.productionSourceMap 7 | : config.dev.cssSourceMap; 8 | 9 | module.exports = { 10 | loaders: utils.cssLoaders({ 11 | sourceMap: sourceMapEnabled, 12 | extract: isProduction 13 | }), 14 | cssSourceMap: sourceMapEnabled, 15 | cacheBusting: config.dev.cacheBusting, 16 | transformToRequire: { 17 | video: ['src', 'poster'], 18 | source: 'src', 19 | img: 'src', 20 | image: 'xlink:href' 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('path'); 3 | const utils = require('./utils'); 4 | const config = require('../config'); 5 | const vueLoaderConfig = require('./vue-loader.conf'); 6 | 7 | function resolve(dir) { 8 | return path.join(__dirname, '..', dir); 9 | } 10 | 11 | module.exports = { 12 | context: path.resolve(__dirname, '../'), 13 | entry: { 14 | app: './src/main.js' 15 | }, 16 | output: { 17 | path: config.build.assetsRoot, 18 | filename: '[name].js', 19 | publicPath: process.env.NODE_ENV === 'production' 20 | ? config.build.assetsPublicPath 21 | : config.dev.assetsPublicPath 22 | }, 23 | resolve: { 24 | extensions: ['.js', '.vue', '.json'], 25 | alias: { 26 | 'vue$': 'vue/dist/vue.esm.js', 27 | '@': resolve('src'), 28 | } 29 | }, 30 | module: { 31 | rules: [ 32 | { 33 | test: /\.vue$/, 34 | loader: 'vue-loader', 35 | options: vueLoaderConfig 36 | }, 37 | { 38 | test: /\.js$/, 39 | loader: 'babel-loader', 40 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] 41 | }, 42 | { 43 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 44 | loader: 'url-loader', 45 | options: { 46 | limit: 1, 47 | name: utils.assetsPath('build/img/[hash:32].[ext]') 48 | } 49 | }, 50 | { 51 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 52 | loader: 'url-loader', 53 | options: { 54 | limit: 1, 55 | name: utils.assetsPath('build/media/[hash:32].[ext]') 56 | } 57 | }, 58 | { 59 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 60 | loader: 'url-loader', 61 | options: { 62 | limit: 1, 63 | name: utils.assetsPath('build/fonts/[hash:32].[ext]') 64 | } 65 | }, 66 | { 67 | test: /\.md$/, 68 | use: [ 69 | { 70 | loader: 'html-loader' 71 | }, 72 | { 73 | loader: 'markdown-loader', 74 | options: { 75 | /* your options here */ 76 | } 77 | } 78 | ] 79 | } 80 | ] 81 | }, 82 | node: { 83 | // prevent webpack from injecting useless setImmediate polyfill because Vue 84 | // source contains it (although only uses it if it's native). 85 | setImmediate: false, 86 | // prevent webpack from injecting mocks to Node native modules 87 | // that does not make sense for the client 88 | dgram: 'empty', 89 | fs: 'empty', 90 | net: 'empty', 91 | tls: 'empty', 92 | child_process: 'empty' 93 | } 94 | }; 95 | -------------------------------------------------------------------------------- /build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const utils = require('./utils'); 3 | const webpack = require('webpack'); 4 | const config = require('../config'); 5 | const merge = require('webpack-merge'); 6 | const path = require('path'); 7 | const baseWebpackConfig = require('./webpack.base.conf'); 8 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 9 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin'); 11 | const portfinder = require('portfinder'); 12 | const HOST = process.env.HOST; 13 | const PORT = process.env.PORT && Number(process.env.PORT); 14 | const address = require('address'); 15 | 16 | const devWebpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({sourceMap: config.dev.cssSourceMap, usePostCSS: true}) 19 | }, 20 | // cheap-module-eval-source-map is faster for development 21 | devtool: config.dev.devtool, 22 | 23 | // these devServer options should be customized in /config/index.js 24 | devServer: { 25 | clientLogLevel: 'warning', 26 | historyApiFallback: { 27 | rewrites: [ 28 | {from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html')}, 29 | ], 30 | }, 31 | hot: true, 32 | contentBase: false, // since we use CopyWebpackPlugin. 33 | compress: true, 34 | host: HOST || config.dev.host, 35 | port: PORT || config.dev.port, 36 | open: config.dev.autoOpenBrowser, 37 | overlay: config.dev.errorOverlay 38 | ? {warnings: false, errors: true} 39 | : false, 40 | publicPath: config.dev.assetsPublicPath, 41 | proxy: config.dev.proxyTable, 42 | quiet: true, // necessary for FriendlyErrorsPlugin 43 | watchOptions: { 44 | poll: config.dev.poll, 45 | } 46 | }, 47 | plugins: [ 48 | new webpack.DefinePlugin({ 49 | 'process.env': require('../config/local/'+ process.env.ENV_CONFIG +'.env') 50 | }), 51 | new webpack.HotModuleReplacementPlugin(), 52 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 53 | new webpack.NoEmitOnErrorsPlugin(), 54 | // https://github.com/ampedandwired/html-webpack-plugin 55 | new HtmlWebpackPlugin({ 56 | filename: 'index.html', 57 | template: 'index.html', 58 | inject: true 59 | }), 60 | // copy custom static assets 61 | new CopyWebpackPlugin([ 62 | { 63 | from: path.resolve(__dirname, '../static'), 64 | to: config.dev.assetsSubDirectory, 65 | ignore: ['.*'] 66 | } 67 | ]) 68 | ] 69 | }); 70 | 71 | module.exports = new Promise((resolve, reject) => { 72 | portfinder.basePort = process.env.PORT || config.dev.port; 73 | portfinder.getPort((err, port) => { 74 | if (err) { 75 | reject(err); 76 | } else { 77 | // publish the new Port, necessary for e2e tests 78 | process.env.PORT = port; 79 | // add port to devServer config 80 | devWebpackConfig.devServer.port = port; 81 | 82 | // Add FriendlyErrorsPlugin 83 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 84 | compilationSuccessInfo: { 85 | messages: [`http://${address.ip()}:${port} | http://localhost:${port}`], 86 | }, 87 | onErrors: config.dev.notifyOnErrors 88 | ? utils.createNotifierCallback() 89 | : undefined 90 | })); 91 | 92 | resolve(devWebpackConfig); 93 | } 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('path'); 3 | const utils = require('./utils'); 4 | const webpack = require('webpack'); 5 | const config = require('../config'); 6 | const merge = require('webpack-merge'); 7 | const baseWebpackConfig = require('./webpack.base.conf'); 8 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 9 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 10 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin'); 12 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 13 | 14 | 15 | const webpackConfig = merge(baseWebpackConfig, { 16 | module: { 17 | rules: utils.styleLoaders({ 18 | sourceMap: config.build.productionSourceMap, 19 | extract: true, 20 | usePostCSS: true 21 | }) 22 | }, 23 | devtool: config.build.productionSourceMap ? config.build.devtool : false, 24 | output: { 25 | path: config.build.assetsRoot, 26 | filename: utils.assetsPath('build/js/core/[name]-vue.js'), 27 | chunkFilename: utils.assetsPath('build/js/page/[id]-[hash:32]-chunk.js') 28 | }, 29 | plugins: [ 30 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 31 | new webpack.DefinePlugin({ 32 | 'process.env': require('../config/build/' + process.env.ENV_CONFIG + '.env') 33 | }), 34 | new UglifyJsPlugin({ 35 | uglifyOptions: { 36 | compress: { 37 | warnings: false 38 | } 39 | }, 40 | sourceMap: config.build.productionSourceMap, 41 | parallel: true 42 | }), 43 | // extract css into its own file 44 | new ExtractTextPlugin({ 45 | filename: utils.assetsPath('build/css/core/[name]-vue.css'), 46 | // Setting the following option to `false` will not extract CSS from codesplit chunks. 47 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. 48 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 49 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 50 | allChunks: true, 51 | }), 52 | // Compress extracted CSS. We are using this plugin so that possible 53 | // duplicated CSS from different components can be deduped. 54 | new OptimizeCSSPlugin({ 55 | cssProcessorOptions: config.build.productionSourceMap 56 | ? {safe: true, map: {inline: false}} 57 | : {safe: true} 58 | }), 59 | // generate dist index.html with correct asset hash for caching. 60 | // you can customize output by editing /index.html 61 | // see https://github.com/ampedandwired/html-webpack-plugin 62 | new HtmlWebpackPlugin({ 63 | filename: config.build.index, 64 | template: 'index.html', 65 | inject: true, 66 | minify: { 67 | removeComments: true, 68 | collapseWhitespace: true, 69 | removeAttributeQuotes: true 70 | // more options: 71 | // https://github.com/kangax/html-minifier#options-quick-reference 72 | }, 73 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 74 | chunksSortMode: 'dependency' 75 | }), 76 | // keep module.id stable when vendor modules does not change 77 | new webpack.HashedModuleIdsPlugin(), 78 | // enable scope hoisting 79 | new webpack.optimize.ModuleConcatenationPlugin(), 80 | // split vendor js into its own file 81 | new webpack.optimize.CommonsChunkPlugin({ 82 | name: 'vendor', 83 | minChunks(module) { 84 | // any required modules inside node_modules are extracted to vendor 85 | return ( 86 | module.resource && 87 | /\.js$/.test(module.resource) && 88 | module.resource.indexOf( 89 | path.join(__dirname, '../node_modules') 90 | ) === 0 91 | ); 92 | } 93 | }), 94 | // extract webpack runtime and module manifest to its own file in order to 95 | // prevent vendor hash from being updated whenever app bundle is updated 96 | new webpack.optimize.CommonsChunkPlugin({ 97 | name: 'manifest', 98 | minChunks: Infinity 99 | }), 100 | // This instance extracts shared chunks from code splitted chunks and bundles them 101 | // in a separate chunk, similar to the vendor chunk 102 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk 103 | new webpack.optimize.CommonsChunkPlugin({ 104 | name: 'app', 105 | async: 'vendor-async', 106 | children: true, 107 | minChunks: 3 108 | }), 109 | 110 | // copy custom static assets 111 | new CopyWebpackPlugin([ 112 | { 113 | from: path.resolve(__dirname, '../static'), 114 | to: config.build.assetsSubDirectory, 115 | ignore: ['.*'] 116 | } 117 | ]) 118 | ] 119 | }); 120 | 121 | if (config.build.productionGzip) { 122 | const CompressionWebpackPlugin = require('compression-webpack-plugin'); 123 | 124 | webpackConfig.plugins.push( 125 | new CompressionWebpackPlugin({ 126 | asset: '[path].gz[query]', 127 | algorithm: 'gzip', 128 | test: new RegExp( 129 | '\\.(' + 130 | config.build.productionGzipExtensions.join('|') + 131 | ')$' 132 | ), 133 | threshold: 10240, 134 | minRatio: 0.8 135 | }) 136 | ); 137 | } 138 | 139 | if (config.build.bundleAnalyzerReport) { 140 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 141 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()); 142 | } 143 | 144 | module.exports = webpackConfig; 145 | -------------------------------------------------------------------------------- /config/build.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | buildRootStaticPath: '/' 3 | }; 4 | -------------------------------------------------------------------------------- /config/build/beta.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"', 3 | ENV_CONFIG: '"beta"' 4 | }; 5 | -------------------------------------------------------------------------------- /config/build/dev.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"', 3 | ENV_CONFIG: '"development"' 4 | }; 5 | -------------------------------------------------------------------------------- /config/build/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"', 3 | ENV_CONFIG: '"production"' 4 | }; 5 | -------------------------------------------------------------------------------- /config/build/test.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"', 3 | ENV_CONFIG: '"test"' 4 | }; 5 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path'); 6 | module.exports = { 7 | dev: { 8 | // Paths 9 | assetsSubDirectory: 'static', 10 | assetsPublicPath: '/', 11 | proxyTable: {}, 12 | 13 | // Various Dev Server settings 14 | // host: address.ip(), 15 | host: '0.0.0.0', 16 | port: 8910, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 17 | autoOpenBrowser: false, 18 | errorOverlay: true, 19 | notifyOnErrors: true, 20 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 21 | 22 | /** 23 | * Source Maps 24 | */ 25 | 26 | // https://webpack.js.org/configuration/devtool/#development 27 | devtool: 'cheap-module-eval-source-map', 28 | 29 | // If you have problems debugging vue-files in devtools, 30 | // set this to false - it *may* help 31 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 32 | cacheBusting: true, 33 | 34 | cssSourceMap: true 35 | }, 36 | 37 | build: { 38 | // Template for index.html 39 | index: path.resolve(__dirname, '../dist/index.html'), 40 | 41 | // Paths 42 | assetsRoot: path.resolve(__dirname, '../dist'), 43 | assetsSubDirectory: 'static', 44 | assetsPublicPath: '/', 45 | 46 | /** 47 | * Source Maps 48 | */ 49 | 50 | productionSourceMap: false, 51 | // https://webpack.js.org/configuration/devtool/#production 52 | devtool: '#source-map', 53 | 54 | // Gzip off by default as many popular static hosts such as 55 | // Surge or Netlify already gzip all static assets for you. 56 | // Before setting to `true`, make sure to: 57 | // npm install --save-dev compression-webpack-plugin 58 | productionGzip: false, 59 | productionGzipExtensions: ['js', 'css'], 60 | 61 | // Run the build command with an extra argument to 62 | // View the bundle analyzer report after build finishes: 63 | // `npm run build --report` 64 | // Set to `true` or `false` to always turn it on or off 65 | bundleAnalyzerReport: process.env.npm_config_report 66 | } 67 | }; 68 | -------------------------------------------------------------------------------- /config/local/beta.env.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | NODE_ENV: '"development"', 4 | ENV_CONFIG: '"beta"' 5 | } 6 | -------------------------------------------------------------------------------- /config/local/dev.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"development"', 3 | ENV_CONFIG: '"development"' 4 | } 5 | -------------------------------------------------------------------------------- /config/local/prod.env.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | NODE_ENV: '"development"', 4 | ENV_CONFIG: '"production"' 5 | } 6 | -------------------------------------------------------------------------------- /config/local/test.env.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | NODE_ENV: '"development"', 4 | ENV_CONFIG: '"test"' 5 | } 6 | -------------------------------------------------------------------------------- /git.code.lines.js: -------------------------------------------------------------------------------- 1 | const {execSync} = require('child_process'); 2 | 3 | console.log(execSync('git log --pretty=tformat: --numstat | awk \'{ add += $1; subs += $2; loc += $1 - $2 } END { printf "added: %s | removed: %s | total: %s\\n", add, subs, loc }\'').toString()) 4 | -------------------------------------------------------------------------------- /git.push.js: -------------------------------------------------------------------------------- 1 | 2 | const {execSync} = require('child_process'); 3 | 4 | execSync('git add .'); 5 | 6 | execSync('git commit -m "' + new Date().toString() + '"'); 7 | 8 | execSync('git push origin dev'); 9 | 10 | 11 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | Vue Admin template 8 | <% if (process.env.NODE_ENV === 'production') { %> 9 | 10 | <% }else { %> 11 | 12 | <% } %> 13 | 14 | <% if (process.env.NODE_ENV === 'production') { %> 15 | <% }else { %> 16 | 19 | <% } %> 20 | 21 | 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-admin-template", 3 | "version": "0.0.2019", 4 | "description": "基于vue2.x+element-ui为基础搭建的后台管理系统模板,可以满足大部分中小应用", 5 | "scripts": { 6 | "local:dev": "cross-env ENV_CONFIG=dev webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 7 | "local:test": "cross-env ENV_CONFIG=test webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 8 | "local:beta": "cross-env ENV_CONFIG=beta webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "local:prod": "cross-env ENV_CONFIG=prod webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 10 | "build:dev": "cross-env ENV_CONFIG=dev node build/build.js production", 11 | "build:test": "cross-env ENV_CONFIG=test node build/build.js production", 12 | "build:beta": "cross-env ENV_CONFIG=beta node build/build.js production", 13 | "build:prod": "cross-env ENV_CONFIG=prod node build/build.js production", 14 | "put:oss": "node script/putOss.js" 15 | }, 16 | "private": false, 17 | "author": { 18 | "name": "longjinwen", 19 | "email": "204084802@qq.com" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/8696/vue-admin-template" 24 | }, 25 | "bugs": { 26 | "url": "https://github.com/8696/vue-admin-template/issues" 27 | }, 28 | "homepage": "https://github.com/8696/vue-admin-template#readme", 29 | "license": "ISC", 30 | "dependencies": {}, 31 | "devDependencies": { 32 | "@antv/data-set": "^0.11.8", 33 | "@antv/g2": "^4.2.9", 34 | "@fortawesome/fontawesome-svg-core": "^1.2.36", 35 | "@fortawesome/free-solid-svg-icons": "^5.15.4", 36 | "@fortawesome/vue-fontawesome": "^0.1.10", 37 | "address": "^1.2.2", 38 | "ali-oss": "^6.17.1", 39 | "autoprefixer": "^7.2.6", 40 | "axios": "^0.27.2", 41 | "babel-core": "^6.26.3", 42 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 43 | "babel-loader": "^7.1.5", 44 | "babel-plugin-component": "^1.1.1", 45 | "babel-plugin-syntax-jsx": "^6.18.0", 46 | "babel-plugin-transform-runtime": "^6.23.0", 47 | "babel-plugin-transform-vue-jsx": "^3.7.0", 48 | "babel-polyfill": "^6.26.0", 49 | "babel-preset-env": "^1.7.0", 50 | "babel-preset-stage-2": "^6.24.1", 51 | "chalk": "^2.4.2", 52 | "cheerio": "^1.0.0-rc.12", 53 | "copy-webpack-plugin": "^4.6.0", 54 | "cross-env": "^7.0.3", 55 | "css-loader": "^0.28.11", 56 | "element-ui": "^2.15.13", 57 | "es6-promise": "^4.2.8", 58 | "extract-text-webpack-plugin": "^3.0.0", 59 | "file-loader": "^1.1.11", 60 | "friendly-errors-webpack-plugin": "^1.7.0", 61 | "fs-extra": "^8.1.0", 62 | "glob": "^7.2.3", 63 | "highlight.js": "^10.7.3", 64 | "html-loader": "^1.3.2", 65 | "html-webpack-plugin": "^2.30.1", 66 | "js-big-decimal": "^1.4.1", 67 | "koa": "^2.14.1", 68 | "koa2-static2": "^1.0.0", 69 | "lodash": "^4.17.21", 70 | "markdown-loader": "^5.1.0", 71 | "marked": "^1.2.9", 72 | "mavon-editor": "^2.10.4", 73 | "node-notifier": "^5.4.5", 74 | "nprogress": "^0.2.0", 75 | "optimize-css-assets-webpack-plugin": "^3.2.1", 76 | "ora": "^1.4.0", 77 | "portfinder": "^1.0.32", 78 | "postcss-import": "^11.1.0", 79 | "postcss-loader": "^2.1.6", 80 | "postcss-url": "^7.3.2", 81 | "qs": "^6.11.0", 82 | "rimraf": "^2.7.1", 83 | "sass": "1.58.3", 84 | "sass-loader": "^7.3.1", 85 | "screenfull": "^5.2.0", 86 | "semver": "^5.7.1", 87 | "shelljs": "^0.8.5", 88 | "uglifyjs-webpack-plugin": "^1.3.0", 89 | "url-loader": "^0.6.2", 90 | "vue": "^2.7.14", 91 | "vue-clipboard2": "^0.3.3", 92 | "vue-loader": "^13.7.3", 93 | "vue-property-decorator": "^8.5.1", 94 | "vue-router": "^3.6.5", 95 | "vue-style-loader": "^3.1.2", 96 | "vue-template-compiler": "^2.7.14", 97 | "vuescroll": "^4.17.5", 98 | "vuex": "^3.6.2", 99 | "wangeditor": "^3.1.1", 100 | "webpack": "^3.12.0", 101 | "webpack-bundle-analyzer": "^2.13.1", 102 | "webpack-dev-server": "^2.11.5", 103 | "webpack-merge": "^4.2.2" 104 | }, 105 | "engines": { 106 | "node": ">= 14.0.0", 107 | "npm": ">= 6.0.0" 108 | }, 109 | "browserslist": [ 110 | "> 1%", 111 | "last 2 versions", 112 | "not ie <= 8" 113 | ] 114 | } 115 | -------------------------------------------------------------------------------- /progress: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8696/vue-admin-template/b463739db96c2c4b20290f824bb44a5b4760dbd2/progress -------------------------------------------------------------------------------- /readme/chrome_48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8696/vue-admin-template/b463739db96c2c4b20290f824bb44a5b4760dbd2/readme/chrome_48x48.png -------------------------------------------------------------------------------- /readme/edge_48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8696/vue-admin-template/b463739db96c2c4b20290f824bb44a5b4760dbd2/readme/edge_48x48.png -------------------------------------------------------------------------------- /readme/firefox_48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8696/vue-admin-template/b463739db96c2c4b20290f824bb44a5b4760dbd2/readme/firefox_48x48.png -------------------------------------------------------------------------------- /readme/node.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8696/vue-admin-template/b463739db96c2c4b20290f824bb44a5b4760dbd2/readme/node.jpeg -------------------------------------------------------------------------------- /readme/nodejs_48_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8696/vue-admin-template/b463739db96c2c4b20290f824bb44a5b4760dbd2/readme/nodejs_48_48.png -------------------------------------------------------------------------------- /readme/safari_48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8696/vue-admin-template/b463739db96c2c4b20290f824bb44a5b4760dbd2/readme/safari_48x48.png -------------------------------------------------------------------------------- /readme/vue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8696/vue-admin-template/b463739db96c2c4b20290f824bb44a5b4760dbd2/readme/vue.png -------------------------------------------------------------------------------- /script/ossConfig/example.config.js: -------------------------------------------------------------------------------- 1 | module.exports = () => { 2 | return { 3 | region: '', 4 | accessKeyId: '', 5 | accessKeySecret: '', 6 | bucket: '' 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /script/putOss.js: -------------------------------------------------------------------------------- 1 | const glob = require("glob") 2 | const path = require("path") 3 | const fs = require("fs") 4 | const OSS = require('ali-oss'); 5 | const config = require('./ossConfig/config') 6 | 7 | const client = new OSS(config()); 8 | 9 | client.listV2({ 10 | 'max-keys': 1000 11 | }) 12 | .then(async (res) => { 13 | 14 | for (let i = 0; i < res.objects.length; i++) { 15 | const item = res.objects[i] 16 | if (!/readme\.img/.test(item.name)) { 17 | console.log('remove:' + item.name) 18 | await client.delete(item.name) 19 | } 20 | } 21 | 22 | glob(path.resolve(__dirname, '../dist/production-static/**/*'), async (err, files) => { 23 | if (err) return; 24 | for (let i = 0; i < files.length; i++) { 25 | let file = files[i] 26 | if (!fs.statSync(file).isDirectory()) { 27 | console.log('put:' + file) 28 | file = file.replace(path.resolve(__dirname, '../dist/production-static') + '/', ''); 29 | const isHtml = /\.html$/.test(file) 30 | console.log(file) 31 | await client.put(file, files[i], { 32 | headers: JSON.parse(JSON.stringify({ 33 | 'Cache-Control': isHtml ? 'no-store' : 'max-age=' + 3600 * 24, 34 | 'Expires': isHtml ? -1 : undefined, 35 | 'Pragma': isHtml ? 'no-cache': undefined 36 | })) 37 | }) 38 | } 39 | } 40 | }) 41 | }) 42 | 43 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const Koa = require('koa'); 2 | const app = new Koa(); 3 | const path = require('path'); 4 | const address = require('address'); 5 | const port = 8912; 6 | 7 | require('koa2-static2').install(app, 8 | path.resolve(__dirname, './dist/production-static'), 9 | {maxAge: 3 * 1000}); 10 | 11 | app.listen(port); 12 | 13 | console.log(`http://${address.ip()}:${port}`); 14 | -------------------------------------------------------------------------------- /src/api/api.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | import * as demo from './module/demo'; 4 | 5 | export default { 6 | demo 7 | }; 8 | -------------------------------------------------------------------------------- /src/api/http.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | class Http { 4 | 5 | request(config) { 6 | return new Promise((resolve, reject) => { 7 | axios.create({ 8 | baseURL: 'http://localhost', 9 | timeout: 1000 * 60, 10 | validateStatus: function (status) { 11 | return status === 200; 12 | } 13 | }) 14 | .request(config) 15 | .then(response => { 16 | 17 | // 这里进行判断后端响应情况 18 | if (response.data.code === 0) { 19 | return resolve(response.data); 20 | } else { 21 | return reject({httpStatus: 200, message: response.data.message}); 22 | } 23 | 24 | }) 25 | .catch(error => { 26 | if (error.response) { 27 | // 服务端抛出非200状态码 28 | return reject({httpStatus: error.response.status, message: `Network error(${error.response.status})`}); 29 | } 30 | // 服务端无响应,或者断网 31 | reject({httpStatus: 0, message: 'Network error(0)'}); 32 | 33 | }); 34 | 35 | }); 36 | 37 | } 38 | 39 | } 40 | 41 | export default Http; 42 | -------------------------------------------------------------------------------- /src/api/module/demo.js: -------------------------------------------------------------------------------- 1 | import Http from '../http'; 2 | 3 | import * as httpUtils from '../utils'; 4 | 5 | /** 6 | * @description 测试 get 请求 7 | * @param param {Object} 8 | * */ 9 | export function get(param = {}) { 10 | return new Http().request({ 11 | url: '/get', 12 | params: param, 13 | method: 'get' 14 | }); 15 | } 16 | 17 | /** 18 | * @description 测试 post 请求 19 | * @param param {Object} 20 | * */ 21 | export function post(param = {}) { 22 | return new Http().request({ 23 | url: '/post', 24 | // data: httpUtils.makeFormData(param), // multipart/form-data 25 | // data: httpUtils.makeQueryString(param), // application/x-www-form-urlencoded 26 | data: param, // application/json 27 | method: 'post' 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /src/api/utils.js: -------------------------------------------------------------------------------- 1 | import qs from 'qs'; 2 | 3 | /** 4 | * @deprecated 将对象转成 FormData 5 | * @param object {Object} 6 | * @return {FormData} 7 | * */ 8 | export function makeFormData (object) { 9 | let formData = new FormData(); 10 | Object.keys(object).forEach(key => { 11 | formData.append(key, object[key]); 12 | }); 13 | return formData; 14 | } 15 | 16 | /** 17 | * @deprecated 将对象转成 query string 18 | * @param object {Object} 19 | * @return {String} 20 | * */ 21 | export function makeQueryString (object) { 22 | return qs.stringify(object); 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/assets/images/avatar.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8696/vue-admin-template/b463739db96c2c4b20290f824bb44a5b4760dbd2/src/assets/images/avatar.jpeg -------------------------------------------------------------------------------- /src/assets/scss/app.scss: -------------------------------------------------------------------------------- 1 | *, 2 | *::before, 3 | *::after { 4 | margin: 0; 5 | box-sizing: border-box; 6 | } 7 | 8 | body { 9 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 10 | color: #212529; 11 | } 12 | 13 | html, body, #app { 14 | width: 100%; 15 | height: 100%; 16 | } 17 | body{ 18 | #nprogress .peg{ 19 | box-shadow: none; 20 | display: none; 21 | } 22 | #nprogress .spinner-icon{ 23 | border-top-color: #409EFF; 24 | border-left-color: rgba(0,0,0,0); 25 | } 26 | #nprogress .bar{ 27 | background-image: linear-gradient(to right, #41b883, #41b883, #409EFF); 28 | } 29 | #nprogress .spinner{ 30 | top: 20px; 31 | } 32 | } 33 | @import "common"; 34 | @import "base-layout.component"; 35 | @import "card.component"; 36 | @import "plus"; 37 | -------------------------------------------------------------------------------- /src/assets/scss/card.component.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | /** 3 | * Created by VsCode 4 | * Description: next.. 5 | * User: JinwenLong 6 | * Author: longjinwen 7 | * Email: 204084802@qq.com 8 | * Date: 2020/4/26 9 | * Time: 9:17 下午 10 | */ 11 | .row { 12 | display: flex; 13 | padding: 0 15px; 14 | } 15 | 16 | .el-row { 17 | width: 100%; 18 | padding-left: 15px; 19 | padding-right: 15px; 20 | 21 | .card { 22 | margin: 15px 15px; 23 | } 24 | } 25 | 26 | .card { 27 | margin: 15px 30px; 28 | background-color: #ffffff; 29 | box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08); 30 | 31 | .card-header { 32 | font-size: 14px; 33 | padding-bottom: 15px; 34 | padding-top: 15px; 35 | padding-left: 15px; 36 | padding-right: 15px; 37 | border-bottom: 1px solid #ebebeb; 38 | 39 | } 40 | 41 | .card-body { 42 | padding-bottom: 15px; 43 | padding-top: 15px; 44 | padding-left: 15px; 45 | padding-right: 15px; 46 | width: 100%; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/assets/scss/common.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | 4 | // 全居中 5 | .flex-center { 6 | display: flex; 7 | justify-content: center; 8 | align-items: center; 9 | } 10 | 11 | // 垂直居中 12 | .flex-align-items-center { 13 | display: flex; 14 | align-items: center; 15 | } 16 | 17 | // 左右居中 18 | .flex-justify-content-center { 19 | display: flex; 20 | justify-content: center; 21 | } 22 | 23 | // 上下排列 24 | .flex-direction-column { 25 | display: flex; 26 | flex-direction: column; 27 | } 28 | 29 | // 左右排列 30 | .flex-direction-row { 31 | display: flex; 32 | flex-direction: row; 33 | } 34 | 35 | .flex-justify-content-space-between { 36 | display: flex; 37 | justify-content: space-between; 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/assets/scss/plus.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | /** 3 | * Created by VsCode 4 | * Description: next.. 5 | * User: JinwenLong 6 | * Author: longjinwen 7 | * Email: 204084802@qq.com 8 | * Date: 2020/2/30 9 | * Time: 7:40 下午 10 | */ 11 | 12 | .init-enter-active, 13 | .init-leave-active { 14 | transition: all .3s cubic-bezier(0, 0.1, 0.25, .8), opacity 1s ease-out; 15 | transform: translate(0, 0); 16 | } 17 | 18 | .init-enter, 19 | .init-leave-to { 20 | opacity: 0; 21 | transform: translate(30%, 0); 22 | } 23 | 24 | // 25 | .router-transition-enter-active, 26 | .router-transition-leave-active { 27 | transition: all .3s cubic-bezier(0, 0.1, 0.25, .8), opacity 1s ease-out; 28 | transform: translate(0, 0); 29 | } 30 | 31 | .router-transition-enter, 32 | .router-transition-leave-to { 33 | opacity: 0; 34 | transform: translate(30%, 0); 35 | } 36 | 37 | // 滚动条 38 | $scrollbar-color: rgba(0, 0, 0, 0); 39 | ::-webkit-scrollbar { 40 | width: 6px; 41 | height: 6px; 42 | background-color: $scrollbar-color; 43 | } 44 | 45 | ::-webkit-scrollbar-track { 46 | background-color: $scrollbar-color; 47 | } 48 | 49 | ::-webkit-scrollbar-thumb { 50 | border-radius: 4px; 51 | background-color: #d0d0d0; 52 | } 53 | -------------------------------------------------------------------------------- /src/components/global.components.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by VsCode 3 | * Description: 注册全局组件 4 | * User: JinwenLong 5 | * Author: longjinwen 6 | * Email: 204084802@qq.com 7 | * Date: 2020/5/1 8 | * Time: 1:41 下午 9 | */ 10 | 11 | 12 | export default function install (Vue) { 13 | Vue.component('va-container', () => import('@/views/global/va-container.component')); 14 | Vue.component('va-table', () => import('@/views/global/va-table.component')); 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/components/service.components.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by VsCode 3 | * Description: 注册以服务调用的组件 4 | * User: JinwenLong 5 | * Author: longjinwen 6 | * Email: 204084802@qq.com 7 | * Date: 2020/5/3 8 | * Time: 5:10 下午 9 | */ 10 | 11 | import ListFilterComponent from '../views/service/va-list-filter.service'; 12 | 13 | export default function install(Vue) { 14 | /** 15 | * $vaListFilter 16 | * */ 17 | let VaListFilterComponentConstructor = Vue.extend(ListFilterComponent); 18 | 19 | Vue.prototype.$vaTableFilter = function (data) { 20 | let visible = data.visible; 21 | data.visible = false; 22 | let instance = new VaListFilterComponentConstructor({ 23 | el: document.createElement('div'), 24 | data 25 | }); 26 | document.body.appendChild(instance.$el); 27 | Vue.nextTick(() => { 28 | [undefined, true].includes(visible) && instance.show(); 29 | }); 30 | return instance; 31 | }; 32 | 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @description 配置文件 4 | * */ 5 | 6 | /** 7 | * @description 文档后缀 8 | * */ 9 | export const titleSuffix = ' - Vue Admin template'; 10 | 11 | /** 12 | * @description 开启权限 13 | * @type {Boolean} 14 | * */ 15 | export const permission = true 16 | 17 | /** 18 | * @description 权限白名单 19 | * */ 20 | export const permissionWhiteList = [ 21 | 'personal', '404', '401', 'reload' 22 | ]; 23 | 24 | /** 25 | * @description 路由白名单 26 | * */ 27 | export const routeWhiteList = [ 28 | 'login' 29 | ]; 30 | 31 | /** 32 | * @description localStorage token key 33 | * */ 34 | export const localStorageTokenKey = 'token' 35 | -------------------------------------------------------------------------------- /src/directive/permission.directive.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 方式一: 3 | * 4 | * 推荐:*** 5 | * 将被作用的元素的display值设置为none和还原设置 6 | * 优点: 7 | * 1、可以动态设置权限 8 | * 缺点: 9 | * 1、消耗性能,因为如果该元素具有事情的情况下依然会进行绑定 10 | * 2、css设置问题,因为该元素只是简单的进行隐藏。在css设置元素顺序可能会产生问题 11 | * 12 | * */ 13 | 14 | function is(el, binding, vnode) { 15 | let value = Array.isArray(binding.value) ? binding.value : [binding.value]; 16 | let has = vnode.context.hasPermission(value); 17 | if (has) { 18 | el.style.display = el.getAttribute('display') || getComputedStyle(el).display; 19 | } else { 20 | !el.getAttribute('display') && el.setAttribute('display', getComputedStyle(el).display); 21 | el.style.display = 'none'; 22 | } 23 | } 24 | 25 | /** 26 | * 方式二: 27 | * 28 | * 推荐:***** 29 | * 将被作用的元素的进行移除 30 | * 优点: 31 | * 1、节省性能、会自动进行事件解绑 32 | * 2、由于元素被删除,css等设置相对好设置 33 | * 缺点: 34 | * 1、不能进行动态的设置权限,因为移除后无法再进行还原 35 | * 36 | * */ 37 | /* 38 | function is(el, {value}, vnode) { 39 | value = Array.isArray(value) ? value : [value]; 40 | let has = vnode.context.hasPermission(value); 41 | if (!has) { 42 | el.remove(); 43 | } 44 | } 45 | */ 46 | 47 | 48 | export default { 49 | install(Vue) { 50 | Vue.directive('permission', { 51 | inserted: function (...args) { 52 | is(...args); 53 | }, 54 | update(...args) { 55 | is(...args); 56 | } 57 | }); 58 | } 59 | }; 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/element-ui/element-ui.js: -------------------------------------------------------------------------------- 1 | import { 2 | Pagination, 3 | Dialog, 4 | // Autocomplete, 5 | Dropdown, 6 | DropdownMenu, 7 | DropdownItem, 8 | Menu, 9 | Submenu, 10 | MenuItem, 11 | MenuItemGroup, 12 | Input, 13 | InputNumber, 14 | Radio, 15 | RadioGroup, 16 | // RadioButton, 17 | Checkbox, 18 | // CheckboxButton, 19 | CheckboxGroup, 20 | // Switch, 21 | Select, 22 | Option, 23 | // OptionGroup, 24 | Button, 25 | // ButtonGroup, 26 | Table, 27 | TableColumn, 28 | // DatePicker, 29 | // TimeSelect, 30 | // TimePicker, 31 | Popover, 32 | Tooltip, 33 | Breadcrumb, 34 | BreadcrumbItem, 35 | Form, 36 | FormItem, 37 | // Tabs, 38 | // TabPane, 39 | Tag, 40 | Tree, 41 | Alert, 42 | // Slider, 43 | // Icon, 44 | Row, 45 | Col, 46 | Upload, 47 | // Progress, 48 | // Spinner, 49 | // Badge, 50 | Card, 51 | // Rate, 52 | // Steps, 53 | // Step, 54 | // Carousel, 55 | // CarouselItem, 56 | // Collapse, 57 | // CollapseItem, 58 | Cascader, 59 | // ColorPicker, 60 | // Transfer, 61 | Container, 62 | Header, 63 | Aside, 64 | Main, 65 | Footer, 66 | // Timeline, 67 | // TimelineItem, 68 | Link, 69 | // Divider, 70 | // Image, 71 | // Calendar, 72 | // Backtop, 73 | // PageHeader, 74 | // CascaderPanel, 75 | Loading, 76 | MessageBox, 77 | Message, 78 | // Notification, 79 | // Drawer 80 | } from 'element-ui'; 81 | 82 | export default function install(Vue) { 83 | Vue.use(Pagination); 84 | Vue.use(Dialog); 85 | // Vue.use(Autocomplete); 86 | Vue.use(Dropdown); 87 | Vue.use(DropdownMenu); 88 | Vue.use(DropdownItem); 89 | Vue.use(Menu); 90 | Vue.use(Submenu); 91 | Vue.use(MenuItem); 92 | Vue.use(MenuItemGroup); 93 | Vue.use(Input); 94 | Vue.use(InputNumber); 95 | Vue.use(Radio); 96 | Vue.use(RadioGroup); 97 | // Vue.use(RadioButton); 98 | Vue.use(Checkbox); 99 | // Vue.use(CheckboxButton); 100 | Vue.use(CheckboxGroup); 101 | // Vue.use(Switch); 102 | Vue.use(Select); 103 | Vue.use(Option); 104 | // Vue.use(OptionGroup); 105 | Vue.use(Button); 106 | // Vue.use(ButtonGroup); 107 | Vue.use(Table); 108 | Vue.use(TableColumn); 109 | // Vue.use(DatePicker); 110 | // Vue.use(TimeSelect); 111 | // Vue.use(TimePicker); 112 | Vue.use(Popover); 113 | Vue.use(Tooltip); 114 | Vue.use(Breadcrumb); 115 | Vue.use(BreadcrumbItem); 116 | Vue.use(Form); 117 | Vue.use(FormItem); 118 | // Vue.use(Tabs); 119 | // Vue.use(TabPane); 120 | Vue.use(Tag); 121 | Vue.use(Tree); 122 | Vue.use(Alert); 123 | // Vue.use(Slider); 124 | // Vue.use(Icon); 125 | Vue.use(Row); 126 | Vue.use(Col); 127 | Vue.use(Upload); 128 | // Vue.use(Progress); 129 | // Vue.use(Spinner); 130 | // Vue.use(Badge); 131 | Vue.use(Card); 132 | // Vue.use(Rate); 133 | // Vue.use(Steps); 134 | // Vue.use(Step); 135 | // Vue.use(Carousel); 136 | // Vue.use(CarouselItem); 137 | // Vue.use(Collapse); 138 | // Vue.use(CollapseItem); 139 | Vue.use(Cascader); 140 | // Vue.use(ColorPicker); 141 | // Vue.use(Transfer); 142 | Vue.use(Container); 143 | Vue.use(Header); 144 | Vue.use(Aside); 145 | Vue.use(Main); 146 | Vue.use(Footer); 147 | // Vue.use(Timeline); 148 | // Vue.use(TimelineItem); 149 | Vue.use(Link); 150 | // Vue.use(Divider); 151 | // Vue.use(Image); 152 | // Vue.use(Calendar); 153 | // Vue.use(Backtop); 154 | // Vue.use(PageHeader); 155 | // Vue.use(CascaderPanel); 156 | // Vue.use(Drawer); 157 | // 158 | Vue.use(Loading.directive); 159 | 160 | Vue.prototype.$loading = Loading.service; 161 | Vue.prototype.$msgbox = MessageBox; 162 | Vue.prototype.$alert = MessageBox.alert; 163 | Vue.prototype.$confirm = MessageBox.confirm; 164 | // Vue.prototype.$prompt = MessageBox.prompt; 165 | // Vue.prototype.$notify = Notification; 166 | Vue.prototype.$message = Message; 167 | } 168 | -------------------------------------------------------------------------------- /src/filter/global.filter.js: -------------------------------------------------------------------------------- 1 | 2 | export {parseDateTime} from '../utils/utils'; 3 | -------------------------------------------------------------------------------- /src/font-awesome/font-awesome.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description fort awesome 字体 3 | * @doc https://fontawesome.com/ 4 | * */ 5 | 6 | // 全量引入 7 | /* 8 | import {library} from '@fortawesome/fontawesome-svg-core'; 9 | import {fas} from '@fortawesome/free-solid-svg-icons'; 10 | import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome'; 11 | Object.keys(fas).forEach(item => { 12 | library.add(fas[item]); 13 | }); 14 | */ 15 | 16 | // 按需引入 17 | import {library} from '@fortawesome/fontawesome-svg-core'; 18 | import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome'; 19 | 20 | import { 21 | faTachometerAlt, 22 | faFileAlt, 23 | faChartArea, 24 | faTable, 25 | faLink, 26 | faBars, 27 | faShoppingCart, 28 | faUsers, 29 | faMoneyBillAlt, 30 | faCity, 31 | faStickyNote, 32 | faMarker, 33 | faMapMarked, 34 | faFont, 35 | faCopy, 36 | faLaptopCode, 37 | faStore, 38 | faThumbtack, 39 | faCode, 40 | faEdit, 41 | faBraille, 42 | faColumns, 43 | faCogs, 44 | faDivide, 45 | faUserLock, 46 | faSync 47 | } from '@fortawesome/free-solid-svg-icons'; 48 | 49 | library.add(faTachometerAlt); 50 | library.add(faFileAlt); 51 | library.add(faChartArea); 52 | library.add(faTable); 53 | library.add(faLink); 54 | library.add(faBars); 55 | library.add(faShoppingCart); 56 | library.add(faUsers); 57 | library.add(faMoneyBillAlt); 58 | library.add(faCity); 59 | library.add(faMapMarked); 60 | library.add(faMarker); 61 | library.add(faStickyNote); 62 | library.add(faFont); 63 | library.add(faCopy); 64 | library.add(faLaptopCode); 65 | library.add(faStore); 66 | library.add(faThumbtack); 67 | library.add(faCode); 68 | library.add(faEdit); 69 | library.add(faBraille); 70 | library.add(faColumns); 71 | library.add(faCogs); 72 | library.add(faDivide); 73 | library.add(faUserLock); 74 | library.add(faSync); 75 | 76 | 77 | export default function install(Vue) { 78 | Vue.component('font-awesome-icon', FontAwesomeIcon); 79 | } 80 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | 4 | import Vue from 'vue'; 5 | import router from './route/route'; 6 | import store from './store/store'; 7 | 8 | console.log('vue version: ' + Vue.version); 9 | console.log(process.env.NODE_ENV); 10 | console.log(process.env.ENV_CONFIG); 11 | 12 | /** 13 | * @description 全局 css 14 | * */ 15 | import './assets/scss/app.scss'; 16 | 17 | /** 18 | * @description 自定义 vm 实例 19 | * */ 20 | import vm from './vm.vue'; 21 | 22 | Vue.vm = Vue.prototype.vm = vm; 23 | 24 | /** 25 | * @description 全局 mixin 26 | * */ 27 | const __mixins = () => import('./mixins/__mixins'); 28 | 29 | /** 30 | * @description 全局 filter 31 | * */ 32 | import * as globalFilter from './filter/global.filter'; 33 | 34 | Object.keys(globalFilter).forEach(key => { 35 | Vue.filter(key, globalFilter[key]); 36 | }); 37 | /** 38 | * @description 全局权限指令 39 | * */ 40 | import permissionDirective from './directive/permission.directive'; 41 | 42 | Vue.use(permissionDirective); 43 | 44 | /** 45 | * @description element-ui 46 | * @doc https://element.eleme.cn/ 47 | * */ 48 | const elementUI = () => import('./element-ui/element-ui'); 49 | 50 | /** 51 | * @description fort awesome 字体 52 | * @doc https://fontawesome.com/ 53 | * */ 54 | const fontAwesome = () => import('./font-awesome/font-awesome'); 55 | 56 | /** 57 | * @description 全局组件 58 | * */ 59 | const globalComponents = () => import('./components/global.components'); 60 | /** 61 | * @description 服务组件 62 | * */ 63 | const serviceComponents = () => import('./components/service.components'); 64 | 65 | const App = () => import('./views/App'); 66 | 67 | /** 68 | * @description 复制插件 69 | * @doc https://github.com/Inndy/vue-clipboard2#readme 70 | * */ 71 | import VueClipboard from 'vue-clipboard2'; 72 | 73 | Vue.use(VueClipboard); 74 | 75 | 76 | Vue.config.productionTip = false; 77 | 78 | 79 | ;(async () => { 80 | 81 | let {default: __mixinsInstall} = await __mixins(); 82 | let {default: elementUIInstall} = await elementUI(); 83 | let {default: fontAwesomeInstall} = await fontAwesome(); 84 | let {default: globalComponentsInstall} = await globalComponents(); 85 | let {default: serviceComponentsInstall} = await serviceComponents(); 86 | 87 | 88 | Vue.mixin(__mixinsInstall); 89 | elementUIInstall(Vue); 90 | fontAwesomeInstall(Vue); 91 | globalComponentsInstall(Vue); 92 | serviceComponentsInstall(Vue); 93 | 94 | new Vue({ 95 | el: '#app', 96 | router, 97 | store, 98 | components: {App}, 99 | template: '' 100 | }); 101 | 102 | })(); 103 | 104 | -------------------------------------------------------------------------------- /src/store.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 菜单数据 3 | * @type {Array} 4 | * */ 5 | 6 | export const menuList = []; 7 | 8 | /** 9 | * @description 菜单折合状态 10 | * @type {Boolean} 11 | * */ 12 | export const menuCollapseStatus = false; 13 | 14 | /** 15 | * @description logo 16 | * @type {Null | Object} 17 | * */ 18 | 19 | export const logo = { 20 | path: '/static/logo/element-ui.svg', 21 | miniPath: '/static/logo/eleme.png', 22 | backgroundColor: '#1e3040', 23 | fixed: false 24 | }; 25 | // 无 logo 26 | // export const logo = null; 27 | 28 | /** 29 | * @description 固定 Header 30 | * @type {Boolean} 31 | * */ 32 | export const fixedHeader = true; 33 | -------------------------------------------------------------------------------- /src/store/__module/base.store.js: -------------------------------------------------------------------------------- 1 | import merge from 'webpack-merge'; 2 | 3 | export default { 4 | namespaced: true, 5 | state: { 6 | /** 7 | * @description logo 8 | * */ 9 | logo: null, 10 | /** 11 | * @description 头部固定 12 | * */ 13 | fixedHeader: true 14 | }, 15 | mutations: { 16 | /** 17 | * @description 设置 logo 18 | * @param state {Object} 19 | * @param logo {Null | Object} 20 | * */ 21 | setLogo(state, logo) { 22 | if (!logo) return state.logo = null; 23 | // 24 | state.logo = !state.logo ? {} : state.logo; 25 | state.logo = merge(state.logo, logo); 26 | }, 27 | /** 28 | * @description 设置头部固定 29 | * @param state {Object} 30 | * @param fixedHeader {Boolean} 31 | * */ 32 | setFixedHeader(state, fixedHeader) { 33 | state.fixedHeader = fixedHeader; 34 | } 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /src/store/__module/menu.store.js: -------------------------------------------------------------------------------- 1 | import {parseJsonTree, cloneDeep} from '@/utils/utils'; 2 | 3 | export default { 4 | namespaced: true, 5 | state: { 6 | /** 7 | * @description 是否折合菜单 8 | * */ 9 | collapse: false, 10 | /** 11 | * @description 当前页面的菜单父子路径 12 | * */ 13 | currentPaths: [], 14 | /** 15 | * @description 菜单列表 16 | * */ 17 | list: [], 18 | /** 19 | * @description 格式化菜单 20 | * */ 21 | formatList: [], 22 | /** 23 | * @description 权限列表 24 | * */ 25 | permissions: [] 26 | }, 27 | mutations: { 28 | /** 29 | * @description 设置菜单折叠状态 30 | * @param state {Object} 31 | * @param status {Boolean} 32 | * */ 33 | setCollapseStatus(state, status) { 34 | state.collapse = status; 35 | }, 36 | /** 设置当前页面菜单路径 37 | * @description 38 | * @param state {Object} 39 | * @param list {Array} 40 | * */ 41 | setCurrentPaths(state, list) { 42 | state.currentPaths = list; 43 | }, 44 | /** 45 | * @description 设置菜单数据 46 | * @param state {Object} 47 | * @param list {Array} 48 | * */ 49 | setList(state, list) { 50 | let menuList = list.filter(item => item.type !== 2); 51 | // 筛选权限 52 | let permissionList = list.filter(item => item.permission && item.permission.trim()).map(item => item.permission); 53 | this.commit('__menu/pushPermissions', permissionList); 54 | state.list = cloneDeep(menuList); 55 | state.formatList = parseJsonTree(cloneDeep(menuList)); 56 | }, 57 | /** 58 | * @description 设置权限列表 59 | * @param state {Object} 60 | * @param permissions {Array} 61 | * */ 62 | setPermissions(state, permissions) { 63 | permissions = permissions 64 | // 支持 ['aaa', 'bbb,ccc', '', ' '] 65 | .map(item => item.split(',')) 66 | // 扁平化 67 | .flat() 68 | // 去除空格 69 | .filter(item => item && item.trim()) 70 | .map(item => item.trim()); 71 | state.permissions = permissions; 72 | }, 73 | /** 74 | * @description 追加一个或多个权限 75 | * @param state {Object} 76 | * @param permissions {String | Array} 77 | * */ 78 | pushPermissions(state, permissions) { 79 | let p = cloneDeep(state.permissions); 80 | permissions = Array.isArray(permissions) ? permissions : [permissions]; 81 | permissions = permissions 82 | // 支持 ['aaa', 'bbb,ccc', '', ' '] 83 | .map(item => item.split(',')) 84 | .flat() 85 | .filter(item => item && item.trim()) 86 | .map(item => item.trim()); 87 | state.permissions = [...new Set(p.concat(permissions))]; 88 | }, 89 | /** 90 | * @description 删除一个或多个权限 91 | * @param state {Object} 92 | * @param permission {String | Array} 93 | * */ 94 | removePermissions(state, permission) { 95 | permission = Array.isArray(permission) ? permission : [permission]; 96 | state.permissions = cloneDeep(state.permissions).filter(item => { 97 | return !permission.includes(item); 98 | }); 99 | } 100 | 101 | } 102 | }; 103 | -------------------------------------------------------------------------------- /src/store/__module/tags.store.js: -------------------------------------------------------------------------------- 1 | import {cloneDeep, parseJsonTree} from '@/utils/utils'; 2 | 3 | export default { 4 | namespaced: true, 5 | state: { 6 | /** 7 | * @description tags 列表 8 | * */ 9 | list: [], // [ {title: '', active:true, route: ''} ,..] 10 | 11 | }, 12 | mutations: { 13 | 14 | /** 15 | * @description 16 | * @param state {Object} 17 | * @param route {Object} 18 | * */ 19 | pushList(state, route) { 20 | if (!route) return; 21 | route = cloneDeep(route); 22 | let findStatus = state.list.some(item => { 23 | return item.route === route.route; 24 | }); 25 | if (!findStatus) { 26 | state.list.push(route); 27 | } 28 | state.list = state.list.map(item => { 29 | item.active = item.route === route.route; 30 | return item; 31 | }); 32 | }, 33 | /** 34 | * @description 根据 ID 删除一项 35 | * */ 36 | sliceOneItem(state, id) { 37 | state.list = state.list.filter(item => { 38 | return item.id !== Number(id); 39 | }); 40 | }, 41 | /** 42 | * @description 根据 ID 设置一项为 active 43 | * */ 44 | setOneItemActive(state, id) { 45 | state.list = state.list.map(item => { 46 | item.active = item.id === Number(id); 47 | return item; 48 | }); 49 | }, 50 | /** 51 | * @description 根据ID将移动到指定位置 52 | * */ 53 | setIdToIndex(state, {id = '', index = ''}) { 54 | let list = cloneDeep(state.list); 55 | let tag = list.splice(list.findIndex(item => { 56 | return item.id === Number(id); 57 | }), 1); 58 | list.splice(index, 0, tag[0]); 59 | state.list = list; 60 | }, 61 | /** 62 | * @description 清除 tags 列表 63 | * */ 64 | clearList(state) { 65 | state.list = []; 66 | } 67 | 68 | } 69 | }; 70 | -------------------------------------------------------------------------------- /src/store/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | import merge from 'webpack-merge'; 4 | 5 | Vue.use(Vuex); 6 | import __menu from './__module/menu.store'; 7 | import __tags from './__module/tags.store'; 8 | import __base from './__module/base.store'; 9 | 10 | // 测试模块 11 | import todo from './todo.store.module'; 12 | 13 | 14 | export default new Vuex.Store(merge({ 15 | // 测试 16 | state: { 17 | count: 0 18 | }, 19 | mutations: { 20 | increment(state) { 21 | state.count++; 22 | }, 23 | decrement(state) { 24 | state.count--; 25 | }, 26 | }, 27 | getters: { 28 | getCount(state) { 29 | return state.count; 30 | } 31 | }, 32 | actions: { 33 | asyncIncrement(store) { 34 | store.commit('increment'); 35 | }, 36 | asyncDecrement(store) { 37 | store.commit('decrement'); 38 | } 39 | }, 40 | // module 41 | modules: { 42 | todo 43 | } 44 | }, 45 | { 46 | modules: { 47 | __menu, 48 | __tags, 49 | __base 50 | } 51 | })); 52 | -------------------------------------------------------------------------------- /src/store/todo.store.module.js: -------------------------------------------------------------------------------- 1 | export default { 2 | namespaced: true, 3 | state: { 4 | todoList: [] // [ {status: true}, {status: false} ... ] 5 | }, 6 | mutations: { 7 | pushTodoList(state, todo) { 8 | state.todoList.push(todo); 9 | }, 10 | clearTodoList(state) { 11 | state.todoList = []; 12 | } 13 | }, 14 | getters: { 15 | getStatusIsTrue(state) { 16 | return state.todoList.filter(item => item.status); 17 | }, 18 | getStatusIsFalse(state) { 19 | return state.todoList.filter(item => !item.status); 20 | } 21 | } 22 | 23 | }; 24 | -------------------------------------------------------------------------------- /src/utils/utils.js: -------------------------------------------------------------------------------- 1 | import _cloneDeep from 'lodash/cloneDeep'; 2 | 3 | /** 4 | * @description 模拟睡眠 5 | * @param time {Number} 睡眠时间(ms) | default: 1000 6 | * @return {Promise} 7 | * */ 8 | export function sleep(time = 1000) { 9 | return new Promise(resolve => { 10 | setTimeout(() => { 11 | resolve(); 12 | }, time); 13 | }); 14 | } 15 | 16 | /** 17 | * @description 生成随机数 18 | * @param minNum {Number} 最小范围 | default : 1 19 | * @param maxNum {Number} 最大范围 | default : 99999 20 | * */ 21 | export function makeRandomNumber(minNum = 1, maxNum = 99999) { 22 | return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10); 23 | } 24 | 25 | /** 26 | * @description 生成随机字符串 27 | * @param length {Number} 字符串长度 | default: 32 28 | * @return {String} 29 | * */ 30 | export function makeRandomString(length = 32) { 31 | 32 | let chars = 'QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890'; 33 | let max = chars.length; 34 | let string = ''; 35 | for (let i = 0; i < length; i++) { 36 | string += chars.charAt(Math.floor(Math.random() * max)); 37 | } 38 | return string; 39 | } 40 | 41 | /** 42 | * @description 获取可视化时间 43 | * @return {String} 44 | * */ 45 | export function parseDateTime(timestamp = null, format = null) { 46 | !format && (format = 'y-m-d h:i:s'); 47 | !timestamp && (timestamp = null); 48 | const date = timestamp === null ? new Date() : new Date(Number(timestamp)), 49 | y = date.getFullYear(), m = date.getMonth() + 1, 50 | d = date.getDate(), h = date.getHours(), 51 | i = date.getMinutes(), s = date.getSeconds(); 52 | return format 53 | .replace(/y/g, y).replace(/m/g, m < 10 ? '0' + m : m) 54 | .replace(/d/g, d < 10 ? '0' + d : d).replace(/h/g, h < 10 ? '0' + h : h) 55 | .replace(/i/g, i < 10 ? '0' + i : i).replace(/s/g, s < 10 ? '0' + s : s); 56 | } 57 | 58 | /** 59 | * @description 深拷贝 60 | * @param value {*} 61 | * @return {*} 62 | * */ 63 | export function cloneDeep(value) { 64 | if (!['array', 'object'].includes(getType(value))) { 65 | return value; 66 | } 67 | return _cloneDeep(value); 68 | } 69 | 70 | /** 71 | * @description 将集合通过子父 ID 关联成树 72 | * @param list {Array} 73 | * @param parentId {Number} | default: 0 74 | * @return {Array} 75 | * */ 76 | export function parseJsonTree(list, parentId = 0) { 77 | let tree = []; 78 | let temp; 79 | for (let i = 0; i < list.length; i++) { 80 | if (list[i].parentId === parentId) { 81 | let obj = list[i]; 82 | temp = parseJsonTree(list, list[i].id); 83 | if (temp.length > 0) { 84 | obj.children = temp; 85 | } 86 | tree.push(obj); 87 | } 88 | } 89 | return tree; 90 | } 91 | 92 | /** 93 | * @description 获取一项指定 ID 的向上所有集合 94 | * @param list {Array} 95 | * @param id {Number} 96 | * @param parents {Array} 97 | * @return {Array} 98 | * */ 99 | export function getParentJson(list, id, parents = []) { 100 | for (let i = 0; i < list.length; i++) { 101 | if (list[i].id === id) { 102 | parents.push(list[i]); 103 | getParentJson(list, list[i].parentId, parents); 104 | } 105 | } 106 | return parents; 107 | } 108 | 109 | /** 110 | * @description 获取数据类型 111 | * @param value {*} 112 | * @return {String} 113 | * */ 114 | export function getType(value) { 115 | return /\[object ([\w\W]+)\]/.exec(({}).toString.call(value))[1].toLowerCase(); 116 | } 117 | 118 | 119 | /** 120 | * @description 异步加载js和css 121 | * */ 122 | export function asyncLoadScript() { 123 | return { 124 | js(link) { 125 | return new Promise((resolve, reject) => { 126 | let script = document.createElement('script'); 127 | script.type = 'text/javascript'; 128 | script.src = link; 129 | document.body.appendChild(script); 130 | if (script.readyState) { // ie 131 | script.onreadystatechange = function () { 132 | if (script.readyState === 'loaded' || script.readyState === 'complete') { 133 | resolve(); 134 | } 135 | }; 136 | } else { 137 | script.onload = function () { 138 | resolve(); 139 | }; 140 | script.onerror = function () { 141 | reject(); 142 | }; 143 | } 144 | }); 145 | }, 146 | css(link) { 147 | let css = document.createElement('link'); 148 | css.rel = 'stylesheet'; 149 | css.href = link; 150 | document.body.appendChild(css); 151 | return Promise.resolve(); 152 | }, 153 | }; 154 | } 155 | -------------------------------------------------------------------------------- /src/views/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/views/default/layout/header.component.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 83 | 84 | 98 | -------------------------------------------------------------------------------- /src/views/default/layout/menu.component.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 54 | -------------------------------------------------------------------------------- /src/views/default/layout/menu.scroll.component.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 61 | 87 | -------------------------------------------------------------------------------- /src/views/default/page/401.page.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 33 | -------------------------------------------------------------------------------- /src/views/default/page/404.page.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 33 | -------------------------------------------------------------------------------- /src/views/default/page/reload.page.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 23 | -------------------------------------------------------------------------------- /src/views/default/route/base.route.component.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 190 | -------------------------------------------------------------------------------- /src/views/default/route/default.route.component.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /src/views/general/antv-g2/g2-area/area-stacked.component.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 105 | 106 | 109 | -------------------------------------------------------------------------------- /src/views/general/antv-g2/g2-funnel/funnel-basic.componeent.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 153 | 154 | 157 | -------------------------------------------------------------------------------- /src/views/general/antv-g2/g2-line/line-curved.component.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 102 | 103 | 106 | -------------------------------------------------------------------------------- /src/views/general/antv-g2/g2-line/line-double-y-axes.component.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 107 | 108 | 111 | -------------------------------------------------------------------------------- /src/views/general/antv-g2/g2-line/line-step-series.component.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 92 | -------------------------------------------------------------------------------- /src/views/general/antv-g2/g2-pie/pie-basic.componeent.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 113 | 114 | 117 | -------------------------------------------------------------------------------- /src/views/general/dashboard-card/dashboard-card.component.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 65 | 66 | 202 | -------------------------------------------------------------------------------- /src/views/global/va-container.component.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 54 | 55 | 87 | -------------------------------------------------------------------------------- /src/views/global/va-table.component.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 63 | -------------------------------------------------------------------------------- /src/views/pages/404/404.page.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/views/pages/antv/antv-g2/area-stacked.page.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 86 | 87 | 90 | -------------------------------------------------------------------------------- /src/views/pages/antv/antv-g2/basic-pie.page.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 69 | 70 | 73 | -------------------------------------------------------------------------------- /src/views/pages/antv/antv-g2/basic-pie2.page.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 69 | 70 | 73 | -------------------------------------------------------------------------------- /src/views/pages/antv/antv-g2/funnel-basic.page.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 50 | 51 | 54 | -------------------------------------------------------------------------------- /src/views/pages/antv/antv-g2/line-curved.page.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 86 | 87 | 90 | -------------------------------------------------------------------------------- /src/views/pages/antv/antv-g2/line-double-y-axes.page.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 76 | 77 | 80 | -------------------------------------------------------------------------------- /src/views/pages/antv/antv-g2/line-step-series.page.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 79 | 80 | 83 | -------------------------------------------------------------------------------- /src/views/pages/base-layout/base-layout.page.vue: -------------------------------------------------------------------------------- 1 | 28 | 40 | 41 | -------------------------------------------------------------------------------- /src/views/pages/card-layout/card-layout.page.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 99 | 100 | 103 | -------------------------------------------------------------------------------- /src/views/pages/component/component-va-table-filter.page.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | -------------------------------------------------------------------------------- /src/views/pages/component/component-va-table-filter.page/example.1.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 84 | -------------------------------------------------------------------------------- /src/views/pages/component/component-va-table-filter.page/example.2.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 113 | -------------------------------------------------------------------------------- /src/views/pages/copy-text/copy-text.page.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 49 | 50 | 54 | -------------------------------------------------------------------------------- /src/views/pages/doc/build.page.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 30 | -------------------------------------------------------------------------------- /src/views/pages/doc/folder.page.vue: -------------------------------------------------------------------------------- 1 | 83 | 84 | 105 | -------------------------------------------------------------------------------- /src/views/pages/doc/font-awesome.page.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 30 | -------------------------------------------------------------------------------- /src/views/pages/doc/general-component.page.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 28 | 29 | -------------------------------------------------------------------------------- /src/views/pages/doc/global-component/global-component.page.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 28 | 29 | -------------------------------------------------------------------------------- /src/views/pages/doc/global-component/va-container.page.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 30 | 31 | -------------------------------------------------------------------------------- /src/views/pages/doc/global-component/va-table.page.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 30 | 31 | -------------------------------------------------------------------------------- /src/views/pages/doc/markdown/build.md: -------------------------------------------------------------------------------- 1 | # 打包配置 2 | 3 | ### 打包: 4 | 5 | > yarn run (build:dev | build:test | build:beta | build:prod) 6 | 7 | ### 版本 8 | 9 | 该项目每次打包以`打包时间`作为一个'版本'。每次打包之后都会以当前时间作为`根目录`,可防止缓存问题 10 | 11 | 在打包完成之后,项目根目录会自动生成 `dist` 文件及,该文件下包含 12 | ```text 13 | |-- history-static 历史版本 14 | |-- 20200501170055 15 | |-- ... 16 | |-- production-static 当前版本 17 | |-- 20200501170055 18 | |-- index.html 19 | ``` 20 | 21 | ### 预览: 22 | 23 | > node server.js 24 | 25 | ### 扩展 26 | 27 | 当现有服务器的站点包含多个站点或者多个静态资源,这里为区分当前项目提供了扩展配置 28 | 29 | 默认情况下静态根文件夹采用`打包时间`。可以通过更改`/config/build.config.js`中`buildRootStaticPath`来进行区分,例如: 30 | ```javascript 31 | module.exports = { 32 | buildRootStaticPath: '/vue-admin-template' 33 | }; 34 | ``` 35 | 通过上面配置之后打包完成后`dist`文件夹结构如下: 36 | ```text 37 | |-- history-static 历史版本 38 | |-- 20200501170055 39 | |-- ... 40 | |-- production-static 当前版本 41 | |-- vue-admin-template 根目录 42 | |-- 20200501172626 43 | |-- index.html 44 | ``` 45 | 在部署时直接将`20200501172626`放置站点`vue-admin-template`下即可 46 | 47 | `buildRootStaticPath`配置支持多层级目录结构 48 | ```javascript 49 | module.exports = { 50 | buildRootStaticPath: '/vue-admin-template/a/b/c' 51 | }; 52 | ``` 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/views/pages/doc/markdown/folder.md: -------------------------------------------------------------------------------- 1 | # 目录介绍 2 | -------------------------------------------------------------------------------- /src/views/pages/doc/markdown/font-awesome.md: -------------------------------------------------------------------------------- 1 | # Font Awesome字体图标 2 | 3 | Font Awesome是一款基于css框架的网页字体图标库,可以即时定制的尺寸、颜色、阴影,使用简单,本项目已经集成。具体可查看配置文件`/src/font-awesome/font-awesome.js` 4 | 5 | ```javascript 6 | /** 7 | * @description fort awesome 字体 8 | * @doc https://fontawesome.com/ 9 | * */ 10 | // 全量引入 11 | /* 12 | import {library} from '@fortawesome/fontawesome-svg-core'; 13 | import {fas} from '@fortawesome/free-solid-svg-icons'; 14 | import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome'; 15 | Object.keys(fas).forEach(item => { 16 | library.add(fas[item]); 17 | }); 18 | */ 19 | // 按需引入 20 | import {library} from '@fortawesome/fontawesome-svg-core'; 21 | import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome'; 22 | import { 23 | faTachometerAlt, 24 | faFileAlt, 25 | faChartArea, 26 | faTable, 27 | faLink, 28 | faBars, 29 | faShoppingCart, 30 | faUsers, 31 | faMoneyBillAlt, 32 | faCity, 33 | faStickyNote, 34 | faMarker, 35 | faMapMarked, 36 | faFont, 37 | faCopy 38 | } from '@fortawesome/free-solid-svg-icons'; 39 | library.add(faTachometerAlt); 40 | library.add(faFileAlt); 41 | library.add(faChartArea); 42 | library.add(faTable); 43 | library.add(faLink); 44 | library.add(faBars); 45 | library.add(faShoppingCart); 46 | library.add(faUsers); 47 | library.add(faMoneyBillAlt); 48 | library.add(faCity); 49 | library.add(faMapMarked); 50 | library.add(faMarker); 51 | library.add(faStickyNote); 52 | library.add(faFont); 53 | library.add(faCopy); 54 | export default function install(Vue) { 55 | Vue.component('font-awesome-icon', FontAwesomeIcon); 56 | } 57 | ``` 58 | ##### 使用 59 | ```html 60 | 61 | 62 | ``` 63 | -------------------------------------------------------------------------------- /src/views/pages/doc/markdown/general-component.md: -------------------------------------------------------------------------------- 1 | # 基本组件 2 | 3 | 该项目封装了一些比较常见的组件,位置位于`/src/views/general`。 4 | 5 | 这些组件直接在被需要使用的组件中直接`import`或者`懒加载`的方式导入使用即可。无需考虑这个组件使用的了哪些依赖,也不必考虑重复使用多次数据错误、样式错乱的问题 6 | 7 | 8 | ### 一、案例 9 | 10 | ```html 11 | 23 | 64 | ``` 65 | 66 | ### 二、开发建议 67 | 68 | - 使用`懒加载`方式加载组件 69 | 70 | 在用户实际操作中,例如修改密码这个组件。使用频率并不是很高,所以并不用每次都同步加载。在`webpack`中使用懒加载非常简单,例如上面代码第14行直接改成: 71 | ```javascript 72 | const G2LineBasicComponent = () => import('@/views/general/antv-g2/g2-line/line-basic.component'); 73 | ``` 74 | 75 | -------------------------------------------------------------------------------- /src/views/pages/doc/markdown/global-component/global-component.md: -------------------------------------------------------------------------------- 1 | # 全局组件 2 | 3 | 全局组件无需`import`,因为它已经注册在全局中。目前全局组件有两个,也是认为在现在框架基础上没有满足个人需求所添加的 4 | -------------------------------------------------------------------------------- /src/views/pages/doc/markdown/global-component/va-container.md: -------------------------------------------------------------------------------- 1 | # va-container 2 | 3 | 该组件采用的在一定高度容器下固定头部、底部,并且头部、内容区、底部都是自适应高度。可以适用于一些`列表`。在数据内容比较多、顶部和底部都有操作区域时采用。[查看实例](#/base-layout) 4 | 5 | ```html 6 | 30 | 40 | ``` 41 | va-container 组件通过 `slot` 为 `header`、`body`、`footer`区分容器位置 42 | 43 | 接收一个 prop 44 | 45 | > containerHeight: String | default: 100% 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/views/pages/doc/markdown/global-component/va-table.md: -------------------------------------------------------------------------------- 1 | # va-table 2 | 3 | 该组件继承了 `el-table`,用法跟 `el-table` 一致。额外集成了[vaTableFilter-组合查询](#/doc/va-table-filter-service)组件。[查看实例](#store) 4 | 5 | ### 使用 6 | 7 | - ##### 打开组合查询组件 8 | 9 | ```javascript 10 | this.$refs.table.vaTableFilter(); 11 | ``` 12 | - vaTableFilter 13 | + param 14 | + options:Object 和`vaTableFilter 查询组件`参数一致 15 | 16 | + return 17 | + void 18 | 19 | --- 20 | 21 | - ##### 接收两个 props 22 | + va-table-filter-cancel:Function 23 | + callParam: 24 | + void 25 | 26 | + va-table-filter-confirm:Function 27 | + callParam: 28 | + filterData:Object 29 | 30 | 示例 31 | 32 | ```html 33 | 39 | 40 | export default { 41 | methods: { 42 | // 打开查询组件 43 | openFilter() { 44 | this.$refs.table.vaTableFilter(); 45 | }, 46 | // 组件关闭回调 47 | vaTableFilterCancel() { 48 | this.$message.info('取消查询'); 49 | return Promise.resolve(); 50 | }, 51 | // 组件确认回调 52 | vaTableFilterConfirm(data) { 53 | this.$message.success(JSON.stringify(data)); 54 | return Promise.resolve(); 55 | } 56 | } 57 | }; 58 | ``` 59 | 60 | ### 其他 61 | 62 | - `va-table` 组件在销毁时会自动销毁 `vaTableFilter 组件`,无需关心该组件的生命周期的情况 63 | - 无需保存 `vaTableFilter 组件` 的实例,也没有提供方法保存。在 `this.$refs.table.vaTableFilter()` 中会自动维护该组件的单例 64 | -------------------------------------------------------------------------------- /src/views/pages/doc/markdown/permission.md: -------------------------------------------------------------------------------- 1 | # 权限配置 2 | 3 | 权限的具体格式可参考[菜单配置](#/store-data-menu-list)和[菜单管理](#/system-menu) 4 | 5 | ### 一、设置权限 6 | 7 | #### 自动设置 8 | ```javascript 9 | this.__setStoreConfig('menuList', menuList); 10 | ``` 11 | 根据菜单的格式,在使用 `__setStoreConfig` 时菜单项会自动将 `type = 2` 的筛选出来设置为 `权限` 列表。[示例中](#/permission) `system-user-list`、`system-add-user` 两项就是在设置菜单数据时自动设置的。 12 | 13 | #### 手动设置 14 | 15 | 在实例全局中混入了四个方法用于设置权限列表 `__setPermissions`、`__pushPermissions`、`__removePermissions` 和 `__clearPermission` 16 | 17 | - #### `__setPermissions` 设置权限列表,会覆盖已经存在的列表 18 | + param 19 | - permissionsList: Array\ 20 | + return 21 | - void 22 | 23 | ```javascript 24 | this.__setPermissions(['p1','p2']) 25 | this.__setPermissions(['p1','p2,p3']) 26 | ``` 27 | 28 | --- 29 | 30 | - #### `__pushPermissions` 追加一个或者多个权限 31 | + param 32 | - permission: Array\ | String 33 | + return 34 | - void 35 | 36 | ```javascript 37 | this.__pushPermissions(['p1','p2']) 38 | this.__pushPermissions('p3') 39 | this.__pushPermissions('p3,p4') 40 | ``` 41 | 42 | --- 43 | 44 | - #### `__removePermissions` 移除一项或者多项权限 45 | + param 46 | - permission: Array\ | String 47 | + return 48 | - void 49 | 50 | ```javascript 51 | this.__removePermissions(['p1','p2']) 52 | this.__removePermissions('p3') 53 | ``` 54 | 55 | --- 56 | 57 | - #### `__clearPermission` 清除权限 58 | + param 59 | + return 60 | - void 61 | 62 | ```javascript 63 | this.__clearPermission() 64 | ``` 65 | 66 | ### 二、获取权限 67 | 68 | 可在实例中通过 `__permissions` 获取 69 | 70 | ```html 71 | 76 | ``` 77 | 78 | ```javascript 79 | methods:{ 80 | getPermission(){ 81 | return this.__permissions 82 | } 83 | } 84 | ``` 85 | 86 | ### 三、权限使用 87 | 88 | [示例](#/permission) 89 | 90 | #### 指令方式 91 | 92 | 全局封装了一个 `指令` `v-permission` 来进行权限的控制 93 | 94 | - #### v-permission 95 | + param 96 | - permission: Array\ | String 97 | + return 98 | 99 | 100 | ```html 101 | 修改 102 | 删除 103 | ``` 104 | 105 | 106 | #### 表达式方式 107 | 108 | 全局混入了一个方法 `hasPermission` 来通过指定权限项来判断是否存在权限,`v-permission` 也是基于这个方法 109 | 110 | 111 | - #### hasPermission 112 | + param 113 | - permission: Array\ | String 114 | + return 115 | - has: Boolean 116 | 117 | ```html 118 | 修改 119 | 删除 120 | ``` 121 | 122 | ```javascript 123 | methods:{ 124 | hasPermission(){ 125 | // return this.hasPermission('update') 126 | return this.hasPermission(['remove']) 127 | } 128 | } 129 | ``` 130 | 131 | ### 四、指令相关 132 | 133 | 具体可查看 `/src/directive/permission.directive.js` 备注信息 134 | -------------------------------------------------------------------------------- /src/views/pages/doc/markdown/service-component/va-table-filter-service.md: -------------------------------------------------------------------------------- 1 | # 表格组合查询 2 | 3 | 在`Vue.prototype`中注册了一个专为表格`el-table`扩展的弹出框组件 `vaTableFilter` [查看实例](#/component-va-table-filter) 4 | 5 | ### 使用 6 | 7 | - 组件中 8 | 9 | ```javascript 10 | let instance = this.$vaTableFilter({ 11 | fields: [ 12 | { 13 | name: '姓名', 14 | field: 'username' 15 | }, 16 | { 17 | name: '地址', 18 | field: 'address' 19 | }, 20 | ], 21 | visible: true 22 | }); 23 | ``` 24 | 25 | or 26 | 27 | ```javascript 28 | let instance = this.$vaTableFilter({ 29 | tableVm: this.$refs.table, 30 | visible: true, 31 | excludeField: [ 32 | '操作','Logo' 33 | ] 34 | }); 35 | ``` 36 | 37 | 38 | - $vaTableFilter(options: Object) 39 | + param 40 | + options:Object 41 | + fields: Array 可选 | 筛选字段、和 tableVm 二选一 fields 存在时优先级为 fields 42 | + tableVm: Object 可选 | el-table 组件实例 43 | + visible: Boolean 可选 | 默认:true | 初始化是否显示组件 44 | + customClass: String 可选 | 默认:'' | 自定义 dom class 45 | + excludeField: Array 可选 | 默认:[] | 需要排除的字段,可设置为`名称`、`字段`。一般配合 `tableVm` 使用 46 | + return 47 | + instance: Object 组件 vm 实例。为实现下次重新打开组件实现之前输入的数据还存在,需要保存该实例。该组件向外提供了四个方法`show`、`on`、`destroy`、`getFilterData` 48 | 49 | --- 50 | 51 | - #### `show` 显示组件 52 | + param 53 | - void 54 | + return 55 | - undefined 56 | 57 | ```javascript 58 | instance.show(); 59 | ``` 60 | --- 61 | 62 | - #### `on` 监听关闭、确认 63 | + param 64 | - type: String 监听类型 `cancel` or `confirm` 65 | - handleFunction: Function 监听函数 66 | + return 67 | - undefined 68 | 69 | ```javascript 70 | instance.on('cancel', () => { 71 | return Promise.resolve(); 72 | }); 73 | instance.on('confirm', (filterData) => { 74 | console.log(filterData); // [{join: "and", field: "date", op: "=", value: "value"}] 75 | return Promise.reject(); 76 | }); 77 | ``` 78 | 79 | - cancel 监听函数不接收参数 80 | 81 | - confirm 监听函数接收一个参数、为筛选输入的数据 82 | + data // [{join: "and", field: "date", op: "=", value: "value"}] 83 | 84 | 85 | 86 | on 监听函数中。返回 `false` 或者 `reject状态` 的 `promise实例` 状态的数据可以阻止关闭。可以返回 `undefined` `true`或者`resolve状态` 的 `promise实例` 关闭 87 | 88 | --- 89 | 90 | - #### `destroy` 销毁组件 91 | + param 92 | - 无 93 | + return 94 | - null 95 | 96 | ```javascript 97 | instance.destroy(); 98 | ``` 99 | 当销毁完组件相应的`dom`会在页面中移除 100 | 101 | --- 102 | - #### `getFilterData` 获取筛选条件。和 on 方法监听类型为 confirm 数据一致 103 | + param 104 | - 无 105 | + return 106 | - filterData: Array 107 | 108 | ```javascript 109 | instance.getFilterData(); 110 | ``` 111 | 112 | ### 参数 tableVm 113 | 114 | 在构建一个实例如果使用 `tableVm` 时必须是 `el-table` 组件实例,可使用 `vue ref` 获取。它会自动获取表格这个组件的表头字段,实现代码: 115 | ```javascript 116 | if (tableVm instanceof Vue && Array.isArray(tableVm.columns)) { 117 | let fieldsList = tableVm.columns.map((item) => { 118 | if (item.hasOwnProperty('property') 119 | && item.hasOwnProperty('label')) { 120 | return { 121 | name: item.label, 122 | field: item.property, 123 | }; 124 | } 125 | return null; 126 | }).filter(item => item); 127 | console.log(fieldsList); 128 | /** 129 | [ 130 | { 131 | name: '姓名', 132 | field: 'username' 133 | }, 134 | { 135 | name: '地址', 136 | field: 'address' 137 | }, 138 | ] 139 | */ 140 | } 141 | ``` 142 | -------------------------------------------------------------------------------- /src/views/pages/doc/markdown/store-data.md: -------------------------------------------------------------------------------- 1 | # 响应式配置 2 | 3 | 该项目的`菜单`、`Logo`、`页面路径`、`tags`等配置都是采用 `Vuex`实现 4 | 5 | 其中:在此基础上分为两类: 6 | - 内置(可读) 7 | - 自定义(可读写) 8 | 9 | 另外,还全局混入了`__getStoreConfig`获取配置以及`__setStoreConfig`设置配置等两个方法来配合使用,这两个方法只支持‘自定义’ 10 | 11 | ### 一、自定义配置 12 | 13 | 自定义配置全部在`/src/store.config.js`中可以查看,这些配置会在页面渲染时在合适的时机会自动进行设置,也可以动态的使用`__setStoreConfig`方法设置。 14 | 15 | **访问配置:** 16 | 17 | 在`/src/store.config.js`的每一项,直接在模板中或者vue实例中加上前缀`__`即可。例如: 18 | 19 | - logo 20 | 21 | ```html 22 | 23 |
{{__logo}}
24 | ``` 25 | --- 26 | ```javascript 27 | // vue 实例 28 | mounted() { 29 | console.log(this.__logo); 30 | } 31 | ``` 32 | 33 | 其他配置可查看`/src/store.config.js` 34 | 35 | ### 二、内置配置 36 | 37 | 内置包含以下几个,均为可读 38 | 39 | **访问配置:** 40 | 41 | - __menuCurrentPaths 42 | 43 | ```html 44 | 45 |
{{__menuCurrentPaths}}
46 | ``` 47 | --- 48 | ```javascript 49 | // vue 实例 50 | mounted() { 51 | console.log(this.__menuCurrentPaths); 52 | } 53 | ``` 54 | 55 | - __tagsList 56 | 57 | 如上 58 | 59 | ### 三、__getStoreConfig 、__setStoreConfig 60 | 61 | 这两个方法为全局混入方法 62 | 63 | 64 | - __setStoreConfig(key: String, config: Any) 65 | + param 66 | + key:String 对应 store.config.js 文件中的配置 67 | + config:Any 对应配置中的具体类型 68 | + return 69 | + undefined 70 | 71 | - __getStoreConfig(key: String) 72 | + param 73 | + key:String 对应 store.config.js 文件中的配置 74 | + return 75 | + Any 具体类型为配置可支持的类型 76 | 77 | 目前只支持自定义配置,例如场景: 78 | 79 | - 动态设置菜单 80 | - 动态设置Logo 81 | 82 | **案例** 83 | ```html 84 | 90 | 30 | -------------------------------------------------------------------------------- /src/views/pages/doc/readme.page.vue: -------------------------------------------------------------------------------- 1 | 13 | 36 | 37 | 40 | -------------------------------------------------------------------------------- /src/views/pages/doc/service-component/va-table-filter-service.page.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 30 | -------------------------------------------------------------------------------- /src/views/pages/doc/store-data.page.vue: -------------------------------------------------------------------------------- 1 | 13 | 36 | 37 | 40 | -------------------------------------------------------------------------------- /src/views/pages/editor/editor-ueditor.page.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 65 | 66 | 69 | -------------------------------------------------------------------------------- /src/views/pages/editor/wangEditor.page.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 50 | 51 | 53 | -------------------------------------------------------------------------------- /src/views/pages/font-awesome/font-awesome.page.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 81 | 82 | 104 | -------------------------------------------------------------------------------- /src/views/pages/home/home.page.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 157 | 158 | -------------------------------------------------------------------------------- /src/views/pages/js-big-decimal/js-big-decimal.page.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 107 | -------------------------------------------------------------------------------- /src/views/pages/login/login.page.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 97 | 98 | 190 | -------------------------------------------------------------------------------- /src/views/pages/menu/menu.page.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | 19 | 22 | -------------------------------------------------------------------------------- /src/views/pages/permission/permission.page.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 102 | 103 | 106 | -------------------------------------------------------------------------------- /src/views/pages/personal/personal.page.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 77 | 78 | -------------------------------------------------------------------------------- /src/views/pages/store-data/fixed-header.page.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 52 | -------------------------------------------------------------------------------- /src/views/pages/store-data/logo.page.vue: -------------------------------------------------------------------------------- 1 | 78 | 79 | 115 | 116 | 119 | -------------------------------------------------------------------------------- /src/views/pages/store-data/menu-collapse.page.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 58 | 59 | 62 | -------------------------------------------------------------------------------- /src/views/pages/store/store.page.vue: -------------------------------------------------------------------------------- 1 | 107 | 108 | 154 | 189 | -------------------------------------------------------------------------------- /src/views/pages/system/system-user/system-user.page.vue: -------------------------------------------------------------------------------- 1 | 113 | 114 | 172 | 206 | -------------------------------------------------------------------------------- /src/views/pages/table/table-example-1.page.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 66 | -------------------------------------------------------------------------------- /src/views/pages/test/test.page.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 53 | -------------------------------------------------------------------------------- /src/vm.vue.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | export default new Vue(); 4 | 5 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8696/vue-admin-template/b463739db96c2c4b20290f824bb44a5b4760dbd2/static/favicon.ico -------------------------------------------------------------------------------- /static/logo/eleme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8696/vue-admin-template/b463739db96c2c4b20290f824bb44a5b4760dbd2/static/logo/eleme.png -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const os = require('os') 2 | console.log(os.arch()) 3 | --------------------------------------------------------------------------------