New file |
| | |
| | | { |
| | | "presets": [ |
| | | ["env", { |
| | | "modules": false, |
| | | "targets": { |
| | | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] |
| | | } |
| | | }], |
| | | "stage-2" |
| | | ], |
| | | "plugins": ["transform-vue-jsx", "transform-runtime"], |
| | | "env": { |
| | | "test": { |
| | | "presets": ["env", "stage-2"], |
| | | "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"] |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | root = true |
| | | |
| | | [*] |
| | | charset = utf-8 |
| | | indent_style = space |
| | | indent_size = 2 |
| | | end_of_line = lf |
| | | insert_final_newline = true |
| | | trim_trailing_whitespace = true |
New file |
| | |
| | | /build/ |
| | | /config/ |
| | | /dist/ |
| | | /*.js |
| | | /test/unit/coverage/ |
New file |
| | |
| | | // https://eslint.org/docs/user-guide/configuring |
| | | |
| | | module.exports = { |
| | | root: true, |
| | | parserOptions: { |
| | | parser: 'babel-eslint' |
| | | }, |
| | | env: { |
| | | browser: true, |
| | | }, |
| | | extends: [ |
| | | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention |
| | | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. |
| | | 'plugin:vue/essential', |
| | | // https://github.com/standard/standard/blob/master/docs/RULES-en.md |
| | | 'standard' |
| | | ], |
| | | // required to lint *.vue files |
| | | plugins: [ |
| | | 'vue' |
| | | ], |
| | | // add your custom rules here |
| | | rules: { |
| | | // allow async-await |
| | | 'generator-star-spacing': 'off', |
| | | // allow debugger during development |
| | | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' |
| | | } |
| | | } |
New file |
| | |
| | | // https://github.com/michael-ciniawsky/postcss-load-config |
| | | |
| | | module.exports = { |
| | | "plugins": { |
| | | "postcss-import": {}, |
| | | "postcss-url": {}, |
| | | // to edit target browsers: use "browserslist" field in package.json |
| | | "autoprefixer": {} |
| | | } |
| | | } |
New file |
| | |
| | | 'use strict' |
| | | require('./check-versions')() |
| | | |
| | | process.env.NODE_ENV = 'production' |
| | | |
| | | const ora = require('ora') |
| | | const rm = require('rimraf') |
| | | const path = require('path') |
| | | const chalk = require('chalk') |
| | | const webpack = require('webpack') |
| | | const config = require('../config') |
| | | const webpackConfig = require('./webpack.prod.conf') |
| | | |
| | | const spinner = ora('building for production...') |
| | | spinner.start() |
| | | |
| | | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { |
| | | if (err) throw err |
| | | webpack(webpackConfig, (err, stats) => { |
| | | spinner.stop() |
| | | if (err) throw err |
| | | process.stdout.write(stats.toString({ |
| | | colors: true, |
| | | modules: false, |
| | | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. |
| | | chunks: false, |
| | | chunkModules: false |
| | | }) + '\n\n') |
| | | |
| | | if (stats.hasErrors()) { |
| | | console.log(chalk.red(' Build failed with errors.\n')) |
| | | process.exit(1) |
| | | } |
| | | |
| | | console.log(chalk.cyan(' Build complete.\n')) |
| | | console.log(chalk.yellow( |
| | | ' Tip: built files are meant to be served over an HTTP server.\n' + |
| | | ' Opening index.html over file:// won\'t work.\n' |
| | | )) |
| | | }) |
| | | }) |
New file |
| | |
| | | 'use strict' |
| | | const chalk = require('chalk') |
| | | const semver = require('semver') |
| | | const packageConfig = require('../package.json') |
| | | const shell = require('shelljs') |
| | | |
| | | function exec (cmd) { |
| | | return require('child_process').execSync(cmd).toString().trim() |
| | | } |
| | | |
| | | const versionRequirements = [ |
| | | { |
| | | name: 'node', |
| | | currentVersion: semver.clean(process.version), |
| | | versionRequirement: packageConfig.engines.node |
| | | } |
| | | ] |
| | | |
| | | if (shell.which('npm')) { |
| | | versionRequirements.push({ |
| | | name: 'npm', |
| | | currentVersion: exec('npm --version'), |
| | | versionRequirement: packageConfig.engines.npm |
| | | }) |
| | | } |
| | | |
| | | module.exports = function () { |
| | | const warnings = [] |
| | | |
| | | for (let i = 0; i < versionRequirements.length; i++) { |
| | | const mod = versionRequirements[i] |
| | | |
| | | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { |
| | | warnings.push(mod.name + ': ' + |
| | | chalk.red(mod.currentVersion) + ' should be ' + |
| | | chalk.green(mod.versionRequirement) |
| | | ) |
| | | } |
| | | } |
| | | |
| | | if (warnings.length) { |
| | | console.log('') |
| | | console.log(chalk.yellow('To use this template, you must update following to modules:')) |
| | | console.log() |
| | | |
| | | for (let i = 0; i < warnings.length; i++) { |
| | | const warning = warnings[i] |
| | | console.log(' ' + warning) |
| | | } |
| | | |
| | | console.log() |
| | | process.exit(1) |
| | | } |
| | | } |
New file |
| | |
| | | 'use strict' |
| | | const path = require('path') |
| | | const config = require('../config') |
| | | const ExtractTextPlugin = require('extract-text-webpack-plugin') |
| | | const packageConfig = require('../package.json') |
| | | |
| | | exports.assetsPath = function (_path) { |
| | | const assetsSubDirectory = process.env.NODE_ENV === 'production' |
| | | ? config.build.assetsSubDirectory |
| | | : config.dev.assetsSubDirectory |
| | | |
| | | return path.posix.join(assetsSubDirectory, _path) |
| | | } |
| | | |
| | | exports.cssLoaders = function (options) { |
| | | options = options || {} |
| | | |
| | | const cssLoader = { |
| | | loader: 'css-loader', |
| | | options: { |
| | | sourceMap: options.sourceMap |
| | | } |
| | | } |
| | | |
| | | const postcssLoader = { |
| | | loader: 'postcss-loader', |
| | | options: { |
| | | sourceMap: options.sourceMap |
| | | } |
| | | } |
| | | |
| | | // generate loader string to be used with extract text plugin |
| | | function generateLoaders (loader, loaderOptions) { |
| | | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] |
| | | |
| | | if (loader) { |
| | | loaders.push({ |
| | | loader: loader + '-loader', |
| | | options: Object.assign({}, loaderOptions, { |
| | | sourceMap: options.sourceMap |
| | | }) |
| | | }) |
| | | } |
| | | |
| | | // Extract CSS when that option is specified |
| | | // (which is the case during production build) |
| | | if (options.extract) { |
| | | return ExtractTextPlugin.extract({ |
| | | use: loaders, |
| | | fallback: 'vue-style-loader' |
| | | }) |
| | | } else { |
| | | return ['vue-style-loader'].concat(loaders) |
| | | } |
| | | } |
| | | |
| | | // https://vue-loader.vuejs.org/en/configurations/extract-css.html |
| | | return { |
| | | css: generateLoaders(), |
| | | postcss: generateLoaders(), |
| | | less: generateLoaders('less'), |
| | | sass: generateLoaders('sass', { indentedSyntax: true }), |
| | | scss: generateLoaders('sass'), |
| | | stylus: generateLoaders('stylus'), |
| | | styl: generateLoaders('stylus') |
| | | } |
| | | } |
| | | |
| | | // Generate loaders for standalone style files (outside of .vue) |
| | | exports.styleLoaders = function (options) { |
| | | const output = [] |
| | | const loaders = exports.cssLoaders(options) |
| | | |
| | | for (const extension in loaders) { |
| | | const loader = loaders[extension] |
| | | output.push({ |
| | | test: new RegExp('\\.' + extension + '$'), |
| | | use: loader |
| | | }) |
| | | } |
| | | |
| | | return output |
| | | } |
| | | |
| | | exports.createNotifierCallback = () => { |
| | | const notifier = require('node-notifier') |
| | | |
| | | return (severity, errors) => { |
| | | if (severity !== 'error') return |
| | | |
| | | const error = errors[0] |
| | | const filename = error.file && error.file.split('!').pop() |
| | | |
| | | notifier.notify({ |
| | | title: packageConfig.name, |
| | | message: severity + ': ' + error.name, |
| | | subtitle: filename || '', |
| | | icon: path.join(__dirname, 'logo.png') |
| | | }) |
| | | } |
| | | } |
New file |
| | |
| | | 'use strict' |
| | | const utils = require('./utils') |
| | | const config = require('../config') |
| | | const isProduction = process.env.NODE_ENV === 'production' |
| | | const sourceMapEnabled = isProduction |
| | | ? config.build.productionSourceMap |
| | | : config.dev.cssSourceMap |
| | | |
| | | module.exports = { |
| | | loaders: utils.cssLoaders({ |
| | | sourceMap: sourceMapEnabled, |
| | | extract: isProduction |
| | | }), |
| | | cssSourceMap: sourceMapEnabled, |
| | | cacheBusting: config.dev.cacheBusting, |
| | | transformToRequire: { |
| | | video: ['src', 'poster'], |
| | | source: 'src', |
| | | img: 'src', |
| | | image: 'xlink:href' |
| | | } |
| | | } |
New file |
| | |
| | | 'use strict' |
| | | const path = require('path') |
| | | const utils = require('./utils') |
| | | const config = require('../config') |
| | | const vueLoaderConfig = require('./vue-loader.conf') |
| | | |
| | | function resolve (dir) { |
| | | return path.join(__dirname, '..', dir) |
| | | } |
| | | |
| | | const createLintingRule = () => ({ |
| | | test: /\.(js|vue)$/, |
| | | loader: 'eslint-loader', |
| | | enforce: 'pre', |
| | | include: [resolve('src'), resolve('test')], |
| | | options: { |
| | | formatter: require('eslint-friendly-formatter'), |
| | | emitWarning: !config.dev.showEslintErrorsInOverlay |
| | | } |
| | | }) |
| | | |
| | | module.exports = { |
| | | context: path.resolve(__dirname, '../'), |
| | | entry: { |
| | | app: './src/main.js' |
| | | }, |
| | | output: { |
| | | path: config.build.assetsRoot, |
| | | filename: '[name].js', |
| | | publicPath: process.env.NODE_ENV === 'production' |
| | | ? config.build.assetsPublicPath |
| | | : config.dev.assetsPublicPath |
| | | }, |
| | | resolve: { |
| | | extensions: ['.js', '.vue', '.json'], |
| | | alias: { |
| | | 'vue$': 'vue/dist/vue.esm.js', |
| | | '@': resolve('src'), |
| | | } |
| | | }, |
| | | module: { |
| | | rules: [ |
| | | ...(config.dev.useEslint ? [createLintingRule()] : []), |
| | | { |
| | | test: /\.vue$/, |
| | | loader: 'vue-loader', |
| | | options: vueLoaderConfig |
| | | }, |
| | | { |
| | | test: /\.js$/, |
| | | loader: 'babel-loader', |
| | | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] |
| | | }, |
| | | { |
| | | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, |
| | | loader: 'url-loader', |
| | | options: { |
| | | limit: 10000, |
| | | name: utils.assetsPath('img/[name].[hash:7].[ext]') |
| | | } |
| | | }, |
| | | { |
| | | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, |
| | | loader: 'url-loader', |
| | | options: { |
| | | limit: 10000, |
| | | name: utils.assetsPath('media/[name].[hash:7].[ext]') |
| | | } |
| | | }, |
| | | { |
| | | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, |
| | | loader: 'url-loader', |
| | | options: { |
| | | limit: 10000, |
| | | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') |
| | | } |
| | | } |
| | | ] |
| | | }, |
| | | node: { |
| | | // prevent webpack from injecting useless setImmediate polyfill because Vue |
| | | // source contains it (although only uses it if it's native). |
| | | setImmediate: false, |
| | | // prevent webpack from injecting mocks to Node native modules |
| | | // that does not make sense for the client |
| | | dgram: 'empty', |
| | | fs: 'empty', |
| | | net: 'empty', |
| | | tls: 'empty', |
| | | child_process: 'empty' |
| | | } |
| | | } |
New file |
| | |
| | | 'use strict' |
| | | const utils = require('./utils') |
| | | const webpack = require('webpack') |
| | | const config = require('../config') |
| | | const merge = require('webpack-merge') |
| | | const path = require('path') |
| | | const baseWebpackConfig = require('./webpack.base.conf') |
| | | const CopyWebpackPlugin = require('copy-webpack-plugin') |
| | | const HtmlWebpackPlugin = require('html-webpack-plugin') |
| | | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') |
| | | const portfinder = require('portfinder') |
| | | |
| | | const HOST = process.env.HOST |
| | | const PORT = process.env.PORT && Number(process.env.PORT) |
| | | |
| | | const devWebpackConfig = merge(baseWebpackConfig, { |
| | | module: { |
| | | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) |
| | | }, |
| | | // cheap-module-eval-source-map is faster for development |
| | | devtool: config.dev.devtool, |
| | | |
| | | // these devServer options should be customized in /config/index.js |
| | | devServer: { |
| | | clientLogLevel: 'warning', |
| | | historyApiFallback: { |
| | | rewrites: [ |
| | | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, |
| | | ], |
| | | }, |
| | | hot: true, |
| | | contentBase: false, // since we use CopyWebpackPlugin. |
| | | compress: true, |
| | | host: HOST || config.dev.host, |
| | | port: PORT || config.dev.port, |
| | | open: config.dev.autoOpenBrowser, |
| | | overlay: config.dev.errorOverlay |
| | | ? { warnings: false, errors: true } |
| | | : false, |
| | | publicPath: config.dev.assetsPublicPath, |
| | | proxy: config.dev.proxyTable, |
| | | quiet: true, // necessary for FriendlyErrorsPlugin |
| | | watchOptions: { |
| | | poll: config.dev.poll, |
| | | } |
| | | }, |
| | | plugins: [ |
| | | new webpack.DefinePlugin({ |
| | | 'process.env': require('../config/dev.env') |
| | | }), |
| | | new webpack.HotModuleReplacementPlugin(), |
| | | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. |
| | | new webpack.NoEmitOnErrorsPlugin(), |
| | | // https://github.com/ampedandwired/html-webpack-plugin |
| | | new HtmlWebpackPlugin({ |
| | | filename: 'index.html', |
| | | template: 'index.html', |
| | | inject: true |
| | | }), |
| | | // copy custom static assets |
| | | new CopyWebpackPlugin([ |
| | | { |
| | | from: path.resolve(__dirname, '../static'), |
| | | to: config.dev.assetsSubDirectory, |
| | | ignore: ['.*'] |
| | | } |
| | | ]) |
| | | ] |
| | | }) |
| | | |
| | | module.exports = new Promise((resolve, reject) => { |
| | | portfinder.basePort = process.env.PORT || config.dev.port |
| | | portfinder.getPort((err, port) => { |
| | | if (err) { |
| | | reject(err) |
| | | } else { |
| | | // publish the new Port, necessary for e2e tests |
| | | process.env.PORT = port |
| | | // add port to devServer config |
| | | devWebpackConfig.devServer.port = port |
| | | |
| | | // Add FriendlyErrorsPlugin |
| | | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ |
| | | compilationSuccessInfo: { |
| | | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], |
| | | }, |
| | | onErrors: config.dev.notifyOnErrors |
| | | ? utils.createNotifierCallback() |
| | | : undefined |
| | | })) |
| | | |
| | | resolve(devWebpackConfig) |
| | | } |
| | | }) |
| | | }) |
New file |
| | |
| | | 'use strict' |
| | | const path = require('path') |
| | | const utils = require('./utils') |
| | | const webpack = require('webpack') |
| | | const config = require('../config') |
| | | const merge = require('webpack-merge') |
| | | const baseWebpackConfig = require('./webpack.base.conf') |
| | | const CopyWebpackPlugin = require('copy-webpack-plugin') |
| | | const HtmlWebpackPlugin = require('html-webpack-plugin') |
| | | const ExtractTextPlugin = require('extract-text-webpack-plugin') |
| | | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') |
| | | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') |
| | | |
| | | const env = process.env.NODE_ENV === 'testing' |
| | | ? require('../config/test.env') |
| | | : require('../config/prod.env') |
| | | |
| | | const webpackConfig = merge(baseWebpackConfig, { |
| | | module: { |
| | | rules: utils.styleLoaders({ |
| | | sourceMap: config.build.productionSourceMap, |
| | | extract: true, |
| | | usePostCSS: true |
| | | }) |
| | | }, |
| | | devtool: config.build.productionSourceMap ? config.build.devtool : false, |
| | | output: { |
| | | path: config.build.assetsRoot, |
| | | filename: utils.assetsPath('js/[name].[chunkhash].js'), |
| | | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') |
| | | }, |
| | | plugins: [ |
| | | // http://vuejs.github.io/vue-loader/en/workflow/production.html |
| | | new webpack.DefinePlugin({ |
| | | 'process.env': env |
| | | }), |
| | | new UglifyJsPlugin({ |
| | | uglifyOptions: { |
| | | compress: { |
| | | warnings: false |
| | | } |
| | | }, |
| | | sourceMap: config.build.productionSourceMap, |
| | | parallel: true |
| | | }), |
| | | // extract css into its own file |
| | | new ExtractTextPlugin({ |
| | | filename: utils.assetsPath('css/[name].[contenthash].css'), |
| | | // Setting the following option to `false` will not extract CSS from codesplit chunks. |
| | | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. |
| | | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, |
| | | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 |
| | | allChunks: true, |
| | | }), |
| | | // Compress extracted CSS. We are using this plugin so that possible |
| | | // duplicated CSS from different components can be deduped. |
| | | new OptimizeCSSPlugin({ |
| | | cssProcessorOptions: config.build.productionSourceMap |
| | | ? { safe: true, map: { inline: false } } |
| | | : { safe: true } |
| | | }), |
| | | // generate dist index.html with correct asset hash for caching. |
| | | // you can customize output by editing /index.html |
| | | // see https://github.com/ampedandwired/html-webpack-plugin |
| | | new HtmlWebpackPlugin({ |
| | | filename: process.env.NODE_ENV === 'testing' |
| | | ? 'index.html' |
| | | : config.build.index, |
| | | template: 'index.html', |
| | | inject: true, |
| | | minify: { |
| | | removeComments: true, |
| | | collapseWhitespace: true, |
| | | removeAttributeQuotes: true |
| | | // more options: |
| | | // https://github.com/kangax/html-minifier#options-quick-reference |
| | | }, |
| | | // necessary to consistently work with multiple chunks via CommonsChunkPlugin |
| | | chunksSortMode: 'dependency' |
| | | }), |
| | | // keep module.id stable when vendor modules does not change |
| | | new webpack.HashedModuleIdsPlugin(), |
| | | // enable scope hoisting |
| | | new webpack.optimize.ModuleConcatenationPlugin(), |
| | | // split vendor js into its own file |
| | | new webpack.optimize.CommonsChunkPlugin({ |
| | | name: 'vendor', |
| | | minChunks (module) { |
| | | // any required modules inside node_modules are extracted to vendor |
| | | return ( |
| | | module.resource && |
| | | /\.js$/.test(module.resource) && |
| | | module.resource.indexOf( |
| | | path.join(__dirname, '../node_modules') |
| | | ) === 0 |
| | | ) |
| | | } |
| | | }), |
| | | // extract webpack runtime and module manifest to its own file in order to |
| | | // prevent vendor hash from being updated whenever app bundle is updated |
| | | new webpack.optimize.CommonsChunkPlugin({ |
| | | name: 'manifest', |
| | | minChunks: Infinity |
| | | }), |
| | | // This instance extracts shared chunks from code splitted chunks and bundles them |
| | | // in a separate chunk, similar to the vendor chunk |
| | | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk |
| | | new webpack.optimize.CommonsChunkPlugin({ |
| | | name: 'app', |
| | | async: 'vendor-async', |
| | | children: true, |
| | | minChunks: 3 |
| | | }), |
| | | |
| | | // copy custom static assets |
| | | new CopyWebpackPlugin([ |
| | | { |
| | | from: path.resolve(__dirname, '../static'), |
| | | to: config.build.assetsSubDirectory, |
| | | ignore: ['.*'] |
| | | } |
| | | ]) |
| | | ] |
| | | }) |
| | | |
| | | if (config.build.productionGzip) { |
| | | const CompressionWebpackPlugin = require('compression-webpack-plugin') |
| | | |
| | | webpackConfig.plugins.push( |
| | | new CompressionWebpackPlugin({ |
| | | asset: '[path].gz[query]', |
| | | algorithm: 'gzip', |
| | | test: new RegExp( |
| | | '\\.(' + |
| | | config.build.productionGzipExtensions.join('|') + |
| | | ')$' |
| | | ), |
| | | threshold: 10240, |
| | | minRatio: 0.8 |
| | | }) |
| | | ) |
| | | } |
| | | |
| | | if (config.build.bundleAnalyzerReport) { |
| | | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin |
| | | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) |
| | | } |
| | | |
| | | module.exports = webpackConfig |
New file |
| | |
| | | 'use strict' |
| | | const merge = require('webpack-merge') |
| | | const prodEnv = require('./prod.env') |
| | | |
| | | module.exports = merge(prodEnv, { |
| | | NODE_ENV: '"development"' |
| | | }) |
New file |
| | |
| | | 'use strict' |
| | | // Template version: 1.3.1 |
| | | // see http://vuejs-templates.github.io/webpack for documentation. |
| | | |
| | | const path = require('path') |
| | | |
| | | module.exports = { |
| | | dev: { |
| | | |
| | | // Paths |
| | | assetsSubDirectory: 'static', |
| | | assetsPublicPath: '/', |
| | | proxyTable: {}, |
| | | |
| | | // Various Dev Server settings |
| | | host: 'localhost', // can be overwritten by process.env.HOST |
| | | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined |
| | | autoOpenBrowser: false, |
| | | errorOverlay: true, |
| | | notifyOnErrors: true, |
| | | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- |
| | | |
| | | // Use Eslint Loader? |
| | | // If true, your code will be linted during bundling and |
| | | // linting errors and warnings will be shown in the console. |
| | | useEslint: true, |
| | | // If true, eslint errors and warnings will also be shown in the error overlay |
| | | // in the browser. |
| | | showEslintErrorsInOverlay: false, |
| | | |
| | | /** |
| | | * Source Maps |
| | | */ |
| | | |
| | | // https://webpack.js.org/configuration/devtool/#development |
| | | devtool: 'cheap-module-eval-source-map', |
| | | |
| | | // If you have problems debugging vue-files in devtools, |
| | | // set this to false - it *may* help |
| | | // https://vue-loader.vuejs.org/en/options.html#cachebusting |
| | | cacheBusting: true, |
| | | |
| | | cssSourceMap: true |
| | | }, |
| | | |
| | | build: { |
| | | // Template for index.html |
| | | index: path.resolve(__dirname, '../dist/index.html'), |
| | | |
| | | // Paths |
| | | assetsRoot: path.resolve(__dirname, '../dist'), |
| | | assetsSubDirectory: 'static', |
| | | assetsPublicPath: './', |
| | | |
| | | /** |
| | | * Source Maps |
| | | */ |
| | | |
| | | productionSourceMap: true, |
| | | // https://webpack.js.org/configuration/devtool/#production |
| | | devtool: '#source-map', |
| | | |
| | | // Gzip off by default as many popular static hosts such as |
| | | // Surge or Netlify already gzip all static assets for you. |
| | | // Before setting to `true`, make sure to: |
| | | // npm install --save-dev compression-webpack-plugin |
| | | productionGzip: false, |
| | | productionGzipExtensions: ['js', 'css'], |
| | | |
| | | // Run the build command with an extra argument to |
| | | // View the bundle analyzer report after build finishes: |
| | | // `npm run build --report` |
| | | // Set to `true` or `false` to always turn it on or off |
| | | bundleAnalyzerReport: process.env.npm_config_report |
| | | } |
| | | } |
New file |
| | |
| | | 'use strict' |
| | | module.exports = { |
| | | NODE_ENV: '"production"' |
| | | } |
New file |
| | |
| | | 'use strict' |
| | | const merge = require('webpack-merge') |
| | | const devEnv = require('./dev.env') |
| | | |
| | | module.exports = merge(devEnv, { |
| | | NODE_ENV: '"testing"' |
| | | }) |
New file |
| | |
| | | <!DOCTYPE html> |
| | | <html> |
| | | <head> |
| | | <meta charset="utf-8"> |
| | | <meta name="viewport" content="width=device-width,initial-scale=1.0"> |
| | | <title>vue-antd-admin</title> |
| | | </head> |
| | | <body> |
| | | <div id="app"></div> |
| | | <!-- built files will be auto injected --> |
| | | </body> |
| | | </html> |
New file |
| | |
| | | { |
| | | "name": "vue-antd-admin", |
| | | "version": "1.0.0", |
| | | "description": "A Vue.js project", |
| | | "author": "chenghx <chenghx@nfex.com>", |
| | | "private": true, |
| | | "scripts": { |
| | | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", |
| | | "start": "npm run dev", |
| | | "unit": "jest --config test/unit/jest.conf.js --coverage", |
| | | "e2e": "node test/e2e/runner.js", |
| | | "test": "npm run unit && npm run e2e", |
| | | "lint": "eslint --ext .js,.vue src test/unit test/e2e/specs", |
| | | "build": "node build/build.js" |
| | | }, |
| | | "dependencies": { |
| | | "viser-vue": "^2.2.5", |
| | | "vue": "^2.5.2", |
| | | "vue-antd-ui": "^0.7.0", |
| | | "vue-router": "^3.0.1" |
| | | }, |
| | | "devDependencies": { |
| | | "autoprefixer": "^7.1.2", |
| | | "babel-core": "^6.22.1", |
| | | "babel-eslint": "^8.2.1", |
| | | "babel-helper-vue-jsx-merge-props": "^2.0.3", |
| | | "babel-jest": "^21.0.2", |
| | | "babel-loader": "^7.1.1", |
| | | "babel-plugin-dynamic-import-node": "^1.2.0", |
| | | "babel-plugin-syntax-jsx": "^6.18.0", |
| | | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", |
| | | "babel-plugin-transform-runtime": "^6.22.0", |
| | | "babel-plugin-transform-vue-jsx": "^3.5.0", |
| | | "babel-preset-env": "^1.3.2", |
| | | "babel-preset-stage-2": "^6.22.0", |
| | | "babel-register": "^6.22.0", |
| | | "chalk": "^2.0.1", |
| | | "chromedriver": "^2.27.2", |
| | | "copy-webpack-plugin": "^4.0.1", |
| | | "cross-spawn": "^5.0.1", |
| | | "css-loader": "^0.28.0", |
| | | "eslint": "^4.15.0", |
| | | "eslint-config-standard": "^10.2.1", |
| | | "eslint-friendly-formatter": "^3.0.0", |
| | | "eslint-loader": "^1.7.1", |
| | | "eslint-plugin-import": "^2.7.0", |
| | | "eslint-plugin-node": "^5.2.0", |
| | | "eslint-plugin-promise": "^3.4.0", |
| | | "eslint-plugin-standard": "^3.0.1", |
| | | "eslint-plugin-vue": "^4.0.0", |
| | | "extract-text-webpack-plugin": "^3.0.0", |
| | | "file-loader": "^1.1.4", |
| | | "friendly-errors-webpack-plugin": "^1.6.1", |
| | | "html-webpack-plugin": "^2.30.1", |
| | | "jest": "^22.0.4", |
| | | "jest-serializer-vue": "^0.3.0", |
| | | "nightwatch": "^0.9.12", |
| | | "node-notifier": "^5.1.2", |
| | | "optimize-css-assets-webpack-plugin": "^3.2.0", |
| | | "ora": "^1.2.0", |
| | | "portfinder": "^1.0.13", |
| | | "postcss-import": "^11.0.0", |
| | | "postcss-loader": "^2.0.8", |
| | | "postcss-url": "^7.2.1", |
| | | "rimraf": "^2.6.0", |
| | | "selenium-server": "^3.0.1", |
| | | "semver": "^5.3.0", |
| | | "shelljs": "^0.7.6", |
| | | "uglifyjs-webpack-plugin": "^1.1.1", |
| | | "url-loader": "^0.5.8", |
| | | "vue-jest": "^1.0.2", |
| | | "vue-loader": "^13.3.0", |
| | | "vue-style-loader": "^3.0.1", |
| | | "vue-template-compiler": "^2.5.2", |
| | | "webpack": "^3.6.0", |
| | | "webpack-bundle-analyzer": "^2.9.0", |
| | | "webpack-dev-server": "^2.9.1", |
| | | "webpack-merge": "^4.1.0" |
| | | }, |
| | | "engines": { |
| | | "node": ">= 6.0.0", |
| | | "npm": ">= 3.0.0" |
| | | }, |
| | | "browserslist": [ |
| | | "> 1%", |
| | | "last 2 versions", |
| | | "not ie <= 8" |
| | | ] |
| | | } |
New file |
| | |
| | | <template> |
| | | <div id="app"> |
| | | <!--<img src="./assets/logo.png">--> |
| | | <gloabl-layout> |
| | | <router-view/> |
| | | </gloabl-layout> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import GloablLayout from './components/layout/GloablLayout' |
| | | export default { |
| | | name: 'App', |
| | | components: {GloablLayout} |
| | | } |
| | | </script> |
| | | |
| | | <style> |
| | | #app { |
| | | font-family: 'Avenir', Helvetica, Arial, sans-serif; |
| | | -webkit-font-smoothing: antialiased; |
| | | -moz-osx-font-smoothing: grayscale; |
| | | /*text-align: center;*/ |
| | | color: #2c3e50; |
| | | /*margin-top: 60px;*/ |
| | | } |
| | | </style> |
New file |
| | |
| | | <template> |
| | | <div class="hello"> |
| | | <h1>{{ msg }}</h1> |
| | | <h2>Essential Links</h2> |
| | | <ul> |
| | | <li> |
| | | <a |
| | | href="https://vuejs.org" |
| | | target="_blank" |
| | | > |
| | | Core Docs |
| | | </a> |
| | | </li> |
| | | <li> |
| | | <a |
| | | href="https://forum.vuejs.org" |
| | | target="_blank" |
| | | > |
| | | Forum |
| | | </a> |
| | | </li> |
| | | <li> |
| | | <a |
| | | href="https://chat.vuejs.org" |
| | | target="_blank" |
| | | > |
| | | Community Chat |
| | | </a> |
| | | </li> |
| | | <li> |
| | | <a |
| | | href="https://twitter.com/vuejs" |
| | | target="_blank" |
| | | > |
| | | Twitter |
| | | </a> |
| | | </li> |
| | | <br> |
| | | <li> |
| | | <a |
| | | href="http://vuejs-templates.github.io/webpack/" |
| | | target="_blank" |
| | | > |
| | | Docs for This Template |
| | | </a> |
| | | </li> |
| | | </ul> |
| | | <h2>Ecosystem</h2> |
| | | <ul> |
| | | <li> |
| | | <a |
| | | href="http://router.vuejs.org/" |
| | | target="_blank" |
| | | > |
| | | vue-router |
| | | </a> |
| | | </li> |
| | | <li> |
| | | <a |
| | | href="http://vuex.vuejs.org/" |
| | | target="_blank" |
| | | > |
| | | vuex |
| | | </a> |
| | | </li> |
| | | <li> |
| | | <a |
| | | href="http://vue-loader.vuejs.org/" |
| | | target="_blank" |
| | | > |
| | | vue-loader |
| | | </a> |
| | | </li> |
| | | <li> |
| | | <a |
| | | href="https://github.com/vuejs/awesome-vue" |
| | | target="_blank" |
| | | > |
| | | awesome-vue |
| | | </a> |
| | | </li> |
| | | </ul> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | name: 'HelloWorld', |
| | | data () { |
| | | return { |
| | | msg: 'Welcome to Your Vue.js App' |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <!-- Add "scoped" attribute to limit CSS to this component only --> |
| | | <style scoped> |
| | | h1, h2 { |
| | | font-weight: normal; |
| | | } |
| | | ul { |
| | | list-style-type: none; |
| | | padding: 0; |
| | | } |
| | | li { |
| | | display: inline-block; |
| | | margin: 0 10px; |
| | | } |
| | | a { |
| | | color: #42b983; |
| | | } |
| | | </style> |
New file |
| | |
| | | <template> |
| | | <div> |
| | | <v-chart :forceFit="true" :height="height" :data="data" :scale="scale"> |
| | | <v-tooltip /> |
| | | <v-axis /> |
| | | <v-smooth-area position="time*value" /> |
| | | </v-chart> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import 'viser-vue' |
| | | |
| | | const data = [ |
| | | { time: 0, value: 23620 }, |
| | | { time: 2, value: 16100 }, |
| | | { time: 4, value: 15900 }, |
| | | { time: 6, value: 17409 }, |
| | | { time: 8, value: 17000 }, |
| | | { time: 10, value: 31056 }, |
| | | { time: 12, value: 31982 }, |
| | | { time: 14, value: 32040 }, |
| | | { time: 16, value: 33233 }, |
| | | { time: 18, value: 33233 }, |
| | | { time: 20, value: 33233 }, |
| | | { time: 22, value: 33233 } |
| | | ] |
| | | |
| | | const scale = [{ |
| | | dataKey: 'value', |
| | | min: 10000 |
| | | }, { |
| | | dataKey: 'year', |
| | | min: 0, |
| | | max: 1 |
| | | }] |
| | | export default { |
| | | name: 'MiniChart', |
| | | data () { |
| | | return { |
| | | data, |
| | | scale, |
| | | height: 400 |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
New file |
| | |
| | | <template> |
| | | <a-card :body-style="{padding: '20px 24px 8px'}" :bordered="false"> |
| | | <div class="chart-card-header"> |
| | | <div class="meta"> |
| | | <span class="chart-card-title">{{title}}</span> |
| | | <span class="chart-card-action"> |
| | | <slot name="action"></slot> |
| | | </span> |
| | | </div> |
| | | <div class="total"><span>¥ 1358.34</span></div> |
| | | </div> |
| | | <div class="chart-card-content"> |
| | | <slot></slot> |
| | | </div> |
| | | <div class="chart-card-footer"> |
| | | <slot name="footer"></slot> |
| | | </div> |
| | | </a-card> |
| | | </template> |
| | | |
| | | <script> |
| | | import ACard from 'vue-antd-ui/es/card/Card' |
| | | export default { |
| | | name: 'ChartCard', |
| | | components: {ACard}, |
| | | props: ['title'] |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .chart-card-header{ |
| | | position: relative; |
| | | overflow: hidden; |
| | | width: 100%; |
| | | } |
| | | .chart-card-header .meta{ |
| | | position: relative; |
| | | overflow: hidden; |
| | | width: 100%; |
| | | color: rgba(0,0,0,.45); |
| | | font-size: 14px; |
| | | line-height: 22px; |
| | | } |
| | | .chart-card-action{ |
| | | cursor: pointer; |
| | | position: absolute; |
| | | top: 0; |
| | | right: 0; |
| | | } |
| | | .total { |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | word-break: break-all; |
| | | white-space: nowrap; |
| | | color: #000; |
| | | margin-top: 4px; |
| | | margin-bottom: 0; |
| | | font-size: 30px; |
| | | line-height: 38px; |
| | | height: 38px; |
| | | } |
| | | .chart-card-footer{ |
| | | border-top: 1px solid #e8e8e8; |
| | | padding-top: 9px; |
| | | margin-top: 8px; |
| | | } |
| | | .chart-card-content{ |
| | | margin-bottom: 12px; |
| | | position: relative; |
| | | width: 100%; |
| | | } |
| | | </style> |
New file |
| | |
| | | <template> |
| | | <div> |
| | | <a-row style="margin: -12px"> |
| | | <a-col :sm="24" :md="12" :xl="6" style="padding: 12px 12px 24px;"> |
| | | <chart-card title="总销售额"> |
| | | <a-tooltip title="指标说明" slot="action"> |
| | | <a-icon type="info-circle-o" /> |
| | | </a-tooltip> |
| | | <div> |
| | | <div style="display: inline-block; font-size: 14px; line-height: 22px; margin-right: 16px"> |
| | | 同周比 |
| | | <span>12%</span> |
| | | <span style="color: #f5222d; font-size: 12px"><a-icon type="caret-up" /></span> |
| | | </div> |
| | | <div style="display: inline-block; font-size: 14px; line-height: 22px;"> |
| | | 日环比 |
| | | <span>11%</span> |
| | | <span style="color: #52c41a; font-size: 12px"><a-icon type="caret-down" /></span> |
| | | </div> |
| | | </div> |
| | | <div slot="footer">日均销售额 <span>¥ 234.56</span></div> |
| | | </chart-card> |
| | | </a-col> |
| | | <a-col :sm="24" :md="12" :xl="6" style="padding: 12px 12px 24px;"> |
| | | <chart-card title="总销售额"> |
| | | <a-tooltip title="指标说明" slot="action"> |
| | | <a-icon type="info-circle-o" /> |
| | | </a-tooltip> |
| | | <div> |
| | | <mini-chart /> |
| | | </div> |
| | | <div slot="footer">日均销售额 <span>¥ 234.56</span></div> |
| | | </chart-card> |
| | | </a-col> |
| | | <a-col :sm="24" :md="12" :xl="6" style="padding: 12px 12px 24px;"> |
| | | <chart-card title="总销售额"> |
| | | <a-tooltip title="指标说明" slot="action"> |
| | | <a-icon type="info-circle-o" /> |
| | | </a-tooltip> |
| | | <div> |
| | | <div style="display: inline-block; font-size: 14px; line-height: 22px; margin-right: 16px"> |
| | | 同周比 |
| | | <span>12%</span> |
| | | <span style="color: #f5222d; font-size: 12px"><a-icon type="caret-up" /></span> |
| | | </div> |
| | | <div style="display: inline-block; font-size: 14px; line-height: 22px;"> |
| | | 日环比 |
| | | <span>11%</span> |
| | | <span style="color: #52c41a; font-size: 12px"><a-icon type="caret-down" /></span> |
| | | </div> |
| | | </div> |
| | | <div slot="footer">日均销售额 <span>¥ 234.56</span></div> |
| | | </chart-card> |
| | | </a-col> |
| | | <a-col :sm="24" :md="12" :xl="6" style="padding: 12px 12px 24px;"> |
| | | <chart-card title="总销售额"> |
| | | <a-tooltip title="指标说明" slot="action"> |
| | | <a-icon type="info-circle-o" /> |
| | | </a-tooltip> |
| | | <div> |
| | | <div style="display: inline-block; font-size: 14px; line-height: 22px; margin-right: 16px"> |
| | | 同周比 |
| | | <span>12%</span> |
| | | <span style="color: #f5222d; font-size: 12px"><a-icon type="caret-up" /></span> |
| | | </div> |
| | | <div style="display: inline-block; font-size: 14px; line-height: 22px;"> |
| | | 日环比 |
| | | <span>11%</span> |
| | | <span style="color: #52c41a; font-size: 12px"><a-icon type="caret-down" /></span> |
| | | </div> |
| | | </div> |
| | | <div slot="footer">日均销售额 <span>¥ 234.56</span></div> |
| | | </chart-card> |
| | | </a-col> |
| | | </a-row> |
| | | </div> |
| | | </template> |
| | | |
| | | <script> |
| | | import ACol from 'vue-antd-ui/es/grid/Col' |
| | | import ARow from 'vue-antd-ui/es/grid/Row' |
| | | import ACard from 'vue-antd-ui/es/card/Card' |
| | | import ChartCard from './ChartCard' |
| | | import ATooltip from 'vue-antd-ui/es/tooltip/Tooltip' |
| | | import AIcon from 'vue-antd-ui/es/icon/icon' |
| | | import MiniChart from '../chart/MiniChart' |
| | | export default { |
| | | name: 'dashboard', |
| | | components: {MiniChart, AIcon, ATooltip, ChartCard, ACard, ARow, ACol} |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
New file |
| | |
| | | <template> |
| | | <a-layout-header style="background: #fff; padding: 0 12px 0 0;box-shadow: 0 1px 4px rgba(0,21,41,.08);"> |
| | | <a-icon class="trigger" :type="collapsed ? 'menu-unfold' : 'menu-fold'" @click="toggleCollapse"/> |
| | | <div style="float: right"> |
| | | <header-search class="header-item"/> |
| | | <a-tooltip class="header-item" title="帮助文档" placement="bottom" > |
| | | <a> |
| | | <a-icon type="question-circle-o" /> |
| | | </a> |
| | | </a-tooltip> |
| | | <header-notice class="header-item"/> |
| | | <header-avatar class="header-item"/> |
| | | </div> |
| | | </a-layout-header> |
| | | </template> |
| | | |
| | | <script> |
| | | import ALayout from 'vue-antd-ui/es/layout' |
| | | import AIcon from 'vue-antd-ui/es/icon/icon' |
| | | import AInputSearch from 'vue-antd-ui/es/input/Search' |
| | | import HeaderSearch from './HeaderSearch' |
| | | import HeaderNotice from './HeaderNotice' |
| | | import ATooltip from 'vue-antd-ui/es/tooltip/Tooltip' |
| | | import HeaderAvatar from './HeaderlAvatar' |
| | | |
| | | const ALayoutSider = ALayout.Sider |
| | | const ALayoutHeader = ALayout.Header |
| | | export default { |
| | | name: 'GloablHeader', |
| | | components: { |
| | | HeaderAvatar, |
| | | ATooltip, |
| | | HeaderNotice, |
| | | HeaderSearch, |
| | | AInputSearch, |
| | | AIcon, |
| | | ALayout, |
| | | ALayoutSider, |
| | | ALayoutHeader}, |
| | | props: ['collapsed'], |
| | | data () { |
| | | return { |
| | | } |
| | | }, |
| | | methods: { |
| | | toggleCollapse () { |
| | | this.$emit('toggleCollapse') |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .trigger { |
| | | font-size: 20px; |
| | | line-height: 64px; |
| | | padding: 0 24px; |
| | | cursor: pointer; |
| | | transition: color .3s; |
| | | } |
| | | |
| | | .trigger:hover { |
| | | color: #1890ff; |
| | | } |
| | | |
| | | .header-item{ |
| | | margin: 0 12px; |
| | | display: inline-block; |
| | | height: 100%; |
| | | cursor: pointer; |
| | | } |
| | | .header-item i{ |
| | | font-size: 16px; |
| | | color: rgba(0,0,0,.65); |
| | | } |
| | | </style> |
New file |
| | |
| | | <template> |
| | | <a-layout> |
| | | <a-layout-sider width="256px" collapsible v-model="collapsed" :trigger="null"> |
| | | <div class="logo"> |
| | | <a href="/"> |
| | | <img src="static/img/vue-antd-logo.png"> |
| | | <h1>Vue Ant Pro</h1> |
| | | </a> |
| | | </div> |
| | | <!--<gloabl-menu :collapsed="collapsed"/>--> |
| | | <i-menu :menuData="menuData" /> |
| | | </a-layout-sider> |
| | | <a-layout> |
| | | <gloabl-header :collapsed="collapsed" @toggleCollapse="toggleCollapse"/> |
| | | <a-layout-content :style="{minHeight: minHeight, margin: '24px 24px 0'}"> |
| | | <slot></slot> |
| | | </a-layout-content> |
| | | <a-layout-footer></a-layout-footer> |
| | | </a-layout> |
| | | </a-layout> |
| | | </template> |
| | | |
| | | <script> |
| | | import ALayout from 'vue-antd-ui/es/layout' |
| | | import GloablHeader from './GloablHeader' |
| | | import AIcon from 'vue-antd-ui/es/icon/icon' |
| | | import GloablMenu from './GloablMenu' |
| | | import IMenu from '../menu/menu' |
| | | |
| | | const ALayoutSider = ALayout.Sider |
| | | const ALayoutHeader = ALayout.Header |
| | | const ALayoutContent = ALayout.Content |
| | | const ALayoutFooter = ALayout.Footer |
| | | const minHeight = window.innerHeight - 64 - 48 |
| | | |
| | | const menuData = [ |
| | | { |
| | | title: '统计分析', |
| | | path: '/dashboard' |
| | | }, |
| | | { |
| | | title: '图表', |
| | | path: '/chart', |
| | | icon: 'line-chart' |
| | | }, |
| | | { |
| | | title: '报表系统' |
| | | }, |
| | | { |
| | | title: '日志', |
| | | children: [ |
| | | { |
| | | title: '日志分析', |
| | | icon: 'none' |
| | | }, |
| | | { |
| | | title: '数据分析', |
| | | icon: 'none' |
| | | } |
| | | ] |
| | | } |
| | | ] |
| | | |
| | | export default { |
| | | name: 'GloablLayout', |
| | | components: { |
| | | GloablMenu, |
| | | AIcon, |
| | | GloablHeader, |
| | | ALayout, |
| | | ALayoutSider, |
| | | ALayoutHeader, |
| | | ALayoutContent, |
| | | ALayoutFooter, |
| | | IMenu}, |
| | | data () { |
| | | return { |
| | | minHeight: minHeight + 'px', |
| | | collapsed: false, |
| | | menuData: menuData |
| | | } |
| | | }, |
| | | methods: { |
| | | toggleCollapse () { |
| | | this.collapsed = !this.collapsed |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .logo{ |
| | | height: 64px; |
| | | position: relative; |
| | | line-height: 64px; |
| | | padding-left: 24px; |
| | | -webkit-transition: all .3s; |
| | | transition: all .3s; |
| | | background: #002140; |
| | | overflow: hidden; |
| | | } |
| | | .logo h1{ |
| | | color: #fff; |
| | | font-size: 20px; |
| | | margin: 0 0 0 12px; |
| | | font-family: "Myriad Pro","Helvetica Neue",Arial,Helvetica,sans-serif; |
| | | font-weight: 600; |
| | | display: inline-block; |
| | | } |
| | | .logo img{ |
| | | width: 32px; |
| | | display: inline-block; |
| | | vertical-align: middle; |
| | | } |
| | | </style> |
New file |
| | |
| | | <template> |
| | | <a-menu theme="dark" mode="inline" :inlineCollapsed="collapsed"> |
| | | <slot></slot> |
| | | <a-menu-item> |
| | | <a-icon type="dashboard" /> |
| | | <span>统计分析</span> |
| | | </a-menu-item> |
| | | <a-menu-item> |
| | | <a href="/"> |
| | | <a-icon type="user" /> |
| | | <span>个人中心</span> |
| | | </a> |
| | | </a-menu-item> |
| | | <a-menu-item> |
| | | <a-icon type="form" /> |
| | | <span>个人中心</span> |
| | | </a-menu-item> |
| | | <a-sub-menu> |
| | | <span slot="title"> |
| | | <a-icon type="appstore" /> |
| | | <span>系统日志</span> |
| | | </span> |
| | | <a-menu-item> |
| | | <span>应用日志</span> |
| | | </a-menu-item> |
| | | <a-menu-item> |
| | | <span>数据库日志</span> |
| | | </a-menu-item> |
| | | <a-menu-item> |
| | | <span>操作日志</span> |
| | | </a-menu-item> |
| | | </a-sub-menu> |
| | | </a-menu> |
| | | </template> |
| | | |
| | | <script> |
| | | import AIcon from 'vue-antd-ui/es/icon/icon' |
| | | import AMenu from 'vue-antd-ui/es/menu/index' |
| | | const ASubMenu = AMenu.SubMenu |
| | | const AMenuItem = AMenu.Item |
| | | |
| | | export default { |
| | | name: 'GloablMenu', |
| | | props: { |
| | | collapsed: Boolean |
| | | }, |
| | | components: {AIcon, AMenu, ASubMenu, AMenuItem}, |
| | | data () { |
| | | return { |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
New file |
| | |
| | | <template> |
| | | <a-popover trigger="click" placement="bottomRight"> |
| | | <template slot="content"> |
| | | <a-spin :spinning="loadding"> |
| | | <a-tabs> |
| | | <a-tab-pane tab="通知" key="1"> |
| | | <a-list> |
| | | <a-list-item> |
| | | <a-list-item-meta title="你收到了 14 份新周报" description="一年前"> |
| | | <a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png"/> |
| | | </a-list-item-meta> |
| | | </a-list-item> |
| | | <a-list-item> |
| | | <a-list-item-meta title="你推荐的 曲妮妮 已通过第三轮面试" description="一年前"> |
| | | <a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png"/> |
| | | </a-list-item-meta> |
| | | </a-list-item> |
| | | <a-list-item> |
| | | <a-list-item-meta title="这种模板可以区分多种通知类型" description="一年前"> |
| | | <a-avatar style="background-color: white" slot="avatar" src="https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png"/> |
| | | </a-list-item-meta> |
| | | </a-list-item> |
| | | </a-list> |
| | | </a-tab-pane> |
| | | <a-tab-pane tab="消息" key="2"> |
| | | 123 |
| | | </a-tab-pane> |
| | | <a-tab-pane tab="待办" key="3"> |
| | | 123 |
| | | </a-tab-pane> |
| | | </a-tabs> |
| | | </a-spin> |
| | | </template> |
| | | <span @click="fetchNotice"> |
| | | <a-badge count="12"> |
| | | <a-icon style="font-size: 16px; padding: 4px" type="bell" /> |
| | | </a-badge> |
| | | </span> |
| | | </a-popover> |
| | | </template> |
| | | |
| | | <script> |
| | | import APopover from 'vue-antd-ui/es/popover/index' |
| | | import AIcon from 'vue-antd-ui/es/icon/icon' |
| | | import ABadge from 'vue-antd-ui/es/badge/Badge' |
| | | import ATabs from 'vue-antd-ui/es/tabs' |
| | | import AList from 'vue-antd-ui/es/list/index' |
| | | import AListItem from 'vue-antd-ui/es/list/Item' |
| | | import AAvatar from 'vue-antd-ui/es/avatar/Avatar' |
| | | import ASpin from 'vue-antd-ui/es/spin/Spin' |
| | | |
| | | const ATabPane = ATabs.TabPane |
| | | const AListItemMeta = AListItem.Meta |
| | | |
| | | export default { |
| | | name: 'HeaderNotice', |
| | | components: {ASpin, AAvatar, AListItem, AList, ATabs, ABadge, AIcon, APopover, ATabPane, AListItemMeta}, |
| | | data () { |
| | | return { |
| | | loadding: false |
| | | } |
| | | }, |
| | | methods: { |
| | | fetchNotice () { |
| | | if (this.loadding) { |
| | | this.loadding = false |
| | | return |
| | | } |
| | | this.loadding = true |
| | | setTimeout(() => { |
| | | this.loadding = false |
| | | }, 2000) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
New file |
| | |
| | | <template> |
| | | <span class="header-item"> |
| | | <a-icon type="search" style="font-size: 16px; cursor: pointer;" @click="enterSearchMode"/> |
| | | <a-auto-complete |
| | | ref="input" |
| | | :dataSource="dataSource" |
| | | :class="[searchMode ? 'enterSearch' : 'leaveSearch']" |
| | | placeholder="站内搜索" |
| | | @blur="leaveSearchMode" |
| | | > |
| | | <a-input style="" /> |
| | | </a-auto-complete> |
| | | </span> |
| | | </template> |
| | | |
| | | <script> |
| | | import AIcon from 'vue-antd-ui/es/icon/icon' |
| | | import AAutoComplete from 'vue-antd-ui/es/auto-complete/index' |
| | | import AInput from 'vue-antd-ui/es/input/Input' |
| | | export default { |
| | | name: 'HeaderSearch', |
| | | components: {AInput, AAutoComplete, AIcon}, |
| | | data () { |
| | | return { |
| | | dataSource: ['选项一', '选项二'], |
| | | searchMode: false |
| | | } |
| | | }, |
| | | methods: { |
| | | enterSearchMode () { |
| | | this.searchMode = true |
| | | this.$refs.input.focus() |
| | | }, |
| | | leaveSearchMode () { |
| | | this.searchMode = false |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .enterSearch { |
| | | width: 225px; |
| | | border-bottom: 1px rgba(3, 5, 6, 0.23) solid; |
| | | transition: width 0.3s ease-in-out; |
| | | } |
| | | .leaveSearch { |
| | | width: 0px; |
| | | border-bottom: 1px rgba(3, 5, 6, 0.23) solid; |
| | | transition: width 0.3s ease-in-out; |
| | | } |
| | | .enterSearch input, .leaveSearch input { |
| | | border: 0; |
| | | } |
| | | .enterSearch input:focus { |
| | | border: 0; |
| | | box-shadow: 0 0 0 0; |
| | | } |
| | | .leaveSearch input{ |
| | | width: 0px; |
| | | border: 0; |
| | | } |
| | | .fade-enter-active, .fade-leave-active { |
| | | transition: width .3s; |
| | | } |
| | | .fade-enter{ |
| | | width: 200px; |
| | | } |
| | | .fade-leave{ |
| | | width: 0px; |
| | | } |
| | | .header-item{ |
| | | margin: 0 12px; |
| | | } |
| | | </style> |
New file |
| | |
| | | <template> |
| | | <a-dropdown style="display: inline-block; height: 100%" > |
| | | <span style="vertical-align: middle; cursor: pointer"> |
| | | <a-avatar style="vertical-align: middle;" size="small" shape="circle" src="https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png"/> |
| | | <span>ICZER</span> |
| | | </span> |
| | | <a-menu style="width: 150px" slot="overlay"> |
| | | <a-menu-item> |
| | | <a-icon type="user" /> |
| | | <span>个人中心</span> |
| | | </a-menu-item> |
| | | <a-menu-item> |
| | | <a-icon type="setting" /> |
| | | <span>设置</span> |
| | | </a-menu-item> |
| | | <a-menu-divider /> |
| | | <a-menu-item> |
| | | <a-icon type="poweroff" /> |
| | | <span>退出登录</span> |
| | | </a-menu-item> |
| | | </a-menu> |
| | | </a-dropdown> |
| | | </template> |
| | | |
| | | <script> |
| | | import ADropdown from 'vue-antd-ui/es/dropdown' |
| | | import AAvatar from 'vue-antd-ui/es/avatar/Avatar' |
| | | import AIcon from 'vue-antd-ui/es/icon/icon' |
| | | import AMenu from 'vue-antd-ui/es/menu/index' |
| | | |
| | | const AMenuItem = AMenu.Item |
| | | const AMenuDivider = AMenu.Divider |
| | | |
| | | export default { |
| | | name: 'HeaderAvatar', |
| | | components: {AMenu, AMenuItem, AMenuDivider, AIcon, AAvatar, ADropdown} |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
New file |
| | |
| | | /** |
| | | * 该插件可根据菜单配置自动生成 ANTD menu组件 |
| | | * menuData示例: |
| | | * [ |
| | | * { |
| | | * title: '菜单标题', |
| | | * icon: '菜单图标', |
| | | * path: '菜单路由', |
| | | * invisible: 'boolean, 是否不可见', |
| | | * children: [子菜单配置] |
| | | * }, |
| | | * { |
| | | * title: '菜单标题', |
| | | * icon: '菜单图标', |
| | | * path: '菜单路由', |
| | | * invisible: 'boolean, 是否不可见', |
| | | * children: [子菜单配置] |
| | | * } |
| | | * ] |
| | | **/ |
| | | import Menu from 'vue-antd-ui/es/menu' |
| | | import Icon from 'vue-antd-ui/es/icon/icon' |
| | | |
| | | const {Item, SubMenu} = Menu |
| | | |
| | | // 默认菜单图标数组,如果菜单没配置图标,则会设置从该数组随机取一个图标配置 |
| | | const iconArr = ['dashboard', 'user', 'form', 'setting', 'message', 'safety', 'bell', 'delete', 'code-o', 'poweroff', 'eye-o', 'hourglass'] |
| | | |
| | | export default { |
| | | name: 'IMenu', |
| | | props: { |
| | | menuData: { |
| | | type: Array, |
| | | required: true |
| | | }, |
| | | theme: { |
| | | type: String, |
| | | required: false, |
| | | default: 'dark' |
| | | }, |
| | | mode: { |
| | | type: String, |
| | | required: false, |
| | | default: 'inline' |
| | | } |
| | | }, |
| | | methods: { |
| | | renderIcon: function (h, icon) { |
| | | return icon === 'none' ? null |
| | | : h( |
| | | Icon, |
| | | { |
| | | props: {type: icon !== undefined ? icon : iconArr[Math.floor((Math.random() * iconArr.length))]} |
| | | }) |
| | | }, |
| | | renderMenuItem: function (h, menu, pIndex, index) { |
| | | return h( |
| | | Item, |
| | | { |
| | | key: menu.path ? menu.path : 'item_' + pIndex + '_' + index |
| | | }, |
| | | [ |
| | | h( |
| | | 'a', |
| | | {attrs: {href: '#' + menu.path}}, |
| | | [ |
| | | this.renderIcon(h, menu.icon), |
| | | h('span', [menu.title]) |
| | | ] |
| | | ) |
| | | ] |
| | | ) |
| | | }, |
| | | renderSubMenu: function (h, menu, pIndex, index) { |
| | | var this2_ = this |
| | | var subItem = [h('span', |
| | | {slot: 'title'}, |
| | | [ |
| | | this.renderIcon(h, menu.icon), |
| | | h('span', [menu.title]) |
| | | ] |
| | | )] |
| | | var itemArr = [] |
| | | var pIndex_ = pIndex + '_' + index |
| | | menu.children.forEach(function (item, i) { |
| | | itemArr.push(this2_.renderItem(h, item, pIndex_, i)) |
| | | }) |
| | | return h( |
| | | SubMenu, |
| | | {key: menu.path ? menu.path : 'submenu_' + pIndex + '_' + index}, |
| | | subItem.concat(itemArr) |
| | | ) |
| | | }, |
| | | renderItem: function (h, menu, pIndex, index) { |
| | | if (!menu.invisible) { |
| | | return menu.children ? this.renderSubMenu(h, menu, pIndex, index) : this.renderMenuItem(h, menu, pIndex, index) |
| | | } |
| | | }, |
| | | renderMenu: function (h, menuTree) { |
| | | var this2_ = this |
| | | var menuArr = [] |
| | | menuTree.forEach(function (menu, i) { |
| | | menuArr.push(this2_.renderItem(h, menu, '0', i)) |
| | | }) |
| | | return menuArr |
| | | } |
| | | }, |
| | | render (h) { |
| | | return h( |
| | | Menu, |
| | | { |
| | | props: { |
| | | theme: this.$props.theme, |
| | | mode: this.$props.mode, |
| | | inlineCollapsed: false |
| | | } |
| | | }, this.renderMenu(h, this.menuData) |
| | | ) |
| | | } |
| | | } |
New file |
| | |
| | | // The Vue build version to load with the `import` command |
| | | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. |
| | | import Vue from 'vue' |
| | | import App from './App' |
| | | import router from './router' |
| | | import 'vue-antd-ui/dist/antd.css' |
| | | import Viser from 'viser-vue' |
| | | |
| | | Vue.config.productionTip = false |
| | | Vue.use(Viser) |
| | | |
| | | /* eslint-disable no-new */ |
| | | new Vue({ |
| | | el: '#app', |
| | | router, |
| | | components: { App }, |
| | | template: '<App/>' |
| | | }) |
New file |
| | |
| | | import Vue from 'vue' |
| | | import Router from 'vue-router' |
| | | import Dashboard from '@/components/dashboard/Dashboard' |
| | | import MiniChart from '@/components/chart/MiniChart' |
| | | |
| | | Vue.use(Router) |
| | | |
| | | export default new Router({ |
| | | routes: [ |
| | | { |
| | | path: '/', |
| | | name: 'hello', |
| | | component: Dashboard |
| | | }, |
| | | { |
| | | path: '/dashboard', |
| | | name: 'dashboard', |
| | | component: Dashboard |
| | | }, |
| | | { |
| | | path: '/chart', |
| | | name: 'chart', |
| | | component: MiniChart |
| | | } |
| | | ] |
| | | }) |
New file |
| | |
| | | // A custom Nightwatch assertion. |
| | | // The assertion name is the filename. |
| | | // Example usage: |
| | | // |
| | | // browser.assert.elementCount(selector, count) |
| | | // |
| | | // For more information on custom assertions see: |
| | | // http://nightwatchjs.org/guide#writing-custom-assertions |
| | | |
| | | exports.assertion = function (selector, count) { |
| | | this.message = 'Testing if element <' + selector + '> has count: ' + count |
| | | this.expected = count |
| | | this.pass = function (val) { |
| | | return val === this.expected |
| | | } |
| | | this.value = function (res) { |
| | | return res.value |
| | | } |
| | | this.command = function (cb) { |
| | | var self = this |
| | | return this.api.execute(function (selector) { |
| | | return document.querySelectorAll(selector).length |
| | | }, [selector], function (res) { |
| | | cb.call(self, res) |
| | | }) |
| | | } |
| | | } |
New file |
| | |
| | | require('babel-register') |
| | | var config = require('../../config') |
| | | |
| | | // http://nightwatchjs.org/gettingstarted#settings-file |
| | | module.exports = { |
| | | src_folders: ['test/e2e/specs'], |
| | | output_folder: 'test/e2e/reports', |
| | | custom_assertions_path: ['test/e2e/custom-assertions'], |
| | | |
| | | selenium: { |
| | | start_process: true, |
| | | server_path: require('selenium-server').path, |
| | | host: '127.0.0.1', |
| | | port: 4444, |
| | | cli_args: { |
| | | 'webdriver.chrome.driver': require('chromedriver').path |
| | | } |
| | | }, |
| | | |
| | | test_settings: { |
| | | default: { |
| | | selenium_port: 4444, |
| | | selenium_host: 'localhost', |
| | | silent: true, |
| | | globals: { |
| | | devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port) |
| | | } |
| | | }, |
| | | |
| | | chrome: { |
| | | desiredCapabilities: { |
| | | browserName: 'chrome', |
| | | javascriptEnabled: true, |
| | | acceptSslCerts: true |
| | | } |
| | | }, |
| | | |
| | | firefox: { |
| | | desiredCapabilities: { |
| | | browserName: 'firefox', |
| | | javascriptEnabled: true, |
| | | acceptSslCerts: true |
| | | } |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | // 1. start the dev server using production config |
| | | process.env.NODE_ENV = 'testing' |
| | | |
| | | const webpack = require('webpack') |
| | | const DevServer = require('webpack-dev-server') |
| | | |
| | | const webpackConfig = require('../../build/webpack.prod.conf') |
| | | const devConfigPromise = require('../../build/webpack.dev.conf') |
| | | |
| | | let server |
| | | |
| | | devConfigPromise.then(devConfig => { |
| | | const devServerOptions = devConfig.devServer |
| | | const compiler = webpack(webpackConfig) |
| | | server = new DevServer(compiler, devServerOptions) |
| | | const port = devServerOptions.port |
| | | const host = devServerOptions.host |
| | | return server.listen(port, host) |
| | | }) |
| | | .then(() => { |
| | | // 2. run the nightwatch test suite against it |
| | | // to run in additional browsers: |
| | | // 1. add an entry in test/e2e/nightwatch.conf.js under "test_settings" |
| | | // 2. add it to the --env flag below |
| | | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox` |
| | | // For more information on Nightwatch's config file, see |
| | | // http://nightwatchjs.org/guide#settings-file |
| | | let opts = process.argv.slice(2) |
| | | if (opts.indexOf('--config') === -1) { |
| | | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']) |
| | | } |
| | | if (opts.indexOf('--env') === -1) { |
| | | opts = opts.concat(['--env', 'chrome']) |
| | | } |
| | | |
| | | const spawn = require('cross-spawn') |
| | | const runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' }) |
| | | |
| | | runner.on('exit', function (code) { |
| | | server.close() |
| | | process.exit(code) |
| | | }) |
| | | |
| | | runner.on('error', function (err) { |
| | | server.close() |
| | | throw err |
| | | }) |
| | | }) |
New file |
| | |
| | | // For authoring Nightwatch tests, see |
| | | // http://nightwatchjs.org/guide#usage |
| | | |
| | | module.exports = { |
| | | 'default e2e tests': function (browser) { |
| | | // automatically uses dev Server port from /config.index.js |
| | | // default: http://localhost:8080 |
| | | // see nightwatch.conf.js |
| | | const devServer = browser.globals.devServerURL |
| | | |
| | | browser |
| | | .url(devServer) |
| | | .waitForElementVisible('#app', 5000) |
| | | .assert.elementPresent('.hello') |
| | | .assert.containsText('h1', 'Welcome to Your Vue.js App') |
| | | .assert.elementCount('img', 1) |
| | | .end() |
| | | } |
| | | } |
New file |
| | |
| | | { |
| | | "env": { |
| | | "jest": true |
| | | }, |
| | | "globals": { |
| | | } |
| | | } |
New file |
| | |
| | | const path = require('path') |
| | | |
| | | module.exports = { |
| | | rootDir: path.resolve(__dirname, '../../'), |
| | | moduleFileExtensions: [ |
| | | 'js', |
| | | 'json', |
| | | 'vue' |
| | | ], |
| | | moduleNameMapper: { |
| | | '^@/(.*)$': '<rootDir>/src/$1' |
| | | }, |
| | | transform: { |
| | | '^.+\\.js$': '<rootDir>/node_modules/babel-jest', |
| | | '.*\\.(vue)$': '<rootDir>/node_modules/vue-jest' |
| | | }, |
| | | testPathIgnorePatterns: [ |
| | | '<rootDir>/test/e2e' |
| | | ], |
| | | snapshotSerializers: ['<rootDir>/node_modules/jest-serializer-vue'], |
| | | setupFiles: ['<rootDir>/test/unit/setup'], |
| | | mapCoverage: true, |
| | | coverageDirectory: '<rootDir>/test/unit/coverage', |
| | | collectCoverageFrom: [ |
| | | 'src/**/*.{js,vue}', |
| | | '!src/main.js', |
| | | '!src/router/index.js', |
| | | '!**/node_modules/**' |
| | | ] |
| | | } |
New file |
| | |
| | | import Vue from 'vue' |
| | | |
| | | Vue.config.productionTip = false |
New file |
| | |
| | | import Vue from 'vue' |
| | | import HelloWorld from '@/components/HelloWorld' |
| | | |
| | | describe('HelloWorld.vue', () => { |
| | | it('should render correct contents', () => { |
| | | const Constructor = Vue.extend(HelloWorld) |
| | | const vm = new Constructor().$mount() |
| | | expect(vm.$el.querySelector('.hello h1').textContent) |
| | | .toEqual('Welcome to Your Vue.js App') |
| | | }) |
| | | }) |