├── README.md ├── daily ├── .babelrc ├── app.vue ├── components │ ├── daily-article.vue │ └── item.vue ├── directives │ └── time.js ├── index.ejs ├── index.html ├── libs │ └── util.js ├── main.js ├── package.json ├── proxy.js ├── style.css ├── webpack.config.js └── webpack.prod.config.js ├── demo ├── .babelrc ├── app.vue ├── button.vue ├── images │ └── image.png ├── index.ejs ├── index.html ├── main.js ├── package.json ├── style.css ├── title.vue ├── webpack.config.js └── webpack.prod.config.js ├── router ├── .babelrc ├── app.vue ├── button.vue ├── images │ └── image.png ├── index.ejs ├── index.html ├── main.js ├── package.json ├── style.css ├── title.vue ├── views │ ├── about.vue │ ├── index.vue │ └── user.vue ├── webpack.config.js └── webpack.prod.config.js ├── shopping ├── .babelrc ├── app.vue ├── components │ └── product.vue ├── index.ejs ├── index.html ├── main.js ├── package.json ├── product.js ├── router.js ├── style.css ├── views │ ├── cart.vue │ ├── list.vue │ └── product.vue ├── webpack.config.js └── webpack.prod.config.js ├── vue-bus ├── .babelrc ├── app.vue ├── button.vue ├── images │ └── image.png ├── index.ejs ├── index.html ├── main.js ├── package.json ├── style.css ├── title.vue ├── views │ ├── about.vue │ ├── counter.vue │ ├── index.vue │ └── user.vue ├── vue-bus.js ├── webpack.config.js └── webpack.prod.config.js └── vuex ├── .babelrc ├── app.vue ├── button.vue ├── images └── image.png ├── index.ejs ├── index.html ├── main.js ├── package.json ├── style.css ├── title.vue ├── views ├── about.vue ├── index.vue └── user.vue ├── webpack.config.js └── webpack.prod.config.js /README.md: -------------------------------------------------------------------------------- 1 | # vue-book 2 | 《Vue.js实战》源码及答疑 3 | 4 | ### 正版购买地址 5 | - **京东** https://item.jd.com/12215519.html 6 | - **天猫** https://detail.tmall.com/item.htm?id=559480603657 7 | - **当当** http://product.dangdang.com/25180286.html 8 | - **Vue.js 系列视频教程** https://segmentfault.com/ls/1650000011074057 9 | - **iView 系列视频教程** https://segmentfault.com/ls/1650000016424063 10 | 11 | ## 已知勘误 12 | > 感谢大家的反馈,目前已知的勘误如下,已修改,再次印刷时将更正。 13 | 14 | - [P26] 4.2章节个别错误 https://github.com/icarusion/vue-book/issues/2 15 | - [P60] 应该是:给 `添加属性 https://github.com/icarusion/vue-book/issues/13 16 | - [P81] 父链标题下有错别字 https://github.com/icarusion/vue-book/issues/5 17 | - [P87] 错别字 https://github.com/icarusion/vue-book/issues/23 18 | - [P135] VNode 对象参数 context 说明错别字 https://github.com/icarusion/vue-book/issues/24 19 | - [P204] `htmlwebpackPlugin` 更改为 `htmlWebpackPlugin` https://github.com/icarusion/vue-book/issues/20 20 | - [P271] 代码修改为 `return year + '' + month + '' + day;` https://github.com/icarusion/vue-book/issues/17 21 | - [P326] 倒数第三行少了个字符 's' https://github.com/icarusion/vue-book/issues/1 22 | -------------------------------------------------------------------------------- /daily/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": ["transform-runtime"], 4 | "comments": false 5 | } 6 | -------------------------------------------------------------------------------- /daily/app.vue: -------------------------------------------------------------------------------- 1 | 40 | -------------------------------------------------------------------------------- /daily/components/daily-article.vue: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /daily/components/item.vue: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /daily/directives/time.js: -------------------------------------------------------------------------------- 1 | var Time = { 2 | // 获取当前时间戳 3 | getUnix: function () { 4 | var date = new Date(); 5 | return date.getTime(); 6 | }, 7 | // 获取今天0点0分0秒的时间戳 8 | getTodayUnix: function () { 9 | var date = new Date(); 10 | date.setHours(0); 11 | date.setMinutes(0); 12 | date.setSeconds(0); 13 | date.setMilliseconds(0); 14 | return date.getTime(); 15 | }, 16 | // 获取今年1月1日0点0分0秒的时间戳 17 | getYearUnix: function () { 18 | var date = new Date(); 19 | date.setMonth(0); 20 | date.setDate(1); 21 | date.setHours(0); 22 | date.setMinutes(0); 23 | date.setSeconds(0); 24 | date.setMilliseconds(0); 25 | return date.getTime(); 26 | }, 27 | // 获取标准年月日 28 | getLastDate: function(time) { 29 | var date = new Date(time); 30 | var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1; 31 | var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate(); 32 | return date.getFullYear() + '-' + month + "-" + day; 33 | }, 34 | // 转换时间 35 | getFormatTime: function(timestamp) { 36 | var now = this.getUnix(); //当前时间戳 37 | var today = this.getTodayUnix(); //今天0点时间戳 38 | var year = this.getYearUnix(); //今年0点时间戳 39 | var timer = (now - timestamp) / 1000; // 转换为秒级时间戳 40 | var tip = ''; 41 | 42 | if (timer <= 0) { 43 | tip = '刚刚'; 44 | } else if (Math.floor(timer/60) <= 0) { 45 | tip = '刚刚'; 46 | } else if (timer < 3600) { 47 | tip = Math.floor(timer/60) + '分钟前'; 48 | } else if (timer >= 3600 && (timestamp - today >= 0) ) { 49 | tip = Math.floor(timer/3600) + '小时前'; 50 | } else if (timer/86400 <= 31) { 51 | tip = Math.ceil(timer/86400) + '天前'; 52 | } else { 53 | tip = this.getLastDate(timestamp); 54 | } 55 | return tip; 56 | } 57 | }; 58 | 59 | export default { 60 | bind: function (el, binding) { 61 | el.innerHTML = Time.getFormatTime(binding.value * 1000); 62 | el.__timeout__ = setInterval(function() { 63 | el.innerHTML = Time.getFormatTime(binding.value * 1000); 64 | }, 60000); 65 | }, 66 | unbind: function (el) { 67 | clearInterval(el.__timeout__); 68 | delete el.__timeout__; 69 | } 70 | } -------------------------------------------------------------------------------- /daily/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 知乎日报 6 | 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /daily/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 知乎日报 6 | 7 | 8 | 9 |
10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /daily/libs/util.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const Util = { 4 | imgPath: 'http://127.0.0.1:8011/img/', 5 | apiPath: 'http://127.0.0.1:8010/' 6 | }; 7 | 8 | // 获取今天时间戳 9 | Util.getTodayTime = function () { 10 | const date = new Date(); 11 | date.setHours(0); 12 | date.setMinutes(0); 13 | date.setSeconds(0); 14 | date.setMilliseconds(0); 15 | return date.getTime(); 16 | }; 17 | 18 | // 获取上一天日期 19 | Util.prevDay = function (timestamp = (new Date()).getTime()) { 20 | const date = new Date(timestamp); 21 | const year = date.getFullYear(); 22 | const month = date.getMonth() + 1 < 10 23 | ? '0' + (date.getMonth() + 1) 24 | : date.getMonth() + 1; 25 | const day = date.getDate() < 10 26 | ? '0' + date.getDate() 27 | : date.getDate(); 28 | return year + '' + month + '' + day; 29 | }; 30 | 31 | // Ajax 通用配置 32 | Util.ajax = axios.create({ 33 | baseURL: Util.apiPath 34 | }); 35 | 36 | // 添加响应拦截器 37 | Util.ajax.interceptors.response.use(res => { 38 | return res.data; 39 | }); 40 | 41 | export default Util; -------------------------------------------------------------------------------- /daily/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './app.vue'; 3 | import './style.css'; 4 | 5 | new Vue({ 6 | el: '#app', 7 | render: h => { 8 | return h(App) 9 | } 10 | }); -------------------------------------------------------------------------------- /daily/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "daily", 3 | "version": "1.0.0", 4 | "description": "daily", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "webpack-dev-server --open --history-api-fallback --config webpack.config.js", 9 | "build": "webpack --progress --hide-modules --config webpack.prod.config.js" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "babel": "^6.23.0", 15 | "babel-core": "^6.24.0", 16 | "babel-loader": "^6.4.1", 17 | "babel-plugin-transform-runtime": "^6.23.0", 18 | "babel-preset-es2015": "^6.24.0", 19 | "babel-runtime": "^6.23.0", 20 | "css-loader": "^0.27.3", 21 | "extract-text-webpack-plugin": "^2.1.0", 22 | "file-loader": "^0.10.1", 23 | "html-webpack-plugin": "^2.28.0", 24 | "request": "^2.81.0", 25 | "style-loader": "^0.16.1", 26 | "url-loader": "^0.5.8", 27 | "vue-hot-reload-api": "^2.0.11", 28 | "vue-loader": "^11.3.4", 29 | "vue-style-loader": "^2.0.5", 30 | "vue-template-compiler": "^2.2.6", 31 | "webpack": "^2.3.2", 32 | "webpack-dev-server": "^2.4.2", 33 | "webpack-merge": "^4.1.0" 34 | }, 35 | "dependencies": { 36 | "axios": "^0.16.1", 37 | "vue": "^2.2.6" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /daily/proxy.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const request = require('request'); 3 | 4 | const hostname = '127.0.0.1'; 5 | const port = 8010; 6 | const imgPort = 8011; 7 | 8 | // 创建一个 API 代理服务 9 | const apiServer = http.createServer((req, res) => { 10 | const url = 'http://news-at.zhihu.com/api/4' + req.url; 11 | const options = { 12 | url: url 13 | }; 14 | 15 | function callback (error, response, body) { 16 | if (!error && response.statusCode === 200) { 17 | // 设置编码类型,否则中文会显示为乱码 18 | res.setHeader('Content-Type', 'text/plain;charset=UTF-8'); 19 | // 设置所有域允许跨域 20 | res.setHeader('Access-Control-Allow-Origin', '*'); 21 | // 返回代理后的内容 22 | res.end(body); 23 | } 24 | } 25 | request.get(options, callback); 26 | }); 27 | // 监听 8010 端口 28 | apiServer.listen(port, hostname, () => { 29 | console.log(`接口代理运行在 http://${hostname}:${port}/`); 30 | }); 31 | // 创建一个图片代理服务 32 | const imgServer = http.createServer((req, res) => { 33 | const url = req.url.split('/img/')[1]; 34 | const options = { 35 | url: url, 36 | encoding: null 37 | }; 38 | 39 | function callback (error, response, body) { 40 | if (!error && response.statusCode === 200) { 41 | const contentType = response.headers['content-type']; 42 | res.setHeader('Content-Type', contentType); 43 | res.setHeader('Access-Control-Allow-Origin', '*'); 44 | res.end(body); 45 | } 46 | } 47 | request.get(options, callback); 48 | }); 49 | // 监听 8011 端口 50 | imgServer.listen(imgPort, hostname, () => { 51 | console.log(`图片代理运行在 http://${hostname}:${imgPort}/`); 52 | }); -------------------------------------------------------------------------------- /daily/style.css: -------------------------------------------------------------------------------- 1 | html, body{ 2 | margin: 0; 3 | padding: 0; 4 | height: 100%; 5 | color: #657180; 6 | font-size: 16px; 7 | } 8 | .daily-menu{ 9 | width: 150px; 10 | position: fixed; 11 | top: 0; 12 | bottom: 0; 13 | left: 0; 14 | overflow: auto; 15 | background: #f5f7f9; 16 | } 17 | .daily-menu-item{ 18 | font-size: 18px; 19 | text-align: center; 20 | margin: 5px 0; 21 | padding: 10px 0; 22 | cursor: pointer; 23 | border-right: 2px solid transparent; 24 | transition: all .3s ease-in-out; 25 | } 26 | .daily-menu-item:hover{ 27 | background: #e3e8ee; 28 | } 29 | .daily-menu-item.on{ 30 | border-right: 2px solid #3399ff; 31 | } 32 | 33 | .daily-menu ul{ 34 | list-style: none; 35 | } 36 | .daily-menu ul li a{ 37 | display: block; 38 | color: inherit; 39 | text-decoration: none; 40 | padding: 5px 0; 41 | margin: 5px 0; 42 | cursor: pointer; 43 | } 44 | .daily-menu ul li a:hover, .daily-menu ul li a.on{ 45 | color: #3399ff; 46 | } 47 | 48 | .daily-list{ 49 | width: 300px; 50 | position: fixed; 51 | top: 0; 52 | bottom: 0; 53 | left: 150px; 54 | overflow: auto; 55 | border-right: 1px solid #d7dde4; 56 | } 57 | .daily-date{ 58 | text-align: center; 59 | margin: 10px 0; 60 | } 61 | .daily-item{ 62 | display: block; 63 | color: inherit; 64 | text-decoration: none; 65 | padding: 16px; 66 | overflow: hidden; 67 | cursor: pointer; 68 | transition: all .3s ease-in-out; 69 | } 70 | .daily-item:hover{ 71 | background: #e3e8ee; 72 | } 73 | .daily-img{ 74 | width: 80px; 75 | height: 80px; 76 | float: left; 77 | } 78 | .daily-img img{ 79 | width: 100%; 80 | height: 100%; 81 | border-radius: 3px; 82 | } 83 | .daily-title{ 84 | padding: 10px 5px 10px 90px; 85 | } 86 | .daily-title.noImg{ 87 | padding-left: 5px; 88 | } 89 | .daily-article{ 90 | margin-left: 450px; 91 | padding: 20px; 92 | } 93 | .daily-article-title{ 94 | font-size: 28px; 95 | font-weight: bold; 96 | color: #222; 97 | padding: 10px 0; 98 | } 99 | 100 | .view-more a{ 101 | display: block; 102 | cursor: pointer; 103 | background: #f5f7f9; 104 | text-align: center; 105 | color: inherit; 106 | text-decoration: none; 107 | padding: 4px 0; 108 | border-radius: 3px; 109 | } 110 | 111 | .daily-comments{ 112 | margin: 10px 0; 113 | } 114 | .daily-comments span{ 115 | display: block; 116 | margin: 10px 0; 117 | font-size: 20px; 118 | } 119 | .daily-comment{ 120 | overflow: hidden; 121 | margin-bottom: 20px; 122 | padding-bottom: 20px; 123 | border-bottom: 1px dashed #e3e8ee; 124 | } 125 | .daily-comment-avatar{ 126 | width: 50px; 127 | height: 50px; 128 | float: left; 129 | } 130 | .daily-comment-avatar img{ 131 | width: 100%; 132 | height: 100%; 133 | border-radius: 3px; 134 | } 135 | .daily-comment-content{ 136 | margin-left: 65px; 137 | } 138 | .daily-comment-name{ 139 | 140 | } 141 | .daily-comment-time{ 142 | color: #9ea7b4; 143 | font-size: 14px; 144 | margin-top: 5px; 145 | } 146 | .daily-comment-text{ 147 | margin-top: 10px; 148 | } -------------------------------------------------------------------------------- /daily/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 3 | 4 | var config = { 5 | entry: { 6 | main: './main' 7 | }, 8 | output: { 9 | path: path.join(__dirname, './dist'), 10 | publicPath: '/dist/', 11 | filename: 'main.js' 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.vue$/, 17 | loader: 'vue-loader', 18 | options: { 19 | loaders: { 20 | css: ExtractTextPlugin.extract({ 21 | use: 'css-loader', 22 | fallback: 'vue-style-loader' 23 | }) 24 | } 25 | } 26 | }, 27 | { 28 | test: /\.js$/, 29 | loader: 'babel-loader', 30 | exclude: /node_modules/ 31 | }, 32 | { 33 | test: /\.css$/, 34 | use: ExtractTextPlugin.extract({ 35 | use: 'css-loader', 36 | fallback: 'style-loader' 37 | }) 38 | }, 39 | { 40 | test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/, 41 | loader: 'url-loader?limit=1024' 42 | } 43 | ] 44 | }, 45 | plugins: [ 46 | new ExtractTextPlugin({ 47 | filename: '[name].css', 48 | allChunks: true 49 | }) 50 | ] 51 | }; 52 | 53 | module.exports = config; -------------------------------------------------------------------------------- /daily/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | var merge = require('webpack-merge'); 5 | var webpackBaseConfig = require('./webpack.config.js'); 6 | 7 | // 清空基本配置的插件列表 8 | webpackBaseConfig.plugins = []; 9 | 10 | module.exports = merge(webpackBaseConfig, { 11 | output: { 12 | publicPath: '/dist/', 13 | // 将入口文件重命名为带有 20 位 hash 值的唯一文件 14 | filename: '[name].[hash].js' 15 | }, 16 | plugins: [ 17 | new ExtractTextPlugin({ 18 | // 提取 css,并重命名为带有 20 位 hash 值的唯一文件 19 | filename: '[name].[hash].css', 20 | allChunks: true 21 | }), 22 | // 定义当前 node 环境为生产环境 23 | new webpack.DefinePlugin({ 24 | 'process.env': { 25 | NODE_ENV: '"production"' 26 | } 27 | }), 28 | // 压缩 js 29 | new webpack.optimize.UglifyJsPlugin({ 30 | compress: { 31 | warnings: false 32 | } 33 | }), 34 | // 提取模板,并保存入口 html 文件 35 | new HtmlWebpackPlugin({ 36 | filename: '../index_prod.html', 37 | template: './index.ejs', 38 | inject: false 39 | }) 40 | ] 41 | }); -------------------------------------------------------------------------------- /demo/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": ["transform-runtime"], 4 | "comments": false 5 | } 6 | -------------------------------------------------------------------------------- /demo/app.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /demo/button.vue: -------------------------------------------------------------------------------- 1 | 6 | 28 | -------------------------------------------------------------------------------- /demo/images/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icarusion/vue-book/cd7e9a5c2c86bd7129c71768cc22f9cfdd4b8d5b/demo/images/image.png -------------------------------------------------------------------------------- /demo/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Webpack App 6 | 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Webpack App 6 | 7 | 8 | 9 |
10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /demo/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './app.vue'; 3 | 4 | new Vue({ 5 | el: '#app', 6 | render: h => { 7 | return h(App) 8 | } 9 | }); -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "webpack-dev-server --open --config webpack.config.js", 9 | "build": "webpack --progress --hide-modules --config webpack.prod.config.js" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "babel": "^6.23.0", 15 | "babel-core": "^6.24.0", 16 | "babel-loader": "^6.4.1", 17 | "babel-plugin-transform-runtime": "^6.23.0", 18 | "babel-preset-es2015": "^6.24.0", 19 | "babel-runtime": "^6.23.0", 20 | "css-loader": "^0.27.3", 21 | "extract-text-webpack-plugin": "^2.1.0", 22 | "file-loader": "^0.10.1", 23 | "html-webpack-plugin": "^2.28.0", 24 | "style-loader": "^0.16.1", 25 | "url-loader": "^0.5.8", 26 | "vue-hot-reload-api": "^2.0.11", 27 | "vue-loader": "^11.3.4", 28 | "vue-style-loader": "^2.0.5", 29 | "vue-template-compiler": "^2.2.6", 30 | "webpack": "^2.3.2", 31 | "webpack-dev-server": "^2.4.2", 32 | "webpack-merge": "^4.1.0" 33 | }, 34 | "dependencies": { 35 | "vue": "^2.2.6" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /demo/style.css: -------------------------------------------------------------------------------- 1 | /* style.css */ 2 | #app{ 3 | font-size: 24px; 4 | color: #f50; 5 | } -------------------------------------------------------------------------------- /demo/title.vue: -------------------------------------------------------------------------------- 1 | 6 | 15 | -------------------------------------------------------------------------------- /demo/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 3 | 4 | var config = { 5 | entry: { 6 | main: './main' 7 | }, 8 | output: { 9 | path: path.join(__dirname, './dist'), 10 | publicPath: '/dist/', 11 | filename: 'main.js' 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.vue$/, 17 | loader: 'vue-loader', 18 | options: { 19 | loaders: { 20 | css: ExtractTextPlugin.extract({ 21 | use: 'css-loader', 22 | fallback: 'vue-style-loader' 23 | }) 24 | } 25 | } 26 | }, 27 | { 28 | test: /\.js$/, 29 | loader: 'babel-loader', 30 | exclude: /node_modules/ 31 | }, 32 | { 33 | test: /\.css$/, 34 | use: ExtractTextPlugin.extract({ 35 | use: 'css-loader', 36 | fallback: 'style-loader' 37 | }) 38 | }, 39 | { 40 | test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/, 41 | loader: 'url-loader?limit=1024' 42 | } 43 | ] 44 | }, 45 | plugins: [ 46 | new ExtractTextPlugin("main.css") 47 | ] 48 | }; 49 | 50 | module.exports = config; -------------------------------------------------------------------------------- /demo/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | var merge = require('webpack-merge'); 5 | var webpackBaseConfig = require('./webpack.config.js'); 6 | 7 | // 清空基本配置的插件列表 8 | webpackBaseConfig.plugins = []; 9 | 10 | module.exports = merge(webpackBaseConfig, { 11 | output: { 12 | publicPath: '/dist/', 13 | // 将入口文件重命名为带有 20 位 hash 值的唯一文件 14 | filename: '[name].[hash].js' 15 | }, 16 | plugins: [ 17 | new ExtractTextPlugin({ 18 | // 提取 css,并重命名为带有 20 位 hash 值的唯一文件 19 | filename: '[name].[hash].css', 20 | allChunks: true 21 | }), 22 | // 定义当前 node 环境为生产环境 23 | new webpack.DefinePlugin({ 24 | 'process.env': { 25 | NODE_ENV: '"production"' 26 | } 27 | }), 28 | // 压缩 js 29 | new webpack.optimize.UglifyJsPlugin({ 30 | compress: { 31 | warnings: false 32 | } 33 | }), 34 | // 提取模板,并保存入口 html 文件 35 | new HtmlWebpackPlugin({ 36 | filename: '../index_prod.html', 37 | template: './index.ejs', 38 | inject: false 39 | }) 40 | ] 41 | }); -------------------------------------------------------------------------------- /router/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": ["transform-runtime"], 4 | "comments": false 5 | } 6 | -------------------------------------------------------------------------------- /router/app.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /router/button.vue: -------------------------------------------------------------------------------- 1 | 6 | 28 | -------------------------------------------------------------------------------- /router/images/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icarusion/vue-book/cd7e9a5c2c86bd7129c71768cc22f9cfdd4b8d5b/router/images/image.png -------------------------------------------------------------------------------- /router/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Webpack App 6 | 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /router/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Webpack App 6 | 7 | 8 | 9 |
10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /router/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter from 'vue-router'; 3 | import App from './app.vue'; 4 | 5 | Vue.use(VueRouter); 6 | 7 | // 路由配置 8 | const Routers = [ 9 | { 10 | path: '/index', 11 | meta: { 12 | title: '首页' 13 | }, 14 | component: (resolve) => require(['./views/index.vue'], resolve) 15 | }, 16 | { 17 | path: '/about', 18 | meta: { 19 | title: '关于' 20 | }, 21 | component: (resolve) => require(['./views/about.vue'], resolve) 22 | }, 23 | { 24 | path: '/user/:id', 25 | meta: { 26 | title: '个人主页' 27 | }, 28 | component: (resolve) => require(['./views/user.vue'], resolve) 29 | }, 30 | { 31 | path: '*', 32 | redirect: '/index' 33 | } 34 | ]; 35 | const RouterConfig = { 36 | // 使用 HTML5 的 History 路由模式 37 | mode: 'history', 38 | routes: Routers 39 | }; 40 | const router = new VueRouter(RouterConfig); 41 | 42 | router.beforeEach((to, from, next) => { 43 | window.document.title = to.meta.title; 44 | next(); 45 | }); 46 | 47 | router.afterEach((to, from, next) => { 48 | window.scrollTo(0, 0); 49 | }); 50 | 51 | new Vue({ 52 | el: '#app', 53 | router: router, 54 | render: h => { 55 | return h(App) 56 | } 57 | }); -------------------------------------------------------------------------------- /router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "webpack-dev-server --open --history-api-fallback --config webpack.config.js", 9 | "build": "webpack --progress --hide-modules --config webpack.prod.config.js" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "babel": "^6.23.0", 15 | "babel-core": "^6.24.0", 16 | "babel-loader": "^6.4.1", 17 | "babel-plugin-transform-runtime": "^6.23.0", 18 | "babel-preset-es2015": "^6.24.0", 19 | "babel-runtime": "^6.23.0", 20 | "css-loader": "^0.27.3", 21 | "extract-text-webpack-plugin": "^2.1.0", 22 | "file-loader": "^0.10.1", 23 | "html-webpack-plugin": "^2.28.0", 24 | "style-loader": "^0.16.1", 25 | "url-loader": "^0.5.8", 26 | "vue-hot-reload-api": "^2.0.11", 27 | "vue-loader": "^11.3.4", 28 | "vue-style-loader": "^2.0.5", 29 | "vue-template-compiler": "^2.2.6", 30 | "webpack": "^2.3.2", 31 | "webpack-dev-server": "^2.4.2", 32 | "webpack-merge": "^4.1.0" 33 | }, 34 | "dependencies": { 35 | "vue": "^2.2.6", 36 | "vue-router": "^2.3.1" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /router/style.css: -------------------------------------------------------------------------------- 1 | /* style.css */ 2 | #app{ 3 | font-size: 24px; 4 | color: #f50; 5 | } -------------------------------------------------------------------------------- /router/title.vue: -------------------------------------------------------------------------------- 1 | 6 | 15 | -------------------------------------------------------------------------------- /router/views/about.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /router/views/index.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /router/views/user.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /router/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 3 | 4 | var config = { 5 | entry: { 6 | main: './main' 7 | }, 8 | output: { 9 | path: path.join(__dirname, './dist'), 10 | publicPath: '/dist/', 11 | filename: 'main.js' 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.vue$/, 17 | loader: 'vue-loader', 18 | options: { 19 | loaders: { 20 | css: ExtractTextPlugin.extract({ 21 | use: 'css-loader', 22 | fallback: 'vue-style-loader' 23 | }) 24 | } 25 | } 26 | }, 27 | { 28 | test: /\.js$/, 29 | loader: 'babel-loader', 30 | exclude: /node_modules/ 31 | }, 32 | { 33 | test: /\.css$/, 34 | use: ExtractTextPlugin.extract({ 35 | use: 'css-loader', 36 | fallback: 'style-loader' 37 | }) 38 | }, 39 | { 40 | test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/, 41 | loader: 'url-loader?limit=1024' 42 | } 43 | ] 44 | }, 45 | plugins: [ 46 | new ExtractTextPlugin({ 47 | filename: '[name].css', 48 | allChunks: true 49 | }) 50 | ] 51 | }; 52 | 53 | module.exports = config; -------------------------------------------------------------------------------- /router/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | var merge = require('webpack-merge'); 5 | var webpackBaseConfig = require('./webpack.config.js'); 6 | 7 | // 清空基本配置的插件列表 8 | webpackBaseConfig.plugins = []; 9 | 10 | module.exports = merge(webpackBaseConfig, { 11 | output: { 12 | publicPath: '/dist/', 13 | // 将入口文件重命名为带有 20 位 hash 值的唯一文件 14 | filename: '[name].[hash].js' 15 | }, 16 | plugins: [ 17 | new ExtractTextPlugin({ 18 | // 提取 css,并重命名为带有 20 位 hash 值的唯一文件 19 | filename: '[name].[hash].css', 20 | allChunks: true 21 | }), 22 | // 定义当前 node 环境为生产环境 23 | new webpack.DefinePlugin({ 24 | 'process.env': { 25 | NODE_ENV: '"production"' 26 | } 27 | }), 28 | // 压缩 js 29 | new webpack.optimize.UglifyJsPlugin({ 30 | compress: { 31 | warnings: false 32 | } 33 | }), 34 | // 提取模板,并保存入口 html 文件 35 | new HtmlWebpackPlugin({ 36 | filename: '../index_prod.html', 37 | template: './index.ejs', 38 | inject: false 39 | }) 40 | ] 41 | }); -------------------------------------------------------------------------------- /shopping/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": ["transform-runtime"], 4 | "comments": false 5 | } 6 | -------------------------------------------------------------------------------- /shopping/app.vue: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /shopping/components/product.vue: -------------------------------------------------------------------------------- 1 | 12 | 34 | -------------------------------------------------------------------------------- /shopping/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 电商网站示例 6 | 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /shopping/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Webpack App 6 | 7 | 8 | 9 |
10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /shopping/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter from 'vue-router'; 3 | import Routers from './router'; 4 | import Vuex from 'vuex'; 5 | import App from './app.vue'; 6 | import './style.css'; 7 | 8 | import product_data from './product'; 9 | 10 | Vue.use(VueRouter); 11 | Vue.use(Vuex); 12 | 13 | // 路由配置 14 | const RouterConfig = { 15 | // 使用 HTML5 的 History 路由模式 16 | mode: 'history', 17 | routes: Routers 18 | }; 19 | const router = new VueRouter(RouterConfig); 20 | 21 | router.beforeEach((to, from, next) => { 22 | window.document.title = to.meta.title; 23 | next(); 24 | }); 25 | 26 | router.afterEach((to, from, next) => { 27 | window.scrollTo(0, 0); 28 | }); 29 | 30 | // 数组排重 31 | function getFilterArray (array) { 32 | const res = []; 33 | const json = {}; 34 | for (let i = 0; i < array.length; i++){ 35 | const _self = array[i]; 36 | if(!json[_self]){ 37 | res.push(_self); 38 | json[_self] = 1; 39 | } 40 | } 41 | return res; 42 | } 43 | 44 | const store = new Vuex.Store({ 45 | state: { 46 | productList: [], 47 | cartList: [] 48 | }, 49 | getters: { 50 | brands: state => { 51 | const brands = state.productList.map(item => item.brand); 52 | return getFilterArray(brands); 53 | }, 54 | colors: state => { 55 | const colors = state.productList.map(item => item.color); 56 | return getFilterArray(colors); 57 | } 58 | }, 59 | mutations: { 60 | // 添加商品列表 61 | setProductList (state, data) { 62 | state.productList = data; 63 | }, 64 | // 添加到购物车 65 | addCart (state, id) { 66 | // 先判断购物车是否已有,如果有,数量+1 67 | const isAdded = state.cartList.find(item => item.id === id); 68 | if (isAdded) { 69 | isAdded.count ++; 70 | } else { 71 | state.cartList.push({ 72 | id: id, 73 | count: 1 74 | }) 75 | } 76 | }, 77 | // 修改商品数量 78 | editCartCount (state, payload) { 79 | const product = state.cartList.find(item => item.id === payload.id); 80 | product.count += payload.count; 81 | }, 82 | // 删除商品 83 | deleteCart (state, id) { 84 | const index = state.cartList.findIndex(item => item.id === id); 85 | state.cartList.splice(index, 1); 86 | }, 87 | // 清空购物车 88 | emptyCart (state) { 89 | state.cartList = []; 90 | } 91 | }, 92 | actions: { 93 | // 请求商品列表 94 | getProductList (context) { 95 | // 真实环境通过 ajax 获取,这里用异步模拟 96 | setTimeout(() => { 97 | context.commit('setProductList', product_data); 98 | }, 500); 99 | }, 100 | // 购买 101 | buy (context) { 102 | // 真实环境应通过 ajax 提交购买请求后再清空购物列表 103 | return new Promise(resolve=> { 104 | setTimeout(() => { 105 | context.commit('emptyCart'); 106 | resolve(); 107 | }, 500) 108 | }); 109 | } 110 | } 111 | }); 112 | 113 | new Vue({ 114 | el: '#app', 115 | router: router, 116 | store: store, 117 | render: h => { 118 | return h(App) 119 | } 120 | }); -------------------------------------------------------------------------------- /shopping/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "webpack-dev-server --open --history-api-fallback --config webpack.config.js", 9 | "build": "webpack --progress --hide-modules --config webpack.prod.config.js" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "babel": "^6.23.0", 15 | "babel-core": "^6.24.0", 16 | "babel-loader": "^6.4.1", 17 | "babel-plugin-transform-runtime": "^6.23.0", 18 | "babel-preset-es2015": "^6.24.0", 19 | "babel-runtime": "^6.23.0", 20 | "css-loader": "^0.27.3", 21 | "extract-text-webpack-plugin": "^2.1.0", 22 | "file-loader": "^0.10.1", 23 | "html-webpack-plugin": "^2.28.0", 24 | "style-loader": "^0.16.1", 25 | "url-loader": "^0.5.8", 26 | "vue-hot-reload-api": "^2.0.11", 27 | "vue-loader": "^11.3.4", 28 | "vue-style-loader": "^2.0.5", 29 | "vue-template-compiler": "^2.2.6", 30 | "webpack": "^2.3.2", 31 | "webpack-dev-server": "^2.4.2", 32 | "webpack-merge": "^4.1.0" 33 | }, 34 | "dependencies": { 35 | "vue": "^2.2.6", 36 | "vue-router": "^2.3.1", 37 | "vuex": "^2.2.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /shopping/product.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | id: 1, 4 | name: 'AirPods', 5 | brand: 'Apple', 6 | image: 'http://ordfm6aah.bkt.clouddn.com/shop/1.jpeg', 7 | sales: 10000, 8 | cost: 1288, 9 | color: '白色' 10 | }, 11 | { 12 | id: 2, 13 | name: 'BeatsX 入耳式耳机', 14 | brand: 'Beats', 15 | image: 'http://ordfm6aah.bkt.clouddn.com/shop/2.jpeg', 16 | sales: 11000, 17 | cost: 1188, 18 | color: '白色' 19 | }, 20 | { 21 | id: 3, 22 | name: 'Beats Solo3 Wireless 头戴式式耳机', 23 | brand: 'Beats', 24 | image: 'http://ordfm6aah.bkt.clouddn.com/shop/3.jpeg', 25 | sales: 5000, 26 | cost: 2288, 27 | color: '金色' 28 | }, 29 | { 30 | id: 4, 31 | name: 'Beats Pill+ 便携式扬声器', 32 | brand: 'Beats', 33 | image: 'http://ordfm6aah.bkt.clouddn.com/shop/4.jpeg', 34 | sales: 3000, 35 | cost: 1888, 36 | color: '红色' 37 | }, 38 | { 39 | id: 5, 40 | name: 'Sonos PLAY:1 无线扬声器', 41 | brand: 'Sonos', 42 | image: 'http://ordfm6aah.bkt.clouddn.com/shop/5.jpeg', 43 | sales: 8000, 44 | cost: 1578, 45 | color: '白色' 46 | }, 47 | { 48 | id: 6, 49 | name: 'Powerbeats3 by Dr. Dre Wireless 入耳式耳机', 50 | brand: 'Beats', 51 | image: 'http://ordfm6aah.bkt.clouddn.com/shop/6.jpeg', 52 | sales: 12000, 53 | cost: 1488, 54 | color: '金色' 55 | }, 56 | { 57 | id: 7, 58 | name: 'Beats EP 头戴式耳机', 59 | brand: 'Beats', 60 | image: 'http://ordfm6aah.bkt.clouddn.com/shop/7.jpeg', 61 | sales: 25000, 62 | cost: 788, 63 | color: '蓝色' 64 | }, 65 | { 66 | id: 8, 67 | name: 'B&O PLAY BeoPlay A1 便携式蓝牙扬声器', 68 | brand: 'B&O', 69 | image: 'http://ordfm6aah.bkt.clouddn.com/shop/8.jpeg', 70 | sales: 15000, 71 | cost: 1898, 72 | color: '金色' 73 | }, 74 | { 75 | id: 9, 76 | name: 'Bose® QuietComfort® 35 无线耳机', 77 | brand: 'Bose', 78 | image: 'http://ordfm6aah.bkt.clouddn.com/shop/9.jpeg', 79 | sales: 14000, 80 | cost: 2878, 81 | color: '蓝色' 82 | }, 83 | { 84 | id: 10, 85 | name: 'B&O PLAY Beoplay H4 无线头戴式耳机', 86 | brand: 'B&O', 87 | image: 'http://ordfm6aah.bkt.clouddn.com/shop/10.jpeg', 88 | sales: 9000, 89 | cost: 2298, 90 | color: '金色' 91 | } 92 | ] -------------------------------------------------------------------------------- /shopping/router.js: -------------------------------------------------------------------------------- 1 | const routers = [ 2 | { 3 | path: '/list', 4 | meta: { 5 | title: '商品列表' 6 | }, 7 | component: (resolve) => require(['./views/list.vue'], resolve) 8 | }, 9 | { 10 | path: '/product/:id', 11 | meta: { 12 | title: '商品详情' 13 | }, 14 | component: (resolve) => require(['./views/product.vue'], resolve) 15 | }, 16 | { 17 | path: '/cart', 18 | meta: { 19 | title: '购物车' 20 | }, 21 | component: (resolve) => require(['./views/cart.vue'], resolve) 22 | }, 23 | { 24 | path: '*', 25 | redirect: '/list' 26 | } 27 | ]; 28 | export default routers; -------------------------------------------------------------------------------- /shopping/style.css: -------------------------------------------------------------------------------- 1 | *{ 2 | margin: 0; 3 | padding: 0; 4 | } 5 | a{ 6 | text-decoration: none; 7 | } 8 | body{ 9 | background: #f8f8f9; 10 | } 11 | .header{ 12 | height: 48px; 13 | line-height: 48px; 14 | background: rgba(0,0,0,.8); 15 | color: #fff; 16 | } 17 | .header-title{ 18 | padding: 0 32px; 19 | float: left; 20 | color: #fff; 21 | } 22 | .header-menu{ 23 | float: right; 24 | margin-right: 32px; 25 | } 26 | .header-menu-cart{ 27 | color: #fff; 28 | } 29 | .header-menu-cart span{ 30 | display: inline-block; 31 | width: 16px; 32 | height: 16px; 33 | line-height: 16px; 34 | text-align: center; 35 | border-radius: 50%; 36 | background: #ff5500; 37 | color: #fff; 38 | font-size: 12px; 39 | } -------------------------------------------------------------------------------- /shopping/views/cart.vue: -------------------------------------------------------------------------------- 1 | 54 | 120 | -------------------------------------------------------------------------------- /shopping/views/list.vue: -------------------------------------------------------------------------------- 1 | 47 | 125 | -------------------------------------------------------------------------------- /shopping/views/product.vue: -------------------------------------------------------------------------------- 1 | 19 | 46 | -------------------------------------------------------------------------------- /shopping/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 3 | 4 | var config = { 5 | entry: { 6 | main: './main' 7 | }, 8 | output: { 9 | path: path.join(__dirname, './dist'), 10 | publicPath: '/dist/', 11 | filename: 'main.js' 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.vue$/, 17 | loader: 'vue-loader', 18 | options: { 19 | loaders: { 20 | css: ExtractTextPlugin.extract({ 21 | use: 'css-loader', 22 | fallback: 'vue-style-loader' 23 | }) 24 | } 25 | } 26 | }, 27 | { 28 | test: /\.js$/, 29 | loader: 'babel-loader', 30 | exclude: /node_modules/ 31 | }, 32 | { 33 | test: /\.css$/, 34 | use: ExtractTextPlugin.extract({ 35 | use: 'css-loader', 36 | fallback: 'style-loader' 37 | }) 38 | }, 39 | { 40 | test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/, 41 | loader: 'url-loader?limit=1024' 42 | } 43 | ] 44 | }, 45 | plugins: [ 46 | new ExtractTextPlugin({ 47 | filename: '[name].css', 48 | allChunks: true 49 | }) 50 | ] 51 | }; 52 | 53 | module.exports = config; -------------------------------------------------------------------------------- /shopping/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | var merge = require('webpack-merge'); 5 | var webpackBaseConfig = require('./webpack.config.js'); 6 | 7 | // 清空基本配置的插件列表 8 | webpackBaseConfig.plugins = []; 9 | 10 | module.exports = merge(webpackBaseConfig, { 11 | output: { 12 | publicPath: '/dist/', 13 | // 将入口文件重命名为带有 20 位 hash 值的唯一文件 14 | filename: '[name].[hash].js' 15 | }, 16 | plugins: [ 17 | new ExtractTextPlugin({ 18 | // 提取 css,并重命名为带有 20 位 hash 值的唯一文件 19 | filename: '[name].[hash].css', 20 | allChunks: true 21 | }), 22 | // 定义当前 node 环境为生产环境 23 | new webpack.DefinePlugin({ 24 | 'process.env': { 25 | NODE_ENV: '"production"' 26 | } 27 | }), 28 | // 压缩 js 29 | new webpack.optimize.UglifyJsPlugin({ 30 | compress: { 31 | warnings: false 32 | } 33 | }), 34 | // 提取模板,并保存入口 html 文件 35 | new HtmlWebpackPlugin({ 36 | filename: '../index_prod.html', 37 | template: './index.ejs', 38 | inject: false 39 | }) 40 | ] 41 | }); -------------------------------------------------------------------------------- /vue-bus/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": ["transform-runtime"], 4 | "comments": false 5 | } 6 | -------------------------------------------------------------------------------- /vue-bus/app.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /vue-bus/button.vue: -------------------------------------------------------------------------------- 1 | 6 | 28 | -------------------------------------------------------------------------------- /vue-bus/images/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icarusion/vue-book/cd7e9a5c2c86bd7129c71768cc22f9cfdd4b8d5b/vue-bus/images/image.png -------------------------------------------------------------------------------- /vue-bus/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Webpack App 6 | 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /vue-bus/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Webpack App 6 | 7 | 8 | 9 |
10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /vue-bus/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter from 'vue-router'; 3 | import Vuex from 'vuex'; 4 | import VueBus from './vue-bus'; 5 | import App from './app.vue'; 6 | 7 | Vue.use(VueRouter); 8 | Vue.use(Vuex); 9 | Vue.use(VueBus); 10 | 11 | // 路由配置 12 | const Routers = [ 13 | { 14 | path: '/index', 15 | meta: { 16 | title: '首页' 17 | }, 18 | component: (resolve) => require(['./views/index.vue'], resolve) 19 | }, 20 | { 21 | path: '/about', 22 | meta: { 23 | title: '关于' 24 | }, 25 | component: (resolve) => require(['./views/about.vue'], resolve) 26 | }, 27 | { 28 | path: '/user/:id', 29 | meta: { 30 | title: '个人主页' 31 | }, 32 | component: (resolve) => require(['./views/user.vue'], resolve) 33 | }, 34 | { 35 | path: '*', 36 | redirect: '/index' 37 | } 38 | ]; 39 | const RouterConfig = { 40 | // 使用 HTML5 的 History 路由模式 41 | mode: 'history', 42 | routes: Routers 43 | }; 44 | const router = new VueRouter(RouterConfig); 45 | 46 | router.beforeEach((to, from, next) => { 47 | window.document.title = to.meta.title; 48 | next(); 49 | }); 50 | 51 | router.afterEach((to, from, next) => { 52 | window.scrollTo(0, 0); 53 | }); 54 | 55 | const store = new Vuex.Store({ 56 | state: { 57 | count: 0, 58 | list: [1, 5, 8, 10, 30, 50] 59 | }, 60 | getters: { 61 | filteredList: state => { 62 | return state.list.filter(item => item < 10); 63 | }, 64 | listCount: (state, getters) => { 65 | return getters.filteredList.length; 66 | } 67 | }, 68 | mutations: { 69 | increment (state, n = 1) { 70 | state.count += n; 71 | }, 72 | decrease (state) { 73 | state.count --; 74 | } 75 | }, 76 | actions: { 77 | increment (context) { 78 | context.commit('increment'); 79 | }, 80 | asyncIncrement (context) { 81 | return new Promise(resolve => { 82 | setTimeout(() => { 83 | context.commit('increment'); 84 | resolve(); 85 | }, 1000) 86 | }); 87 | } 88 | } 89 | }); 90 | 91 | new Vue({ 92 | el: '#app', 93 | router: router, 94 | store: store, 95 | render: h => { 96 | return h(App) 97 | } 98 | }); -------------------------------------------------------------------------------- /vue-bus/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "webpack-dev-server --open --history-api-fallback --config webpack.config.js", 9 | "build": "webpack --progress --hide-modules --config webpack.prod.config.js" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "babel": "^6.23.0", 15 | "babel-core": "^6.24.0", 16 | "babel-loader": "^6.4.1", 17 | "babel-plugin-transform-runtime": "^6.23.0", 18 | "babel-preset-es2015": "^6.24.0", 19 | "babel-runtime": "^6.23.0", 20 | "css-loader": "^0.27.3", 21 | "extract-text-webpack-plugin": "^2.1.0", 22 | "file-loader": "^0.10.1", 23 | "html-webpack-plugin": "^2.28.0", 24 | "style-loader": "^0.16.1", 25 | "url-loader": "^0.5.8", 26 | "vue-hot-reload-api": "^2.0.11", 27 | "vue-loader": "^11.3.4", 28 | "vue-style-loader": "^2.0.5", 29 | "vue-template-compiler": "^2.2.6", 30 | "webpack": "^2.3.2", 31 | "webpack-dev-server": "^2.4.2", 32 | "webpack-merge": "^4.1.0" 33 | }, 34 | "dependencies": { 35 | "vue": "^2.2.6", 36 | "vue-router": "^2.3.1", 37 | "vuex": "^2.2.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /vue-bus/style.css: -------------------------------------------------------------------------------- 1 | /* style.css */ 2 | #app{ 3 | font-size: 24px; 4 | color: #f50; 5 | } -------------------------------------------------------------------------------- /vue-bus/title.vue: -------------------------------------------------------------------------------- 1 | 6 | 15 | -------------------------------------------------------------------------------- /vue-bus/views/about.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /vue-bus/views/counter.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /vue-bus/views/index.vue: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /vue-bus/views/user.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /vue-bus/vue-bus.js: -------------------------------------------------------------------------------- 1 | const install = function (Vue) { 2 | const Bus = new Vue({ 3 | methods: { 4 | emit (event, ...args) { 5 | this.$emit(event, ...args); 6 | }, 7 | on (event, callback) { 8 | this.$on(event, callback); 9 | }, 10 | off (event, callback) { 11 | this.$off(event, callback); 12 | } 13 | } 14 | }); 15 | Vue.prototype.$bus = Bus; 16 | }; 17 | 18 | export default install; -------------------------------------------------------------------------------- /vue-bus/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 3 | 4 | var config = { 5 | entry: { 6 | main: './main' 7 | }, 8 | output: { 9 | path: path.join(__dirname, './dist'), 10 | publicPath: '/dist/', 11 | filename: 'main.js' 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.vue$/, 17 | loader: 'vue-loader', 18 | options: { 19 | loaders: { 20 | css: ExtractTextPlugin.extract({ 21 | use: 'css-loader', 22 | fallback: 'vue-style-loader' 23 | }) 24 | } 25 | } 26 | }, 27 | { 28 | test: /\.js$/, 29 | loader: 'babel-loader', 30 | exclude: /node_modules/ 31 | }, 32 | { 33 | test: /\.css$/, 34 | use: ExtractTextPlugin.extract({ 35 | use: 'css-loader', 36 | fallback: 'style-loader' 37 | }) 38 | }, 39 | { 40 | test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/, 41 | loader: 'url-loader?limit=1024' 42 | } 43 | ] 44 | }, 45 | plugins: [ 46 | new ExtractTextPlugin({ 47 | filename: '[name].css', 48 | allChunks: true 49 | }) 50 | ] 51 | }; 52 | 53 | module.exports = config; -------------------------------------------------------------------------------- /vue-bus/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | var merge = require('webpack-merge'); 5 | var webpackBaseConfig = require('./webpack.config.js'); 6 | 7 | // 清空基本配置的插件列表 8 | webpackBaseConfig.plugins = []; 9 | 10 | module.exports = merge(webpackBaseConfig, { 11 | output: { 12 | publicPath: '/dist/', 13 | // 将入口文件重命名为带有 20 位 hash 值的唯一文件 14 | filename: '[name].[hash].js' 15 | }, 16 | plugins: [ 17 | new ExtractTextPlugin({ 18 | // 提取 css,并重命名为带有 20 位 hash 值的唯一文件 19 | filename: '[name].[hash].css', 20 | allChunks: true 21 | }), 22 | // 定义当前 node 环境为生产环境 23 | new webpack.DefinePlugin({ 24 | 'process.env': { 25 | NODE_ENV: '"production"' 26 | } 27 | }), 28 | // 压缩 js 29 | new webpack.optimize.UglifyJsPlugin({ 30 | compress: { 31 | warnings: false 32 | } 33 | }), 34 | // 提取模板,并保存入口 html 文件 35 | new HtmlWebpackPlugin({ 36 | filename: '../index_prod.html', 37 | template: './index.ejs', 38 | inject: false 39 | }) 40 | ] 41 | }); -------------------------------------------------------------------------------- /vuex/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": ["transform-runtime"], 4 | "comments": false 5 | } 6 | -------------------------------------------------------------------------------- /vuex/app.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /vuex/button.vue: -------------------------------------------------------------------------------- 1 | 6 | 28 | -------------------------------------------------------------------------------- /vuex/images/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icarusion/vue-book/cd7e9a5c2c86bd7129c71768cc22f9cfdd4b8d5b/vuex/images/image.png -------------------------------------------------------------------------------- /vuex/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Webpack App 6 | 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /vuex/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Webpack App 6 | 7 | 8 | 9 |
10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /vuex/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter from 'vue-router'; 3 | import Vuex from 'vuex'; 4 | import App from './app.vue'; 5 | 6 | Vue.use(VueRouter); 7 | Vue.use(Vuex); 8 | 9 | // 路由配置 10 | const Routers = [ 11 | { 12 | path: '/index', 13 | meta: { 14 | title: '首页' 15 | }, 16 | component: (resolve) => require(['./views/index.vue'], resolve) 17 | }, 18 | { 19 | path: '/about', 20 | meta: { 21 | title: '关于' 22 | }, 23 | component: (resolve) => require(['./views/about.vue'], resolve) 24 | }, 25 | { 26 | path: '/user/:id', 27 | meta: { 28 | title: '个人主页' 29 | }, 30 | component: (resolve) => require(['./views/user.vue'], resolve) 31 | }, 32 | { 33 | path: '*', 34 | redirect: '/index' 35 | } 36 | ]; 37 | const RouterConfig = { 38 | // 使用 HTML5 的 History 路由模式 39 | mode: 'history', 40 | routes: Routers 41 | }; 42 | const router = new VueRouter(RouterConfig); 43 | 44 | router.beforeEach((to, from, next) => { 45 | window.document.title = to.meta.title; 46 | next(); 47 | }); 48 | 49 | router.afterEach((to, from, next) => { 50 | window.scrollTo(0, 0); 51 | }); 52 | 53 | const store = new Vuex.Store({ 54 | state: { 55 | count: 0, 56 | list: [1, 5, 8, 10, 30, 50] 57 | }, 58 | getters: { 59 | filteredList: state => { 60 | return state.list.filter(item => item < 10); 61 | }, 62 | listCount: (state, getters) => { 63 | return getters.filteredList.length; 64 | } 65 | }, 66 | mutations: { 67 | increment (state, n = 1) { 68 | state.count += n; 69 | }, 70 | decrease (state) { 71 | state.count --; 72 | } 73 | }, 74 | actions: { 75 | increment (context) { 76 | context.commit('increment'); 77 | }, 78 | asyncIncrement (context) { 79 | return new Promise(resolve => { 80 | setTimeout(() => { 81 | context.commit('increment'); 82 | resolve(); 83 | }, 1000) 84 | }); 85 | } 86 | } 87 | }); 88 | 89 | new Vue({ 90 | el: '#app', 91 | router: router, 92 | store: store, 93 | render: h => { 94 | return h(App) 95 | } 96 | }); -------------------------------------------------------------------------------- /vuex/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "webpack-dev-server --open --history-api-fallback --config webpack.config.js", 9 | "build": "webpack --progress --hide-modules --config webpack.prod.config.js" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "babel": "^6.23.0", 15 | "babel-core": "^6.24.0", 16 | "babel-loader": "^6.4.1", 17 | "babel-plugin-transform-runtime": "^6.23.0", 18 | "babel-preset-es2015": "^6.24.0", 19 | "babel-runtime": "^6.23.0", 20 | "css-loader": "^0.27.3", 21 | "extract-text-webpack-plugin": "^2.1.0", 22 | "file-loader": "^0.10.1", 23 | "html-webpack-plugin": "^2.28.0", 24 | "style-loader": "^0.16.1", 25 | "url-loader": "^0.5.8", 26 | "vue-hot-reload-api": "^2.0.11", 27 | "vue-loader": "^11.3.4", 28 | "vue-style-loader": "^2.0.5", 29 | "vue-template-compiler": "^2.2.6", 30 | "webpack": "^2.3.2", 31 | "webpack-dev-server": "^2.4.2", 32 | "webpack-merge": "^4.1.0" 33 | }, 34 | "dependencies": { 35 | "vue": "^2.2.6", 36 | "vue-router": "^2.3.1", 37 | "vuex": "^2.2.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /vuex/style.css: -------------------------------------------------------------------------------- 1 | /* style.css */ 2 | #app{ 3 | font-size: 24px; 4 | color: #f50; 5 | } -------------------------------------------------------------------------------- /vuex/title.vue: -------------------------------------------------------------------------------- 1 | 6 | 15 | -------------------------------------------------------------------------------- /vuex/views/about.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /vuex/views/index.vue: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /vuex/views/user.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /vuex/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 3 | 4 | var config = { 5 | entry: { 6 | main: './main' 7 | }, 8 | output: { 9 | path: path.join(__dirname, './dist'), 10 | publicPath: '/dist/', 11 | filename: 'main.js' 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.vue$/, 17 | loader: 'vue-loader', 18 | options: { 19 | loaders: { 20 | css: ExtractTextPlugin.extract({ 21 | use: 'css-loader', 22 | fallback: 'vue-style-loader' 23 | }) 24 | } 25 | } 26 | }, 27 | { 28 | test: /\.js$/, 29 | loader: 'babel-loader', 30 | exclude: /node_modules/ 31 | }, 32 | { 33 | test: /\.css$/, 34 | use: ExtractTextPlugin.extract({ 35 | use: 'css-loader', 36 | fallback: 'style-loader' 37 | }) 38 | }, 39 | { 40 | test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/, 41 | loader: 'url-loader?limit=1024' 42 | } 43 | ] 44 | }, 45 | plugins: [ 46 | new ExtractTextPlugin({ 47 | filename: '[name].css', 48 | allChunks: true 49 | }) 50 | ] 51 | }; 52 | 53 | module.exports = config; -------------------------------------------------------------------------------- /vuex/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | var merge = require('webpack-merge'); 5 | var webpackBaseConfig = require('./webpack.config.js'); 6 | 7 | // 清空基本配置的插件列表 8 | webpackBaseConfig.plugins = []; 9 | 10 | module.exports = merge(webpackBaseConfig, { 11 | output: { 12 | publicPath: '/dist/', 13 | // 将入口文件重命名为带有 20 位 hash 值的唯一文件 14 | filename: '[name].[hash].js' 15 | }, 16 | plugins: [ 17 | new ExtractTextPlugin({ 18 | // 提取 css,并重命名为带有 20 位 hash 值的唯一文件 19 | filename: '[name].[hash].css', 20 | allChunks: true 21 | }), 22 | // 定义当前 node 环境为生产环境 23 | new webpack.DefinePlugin({ 24 | 'process.env': { 25 | NODE_ENV: '"production"' 26 | } 27 | }), 28 | // 压缩 js 29 | new webpack.optimize.UglifyJsPlugin({ 30 | compress: { 31 | warnings: false 32 | } 33 | }), 34 | // 提取模板,并保存入口 html 文件 35 | new HtmlWebpackPlugin({ 36 | filename: '../index_prod.html', 37 | template: './index.ejs', 38 | inject: false 39 | }) 40 | ] 41 | }); --------------------------------------------------------------------------------