一、前言
环境:VScode+Chrome
技术栈:HTML+CSS+Vue(组件之前的知识运用—Vue基础语法)
将购物车实现分成三部分分别实现:
第一部分:简单购物车功能实现;
第二部分:将商品序号改为勾选框;
第三部分:添加商店对商品进行分类。
二、第一部分
页面效果图:
思路:
分为两部分,上半部分用于添加商品到购物车;下半部分用于展示购物车详情:
1、先把界面的框架(ui效果),大致画出来—这里使用table标签
2、先完成下半部分的逻辑
①循环数组:书籍名单是从服务器传来的,所以用for循环遍历获得;
②格式化价格:实现价格¥xx.00的表示方法(有两种);
③完成 + 和 – 按钮:购买数量的±按钮功能通过点击事件写js实现(注意传入index值知道是哪一行书籍)并且使用按钮的disabled功能使<=1时不能再点(解决会为负数的小bug);
④加移除:移除功能使用splice函数,且用ifelse判断当购物车为空时显示购物车为空;
⑤算总价:书籍总价格通过计算属性实时计算获得,最后返回一个值,通过第二步的过滤器实现最后的样子;
2、完成上半部分逻辑
①v-model绑定每个表单:在点击添加按钮时,要判断每个表单是否为空,有一个表单为空则提示请输入xx内容,然后return false;
②添加至购物车的逻辑:点击添加按钮,商品添加至购物车,并清空此时上半部分的input内容
三、第二部分
页面效果图:
1、把购物车的序列号,改为勾选框,当勾选起来的时候,才会计算总价;默认不勾选;列头有全选勾选框,可以实现全选功能。
2、添加商品的时候,增加判断,如果新增商品ID已经在购物车中,那么这个时候应该是把对应商品的数量增加;否则在购物车中新增商品行。
四、第三部分
页面效果图:
1、每个商品都有对应的商店:在data中用二维数组存放商品数据
(1)“添加要购买商品” 新增2个输入框,商店后台编号和商店名称。
(2)“购物车”按商店先划分,再按商品划分。
(3) 同样需要勾选框。这里的勾选框有3种:
a.全选勾选框,b.某个商店全选勾选框,c.某个商品的勾选框。勾选上的才算总价。默认不勾选。
大概属性定义思路:checkbox的属性是一个布尔属性,用v-model双向绑定一个布尔属性,即可在方法中用来实现各种功能;
这里分成三种复选框,一是isAllChecked(代表全选复选框,决定商店商品所有复选框的状态);二是isStChecked(代表商店复选框,决定某商店下所有商品的复选框状态);三是isChecked(代表商品复选框,决定该商品复选框的状态)
大概功能实现思路:
①先实现全选复选框(商店复选框)让下面所有商品复选框选中和取消选中
思路:让下面所有复选框的checked属性(选中状态) 跟随 全选复选框(商店复选框)即可
②下面商品复选框需要全部选中,全选复选框(商店复选框)才会选中做法:每次点击,都要循环查看下面所有的复选框是否有没选中的,如果有一个没选中的, 上面就不选中。
(4) 同样添加商品的时候要加上判断。分3种情况:
a.新增商品已经在购物车中有了,则该商品数量增加;
b.新增商品不在购物车中,但是其对应的商店在购物车中,则在商店后面新增该商品;
c.新增商品不在购物车中,且对应商店也不在购物车中,新增商店和商品。
(可看问题总结5)
2、页面结构:
页面结构实现使用二维数组来实现商店商品,因为这里使用的是table来做,我的思路是之前的thead不变,对tbody进行更改:
在tbody中再嵌入一个table-外层for循环商店,在table中再分为thead和tbody,tbody的tr再内层for循环商店下的商品即可实现上面效果图。
注意:在table标签中进行v-if判断该商店是否有商品,没有则不显示该商店,另外,这里不显示商品并没有删除存进data中的商店的数据,要在每次对商品进行移除时判断该商店商品是否为空,为空则用splice对该商店数据进行删除。
五、问题总结
这里报错的原因是:
细节问题,Price的input框为text,导致数据类型是字符串,所以报错;将数据类型转换成数字类型的就不会报这个错了;
解决方法:
①在JS中将return ‘¥’ + price.toFixed(2); 改成 return '¥ ’ + parseFloat(val).toFixed(2);
②在html中利用v-model的number修饰符,即v-model.number = “good.price”
2.注意不能直接进行this.goods.push(this.good);.这个方法会把good的地址也push进去,而不是拷贝;要构建一个新对象,再push进去
// (parse必须是json格式,所以先用stringify转换成json字符串类型再用parse转换成对象)
let goodJson = JSON.stringify(this.good);
let addGood = JSON.parse(goodJson);
this.goods.push(addGood);
3.按钮的全选和取消全选
大概属性定义思路:checkbox的属性是一个布尔属性,用v-model双向绑定一个布尔属性,即可在方法中用来实现各种功能;
这里分成三种复选框,一是isAllChecked(代表全选复选框,决定商店商品所有复选框的状态);二是isStChecked(代表商店复选框,决定某商店下所有商品的复选框状态);三是isChecked(代表商品复选框,决定该商品复选框的状态)
大概功能实现思路:
①先实现全选复选框(商店复选框)让下面所有商品复选框选中和取消选中
思路:让下面所有复选框的checked属性(选中状态) 跟随 全选复选框(商店复选框)即可
②下面商品复选框需要全部选中,全选复选框(商店复选框)才会选中做法:每次点击,都要循环查看下面所有的复选框是否有没选中的,如果有一个没选中的, 上面就不选中。
4.在实现当添加的商品编号已经在购物车存在,进行增加购物车商品数量的功能时:
1)我在对购物车商品编号遍历对比的时候用‘ ===’ 等同符时,两边值类型不相等,但是我分别打印后得到的都是number类型,不知道什么原因;所以改为使用等值符,只对值进行比较。
2)/对购物车的商品进行遍历,如果新加入的商品编号已存在就直接进行商品数量增加,否则直接进行push ,有两种实现方法:
①使用flag判断是否进行了数量增加的操作,当进行了购物车商品数量增加时,flag=false,就不会执行push操作;(不推荐此方法,在加入商店对商品进行分类后,此方法不好写)
②直接return,当进行了购物车商品数量增加时,return;此操作return接下来的所有代码都不会执行(推荐此方法,方便又快捷)
1)当商店存在,但是商品不存在,在该商店下新增商品时,新增商品addGood的类型与goods类型不同,所以需要再次构造新增对象addG,使addG的类型与goods相同,然后再在该商店下进行push
2)当商店商品不存在购物车时,要先创建商店,然后再在商店下创建商品,一样类型要一致
六、核心代码
html:
<div id="app">
<div class="add">
<h3>添加要购买的商品:</h3>
<table>
<tr>
<td>商店后台编号:</td>
<td><input type="text" placeholder="请填写商店编号" v-model="good.storeId"></td>
</tr>
<tr>
<td>商店名称:</td>
<td><input type="text" placeholder="请填写商店名称" v-model="good.storeName"></td>
</tr>
<tr>
<td>商品后台编号:</td>
<td><input type="text" placeholder="请填写商品编号" v-model="good.id"></td>
</tr>
<tr>
<td>商品名称:</td>
<td><input type="text" placeholder="请填写商品名称" v-model="good.name"></td>
</tr>
<tr>
<td>商品价格:</td>
<td><input type="text" placeholder="请填写商品价格" v-model="good.price"></td>
</tr>
<tr>
<td>商品数量:</td>
<td><input type="text" placeholder="请填写商品数量" v-model.number="good.count"></td>
</tr>
<tr>
<td colspan="2"><button @click="addToCar()">添加</button></td>
</tr>
</table>
</div>
<!-- 当购物车没有商店时,意味着没有商品,那么就显示购物车为空 -->
<div v-if="infos.length">
<h3 >购物车:</h3>
<table>
<thead>
<tr>
<th class="weightSmall"><input type="checkbox" v-model="isAllChecked" @click="clickAllChecked"></th>
<th class="weightBig">书籍名称</th>
<th class="weightMid">价格</th>
<th class="weightMid">购买数量</th>
<th class="weightMid">操作</th>
</tr>
</thead>
<tbody>
<!-- 外层for循环商店,如果该商店无商品,则不显示 -->
<table v-for="(items, indexs) in infos" v-if="infos[indexs].goods.length">
<thead>
<th colspan="5"><input type="checkbox" v-model="items.isStChecked" @click="clickStChecked(indexs)">{{items.storeName}}</th>
</thead>
<tbody>
<!-- 内层for循环商店下的商品 -->
<tr v-for="(item, index) in items.goods">
<td class="weightSmall"><input type="checkbox" v-model="item.isChecked" @click="clickChecked(indexs,index)"></td>
<td class="weightBig">{{item.name}}</td>
<td class="weightMid">{{item.price | getFinalPrice}}</td>
<td class="weightMid">
<button @click="decrement(indexs,index)" :disabled="item.count <= 1">-</button>
{{item.count}}
<button @click="increment(indexs,index)">+</button>
</td>
<td class="weightMid">
<!-- 每次移除商品时判断该商店商品是否为空,为空就删除该商店 -->
<button @click="removeHandle(indexs,index)" >移除</button>
</td>
</tr>
</tbody>
</table>
<table>
<tr>
<td class="final">总价格:{{totalPrice | getFinalPrice}}</td>
</tr>
</table>
</tbody>
</table>
</div>
<h2 v-else>购物车为空</h2>
</div>
JS:
const app = new Vue({
el: '#app',
data: {
// 创建一个对象用于存放要添加的商品信息
good: {
storeId: '',
storeName: '',
id: '',
name: '',
price: '',
count: '',
},
//checkbox用布尔值表示是否选中,在data里绑定该属性,然后每次点击对该属性进行操作
isAllChecked: false,
infos: [
{
storeId: 1,
storeName: '华为手机店',
isStChecked: false,
goods: [
{
id: 1,
name: '华为手机',
price: 2000,
count: 1,
isChecked: false,
},
{
id: 2,
name: '华为Mate30手机',
price: 5000,
count: 1,
isChecked: false,
},
]
},
{
storeId: 2,
storeName: '体育用品店',
isStChecked: false,
goods: [
{
id: 3,
name: '篮球',
price: 200,
count: 1,
isChecked: false,
},
]
},
{
storeId: 3,
storeName: '手机用品店',
isStChecked: false,
goods: [
{
id: 4,
name: '手机膜',
price: 30,
count: 1,
isChecked: false,
},
]
}
],
},
methods: {
// 方法①调用时用getFinalPrice(item.price)
// getFinalPrice(price) {
// return '¥' + price.toFixed(2);
// },
decrement(indexs, index) {
this.infos[indexs].goods[index].count--;
},
increment(indexs, index) {
this.infos[indexs].goods[index].count++;
},
removeHandle(indexs, index) {
this.infos[indexs].goods.splice(index, 1);
if (this.infos[indexs].goods.length === 0) {
this.infos.splice(indexs, 1);
}
},
// 二、添加到购物车的功能
addToCar() {
//如果有一个表单元素输入空,就返回空值,不会在进行在购物车添加的操作
if (!this.judgeGood()) {
return;
}
else {
//对购物车的商品进行遍历,有三种情况:
for (let j = 0; j < this.infos.length; j++) {
if (this.good.storeId == this.infos[j].storeId) {
for (let i = 0; i < this.infos[j].goods.length; i++) {
//a 商店存在且新增商品在购物车中存在,商品数量增加;
if (this.good.id == this.infos[j].goods[i].id) {
this.infos[j].goods[i].count += this.good.count;
this.clearGood();
return;
}
}
//b 商店存在但新增商品不在购物车中,则在该商店下push新增商品;
//先构造新增的对象
let goodJson = JSON.stringify(this.good);
let addGood = JSON.parse(goodJson);
let addG = {
id: addGood.id,
name: addGood.name,
price: addGood.price,
count: addGood.count,
isChecked: false,
};
this.infos[j].goods.push(addG);
this.clearGood();
}
}
/*1)这个方法会把good的地址也push进去,而不是拷贝
this.goods.push(this.good);
2)构建一个新对象,再push进去
(parse必须是json格式,所以先用stringify转换成json字符串类型再用parse转换成对象)*/
//c 商店不存在且新增商品不在购物车中,则新增商店商品
//先构造新增的对象
let goodJson = JSON.stringify(this.good);
let addGood = JSON.parse(goodJson);
let addS = {
storeId: addGood.storeId,
storeName: addGood.storeName,
isStChecked: false,
goods: [],
};
let addG = {
id: addGood.id,
name: addGood.name,
price: addGood.price,
count: addGood.count,
isChecked: false,
};
this.infos.push(addS);
addS.goods.push(addG);
this.clearGood();
}
},
//判断每个输入框的内容是否输入,让表单每个元素必填
judgeGood() {
if (this.good.storeId == '' || this.good.storeId == undefined || this.good.storeId == null) {
alert('请输入商店后台编号');
return false;
};
if (this.good.storeName == '' || this.good.storeName == undefined || this.good.storeName == null) {
alert('请输入商店名称');
return false;
};
if (this.good.id == '' || this.good.id == undefined || this.good.id == null) {
alert('请输入商品编号');
return false;
};
if (this.good.name == '' || this.good.name == undefined || this.good.name == null) {
alert('请输入商品名称');
return false;
};
if (this.good.price == '' || this.good.price == undefined || this.good.price == null) {
alert('请输入商品价格');
return false;
};
if (this.good.count == '' || this.good.count == undefined || this.good.count == null) {
alert('请输入商品数量');
return false;
};
return true;
},
//当点击添加时,输入框内的内容同时为空
clearGood() {
this.good.storeId = '';
this.good.storeName = '';
this.good.id = '';
this.good.name = '';
this.good.price = '';
this.good.count = '';
},
// 1. 全选复选框选中,让所有商店所有商品被选中
clickAllChecked() {
let afterClickChecked = !this.isAllChecked;
for (let i = 0; i < this.infos.length; i++) {
this.infos[i].isStChecked = afterClickChecked;
for (let j = 0; j < this.infos[i].goods.length; j++) {
this.infos[i].goods[j].isChecked = afterClickChecked;
}
}
},
// 2.点击商店复选框,该商店下面所有商品的复选框被选中
clickStChecked(indexs) {
this.infos[indexs].isStChecked = !this.infos[indexs].isStChecked;//没有这句,4运行不起作用,因为商店isStChecked没有被更改
let afterClickChecked = this.infos[indexs].isStChecked;
for (let i = 0; i < this.infos[indexs].goods.length; i++) {
this.infos[indexs].goods[i].isChecked = afterClickChecked;
}
//4.解决3c中的小bug,如果点击了商店复选框,就调用此方法对商店进行循环判断
let flagSt = true;
for (let j = 0; j < this.infos.length; j++) {
if (!this.infos[j].isStChecked) {
flagSt = false;
break;
}
}
this.isAllChecked = flagSt;
},
//3.
//a.每次点击商品复选框,都循环查看该商店下所有商品复选框是否全部选中,只要有一个商品没选中,商店就不会被选中;
//b.同时,也循环查看商店的复选框是否全部选中,商店复选框全选中,则全选复选框选中
/*c.另外有个小bug,如果所有商店的复选框已全部选中,但最后一次点击的复选框不是商品复选框,全选复选框不会被选中
因为如果最后一次的点击后,所有商店商品被选中,但点击的不是商品复选框,则不会调用此方法(判断商店是否全部选中的循环没有执行)*/
clickChecked(indexs, index) {
//决定商店复选框是否选中
let flag = true;
this.infos[indexs].goods[index].isChecked = !this.infos[indexs].goods[index].isChecked;
for (let i = 0; i < this.infos[indexs].goods.length; i++) {
if (!this.infos[indexs].goods[i].isChecked) {
flag = false;
break;
}
}
this.infos[indexs].isStChecked = flag;
//决定全选复选框是否选中
let flagSt = true;
for (let j = 0; j < this.infos.length; j++) {
if (!this.infos[j].isStChecked) {
flagSt = false;
break;
}
}
this.isAllChecked = flagSt;
},
},
computed: {
totalPrice() {
let totalPrice = 0;
for (let i = 0; i < this.infos.length; i++) {
for (let j = 0; j < this.infos[i].goods.length; j++) {
//被选中的商品才会把其价格加入总价格中
if (this.infos[i].goods[j].isChecked) {
totalPrice += this.infos[i].goods[j].price * this.infos[i].goods[j].count;
}
}
}
return totalPrice;
}
},
filters: {
// 方法②调用时用item.price | getFinalPrice
getFinalPrice(price) {
return '¥' + parseFloat(price).toFixed(2);
},
}
});
以上是我学习Vue的阶段性练习案例,如有不足,欢迎提出指正以及互相学习交流。