《Vuejs实战》--带单选,全选,总价,分类的购物车功能实现

《vuejs实战》这本书中5.5是一道实战题:利用计算属性、指令等知识开发购物车。

练习1:在当前示例基础上扩展商品列表,新增一项是否选中该商品的功能,总价变为只计算选中商品的总价,同时提供一个全选的按钮。

练习2:将商品列表list改为一个二维数组来实现商品的分类,比如可分为“电子产品” “生活用品” 和“果蔬”,同类商品聚合在一起。提示,你可能会用到两次v-for。

练习一:html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<body>
<div id="app" v-cloak>
<template v-if="list.length">
<table>
<thead>
<tr>
<th></th>
<th>商品名称</th>
<th>商品单价</th>
<th>购买数量</th>
<th>操作</th>
<th> </th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in list">
<td>{{ index + 1}}</td>
<td>{{ item.name }}</td>
<td>{{ item.price }}</td>
<td>
<button @click="handleReduce(index)" :disable="item.count === 1">-</button>
{{ item.count }}
<button @click="handleAdd(index)">+</button>
</td>
<td>
<button @click="handleRemove(index)">移除</button>
</td>
<td>
<input type="checkbox" :value="item.id" v-model="checkBoxModel" @click.stop="pickOne(index),checkPick()">
</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td>全选</td>
<td>
<input type="checkbox" @click="allPick(),checkModel()" v-model="allp">
</td>
</tr>
</tbody>
</table>
<div>{{ checkBoxModel }}</div>
<div>总价:¥{{ totalPrice }}</div>
</template>
<div v-else>购物车为空</div>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script src="index1.js"></script>
</body>

练习一、二:css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[v-cloak] {
display: none;
}
table{
border: 1px solid #e9e9e9;
border-collapse: collapse;
border-spacing: 0;
empty-cells: show;
}
th, td{
border: 1px solid #e9e9e9;
text-align: left;
}
th{
background: #f7f7f7;
color: #5c6b77;
font-weight: 600;
white-space: nowrap;
}

练习一:js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
var app = new Vue({
el: '#app',
data: {
list: [
{
id: 1,
name: 'iphone 7',
price: '6188',
count: 1,
isBuy: false
}, {
id: 2,
name: 'ipad Pro',
price: '5888',
count: 1,
isBuy: false
}, {
id: 3,
name: 'macbook pro',
price: '21488',
count: 1,
isBuy: false
}
],
checkBoxModel: [],
allp: false
},
computed: {
totalPrice: function () {
var total = 0;
for (var i = 0; i < this.list.length; i++) {
if (this.list[i].isBuy) {
var item = this.list[i];
total += item.price * item.count;
}
}
return total.toString().replace(/\B(?=(\d{3})+$)/g, ',');
}
},
methods: {
handleReduce: function (index) {
if (this.list[index].count === 1) return;
this.list[index].count--;
},
handleAdd: function (index) {
this.list[index].count++;
},
handleRemove: function (index) {
this.list.splice(index, 1);
},
allPick: function () {
var _this = this;
if (_this.allp) {
_this.checkBoxModel = [];
_this.allp = false;
} else {
_this.checkBoxModel = [];
_this.list.forEach(function (item) {
_this.checkBoxModel.push(item.id);
});
_this.allp = true;
}
//全选的实现通过checkBoxModel的状态
},
pickOne: function (index) {
if (this.list[index].isBuy) {
this.list[index].isBuy = false;
} else {
this.list[index].isBuy = true;
}
//单选的实现依靠的是isBuy通过click的切换实现
},
checkPick: function () {
_this = this;
var sumPic = 0;
for (var i = 0; i < _this.list.length; i++) {
if (_this.list[i].isBuy) {
sumPic++;
}
}
if (sumPic == _this.list.length) {
_this.allp = true;
} else {
_this.allp = false;
}
},
checkModel: function () {
_this = this;
if (_this.checkBoxModel.length) {
newArr = _this.checkBoxModel.concat();
console.log(newArr);
for (var i = 0; i < _this.checkBoxModel.length; i++) {
newone = newArr.shift().toString();
//console.log(newone);
_this.list[newone - 1].isBuy = true;
//console.log(newone);
// console.log(_this.list[newone-1]);
}
} else {
newArr = _this.checkBoxModel.concat();
//console.log(newArr);
for (var i = 0; i < _this.list.length; i++) {
_this.list[i].isBuy = false;
}
}
// 利用checkBoxModel的绑定的状态来分别给每个物品确认isBuy的状态,避免与pickOne的冲突
}
}
});

讲讲思路,练习一稍简单一点,通过v-model的绑定来选中与否,但是仅仅这样不能找到这么一个状态(isBuy)来计算总价。两个练习的css是一样的。

每个checkbox的单一选项通过isBuy来确定是否计入总价(初始值我写的都是false,即不购买状态),并自动写入checkboxModel状态中,html中我单放出来便于观察。

然后全选则相反通过设置checkboxModel来使单个选项响应对应的状态,而checkPick遍历现在isBuy状态为true的物品数量与list总数比较,来判断按下当前这个checkbox时是不是已经全部选中了。

checkModel是用来最后的检查,因为就算看起来checkbox全部选中了,但是isBuy的状态并没有变化,因为全选只是通过v-model的绑定来动态响应状态的,跟isBuy没关系,所以你需要在全部按钮上加上核实isBuy状态的函数。

一句话就是不停通过修改isBuy的状态来确定总价。

