├── .babelrc ├── README.md ├── index.ejs ├── index.html ├── package-lock.json ├── package.json ├── src ├── app.vue ├── components │ └── product.vue ├── images │ ├── BeatsX_1.jpg │ ├── BeatsX_1_detail.jpg │ ├── BeatsX_2.jpg │ ├── BeatsX_2_detail.jpg │ ├── BeatsX_3.jpg │ ├── BeatsX_3_detail.jpg │ ├── BeatsX_4.jpg │ ├── BeatsX_4_detail.jpg │ ├── GIF.gif │ ├── PowerBeatsX_detail.jpg │ ├── PowersBeatsX.png │ ├── airPods.jpg │ ├── airPods_detail.jpg │ ├── bo_1.jpg │ ├── bo_1_detail.jpg │ ├── bo_2.jpg │ ├── bo_2_detail.jpg │ ├── bose.jpg │ ├── bose_detail.jpg │ ├── sonos.jpg │ └── sonos_detail.jpg ├── index.js ├── product.js ├── router.js ├── style.css ├── util.js └── views │ ├── cart.vue │ ├── list.vue │ ├── login.vue │ └── product.vue ├── webpack.config.js └── webpack.prod.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets":["env"], 3 | "plugins":["transform-runtime"], 4 | "comments":false 5 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue实现电商网站项目 2 | `vue` + `vue-router` + `vuex`实现电商网站 3 | 4 | ## 效果展示 5 | 6 | ![](https://user-gold-cdn.xitu.io/2019/3/18/16990d6fd5380b63?w=991&h=606&f=gif&s=709516) 7 | 8 | ## install 9 | + 下载代码: `git clone https://github.com/chenchangyuan/shopping.git` 10 | + 安装依赖: `npm install` 11 | + 启动项目: `npm run dev` 12 | > 运行环境: [node v9.11.1](https://nodejs.org/zh-cn/download/ 'Node.js') *npm 5.6.0* 13 | 14 | ## 需求分析 15 | 1. 登录页面、商品列表页(网站首页)、购物车页(实现结算)、商品详情页 16 | 2. 可按颜色、品牌对商品进行筛选,单击选中,再次点击取消 17 | 3. 根据价格进行升序降序、销量降序排列 18 | 4. 商品列表显示图片、名称、销量、颜色、单价 19 | 5. 实时显示购物车数量(商品类别数) 20 | 6. 购物车页面实现商品总价、总数进行结算,优惠券打折 21 | 22 | ## 数据存储 & 数据处理 23 | * `product.js`存放商品数据(生产环境需通过接口调用获取数据) 24 | 25 | ```javascript 26 | { 27 | id: 1, 28 | name: 'AirPods', 29 | brand: 'Apple', 30 | image: '/src/images/airPods.jpg', 31 | imageDetail: '/src/images/airPods_detail.jpg', 32 | sales: 10000, 33 | cost: 1288, 34 | color: '白色' 35 | }, 36 | ``` 37 | 38 | * `window.localStorage`实现数据存储与验证 39 | 40 | ```javascript 41 | let username = window.localStorage.getItem('username'); 42 | let password = window.localStorage.getItem('password'); 43 | if(!util.trim(this.username) || !util.trim(this.username) ){ 44 | window.alert('账号或密码不能为空'); 45 | return; 46 | } 47 | if(username === this.username && password === this.password){ 48 | this.login = false; 49 | window.localStorage.setItem('loginStatus', 'login'); 50 | this.$store.commit('getUser', this.username); 51 | window.alert('登陆成功,确定进入网站首页'); 52 | window.location.href = '/list'; 53 | }else{ 54 | window.alert('账号或密码错误'); 55 | } 56 | ``` 57 | 58 | **数据过滤与排序处理** 59 | ```javascript 60 | filteredAndOrderedList(){ 61 | //拷贝原数组 62 | let list = [...this.list]; 63 | //品牌过滤 64 | if(this.filterBrand !== ''){ 65 | list = list.filter(item => item.brand === this.filterBrand); 66 | } 67 | //颜色过滤 68 | if(this.filterColor !== ''){ 69 | list = list.filter(item => item.color === this.filterColor); 70 | } 71 | //排序 72 | if(this.order !== ''){ 73 | if(this.order === 'sales'){ 74 | list = list.sort((a, b) => b.sales - a.sales); 75 | }else if(this.order === 'cost-desc'){ 76 | list = list.sort((a, b) => b.cost - a.cost); 77 | }else if(this.order === 'cost-asc'){ 78 | list = list.sort((a, b) => a.cost - b.cost); 79 | } 80 | } 81 | return list; 82 | } 83 | ``` 84 | 85 | **实时显示应付总额与商品数** 86 | ```javascript 87 | //购物车商品总数 88 | countAll(){ 89 | let count = 0; 90 | this.cartList.forEach(item => { 91 | count += item.count; 92 | }); 93 | return count; 94 | }, 95 | //购物车商品总价 96 | costAll(){ 97 | let cost = 0; 98 | this.cartList.forEach(item => { 99 | cost += this.productDictList[item.id].cost * item.count; 100 | }); 101 | return cost; 102 | } 103 | ``` 104 | 105 | **购物车结算处理** 106 | ```javascript 107 | //通知Vuex,完成下单 108 | handleOrder(){ 109 | this.$store.dispatch('buy').then(() => { 110 | window.alert('购买成功'); 111 | }) 112 | }, 113 | ``` 114 | 115 | ## vue-router & vuex 116 | **vue-router路由管理**`/src/views/`目录下的`vue`组件进行设置,`router-views`挂载所有路由,登录界面与商品列表页面之间header做隐藏显示处理,登录状态下刷新页面跳转至列表页,其他页面设置默认跳转 117 | 118 | **跳转处理** 119 | 120 | ```javascript 121 | const router = new VueRouter(RouterConfig); 122 | 123 | //跳转前设置title 124 | router.beforeEach((to, from, next) => { 125 | window.document.title = to.meta.title; 126 | next(); 127 | }); 128 | //跳转后设置scroll为原点 129 | router.afterEach((to, from, next) => { 130 | window.scrollTo(0, 0); 131 | }); 132 | ``` 133 | 134 | **routers配置** 135 | 136 | ```javascript 137 | //商品列表路由配置 138 | const routers = [ 139 | { 140 | path: '/list', 141 | meta: { 142 | title: '商品列表' 143 | }, 144 | component: (resolve) => require(['./views/list.vue'], resolve) 145 | }, 146 | { 147 | path: '/product/:id', 148 | meta: { 149 | title: '商品详情' 150 | }, 151 | component: (resolve) => require(['./views/product.vue'], resolve) 152 | }, 153 | { 154 | path: '/cart', 155 | meta: { 156 | title: '购物车' 157 | }, 158 | component: (resolve) => require(['./views/cart.vue'], resolve) 159 | }, 160 | { 161 | path: '/login/:loginStatus', 162 | meta: { 163 | title: '登录注册' 164 | }, 165 | component: (resolve) => require(['./views/login.vue'], resolve) 166 | }, 167 | { 168 | path: '*', 169 | redirect: '/login/login' 170 | } 171 | ]; 172 | export default routers; 173 | ``` 174 | 175 | **vuex**状态管理,各组件共享数据在`state`中设置,`mutation`实现数据同步,`action`异步加载 176 | 177 | ```javascript 178 | //配置Vuex状态管理 179 | const store = new Vuex.Store({ 180 | state: { 181 | //商品列表信息 182 | productList: [], 183 | //购物车数据,数组形式,数据元素为对象(商品id,购买数量count) 184 | cartList: [], 185 | //当前用户账号 186 | username: window.localStorage.getItem('username'), 187 | //登录状态 188 | loginStatus: !!window.localStorage.getItem('loginStatus'), 189 | }, 190 | getters: { 191 | //品牌、颜色筛选 192 | brands: state => { 193 | const brands = state.productList.map(item => item.brand); 194 | return util.getFilterArray(brands); 195 | }, 196 | colors: state => { 197 | const colors = state.productList.map(item => item.color); 198 | return util.getFilterArray(colors); 199 | } 200 | }, 201 | //mutations只能以同步方式 202 | mutations: { 203 | //添加商品列表 204 | setProductList(state, data){ 205 | state.productList = data; 206 | }, 207 | //添加购物车 208 | addCart(state, id){ 209 | const isAdded = state.cartList.find(item => item.id === id); 210 | //如果不存在设置购物车为1,存在count++ 211 | if(isAdded){ 212 | isAdded.count++; 213 | }else{ 214 | state.cartList.push({ 215 | id: id, 216 | count: 1 217 | }) 218 | } 219 | }, 220 | //修改购物车商品数量 221 | editCartCount(state, payload){ 222 | const product = state.cartList.find(item => item.id === payload.id); 223 | product.count += payload.count; 224 | }, 225 | //删除购物车商品 226 | deleteCart(state, id){ 227 | const index = state.cartList.findIndex(item => item.id === id); 228 | state.cartList.splice(index, 1) 229 | }, 230 | //清空购物车 231 | emptyCart(state){ 232 | state.cartList = []; 233 | }, 234 | getUser(state, username){ 235 | console.log('username',username) 236 | state.username = username; 237 | }, 238 | getLoginStatus(state, flag){ 239 | state.loginStatus = flag; 240 | } 241 | }, 242 | actions: { 243 | //异步请求商品列表,暂且使用setTimeout 244 | getProductList(context){ 245 | setTimeout(() => { 246 | context.commit('setProductList', product_data) 247 | }, 500); 248 | }, 249 | //购买 250 | buy(context){ 251 | //生产环境使用ajax请求服务端响应后再清空购物车 252 | return new Promise(resolve => { 253 | setTimeout(() => { 254 | context.commit('emptyCart'); 255 | resolve(); 256 | }, 500); 257 | }); 258 | }, 259 | } 260 | }); 261 | ``` 262 | 263 | ## 后记 264 | 265 | 项目地址: [github](https://github.com/chenchangyuan/shopping) 266 | 如果对你有所帮助,请start 267 | 268 | 笔者个人微信 `gm4118679254` 欢迎加好友,一起沟通交流 269 | 270 | ## 参考资料 271 | [Vue.js实战](https://item.jd.com/12215519.html 'Vue.js实战') 272 | [Vue.js](https://cn.vuejs.org/ 'Vue.js') 273 | -------------------------------------------------------------------------------- /index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | shopping 6 | 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | shopping 6 | 7 | 8 | 9 |
10 | Hello world 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shopping", 3 | "version": "1.0.0", 4 | "description": "shopping", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "webpack-dev-server --mode development --history-api-fallback --open --config webpack.config.js", 9 | "build": "webpack --progress --mode production --hide-modules --config webpack.prod.config.js" 10 | }, 11 | "keywords": [ 12 | "webpack", 13 | "vue.js", 14 | "vuex", 15 | "vue-router", 16 | "javascript", 17 | "html", 18 | "css" 19 | ], 20 | "author": "Jack Chen", 21 | "license": "ISC", 22 | "devDependencies": { 23 | "babel": "^6.23.0", 24 | "babel-cli": "^6.26.0", 25 | "babel-core": "^6.26.3", 26 | "babel-loader": "^7.1.5", 27 | "babel-plugin-transform-runtime": "^6.23.0", 28 | "babel-preset-env": "^1.7.0", 29 | "babel-runtime": "^6.26.0", 30 | "css-loader": "^1.0.0", 31 | "file-loader": "^2.0.0", 32 | "html-webpack-plugin": "^3.2.0", 33 | "mini-css-extract-plugin": "^0.4.2", 34 | "style-loader": "^0.22.1", 35 | "url-loader": "^1.1.1", 36 | "vue-hot-reload-api": "^2.3.0", 37 | "vue-loader": "^15.4.0", 38 | "vue-style-loader": "^4.1.2", 39 | "vue-template-compiler": "^2.5.17", 40 | "webpack": "^4.17.1", 41 | "webpack-cli": "^3.2.3", 42 | "webpack-dev-server": "^3.1.5", 43 | "webpack-merge": "^4.1.4" 44 | }, 45 | "dependencies": { 46 | "vue": "^2.5.17", 47 | "vue-router": "^3.0.1", 48 | "vuex": "^3.0.1" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/app.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 47 | 48 | -------------------------------------------------------------------------------- /src/components/product.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 41 | 42 | -------------------------------------------------------------------------------- /src/images/BeatsX_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenchangyuan/shopping/68220f9d405277bd67aa562e3dba0a8a32fc7fcc/src/images/BeatsX_1.jpg -------------------------------------------------------------------------------- /src/images/BeatsX_1_detail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenchangyuan/shopping/68220f9d405277bd67aa562e3dba0a8a32fc7fcc/src/images/BeatsX_1_detail.jpg -------------------------------------------------------------------------------- /src/images/BeatsX_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenchangyuan/shopping/68220f9d405277bd67aa562e3dba0a8a32fc7fcc/src/images/BeatsX_2.jpg -------------------------------------------------------------------------------- /src/images/BeatsX_2_detail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenchangyuan/shopping/68220f9d405277bd67aa562e3dba0a8a32fc7fcc/src/images/BeatsX_2_detail.jpg -------------------------------------------------------------------------------- /src/images/BeatsX_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenchangyuan/shopping/68220f9d405277bd67aa562e3dba0a8a32fc7fcc/src/images/BeatsX_3.jpg -------------------------------------------------------------------------------- /src/images/BeatsX_3_detail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenchangyuan/shopping/68220f9d405277bd67aa562e3dba0a8a32fc7fcc/src/images/BeatsX_3_detail.jpg -------------------------------------------------------------------------------- /src/images/BeatsX_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenchangyuan/shopping/68220f9d405277bd67aa562e3dba0a8a32fc7fcc/src/images/BeatsX_4.jpg -------------------------------------------------------------------------------- /src/images/BeatsX_4_detail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenchangyuan/shopping/68220f9d405277bd67aa562e3dba0a8a32fc7fcc/src/images/BeatsX_4_detail.jpg -------------------------------------------------------------------------------- /src/images/GIF.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenchangyuan/shopping/68220f9d405277bd67aa562e3dba0a8a32fc7fcc/src/images/GIF.gif -------------------------------------------------------------------------------- /src/images/PowerBeatsX_detail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenchangyuan/shopping/68220f9d405277bd67aa562e3dba0a8a32fc7fcc/src/images/PowerBeatsX_detail.jpg -------------------------------------------------------------------------------- /src/images/PowersBeatsX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenchangyuan/shopping/68220f9d405277bd67aa562e3dba0a8a32fc7fcc/src/images/PowersBeatsX.png -------------------------------------------------------------------------------- /src/images/airPods.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenchangyuan/shopping/68220f9d405277bd67aa562e3dba0a8a32fc7fcc/src/images/airPods.jpg -------------------------------------------------------------------------------- /src/images/airPods_detail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenchangyuan/shopping/68220f9d405277bd67aa562e3dba0a8a32fc7fcc/src/images/airPods_detail.jpg -------------------------------------------------------------------------------- /src/images/bo_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenchangyuan/shopping/68220f9d405277bd67aa562e3dba0a8a32fc7fcc/src/images/bo_1.jpg -------------------------------------------------------------------------------- /src/images/bo_1_detail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenchangyuan/shopping/68220f9d405277bd67aa562e3dba0a8a32fc7fcc/src/images/bo_1_detail.jpg -------------------------------------------------------------------------------- /src/images/bo_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenchangyuan/shopping/68220f9d405277bd67aa562e3dba0a8a32fc7fcc/src/images/bo_2.jpg -------------------------------------------------------------------------------- /src/images/bo_2_detail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenchangyuan/shopping/68220f9d405277bd67aa562e3dba0a8a32fc7fcc/src/images/bo_2_detail.jpg -------------------------------------------------------------------------------- /src/images/bose.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenchangyuan/shopping/68220f9d405277bd67aa562e3dba0a8a32fc7fcc/src/images/bose.jpg -------------------------------------------------------------------------------- /src/images/bose_detail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenchangyuan/shopping/68220f9d405277bd67aa562e3dba0a8a32fc7fcc/src/images/bose_detail.jpg -------------------------------------------------------------------------------- /src/images/sonos.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenchangyuan/shopping/68220f9d405277bd67aa562e3dba0a8a32fc7fcc/src/images/sonos.jpg -------------------------------------------------------------------------------- /src/images/sonos_detail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chenchangyuan/shopping/68220f9d405277bd67aa562e3dba0a8a32fc7fcc/src/images/sonos_detail.jpg -------------------------------------------------------------------------------- /src/index.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 | import product_data from './product'; 8 | import util from './util'; 9 | Vue.use(VueRouter); 10 | Vue.use(Vuex); 11 | 12 | //路由配置 13 | const RouterConfig = { 14 | //使用H5 history模式 15 | mode: 'history', 16 | routes: Routers 17 | }; 18 | 19 | const router = new VueRouter(RouterConfig); 20 | 21 | //跳转前设置title 22 | router.beforeEach((to, from, next) => { 23 | window.document.title = to.meta.title; 24 | next(); 25 | }); 26 | //跳转后设置scroll为原点 27 | router.afterEach((to, from, next) => { 28 | window.scrollTo(0, 0); 29 | }); 30 | 31 | //配置Vuex状态管理 32 | const store = new Vuex.Store({ 33 | state: { 34 | //商品列表信息 35 | productList: [], 36 | //购物车数据,数组形式,数据元素为对象(商品id,购买数量count) 37 | cartList: [], 38 | //当前用户账号 39 | username: window.localStorage.getItem('username'), 40 | //登录状态 41 | loginStatus: !!window.localStorage.getItem('loginStatus'), 42 | }, 43 | getters: { 44 | //品牌、颜色筛选 45 | brands: state => { 46 | const brands = state.productList.map(item => item.brand); 47 | return util.getFilterArray(brands); 48 | }, 49 | colors: state => { 50 | const colors = state.productList.map(item => item.color); 51 | return util.getFilterArray(colors); 52 | } 53 | }, 54 | //mutations只能以同步方式 55 | mutations: { 56 | //添加商品列表 57 | setProductList(state, data){ 58 | state.productList = data; 59 | }, 60 | //添加购物车 61 | addCart(state, id){ 62 | const isAdded = state.cartList.find(item => item.id === id); 63 | //如果不存在设置购物车为1,存在count++ 64 | if(isAdded){ 65 | isAdded.count++; 66 | }else{ 67 | state.cartList.push({ 68 | id: id, 69 | count: 1 70 | }) 71 | } 72 | }, 73 | //修改购物车商品数量 74 | editCartCount(state, payload){ 75 | const product = state.cartList.find(item => item.id === payload.id); 76 | product.count += payload.count; 77 | }, 78 | //删除购物车商品 79 | deleteCart(state, id){ 80 | const index = state.cartList.findIndex(item => item.id === id); 81 | state.cartList.splice(index, 1) 82 | }, 83 | //清空购物车 84 | emptyCart(state){ 85 | state.cartList = []; 86 | }, 87 | getUser(state, username){ 88 | console.log('username',username) 89 | state.username = username; 90 | }, 91 | getLoginStatus(state, flag){ 92 | state.loginStatus = flag; 93 | } 94 | }, 95 | actions: { 96 | //异步请求商品列表,暂且使用setTimeout 97 | getProductList(context){ 98 | setTimeout(() => { 99 | context.commit('setProductList', product_data) 100 | }, 500); 101 | }, 102 | //购买 103 | buy(context){ 104 | //生产环境使用ajax请求服务端响应后再清空购物车 105 | return new Promise(resolve => { 106 | setTimeout(() => { 107 | context.commit('emptyCart'); 108 | resolve(); 109 | }, 500); 110 | }); 111 | }, 112 | } 113 | }); 114 | const app = new Vue({ 115 | el: '#app', 116 | router, 117 | store, 118 | render: h => { 119 | return h(App) 120 | } 121 | }) 122 | 123 | 124 | -------------------------------------------------------------------------------- /src/product.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | id: 1, 4 | name: 'AirPods', 5 | brand: 'Apple', 6 | image: '/src/images/airPods.jpg', 7 | imageDetail: '/src/images/airPods_detail.jpg', 8 | sales: 10000, 9 | cost: 1288, 10 | color: '白色' 11 | }, 12 | { 13 | id: 2, 14 | name: 'BeatsX 入耳式耳机', 15 | brand: 'Beats', 16 | image: '/src/images/BeatsX_1.jpg', 17 | imageDetail: '/src/images/BeatsX_1_detail.jpg', 18 | sales: 11000, 19 | cost: 1188, 20 | color: '白色' 21 | }, 22 | { 23 | id: 3, 24 | name: 'Beats Solo3 Wireless 头戴式式耳机', 25 | brand: 'Beats', 26 | image: '/src/images/BeatsX_2.jpg', 27 | imageDetail: '/src/images/BeatsX_2_detail.jpg', 28 | sales: 5000, 29 | cost: 2288, 30 | color: '金色' 31 | }, 32 | { 33 | id: 4, 34 | name: 'Beats Pill+ 便携式扬声器', 35 | brand: 'Beats', 36 | image: '/src/images/BeatsX_3.jpg', 37 | imageDetail: '/src/images/BeatsX_3_detail.jpg', 38 | sales: 3000, 39 | cost: 1888, 40 | color: '红色' 41 | }, 42 | { 43 | id: 5, 44 | name: 'Sonos PLAY:1 无线扬声器', 45 | brand: 'Sonos', 46 | image: '/src/images/sonos.jpg', 47 | imageDetail: '/src/images/sonos_detail.jpg', 48 | sales: 8000, 49 | cost: 1578, 50 | color: '白色' 51 | }, 52 | { 53 | id: 6, 54 | name: 'Powerbeats3 by Dr. Dre Wireless 入耳式耳机', 55 | brand: 'Beats', 56 | image: '/src/images/PowersBeatsX.png', 57 | imageDetail: '/src/images/PowerBeatsX_detail.jpg', 58 | sales: 12000, 59 | cost: 1488, 60 | color: '金色' 61 | }, 62 | { 63 | id: 7, 64 | name: 'Beats EP 头戴式耳机', 65 | brand: 'Beats', 66 | image: '/src/images/BeatsX_4.jpg', 67 | imageDetail: '/src/images/BeatsX_4_detail.jpg', 68 | sales: 25000, 69 | cost: 788, 70 | color: '蓝色' 71 | }, 72 | { 73 | id: 8, 74 | name: 'B&O PLAY BeoPlay A1 便携式蓝牙扬声器', 75 | brand: 'B&O', 76 | image: '/src/images/bo_1.jpg', 77 | imageDetail: '/src/images/bo_1_detail.jpg', 78 | sales: 15000, 79 | cost: 1898, 80 | color: '金色' 81 | }, 82 | { 83 | id: 9, 84 | name: 'Bose® QuietComfort® 35 无线耳机', 85 | brand: 'Bose', 86 | image: '/src/images/bose.jpg', 87 | imageDetail: '/src/images/bose_detail.jpg', 88 | sales: 14000, 89 | cost: 2878, 90 | color: '蓝色' 91 | }, 92 | { 93 | id: 10, 94 | name: 'B&O PLAY Beoplay H4 无线头戴式耳机', 95 | brand: 'B&O', 96 | image: '/src/images/bo_2.jpg', 97 | imageDetail: '/src/images/bo_2_detail.jpg', 98 | sales: 9000, 99 | cost: 2298, 100 | color: '金色' 101 | } 102 | ] -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | //商品列表路由配置 2 | const routers = [ 3 | { 4 | path: '/list', 5 | meta: { 6 | title: '商品列表' 7 | }, 8 | component: (resolve) => require(['./views/list.vue'], resolve) 9 | }, 10 | { 11 | path: '/product/:id', 12 | meta: { 13 | title: '商品详情' 14 | }, 15 | component: (resolve) => require(['./views/product.vue'], resolve) 16 | }, 17 | { 18 | path: '/cart', 19 | meta: { 20 | title: '购物车' 21 | }, 22 | component: (resolve) => require(['./views/cart.vue'], resolve) 23 | }, 24 | { 25 | path: '/login/:loginStatus', 26 | meta: { 27 | title: '登录注册' 28 | }, 29 | component: (resolve) => require(['./views/login.vue'], resolve) 30 | }, 31 | { 32 | path: '*', 33 | redirect: '/login/login' 34 | } 35 | ]; 36 | export default routers; 37 | -------------------------------------------------------------------------------- /src/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 | } -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | export default { 2 | //去除前后空格 3 | trim(str){ 4 | return str.replace(/^\s*(.*?)\s*$/g, '$1'); 5 | }, 6 | //数组去重 7 | getFilterArray(array){ 8 | const set = new Set(array); 9 | return [...set]; 10 | } 11 | } -------------------------------------------------------------------------------- /src/views/cart.vue: -------------------------------------------------------------------------------- 1 | 64 | 65 | 141 | 142 | -------------------------------------------------------------------------------- /src/views/list.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 139 | 140 | -------------------------------------------------------------------------------- /src/views/login.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 98 | 99 | -------------------------------------------------------------------------------- /src/views/product.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 49 | 50 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 3 | const { VueLoaderPlugin } = require('vue-loader'); 4 | 5 | const config = { 6 | //配置的entry单入口webpack4默认 ./src/index.js 7 | // webpack打包输出配置 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:[ 21 | 'vue-style-loader', 22 | 'mini-css-extract-plugin', 23 | 'css-loader' 24 | ] 25 | } 26 | } 27 | }, 28 | { 29 | test: /\.css$/, 30 | //数组形式的话,编译是从后往前。 31 | use:[ 32 | MiniCssExtractPlugin.loader, 33 | 'css-loader' 34 | ] 35 | }, 36 | { 37 | test:/\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/, 38 | //文件小于1k就以base64形式加载 39 | loader:'url-loader?limit=1024' 40 | } 41 | ] 42 | }, 43 | plugins:[ 44 | new MiniCssExtractPlugin('main.css'), 45 | new VueLoaderPlugin() 46 | ] 47 | }; 48 | module.exports = config; -------------------------------------------------------------------------------- /webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | const { VueLoaderPlugin } = require('vue-loader'); 5 | const merge = require('webpack-merge'); 6 | const webpackBaseConfig = require('./webpack.config.js'); 7 | 8 | webpackBaseConfig.plugins = []; 9 | 10 | module.exports = merge(webpackBaseConfig,{ 11 | output:{ 12 | publicPath:'/dist/', 13 | filename:'[name].[hash].js' 14 | }, 15 | plugins:[ 16 | new MiniCssExtractPlugin({ 17 | filename:'[name].[hash].css' 18 | }), 19 | new webpack.DefinePlugin({ 20 | 'process.env':{ 21 | NODE_ENV:'"production"' 22 | } 23 | }), 24 | new HtmlWebpackPlugin({ 25 | filename:'./index_prod.html', 26 | template:'./index.ejs', 27 | inject:false 28 | }), 29 | new VueLoaderPlugin() 30 | ] 31 | }); --------------------------------------------------------------------------------