├── .babelrc ├── .editorconfig ├── .gitignore ├── .postcssrc.js ├── README.md ├── build ├── build.js ├── check-versions.js ├── dev-client.js ├── dev-server.js ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js ├── webpack.prod.conf.js └── webpack.test.conf.js ├── config ├── dev.env.js ├── index.js ├── prod.env.js └── test.env.js ├── gulpfile.js ├── index.html ├── mock ├── db.js └── server.js ├── package.json ├── src ├── App.vue ├── assets │ ├── css │ │ └── transition.css │ └── img │ │ ├── 1-1.jpg │ │ ├── 1-2.jpg │ │ ├── 1-3.jpg │ │ ├── 1-4.jpg │ │ ├── 1-5.jpg │ │ ├── next.png │ │ └── pre.png ├── components │ ├── address.vue │ ├── chart.vue │ ├── comment.vue │ ├── header.vue │ ├── index.js │ ├── loadingbar.vue │ ├── qrcode.vue │ └── slider.vue ├── filters │ └── index.js ├── main.js ├── pages │ ├── home │ │ └── index.vue │ └── list │ │ └── index.vue ├── plugins │ ├── index.js │ └── toast │ │ ├── index.css │ │ └── index.js ├── router │ └── index.js ├── store │ ├── actions.js │ ├── getters.js │ ├── index.js │ ├── mutation-types.js │ ├── mutations.js │ └── state.js └── utils │ └── index.js ├── static └── .gitkeep └── test ├── components ├── comment.spec.js └── header.spec.js ├── karma.conf.js ├── utils.js └── vuex └── mutations.spec.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { "modules": false }], 4 | "stage-2" 5 | ], 6 | "plugins": ["transform-runtime"], 7 | "comments": false, 8 | "env": { 9 | "test": { 10 | "presets": ["env", "stage-2"], 11 | "plugins": [ "istanbul" ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | .idea/ 8 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "plugins": { 3 | "autoprefixer": { 4 | browsers: ['iOS >= 7', 'Android >= 4.1'] 5 | }, 6 | "postcss-px2rem": { 7 | remUnit: 75 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-starter-kit 2 | 3 | > A Vue.js project with mock-server and unit-test 4 | 5 | ## components 6 | 7 | * [header](https://github.com/yanm1ng/vue-starter-kit/blob/master/src/components/header.vue) 8 | * [comment](https://github.com/yanm1ng/vue-starter-kit/blob/master/src/components/comment.vue) 9 | * [address-select](https://github.com/yanm1ng/vue-starter-kit/blob/master/src/components/address.vue) 10 | * [image-slider](https://github.com/yanm1ng/vue-starter-kit/blob/master/src/components/slider.vue) 11 | * [qrcode](https://github.com/yanm1ng/vue-starter-kit/blob/master/src/components/qrcode.vue) 12 | * [loading-bar](https://github.com/yanm1ng/vue-starter-kit/blob/master/src/components/loadingbar.vue) 13 | * [chart](https://github.com/yanm1ng/vue-starter-kit/blob/master/src/components/chart.vue) 14 | * waiting for ... 15 | 16 | ## plugins 17 | 18 | * toast 19 | 20 | ## mock-server 21 | 22 | we use `json-server` to get a full fake REST API, use mock.js to build mock data ( as local database :) ), for example: 23 | 24 | ```javascript 25 | // db.js 26 | var Mock = require('mockjs'); 27 | 28 | module.exports = { 29 | project_comments: Mock.mock({ 30 | "error": 0, 31 | "message": "success", 32 | "result|40": [{ 33 | "author": "@name", 34 | "comment": "@cparagraph", 35 | "date": "@datetime" 36 | }] 37 | }), 38 | push_comment: Mock.mock({ 39 | "error": 0, 40 | "message": "success", 41 | "result": [] 42 | }) 43 | }; 44 | ``` 45 | 46 | ```json 47 | // routes.json 48 | { 49 | "/project/:page/comments.action": "/project_comments?_page=:page&_limit=5", 50 | "/comment/add.action": "/push_comment" 51 | } 52 | ``` 53 | 54 | ## unit test 55 | 56 | test components and vuex( include `mutations` `actions` `getters` ), test result: 57 | 58 | ![](https://ww2.sinaimg.cn/large/006tNbRwgy1ff70ybezr0j31ds0bq443.jpg) 59 | 60 | ## build setup 61 | 62 | ``` bash 63 | # install dependencies 64 | npm install 65 | 66 | # serve with hot reload at localhost:8181 67 | npm run dev 68 | 69 | # run mock server at localhost:3000 70 | npm run mock 71 | 72 | # run unit test 73 | npm run test 74 | 75 | # build for production with minification 76 | npm run build 77 | ``` 78 | 79 | ## TODO 80 | 81 | - [x] add `vuex` to manage app state 82 | - [x] add unit test 83 | - [x] refresh the mock server after modifying `mock/db.js` -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | process.env.NODE_ENV = 'production' 4 | 5 | var ora = require('ora') 6 | var rm = require('rimraf') 7 | var path = require('path') 8 | var chalk = require('chalk') 9 | var webpack = require('webpack') 10 | var config = require('../config') 11 | var webpackConfig = require('./webpack.prod.conf') 12 | 13 | var spinner = ora('building for production...') 14 | spinner.start() 15 | 16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 17 | if (err) throw err 18 | webpack(webpackConfig, function (err, stats) { 19 | spinner.stop() 20 | if (err) throw err 21 | process.stdout.write(stats.toString({ 22 | colors: true, 23 | modules: false, 24 | children: false, 25 | chunks: false, 26 | chunkModules: false 27 | }) + '\n\n') 28 | 29 | console.log(chalk.cyan(' Build complete.\n')) 30 | console.log(chalk.yellow( 31 | ' Tip: built files are meant to be served over an HTTP server.\n' + 32 | ' Opening index.html over file:// won\'t work.\n' 33 | )) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /build/check-versions.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk') 2 | var semver = require('semver') 3 | var packageConfig = require('../package.json') 4 | var shell = require('shelljs') 5 | function exec (cmd) { 6 | return require('child_process').execSync(cmd).toString().trim() 7 | } 8 | 9 | var versionRequirements = [ 10 | { 11 | name: 'node', 12 | currentVersion: semver.clean(process.version), 13 | versionRequirement: packageConfig.engines.node 14 | }, 15 | ] 16 | 17 | if (shell.which('npm')) { 18 | versionRequirements.push({ 19 | name: 'npm', 20 | currentVersion: exec('npm --version'), 21 | versionRequirement: packageConfig.engines.npm 22 | }) 23 | } 24 | 25 | module.exports = function () { 26 | var warnings = [] 27 | for (var i = 0; i < versionRequirements.length; i++) { 28 | var mod = versionRequirements[i] 29 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 30 | warnings.push(mod.name + ': ' + 31 | chalk.red(mod.currentVersion) + ' should be ' + 32 | chalk.green(mod.versionRequirement) 33 | ) 34 | } 35 | } 36 | 37 | if (warnings.length) { 38 | console.log('') 39 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 40 | console.log() 41 | for (var i = 0; i < warnings.length; i++) { 42 | var warning = warnings[i] 43 | console.log(' ' + warning) 44 | } 45 | console.log() 46 | process.exit(1) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /build/dev-client.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | require('eventsource-polyfill') 3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 4 | 5 | hotClient.subscribe(function (event) { 6 | if (event.action === 'reload') { 7 | window.location.reload() 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /build/dev-server.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | var config = require('../config') 4 | if (!process.env.NODE_ENV) { 5 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) 6 | } 7 | 8 | var opn = require('opn') 9 | var path = require('path') 10 | var express = require('express') 11 | var webpack = require('webpack') 12 | var proxyMiddleware = require('http-proxy-middleware') 13 | var webpackConfig = require('./webpack.dev.conf') 14 | 15 | // default port where dev server listens for incoming traffic 16 | var port = process.env.PORT || config.dev.port 17 | // automatically open browser, if not set will be false 18 | var autoOpenBrowser = !!config.dev.autoOpenBrowser 19 | 20 | var proxyTable = config.dev.proxyTable 21 | 22 | var app = express() 23 | var compiler = webpack(webpackConfig) 24 | 25 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 26 | publicPath: webpackConfig.output.publicPath, 27 | quiet: true 28 | }) 29 | 30 | var hotMiddleware = require('webpack-hot-middleware')(compiler, { 31 | log: () => {} 32 | }) 33 | // force page reload when html-webpack-plugin template changes 34 | compiler.plugin('compilation', function (compilation) { 35 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 36 | hotMiddleware.publish({ action: 'reload' }) 37 | cb() 38 | }) 39 | }) 40 | 41 | // proxy api requests 42 | Object.keys(proxyTable).forEach(function (context) { 43 | var options = proxyTable[context] 44 | if (typeof options === 'string') { 45 | options = { target: options } 46 | } 47 | app.use(proxyMiddleware(options.filter || context, options)) 48 | }) 49 | 50 | // handle fallback for HTML5 history API 51 | app.use(require('connect-history-api-fallback')()) 52 | 53 | // serve webpack bundle output 54 | app.use(devMiddleware) 55 | 56 | // enable hot-reload and state-preserving 57 | // compilation error display 58 | app.use(hotMiddleware) 59 | 60 | // serve pure static assets 61 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 62 | app.use(staticPath, express.static('./static')) 63 | 64 | var uri = 'http://localhost:' + port 65 | 66 | var _resolve 67 | var readyPromise = new Promise(resolve => { 68 | _resolve = resolve 69 | }) 70 | 71 | console.log('> Starting dev server...') 72 | devMiddleware.waitUntilValid(() => { 73 | console.log('> Listening at ' + uri + '\n') 74 | // when env is testing, don't need open it 75 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { 76 | opn(uri) 77 | } 78 | _resolve() 79 | }) 80 | 81 | var server = app.listen(port) 82 | 83 | module.exports = { 84 | ready: readyPromise, 85 | close: () => { 86 | server.close() 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /build/utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 4 | 5 | exports.assetsPath = function (_path) { 6 | var assetsSubDirectory = process.env.NODE_ENV === 'production' 7 | ? config.build.assetsSubDirectory 8 | : config.dev.assetsSubDirectory 9 | return path.posix.join(assetsSubDirectory, _path) 10 | } 11 | 12 | exports.cssLoaders = function (options) { 13 | options = options || {} 14 | 15 | var cssLoader = { 16 | loader: 'css-loader', 17 | options: { 18 | minimize: process.env.NODE_ENV === 'production', 19 | sourceMap: options.sourceMap 20 | } 21 | } 22 | 23 | function generateLoaders (loader, loaderOptions) { 24 | var loaders = [cssLoader] 25 | if (loader) { 26 | loaders.push({ 27 | loader: loader + '-loader', 28 | options: Object.assign({}, loaderOptions, { 29 | sourceMap: options.sourceMap 30 | }) 31 | }) 32 | } 33 | 34 | if (options.extract) { 35 | return ExtractTextPlugin.extract({ 36 | use: loaders, 37 | fallback: 'vue-style-loader' 38 | }) 39 | } else { 40 | return ['vue-style-loader'].concat(loaders) 41 | } 42 | } 43 | 44 | return { 45 | css: generateLoaders(), 46 | postcss: generateLoaders(), 47 | less: generateLoaders('less'), 48 | sass: generateLoaders('sass', { indentedSyntax: true }), 49 | scss: generateLoaders('sass'), 50 | stylus: generateLoaders('stylus'), 51 | styl: generateLoaders('stylus') 52 | } 53 | } 54 | 55 | exports.styleLoaders = function (options) { 56 | var output = [] 57 | var loaders = exports.cssLoaders(options) 58 | for (var extension in loaders) { 59 | var loader = loaders[extension] 60 | output.push({ 61 | test: new RegExp('\\.' + extension + '$'), 62 | use: loader 63 | }) 64 | } 65 | return output 66 | } 67 | -------------------------------------------------------------------------------- /build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var config = require('../config') 3 | var isProduction = process.env.NODE_ENV === 'production' 4 | 5 | module.exports = { 6 | loaders: utils.cssLoaders({ 7 | sourceMap: isProduction 8 | ? config.build.productionSourceMap 9 | : config.dev.cssSourceMap, 10 | extract: isProduction 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var config = require('../config') 4 | var vueLoaderConfig = require('./vue-loader.conf') 5 | 6 | function resolve (dir) { 7 | return path.join(__dirname, '..', dir) 8 | } 9 | 10 | module.exports = { 11 | entry: { 12 | app: './src/main.js' 13 | }, 14 | output: { 15 | path: config.build.assetsRoot, 16 | filename: '[name].js', 17 | publicPath: process.env.NODE_ENV === 'production' 18 | ? config.build.assetsPublicPath 19 | : config.dev.assetsPublicPath 20 | }, 21 | resolve: { 22 | extensions: ['.js', '.vue', '.json'], 23 | alias: { 24 | 'vue$': 'vue/dist/vue.esm.js', 25 | '@': resolve('src') 26 | } 27 | }, 28 | module: { 29 | rules: [ 30 | { 31 | test: /\.vue$/, 32 | loader: 'vue-loader', 33 | options: vueLoaderConfig 34 | }, 35 | { 36 | test: /\.js$/, 37 | loader: 'babel-loader', 38 | include: [resolve('src'), resolve('test')] 39 | }, 40 | { 41 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 42 | loader: 'url-loader', 43 | options: { 44 | limit: 10000, 45 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 46 | } 47 | }, 48 | { 49 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 50 | loader: 'url-loader', 51 | options: { 52 | limit: 10000, 53 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 54 | } 55 | } 56 | ] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var webpack = require('webpack') 3 | var config = require('../config') 4 | var merge = require('webpack-merge') 5 | var baseWebpackConfig = require('./webpack.base.conf') 6 | var HtmlWebpackPlugin = require('html-webpack-plugin') 7 | var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 8 | 9 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 10 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 11 | }) 12 | 13 | module.exports = merge(baseWebpackConfig, { 14 | module: { 15 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 16 | }, 17 | devtool: '#cheap-module-eval-source-map', 18 | plugins: [ 19 | new webpack.DefinePlugin({ 20 | 'process.env': config.dev.env 21 | }), 22 | new webpack.HotModuleReplacementPlugin(), 23 | new webpack.NoEmitOnErrorsPlugin(), 24 | new HtmlWebpackPlugin({ 25 | filename: 'index.html', 26 | template: 'index.html', 27 | inject: true 28 | }), 29 | new FriendlyErrorsPlugin() 30 | ] 31 | }) 32 | -------------------------------------------------------------------------------- /build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var webpack = require('webpack') 4 | var config = require('../config') 5 | var merge = require('webpack-merge') 6 | var baseWebpackConfig = require('./webpack.base.conf') 7 | var CopyWebpackPlugin = require('copy-webpack-plugin') 8 | var HtmlWebpackPlugin = require('html-webpack-plugin') 9 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 10 | var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 11 | 12 | var env = config.build.env 13 | 14 | var webpackConfig = merge(baseWebpackConfig, { 15 | module: { 16 | rules: utils.styleLoaders({ 17 | sourceMap: config.build.productionSourceMap, 18 | extract: true 19 | }) 20 | }, 21 | devtool: config.build.productionSourceMap ? '#source-map' : false, 22 | output: { 23 | path: config.build.assetsRoot, 24 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 25 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 26 | }, 27 | plugins: [ 28 | new webpack.DefinePlugin({ 29 | 'process.env': env 30 | }), 31 | new webpack.optimize.UglifyJsPlugin({ 32 | compress: { 33 | warnings: false 34 | }, 35 | sourceMap: true 36 | }), 37 | // extract css into its own file 38 | new ExtractTextPlugin({ 39 | filename: utils.assetsPath('css/[name].[contenthash].css') 40 | }), 41 | new OptimizeCSSPlugin({ 42 | cssProcessorOptions: { 43 | safe: true 44 | } 45 | }), 46 | new HtmlWebpackPlugin({ 47 | filename: config.build.index, 48 | template: 'index.html', 49 | inject: true, 50 | minify: { 51 | removeComments: true, 52 | collapseWhitespace: true, 53 | removeAttributeQuotes: true 54 | // more options: 55 | }, 56 | chunksSortMode: 'dependency' 57 | }), 58 | new webpack.optimize.CommonsChunkPlugin({ 59 | name: 'vendor', 60 | minChunks: function (module, count) { 61 | // any required modules inside node_modules are extracted to vendor 62 | return ( 63 | module.resource && 64 | /\.js$/.test(module.resource) && 65 | module.resource.indexOf( 66 | path.join(__dirname, '../node_modules') 67 | ) === 0 68 | ) 69 | } 70 | }), 71 | new webpack.optimize.CommonsChunkPlugin({ 72 | name: 'manifest', 73 | chunks: ['vendor'] 74 | }), 75 | // copy custom static assets 76 | new CopyWebpackPlugin([ 77 | { 78 | from: path.resolve(__dirname, '../static'), 79 | to: config.build.assetsSubDirectory, 80 | ignore: ['.*'] 81 | } 82 | ]) 83 | ] 84 | }) 85 | 86 | if (config.build.productionGzip) { 87 | var CompressionWebpackPlugin = require('compression-webpack-plugin') 88 | 89 | webpackConfig.plugins.push( 90 | new CompressionWebpackPlugin({ 91 | asset: '[path].gz[query]', 92 | algorithm: 'gzip', 93 | test: new RegExp( 94 | '\\.(' + 95 | config.build.productionGzipExtensions.join('|') + 96 | ')$' 97 | ), 98 | threshold: 10240, 99 | minRatio: 0.8 100 | }) 101 | ) 102 | } 103 | 104 | if (config.build.bundleAnalyzerReport) { 105 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 106 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 107 | } 108 | 109 | module.exports = webpackConfig 110 | -------------------------------------------------------------------------------- /build/webpack.test.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var webpack = require('webpack') 3 | var merge = require('webpack-merge') 4 | var baseConfig = require('./webpack.base.conf') 5 | 6 | var webpackConfig = merge(baseConfig, { 7 | // use inline sourcemap for karma-sourcemap-loader 8 | module: { 9 | rules: utils.styleLoaders() 10 | }, 11 | devtool: '#inline-source-map', 12 | resolveLoader: { 13 | alias: { 14 | 'scss-loader': 'sass-loader' 15 | } 16 | }, 17 | plugins: [ 18 | new webpack.DefinePlugin({ 19 | 'process.env': require('../config/test.env') 20 | }) 21 | ] 22 | }) 23 | 24 | // no need for app entry during tests 25 | delete webpackConfig.entry 26 | 27 | module.exports = webpackConfig -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var prodEnv = require('./prod.env') 3 | 4 | module.exports = merge(prodEnv, { 5 | NODE_ENV: '"development"' 6 | }) 7 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | 3 | module.exports = { 4 | build: { 5 | env: require('./prod.env'), 6 | index: path.resolve(__dirname, '../dist/index.html'), 7 | assetsRoot: path.resolve(__dirname, '../dist'), 8 | assetsSubDirectory: 'static', 9 | assetsPublicPath: '/', 10 | productionSourceMap: true, 11 | productionGzip: false, 12 | productionGzipExtensions: ['js', 'css'], 13 | bundleAnalyzerReport: process.env.npm_config_report 14 | }, 15 | dev: { 16 | env: require('./dev.env'), 17 | port: 8181, 18 | assetsSubDirectory: 'static', 19 | assetsPublicPath: '/', 20 | proxyTable: { 21 | '/api/': { 22 | target: 'http://localhost:3000', 23 | changeOrigin: true, 24 | pathRewrite: { 25 | '^/api': '' 26 | } 27 | } 28 | }, 29 | cssSourceMap: false 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /config/test.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var devEnv = require('./dev.env') 3 | 4 | module.exports = merge(devEnv, { 5 | NODE_ENV: '"testing"' 6 | }) -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var nodemon = require('gulp-nodemon'); 3 | var path = require('path'); 4 | var browserSync = require('browser-sync').create(); 5 | var ROOT = path.resolve(__dirname); 6 | var server = path.resolve(ROOT, 'mock'); 7 | 8 | // browser-sync配置,配置里启动nodemon任务 9 | gulp.task('browser-sync', ['nodemon'], function() { 10 | browserSync.init(null, { 11 | proxy: "http://localhost:8181", 12 | port: 8081 13 | }); 14 | }); 15 | 16 | // browser-sync 监听文件 17 | gulp.task('mock', ['browser-sync'], function() { 18 | gulp.watch(['./mock/db.js', './mock/**'], ['bs-delay']); 19 | }); 20 | 21 | // 延时刷新 22 | gulp.task('bs-delay', function() { 23 | setTimeout(function() { 24 | browserSync.reload(); 25 | console.log('restart'); 26 | }, 1000); 27 | }); 28 | 29 | // 服务器重启 30 | gulp.task('nodemon', function(cb) { 31 | // 设个变量来防止重复重启 32 | var started = false; 33 | var stream = nodemon({ 34 | script: './mock/server.js', 35 | // 监听文件的后缀 36 | ext: "js", 37 | env: { 38 | 'NODE_ENV': 'development' 39 | }, 40 | // 监听的路径 41 | watch: [ 42 | server 43 | ] 44 | }); 45 | stream.on('start', function() { 46 | if (!started) { 47 | cb(); 48 | started = true; 49 | } 50 | }).on('crash', function() { 51 | console.error('Application has crashed!\n') 52 | stream.emit('restart', 10) 53 | }) 54 | }); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | vue-project 6 | 7 | 8 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /mock/db.js: -------------------------------------------------------------------------------- 1 | var Mock = require('mockjs'); 2 | 3 | module.exports = { 4 | project_comments: Mock.mock({ 5 | "error": 0, 6 | "message": "success", 7 | "result|40": [{ 8 | "author": "@name", 9 | "comment": "@cparagraph", 10 | "date": "@datetime" 11 | }] 12 | }), 13 | push_comment: Mock.mock({ 14 | "error": 0, 15 | "message": "success", 16 | "result": [] 17 | }) 18 | }; -------------------------------------------------------------------------------- /mock/server.js: -------------------------------------------------------------------------------- 1 | const jsonServer = require('json-server') 2 | const mock = require('mockjs') 3 | const db = require('./db.js') 4 | 5 | const server = jsonServer.create() 6 | const router = jsonServer.router(db) 7 | const middlewares = jsonServer.defaults() 8 | 9 | const rewriter = jsonServer.rewriter({ 10 | "/project/:page/comments.action": "/project_comments?_page=:page&_limit=5", 11 | "/comment/add.action": "/push_comment" 12 | }) 13 | 14 | server.use(middlewares) 15 | server.use((request, res, next) => { 16 | request.method = 'GET'; 17 | next(); 18 | }) 19 | 20 | server.use(rewriter) 21 | server.use(router) 22 | 23 | server.listen(3000, () => { 24 | console.log('mock server is running') 25 | }) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-project", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "yummy1121@foxmail.com ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "node build/dev-server.js", 9 | "mock": "gulp mock", 10 | "build": "node build/build.js", 11 | "test": "karma start test/karma.conf.js --single-run" 12 | }, 13 | "dependencies": { 14 | "axios": "^0.16.1", 15 | "chart.js": "^2.5.0", 16 | "gulp-nodemon": "^2.2.1", 17 | "json-server": "^0.10.1", 18 | "nodemon": "^1.11.0", 19 | "qrious": "^2.2.0", 20 | "vue": "^2.2.2", 21 | "vue-router": "^2.3.0", 22 | "vuex": "^2.3.1" 23 | }, 24 | "devDependencies": { 25 | "autoprefixer": "^6.7.2", 26 | "babel-core": "^6.22.1", 27 | "babel-eslint": "^7.1.1", 28 | "babel-loader": "^6.2.10", 29 | "babel-plugin-transform-runtime": "^6.22.0", 30 | "babel-preset-env": "^1.2.1", 31 | "babel-preset-stage-2": "^6.22.0", 32 | "babel-register": "^6.22.0", 33 | "browser-sync": "^2.18.12", 34 | "chalk": "^1.1.3", 35 | "connect-history-api-fallback": "^1.3.0", 36 | "copy-webpack-plugin": "^4.0.1", 37 | "css-loader": "^0.26.1", 38 | "eslint": "^3.14.1", 39 | "eslint-config-standard": "^6.2.1", 40 | "eslint-friendly-formatter": "^2.0.7", 41 | "eslint-loader": "^1.6.1", 42 | "eslint-plugin-html": "^2.0.0", 43 | "eslint-plugin-promise": "^3.4.0", 44 | "eslint-plugin-standard": "^2.0.1", 45 | "eventsource-polyfill": "^0.9.6", 46 | "express": "^4.14.1", 47 | "extract-text-webpack-plugin": "^2.0.0", 48 | "file-loader": "^0.10.0", 49 | "friendly-errors-webpack-plugin": "^1.1.3", 50 | "gulp": "^3.9.1", 51 | "html-webpack-plugin": "^2.28.0", 52 | "http-proxy-middleware": "^0.17.3", 53 | "jasmine-core": "^2.6.0", 54 | "karma": "^1.6.0", 55 | "karma-jasmine": "^1.1.0", 56 | "karma-phantomjs-launcher": "^1.0.4", 57 | "karma-webpack": "^2.0.3", 58 | "mockjs": "^1.0.1-beta3", 59 | "opn": "^4.0.2", 60 | "optimize-css-assets-webpack-plugin": "^1.3.0", 61 | "ora": "^1.1.0", 62 | "postcss-px2rem": "^0.3.0", 63 | "rimraf": "^2.6.0", 64 | "semver": "^5.3.0", 65 | "shelljs": "^0.7.6", 66 | "url-loader": "^0.5.8", 67 | "vue-loader": "^11.1.4", 68 | "vue-style-loader": "^2.0.0", 69 | "vue-template-compiler": "^2.2.4", 70 | "webpack": "^2.2.1", 71 | "webpack-bundle-analyzer": "^2.2.1", 72 | "webpack-dev-middleware": "^1.10.0", 73 | "webpack-hot-middleware": "^2.16.1", 74 | "webpack-merge": "^2.6.1" 75 | }, 76 | "engines": { 77 | "node": ">= 4.0.0", 78 | "npm": ">= 3.0.0" 79 | }, 80 | "browserslist": [ 81 | "> 1%", 82 | "last 2 versions", 83 | "not ie <= 8" 84 | ] 85 | } 86 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 28 | 29 | 48 | -------------------------------------------------------------------------------- /src/assets/css/transition.css: -------------------------------------------------------------------------------- 1 | .vux-pop-in-enter-active,.vux-pop-in-leave-active,.vux-pop-out-enter-active,.vux-pop-out-leave-active { 2 | will-change: transform; 3 | -webkit-transition: all .5s; 4 | transition: all .5s; 5 | height: 100%; 6 | position: absolute; 7 | -webkit-backface-visibility: hidden; 8 | backface-visibility: hidden; 9 | -webkit-perspective: 1000; 10 | perspective: 1000 11 | } 12 | 13 | .vux-pop-out-enter { 14 | opacity: 0; 15 | -webkit-transform: translate3d(-100%, 0, 0); 16 | transform: translate3d(-100%, 0, 0) 17 | } 18 | 19 | .vux-pop-in-enter,.vux-pop-out-leave-active { 20 | opacity: 0; 21 | -webkit-transform: translate3d(100%, 0, 0); 22 | transform: translate3d(100%, 0, 0) 23 | } 24 | 25 | .vux-pop-in-leave-active { 26 | opacity: 0; 27 | -webkit-transform: translate3d(-100%, 0, 0); 28 | transform: translate3d(-100%, 0, 0) 29 | } 30 | -------------------------------------------------------------------------------- /src/assets/img/1-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanm1ng/vue-starter-kit/365c669cad4c408a2542cadbdcf6c32ae141dc44/src/assets/img/1-1.jpg -------------------------------------------------------------------------------- /src/assets/img/1-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanm1ng/vue-starter-kit/365c669cad4c408a2542cadbdcf6c32ae141dc44/src/assets/img/1-2.jpg -------------------------------------------------------------------------------- /src/assets/img/1-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanm1ng/vue-starter-kit/365c669cad4c408a2542cadbdcf6c32ae141dc44/src/assets/img/1-3.jpg -------------------------------------------------------------------------------- /src/assets/img/1-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanm1ng/vue-starter-kit/365c669cad4c408a2542cadbdcf6c32ae141dc44/src/assets/img/1-4.jpg -------------------------------------------------------------------------------- /src/assets/img/1-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanm1ng/vue-starter-kit/365c669cad4c408a2542cadbdcf6c32ae141dc44/src/assets/img/1-5.jpg -------------------------------------------------------------------------------- /src/assets/img/next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanm1ng/vue-starter-kit/365c669cad4c408a2542cadbdcf6c32ae141dc44/src/assets/img/next.png -------------------------------------------------------------------------------- /src/assets/img/pre.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanm1ng/vue-starter-kit/365c669cad4c408a2542cadbdcf6c32ae141dc44/src/assets/img/pre.png -------------------------------------------------------------------------------- /src/components/address.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 69 | 70 | -------------------------------------------------------------------------------- /src/components/chart.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 75 | 76 | -------------------------------------------------------------------------------- /src/components/comment.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 28 | 29 | 76 | -------------------------------------------------------------------------------- /src/components/header.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 25 | 26 | 63 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import header from './header' 2 | import comment from './comment' 3 | import address from './address' 4 | import slider from './slider' 5 | import qrcode from './qrcode' 6 | import loadingbar from './loadingbar' 7 | import chart from './chart' 8 | 9 | export default { 10 | header, 11 | comment, 12 | address, 13 | slider, 14 | qrcode, 15 | loadingbar, 16 | chart 17 | } -------------------------------------------------------------------------------- /src/components/loadingbar.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 112 | 113 | -------------------------------------------------------------------------------- /src/components/qrcode.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/components/slider.vue: -------------------------------------------------------------------------------- 1 | 21 | 103 | 212 | -------------------------------------------------------------------------------- /src/filters/index.js: -------------------------------------------------------------------------------- 1 | export function wordSlice(str) { 2 | if (str.length > 100) { 3 | str = str.slice(0, 97) + '...'; 4 | } 5 | return str; 6 | } -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import './plugins/toast/index.css'; 2 | 3 | import Vue from 'vue' 4 | import App from './app' 5 | import router from './router' 6 | import store from './store' 7 | import components from './components/' // 加载公共组件 8 | import * as filters from './filters/' 9 | import plugins from './plugins/' 10 | 11 | Object.keys(components).forEach(key => { 12 | var name = key.replace(/(\w)/, (v) => v.toUpperCase()) // 首字母大写 13 | Vue.component(`v${name}`, components[key]) 14 | }) 15 | 16 | Object.keys(plugins).forEach(key => { 17 | Vue.use(plugins[key]); 18 | }) 19 | 20 | Object.keys(filters).forEach(key => { 21 | Vue.filter(key, filters[key]) 22 | }) 23 | 24 | Vue.config.productionTip = false 25 | 26 | new Vue({ 27 | el: '#app', 28 | router, 29 | store, 30 | template: '', 31 | components: { App } 32 | }) 33 | -------------------------------------------------------------------------------- /src/pages/home/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 46 | 47 | -------------------------------------------------------------------------------- /src/pages/list/index.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 142 | 143 | -------------------------------------------------------------------------------- /src/plugins/index.js: -------------------------------------------------------------------------------- 1 | import Toast from './toast/index' 2 | 3 | export default { 4 | Toast 5 | } -------------------------------------------------------------------------------- /src/plugins/toast/index.css: -------------------------------------------------------------------------------- 1 | .lx-toast{ 2 | position: fixed; 3 | bottom:100px; 4 | left:50%; 5 | width:180px; 6 | height:40px; 7 | line-height: 40px; 8 | padding:0 10px; 9 | margin-left: -100px; 10 | text-align: center; 11 | z-index: 9999; 12 | font-size:14px; 13 | color: #fff; 14 | border-radius: 5px; 15 | background: rgba(0,0,0,0.7); 16 | animation: show-toast .5s; 17 | -webkit-animation: show-toast .5s; 18 | overflow: hidden; 19 | text-overflow:ellipsis; 20 | white-space:nowrap; 21 | } 22 | .lx-toast.lx-toast-top{ 23 | top:50px; 24 | bottom:inherit; 25 | } 26 | .lx-toast.lx-toast-center{ 27 | top:50%; 28 | margin-top: -20px; 29 | bottom:inherit; 30 | } 31 | @keyframes show-toast 32 | { 33 | from { 34 | opacity: 0; 35 | transform: translateY(-10px); 36 | -webkit-transform: translateY(-10px); 37 | } 38 | to { 39 | opacity: 1; 40 | transform: translateY(0); 41 | -webkit-transform: translateY(0); 42 | } 43 | } 44 | 45 | .lx-load-mark{ 46 | position: fixed; 47 | left: 0; 48 | top: 0; 49 | width: 100%; 50 | height: 100%; 51 | z-index: 9999; 52 | } 53 | .lx-load-box { 54 | position: fixed; 55 | z-index: 3; 56 | width: 7.6em; 57 | min-height: 7.6em; 58 | top: 180px; 59 | left: 50%; 60 | margin-left: -3.8em; 61 | background: rgba(0,0,0,0.7); 62 | text-align: center; 63 | border-radius: 5px; 64 | color: #FFFFFF; 65 | } 66 | .lx-load-content { 67 | margin-top: 64%; 68 | font-size: 14px; 69 | } 70 | .lx-loading{ 71 | position: absolute; 72 | width: 0px; 73 | left: 50%; 74 | top: 38%; 75 | } 76 | .loading_leaf{ 77 | position: absolute; 78 | top: -1px; 79 | opacity: 0.25; 80 | } 81 | .loading_leaf:before { 82 | content: " "; 83 | position: absolute; 84 | width: 9.14px; 85 | height: 3.08px; 86 | background: #d1d1d5; 87 | box-shadow: rgba(0, 0, 0, 0.0980392) 0px 0px 1px; 88 | border-radius: 1px; 89 | -webkit-transform-origin: left 50% 0px; 90 | transform-origin: left 50% 0px; 91 | } 92 | .loading_leaf_0 { 93 | -webkit-animation: opacity-0 1.25s linear infinite; 94 | animation: opacity-0 1.25s linear infinite; 95 | } 96 | .loading_leaf_0:before { 97 | -webkit-transform: rotate(0deg) translate(7.92px, 0px); 98 | transform: rotate(0deg) translate(7.92px, 0px); 99 | } 100 | .loading_leaf_1 { 101 | -webkit-animation: opacity-1 1.25s linear infinite; 102 | animation: opacity-1 1.25s linear infinite; 103 | } 104 | .loading_leaf_1:before { 105 | -webkit-transform: rotate(30deg) translate(7.92px, 0px); 106 | transform: rotate(30deg) translate(7.92px, 0px); 107 | } 108 | .loading_leaf_2 { 109 | -webkit-animation: opacity-2 1.25s linear infinite; 110 | animation: opacity-2 1.25s linear infinite; 111 | } 112 | .loading_leaf_2:before { 113 | -webkit-transform: rotate(60deg) translate(7.92px, 0px); 114 | transform: rotate(60deg) translate(7.92px, 0px); 115 | } 116 | .loading_leaf_3 { 117 | -webkit-animation: opacity-3 1.25s linear infinite; 118 | animation: opacity-3 1.25s linear infinite; 119 | } 120 | .loading_leaf_3:before { 121 | -webkit-transform: rotate(90deg) translate(7.92px, 0px); 122 | transform: rotate(90deg) translate(7.92px, 0px); 123 | } 124 | .loading_leaf_4 { 125 | -webkit-animation: opacity-4 1.25s linear infinite; 126 | animation: opacity-4 1.25s linear infinite; 127 | } 128 | .loading_leaf_4:before { 129 | -webkit-transform: rotate(120deg) translate(7.92px, 0px); 130 | transform: rotate(120deg) translate(7.92px, 0px); 131 | } 132 | .loading_leaf_5 { 133 | -webkit-animation: opacity-5 1.25s linear infinite; 134 | animation: opacity-5 1.25s linear infinite; 135 | } 136 | .loading_leaf_5:before { 137 | -webkit-transform: rotate(150deg) translate(7.92px, 0px); 138 | transform: rotate(150deg) translate(7.92px, 0px); 139 | } 140 | .loading_leaf_6 { 141 | -webkit-animation: opacity-6 1.25s linear infinite; 142 | animation: opacity-6 1.25s linear infinite; 143 | } 144 | .loading_leaf_6:before { 145 | -webkit-transform: rotate(180deg) translate(7.92px, 0px); 146 | transform: rotate(180deg) translate(7.92px, 0px); 147 | } 148 | .loading_leaf_7 { 149 | -webkit-animation: opacity-7 1.25s linear infinite; 150 | animation: opacity-7 1.25s linear infinite; 151 | } 152 | .loading_leaf_7:before { 153 | -webkit-transform: rotate(210deg) translate(7.92px, 0px); 154 | transform: rotate(210deg) translate(7.92px, 0px); 155 | } 156 | .loading_leaf_8 { 157 | -webkit-animation: opacity-8 1.25s linear infinite; 158 | animation: opacity-8 1.25s linear infinite; 159 | } 160 | .loading_leaf_8:before { 161 | -webkit-transform: rotate(240deg) translate(7.92px, 0px); 162 | transform: rotate(240deg) translate(7.92px, 0px); 163 | } 164 | .loading_leaf_9 { 165 | -webkit-animation: opacity-9 1.25s linear infinite; 166 | animation: opacity-9 1.25s linear infinite; 167 | } 168 | .loading_leaf_9:before { 169 | -webkit-transform: rotate(270deg) translate(7.92px, 0px); 170 | transform: rotate(270deg) translate(7.92px, 0px); 171 | } 172 | .loading_leaf_10 { 173 | -webkit-animation: opacity-10 1.25s linear infinite; 174 | animation: opacity-10 1.25s linear infinite; 175 | } 176 | .loading_leaf_10:before { 177 | -webkit-transform: rotate(300deg) translate(7.92px, 0px); 178 | transform: rotate(300deg) translate(7.92px, 0px); 179 | } 180 | .loading_leaf_11 { 181 | -webkit-animation: opacity-11 1.25s linear infinite; 182 | animation: opacity-11 1.25s linear infinite; 183 | } 184 | .loading_leaf_11:before { 185 | -webkit-transform: rotate(330deg) translate(7.92px, 0px); 186 | transform: rotate(330deg) translate(7.92px, 0px); 187 | } 188 | @-webkit-keyframes opacity-0 { 189 | 0% { opacity: 0.25; } 190 | 0.01% { opacity: 0.25; } 191 | 0.02% { opacity: 1; } 192 | 60.01% { opacity: 0.25; } 193 | 100% { opacity: 0.25; } 194 | } 195 | @-webkit-keyframes opacity-1 { 196 | 0% { opacity: 0.25; } 197 | 8.34333% { opacity: 0.25; } 198 | 8.35333% { opacity: 1; } 199 | 68.3433% { opacity: 0.25; } 200 | 100% { opacity: 0.25; } 201 | } 202 | @-webkit-keyframes opacity-2 { 203 | 0% { opacity: 0.25; } 204 | 16.6767% { opacity: 0.25; } 205 | 16.6867% { opacity: 1; } 206 | 76.6767% { opacity: 0.25; } 207 | 100% { opacity: 0.25; } 208 | } 209 | @-webkit-keyframes opacity-3 { 210 | 0% { opacity: 0.25; } 211 | 25.01% { opacity: 0.25; } 212 | 25.02% { opacity: 1; } 213 | 85.01% { opacity: 0.25; } 214 | 100% { opacity: 0.25; } 215 | } 216 | @-webkit-keyframes opacity-4 { 217 | 0% { opacity: 0.25; } 218 | 33.3433% { opacity: 0.25; } 219 | 33.3533% { opacity: 1; } 220 | 93.3433% { opacity: 0.25; } 221 | 100% { opacity: 0.25; } 222 | } 223 | @-webkit-keyframes opacity-5 { 224 | 0% { opacity: 0.270958333333333; } 225 | 41.6767% { opacity: 0.25; } 226 | 41.6867% { opacity: 1; } 227 | 1.67667% { opacity: 0.25; } 228 | 100% { opacity: 0.270958333333333; } 229 | } 230 | @-webkit-keyframes opacity-6 { 231 | 0% { opacity: 0.375125; } 232 | 50.01% { opacity: 0.25; } 233 | 50.02% { opacity: 1; } 234 | 10.01% { opacity: 0.25; } 235 | 100% { opacity: 0.375125; } 236 | } 237 | @-webkit-keyframes opacity-7 { 238 | 0% { opacity: 0.479291666666667; } 239 | 58.3433% { opacity: 0.25; } 240 | 58.3533% { opacity: 1; } 241 | 18.3433% { opacity: 0.25; } 242 | 100% { opacity: 0.479291666666667; } 243 | } 244 | @-webkit-keyframes opacity-8 { 245 | 0% { opacity: 0.583458333333333; } 246 | 66.6767% { opacity: 0.25; } 247 | 66.6867% { opacity: 1; } 248 | 26.6767% { opacity: 0.25; } 249 | 100% { opacity: 0.583458333333333; } 250 | } 251 | @-webkit-keyframes opacity-9 { 252 | 0% { opacity: 0.687625; } 253 | 75.01% { opacity: 0.25; } 254 | 75.02% { opacity: 1; } 255 | 35.01% { opacity: 0.25; } 256 | 100% { opacity: 0.687625; } 257 | } 258 | @-webkit-keyframes opacity-10 { 259 | 0% { opacity: 0.791791666666667; } 260 | 83.3433% { opacity: 0.25; } 261 | 83.3533% { opacity: 1; } 262 | 43.3433% { opacity: 0.25; } 263 | 100% { opacity: 0.791791666666667; } 264 | } 265 | @-webkit-keyframes opacity-11 { 266 | 0% { opacity: 0.895958333333333; } 267 | 91.6767% { opacity: 0.25; } 268 | 91.6867% { opacity: 1; } 269 | 51.6767% { opacity: 0.25; } 270 | 100% { opacity: 0.895958333333333; } 271 | } -------------------------------------------------------------------------------- /src/plugins/toast/index.js: -------------------------------------------------------------------------------- 1 | var Toast = {}; 2 | 3 | Toast.install = function (Vue, options) { 4 | var opt = { 5 | defaultType: 'bottom', 6 | duration: '2500' 7 | } 8 | for (var property in options) { 9 | opt[property] = options[property]; 10 | } 11 | Vue.prototype.$toast = function (tips, type) { 12 | 13 | var curType = type ? type : opt.defaultType; 14 | 15 | if (document.querySelector('.lx-toast')) { 16 | return; 17 | } 18 | var toastTpl = Vue.extend({ 19 | template: '
' + tips + '
' 20 | }); 21 | var tpl = new toastTpl().$mount().$el; 22 | document.body.appendChild(tpl); 23 | setTimeout(function () { 24 | document.body.removeChild(tpl); 25 | }, opt.duration) 26 | }; 27 | ['bottom', 'center', 'top'].forEach(function (type) { 28 | Vue.prototype.$toast[type] = function (tips) { 29 | return Vue.prototype.$toast(tips, type) 30 | } 31 | }); 32 | 33 | Vue.prototype.$loading = function (tips, type) { 34 | var load = document.querySelector('.lx-load-mark'); 35 | 36 | if (type == 'close') { 37 | load && document.body.removeChild(load); 38 | } else { 39 | if (load) { 40 | // 如果loading还在,则不再执行 41 | return; 42 | } 43 | var loadTpl = Vue.extend({ 44 | template: '
' + tips + '
' 45 | }); 46 | var tpl = new loadTpl().$mount().$el; 47 | document.body.appendChild(tpl); 48 | } 49 | }; 50 | 51 | ['open', 'close'].forEach(function (type) { 52 | Vue.prototype.$loading[type] = function (tips) { 53 | return Vue.prototype.$loading(tips, type) 54 | } 55 | }); 56 | } 57 | 58 | export default Toast; -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Home from '@/pages/home' 4 | import List from '@/pages/list' 5 | 6 | Vue.use(Router) 7 | 8 | export default new Router({ 9 | routes: [ 10 | { 11 | path: '/', 12 | component: Home, 13 | meta: { auth: false } 14 | }, 15 | { 16 | path: '/list', 17 | component: List, 18 | meta: { auth: false } 19 | }, 20 | ] 21 | }) 22 | 23 | -------------------------------------------------------------------------------- /src/store/actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './mutation-types' 2 | import { timeFormat } from '@/utils' 3 | import axios from 'axios' 4 | 5 | export default { 6 | getComments({ commit, state }, data) { 7 | if (data > -1) { 8 | axios.get(`api/project/${data}/comments.action`, {}) 9 | .then((res) => { 10 | const comments = res.data.result; 11 | commit(types.getComments, comments) 12 | }) 13 | } 14 | }, 15 | addComment({ commit, state }) { 16 | const comment = state.home.comment.trim(); 17 | if (comment.length > 0) { 18 | axios.post('api/comment/add.action', { 19 | comment 20 | }).then((res) => { 21 | if (res.data.error === 0) { 22 | commit(types.addComment, { 23 | author: 'Visitor Rookie', 24 | comment, 25 | date: timeFormat('yyyy-MM-dd hh:mm:ss') 26 | }); 27 | } 28 | }) 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/store/getters.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 3 | } -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import state from './state'; 4 | import mutations from './mutations' 5 | import actions from './actions' 6 | 7 | Vue.use(Vuex) 8 | 9 | export default new Vuex.Store({ 10 | state, 11 | actions, 12 | mutations, 13 | }) -------------------------------------------------------------------------------- /src/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | export const getComments = 'getComments' 2 | export const addComment = 'addComment' 3 | export const updateComment = 'updateComment' -------------------------------------------------------------------------------- /src/store/mutations.js: -------------------------------------------------------------------------------- 1 | import * as types from './mutation-types' 2 | 3 | export default { 4 | [types.getComments](state, payload) { 5 | state.home.list = state.home.list.concat(payload); 6 | if (payload.length === 0) { 7 | state.home.page = -1; 8 | } else { 9 | state.home.page++; 10 | } 11 | }, 12 | [types.addComment](state, payload) { 13 | state.home.list.unshift(payload) 14 | state.home.comment = '' 15 | }, 16 | [types.updateComment](state, payload) { 17 | state.home.comment = payload 18 | } 19 | } -------------------------------------------------------------------------------- /src/store/state.js: -------------------------------------------------------------------------------- 1 | export default { 2 | home: { 3 | list: [], 4 | comment: '', 5 | page: 1 6 | } 7 | } -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export function timeFormat(fmt) { 2 | var time = new Date(); 3 | var o = { 4 | "M+": time.getMonth() + 1, 5 | "d+": time.getDate(), 6 | "h+": time.getHours(), 7 | "m+": time.getMinutes(), 8 | "s+": time.getSeconds(), 9 | "q+": Math.floor((time.getMonth() + 3) / 3), 10 | "S": time.getMilliseconds() 11 | }; 12 | if (/(y+)/.test(fmt)) 13 | fmt = fmt.replace(RegExp.$1, (time.getFullYear() + "").substr(4 - RegExp.$1.length)); 14 | for (var k in o) 15 | if (new RegExp("(" + k + ")").test(fmt)) 16 | fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); 17 | return fmt; 18 | } 19 | 20 | export function compact(array) { 21 | function f(element) { 22 | if (element !== false || element !== null || element !== 0 || element !== "" || element !== undefined || element !== NaN) { 23 | return element; 24 | } 25 | } 26 | var filtered = array.filter(f); 27 | return filtered; 28 | } -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanm1ng/vue-starter-kit/365c669cad4c408a2542cadbdcf6c32ae141dc44/static/.gitkeep -------------------------------------------------------------------------------- /test/components/comment.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import comment from '@/components/comment' 3 | 4 | import { getRenderedVm } from '../utils'; 5 | 6 | describe('test components/comment.vue', () => { 7 | it('组件加载后,list', () => { 8 | let vm = getRenderedVm(comment, { 9 | list: [ 10 | { 11 | author: 'author', 12 | comment: 'comment', 13 | date: '2017-04-25 15:50:11' 14 | } 15 | ] 16 | }); 17 | 18 | expect(vm.list).length === 1; 19 | }); 20 | }) -------------------------------------------------------------------------------- /test/components/header.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import header from '@/components/header' 3 | 4 | import { getRenderedVm } from '../utils'; 5 | 6 | describe('test components/header.vue', () => { 7 | it('组件加载后,title应该是「test」', () => { 8 | let vm = getRenderedVm(header, { 9 | title: 'test' 10 | }); 11 | 12 | expect(vm.title).toEqual('test'); 13 | }); 14 | }) -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | var webpackConfig = require('../build/webpack.test.conf') 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | port: 9876, 6 | browsers: ['PhantomJS'], 7 | frameworks: ['jasmine'], 8 | reporters: ['progress'], 9 | files: [ 10 | '**/*.spec.js' 11 | ], 12 | preprocessors: { 13 | '**/*.spec.js': ['webpack'] 14 | }, 15 | 16 | webpack: webpackConfig, 17 | 18 | webpackMiddleware: { 19 | noInfo: true 20 | } 21 | 22 | }) 23 | } -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | export function getRenderedVm(Component, propsData) { 4 | const Ctor = Vue.extend(Component) 5 | const vm = new Ctor({ propsData }).$mount() 6 | return vm; 7 | } -------------------------------------------------------------------------------- /test/vuex/mutations.spec.js: -------------------------------------------------------------------------------- 1 | import mutations from '@/store/mutations' 2 | import state from '@/store/state' 3 | 4 | describe('test mutations', () => { 5 | it('getComments', () => { 6 | mutations.getComments(state, [ 7 | { author: 'yanm1ng', comment: 'test', date: '2017-04-26 11:22:11' }, 8 | { author: 'rookie', comment: 'test', date: '2017-04-24 09:12:32' } 9 | ]) 10 | 11 | expect(state.home.list).length === 3 12 | }); 13 | it('addComment', () => { 14 | 15 | mutations.addComment(state, 5) 16 | 17 | expect(state.home.list).length === 1 && expect(state.home.comment).toEqual('') 18 | }); 19 | it('updateComment', () => { 20 | 21 | mutations.updateComment(state, 'new comment') 22 | 23 | expect(state.home.comment).toEqual('new comment') 24 | }); 25 | }) --------------------------------------------------------------------------------