练习二:html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<body>
<div id="app" v-cloak>
<template v-if="list.length">
<table>
<thead>
<tr>
<th></th>
<th>商品名称</th>
<th>商品单价</th>
<th>购买数量</th>
<th>操作</th>
<th> </th>
</tr>
</thead>
<tbody>
<template v-for="(item1, index) in list">
<tr>
<td>{{ clas(index) }}</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>
</td>
<tr v-for="(initem, inindex) in item1">
<td>{{ initem.id }}</td>
<td>{{ initem.name }}</td>
<td>{{ initem.price }}</td>
<td>
<button @click="handleReduce(index, inindex)" :disable="initem.count === 1">-</button>
{{ initem.count }}
<button @click="handleAdd(index, inindex)">+</button>
</td>
<td>
<button @click="handleRemove(index, inindex)">移除</button>
</td>
<td>
<input type="checkbox" :value="initem.id" v-model="checkBoxModel" @click.stop="pickOne(index, inindex),checkPick()">
</td>
</tr>
</tr>
</tr>
</template>
<td></td>
<td></td>
<td></td>
<td></td>
<td>全选</td>
<td>
<input type="checkbox" @click="allPick(),checkModel()" v-model="allp">
</td>
</tr>
</tbody>
</table>
<div>{{ checkBoxModel }}</div>
<div>总价:¥{{ totalPrice }}</div>
</template>
<div v-else>购物车为空</div>
</div>
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script src="index2.js"></script>
</body>

练习二:js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
var app = new Vue({
el: '#app',
data: {
list: [
[{
id: 1,
name: 'iphone 7',
price: '6188',
count: 1,
isBuy: false,
cls: 'Eletronic Products'
}, {
id: 2,
name: 'ipad Pro',
price: '5888',
count: 1,
isBuy: false
}, {
id: 3,
name: 'macbook pro',
price: '21488',
count: 1,
isBuy: false
}],
[{
id: 4,
name: 'T-shirt',
price: '150',
count: 1,
isBuy: false,
cls: 'Daily Use'
}, {
id: 5,
name: 'cup',
price: '10',
count: 2,
isBuy: false
}, {
id: 6,
name: 'dish',
price: '30',
count: 1,
isBuy: false
}],
[{
id: 7,
name: 'apple',
price: '3',
count: 5,
isBuy: false,
cls: 'Garden Stuff'
}, {
id: 8,
name: 'watermelon',
price: '15',
count: 1,
isBuy: false
}, {
id: 9,
name: 'cabbage',
price: '5',
count: 1,
isBuy: false
}]
],
checkBoxModel: [],
allp: false
},
computed: {
totalPrice: function () {
var total = 0;

for (var i = 0; i < this.list.length; i++) {
for (var j = 0; j < this.list[i].length; j++) {
var itemp = this.list[i][j];
if (itemp.isBuy) {
total += itemp.price * itemp.count;
}
}
}
return total.toString().replace(/\B(?=(\d{3})+$)/g, ',');
}
},
methods: {
handleReduce: function (index, inindex) {
if (this.list[index][inindex].count === 1) return;
this.list[index][inindex].count--;
},
handleAdd: function (index, inindex) {
this.list[index][inindex].count++;
},
handleRemove: function (index, inindex) {
this.list[index].splice(inindex, 1);
},
allPick: function () {
var _this = this;
if (_this.allp) {
_this.checkBoxModel = [];
_this.allp = false;
} else {
_this.checkBoxModel = [];
for (var i = 0; i < _this.list.length; i++) {
_this.list[i].forEach(function (item) {
_this.checkBoxModel.push(item.id);
});
}
_this.allp = true;
}
},
clsallPick: function () {

},
pickOne: function (index, inindex) {
if (this.list[index][inindex].isBuy) {
this.list[index][inindex].isBuy = false;
} else {
this.list[index][inindex].isBuy = true;
}
},
checkPick: function () {
_this = this;
var sumPic = 0;
var alength = 0;
for (var i = 0; i < _this.list.length; i++) {
for (var j = 0; j < _this.list[i].length; j++) {
if (_this.list[i][j].isBuy) {
sumPic++;
}
}
alength += _this.list[i].length
}
if (sumPic == alength) {
_this.allp = true;
console.log('all pick');
} else {
_this.allp = false;
}
},
clas: function (index) {
for (var j = 0; j < this.list.length; j++) {
if (index === j) {
return this.list[j][0].cls;
}
}
},
checkModel: function () {
_this = this;
if (_this.checkBoxModel.length) {
newArr = _this.checkBoxModel.concat();
//console.log(newone);
for (var i = 0; i < _this.list.length; i++) {
for (var j = 0; j < _this.list[i].length; j++) {
var newone = newArr.shift();
if (_this.list[i][j].id === newone) {
_this.list[i][j].isBuy = true;
}
}
}
} else {
for (var i = 0; i < _this.list.length; i++) {
for (var j = 0; j < _this.list[i].length; j++) {
_this.list[i][j].isBuy = false;
}
}
}
}
},
created() {
_this = this;
_this.checkModel();
console.log(_this.list[1][2].name);
}
});

练习二稍难一些,但功能差不多,首先list变成了二维数组,html的模板就要更换了。然后需要两次循环的索引值,本例中分别为外侧的index和内侧inindex。然后函数的传参数变为了两个,分别对应二维数组就行了。

然后有个clas函数是分别获取每个分类第一个物品的类名,把他单独显示出来,这样分类就有了。这其中有个clsallpick函数没有写,题目没有说每个分类再加一个全选按钮,我就没做,但是其实可以试一试的。

这种分类效果有点像淘宝里的购物车,每个店里挑的几件东西算是一个类,然后界面的底下有个总的全选按钮可以选中所有店面的所有商品。所以,我感觉这种效果还算是有点用的。

想写这个题,一方面正在学习vue,另一方面是百度我都没查找类似能实现相对完整的效果。有些地方可能实现不够好,仅供参考。

如果有什么问题,给我留言,共同探讨提升。

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

给阿姨来一杯卡普基诺~

支付宝
微信