├── .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 | 
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 |
2 |
3 |
4 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
47 |
48 |
--------------------------------------------------------------------------------
/src/components/product.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 | {{info.name}}
8 | 销量{{info.sales}}
9 |
11 | ¥ {{info.cost}}
12 |
13 | 加入购物车
15 |
16 |
17 |
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 |
2 |
3 |
13 |
14 |
15 |
16 |
17 |
![]()
18 |
{{productDictList[item.id].name}}
19 |
20 |
21 | ¥ {{productDictList[item.id].cost}}
22 |
23 |
24 | -
26 | {{item.count}}
27 | +
29 |
30 |
31 | ¥ {{productDictList[item.id].cost * item.count}}
32 |
33 |
34 | 删除
36 |
37 |
38 |
购物车为空
39 |
40 |
41 | 使用优惠券
42 |
43 | 验证
45 |
46 |
62 |
63 |
64 |
65 |
141 |
142 |
--------------------------------------------------------------------------------
/src/views/list.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 品牌:
6 | {{item}}
10 |
11 |
12 | 颜色:
13 | {{item}}
17 |
18 |
19 |
20 | 排序:
21 | 默认
24 |
27 | 销量
28 | ↓
29 |
30 |
33 | 价格
34 | ↓
35 | ↑
36 |
37 |
38 |
39 |
40 |
暂无相关商品
42 |
43 |
44 |
45 |
139 |
140 |
--------------------------------------------------------------------------------
/src/views/login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
21 |
22 |
23 |
24 |
25 |
98 |
99 |
--------------------------------------------------------------------------------
/src/views/product.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
![]()
7 |
8 |
9 |
{{product.name}}
10 |
{{product.cost}}
11 |
加入购物车
12 |
13 |
14 |
15 |
产品介绍
16 |
![]()
17 |
18 |
19 |
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 | });
--------------------------------------------------------------------------------