JavaWEB笔记19 Vue组件开发案例–购物车
文章目录
一.案例需求演示:
购物车案例:使用Vue的组件式开发完成如上图效果的购物车界面,其中商品对应+和-号点击可以控制商品数量,且商品数量不能为0或小于0,并且当商品数目进行改变时,对应地总价显示价格也要发生改变;商品后面的×号,点击可以在购物车中去除该条商品信息;商品数目中的数量可以进行输入控制
二.案例给出的代码初始样板:
案例用到的js文件(自备):
- vue.js
- jQuery.js
代码模板:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style type="text/css">
.container {
padding: 0;
margin: 0;
}
.container .cart {
width: 300px;
/*background-color: lightgreen;*/
margin: auto;
}
.container .title {
background-color: lightblue;
height: 40px;
line-height: 40px;
text-align: center;
/*color: #fff;*/
}
.container .total {
background-color: #FFCE46;
height: 50px;
line-height: 50px;
text-align: right;
}
.container .total button {
margin: 0 10px;
background-color: #DC4C40;
height: 35px;
width: 80px;
border: 0;
}
.container .total span {
color: red;
font-weight: bold;
}
.container .item {
height: 55px;
line-height: 55px;
position: relative;
border-top: 1px solid #ADD8E6;
}
.container .item img {
width: 45px;
height: 45px;
margin: 5px;
}
.container .item .name {
position: absolute;
width: 90px;
top: 0;left: 55px;
font-size: 16px;
}
.container .item .change {
width: 100px;
position: absolute;
top: 0;
right: 50px;
}
.container .item .change a {
font-size: 20px;
width: 30px;
text-decoration:none;
background-color: lightgray;
vertical-align: middle;
}
.container .item .change .num {
width: 40px;
height: 25px;
}
.container .item .del {
position: absolute;
top: 0;
right: 0px;
width: 40px;
text-align: center;
font-size: 40px;
cursor: pointer;
color: red;
}
.container .item .del:hover {
background-color: orange;
}
</style>
</head>
<body>
<div id="app">
<div class="container">
<!-- class="cart" 可以作为父组件 -->
<div class="cart">
<!-- class="title" 标题子组件 -->
<div class="title">我的商品</div>
<!-- 中间的列表组件 -->
<div>
<div class="item">
<img src="img/a.jpg"/>
<div class="name"></div>
<div class="change">
<a href="">-</a>
<input type="text" class="num" />
<a href="">+</a>
</div>
<div class="del">×</div>
</div>
<div class="item">
<img src="img/b.jpg"/>
<div class="name"></div>
<div class="change">
<a href="">-</a>
<input type="text" class="num" />
<a href="">+</a>
</div>
<div class="del">×</div>
</div>
<div class="item">
<img src="img/c.jpg"/>
<div class="name"></div>
<div class="change">
<a href="">-</a>
<input type="text" class="num" />
<a href="">+</a>
</div>
<div class="del">×</div>
</div>
<div class="item">
<img src="img/d.jpg"/>
<div class="name"></div>
<div class="change">
<a href="">-</a>
<input type="text" class="num" />
<a href="">+</a>
</div>
<div class="del">×</div>
</div>
<div class="item">
<img src="img/e.jpg"/>
<div class="name"></div>
<div class="change">
<a href="">-</a>
<input type="text" class="num" />
<a href="">+</a>
</div>
<div class="del">×</div>
</div>
</div>
<!--class="total" 底部的子组件 -->
<div class="total">
<span>总价:123</span>
<button>结算</button>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
}
});
</script>
</body>
</html>
img目录下提供的图片:
三.案例实现过程详解:
步骤大致如下:将页面看成,顶部标题,中间条目,底部总价三个组件。然后定义一个全局组件作为顶部标题,中间条目,底部总价三个组件的父组件。
一.修改案例的静态页面,改成组件化
1. 定义父组件和三个子组件
<script type="text/javascript">
//定义三个子组件
var CarTitle={
template:``,
}
var CarList={
template:``,
}
var CarTotal={
template:``,
}
//定义父组件
Vue.component('my-car',{
template:``,
//注册子组件
components:{
'car-title':CarTitle,
'car-list':CarList,
'car-total':CarTotal
}
})
var vm = new Vue({
el: '#app',
data: {
}
});
</script>
2.剪贴对应的html代码放到对应的三个子组件的模板中,也就是template属性中
//定义三个子组件
//顶部标题组件
var CarTitle = {
template: `<div class="title">我的商品</div>`,
}
//中间条目组件
var CarList = {
template: `<div>
<div class="item">
<img src="img/a.jpg"/>
<div class="name"></div>
<div class="change">
<a href="">-</a>
<input type="text" class="num" />
<a href="">+</a>
</div>
<div class="del">×</div>
</div>
<div class="item">
<img src="img/b.jpg"/>
<div class="name"></div>
<div class="change">
<a href="">-</a>
<input type="text" class="num" />
<a href="">+</a>
</div>
<div class="del">×</div>
</div>
<div class="item">
<img src="img/c.jpg"/>
<div class="name"></div>
<div class="change">
<a href="">-</a>
<input type="text" class="num" />
<a href="">+</a>
</div>
<div class="del">×</div>
</div>
<div class="item">
<img src="img/d.jpg"/>
<div class="name"></div>
<div class="change">
<a href="">-</a>
<input type="text" class="num" />
<a href="">+</a>
</div>
<div class="del">×</div>
</div>
<div class="item">
<img src="img/e.jpg"/>
<div class="name"></div>
<div class="change">
<a href="">-</a>
<input type="text" class="num" />
<a href="">+</a>
</div>
<div class="del">×</div>
</div>
</div>`,
}
//底部总结组件
var CarTotal = {
template: `<div class="total">
<span>总价:123</span>
<button>结算</button>
</div>`,
}
3. 添加根组件模板以及使用三个子组件
//定义父组件
Vue.component('my-car', {
template: `
<div class="cart">
<car-title></car-title>
<car-list></car-list>
<car-total></car-total>
</div>`,
//注册子组件
components: {
'car-title': CarTitle,
'car-list': CarList,
'car-total': CarTotal
}
})
4.在 根组件中的模板中使用 my-car 父组件
<div id="app">
<div class="container">
<!-- 使用组件 -->
<my-car></my-car>
</div>
</div>
5. 测试:跟刚才一样
二.顶部组件功能的实现
顶部组件没有什么功能,为了练习我们所学知识,我们可以把 我的
这两个字改成用户名,就是为了练习父组件向子组件如何传递数据
- 在父组件中定义一个数据,传递给顶部的子组件
//定义父组件
Vue.component('my-car', {
data:function(){
return {
username:'张三'
}
},
template: `
<div class="cart">
<car-title :username="username"></car-title>
<car-list></car-list>
<car-total></car-total>
</div>`,
//注册子组件
components: {
'car-title': CarTitle,
'car-list': CarList,
'car-total': CarTotal
}
})
- 在 car-title 子组件中取出父组件传递过来的username,并展示
var CarTitle = {
props:['username'], //获取父组件传递的数据
template: `<div class="title">{{username}}商品</div>`,
}
中间列表所需要的数据,我们在父组件中提供一下,然后传递给中间列表组件和底部总价组件
//定义父组件
Vue.component('my-car', {
data: function() {
return {
username: '张三',
list: [{
id: 1,
name: 'TCL彩电',
price: 1000,
num: 1,
img: 'img/a.jpg'
}, {
id: 2,
name: '机顶盒',
price: 1000,
num: 1,
img: 'img/b.jpg'
}, {
id: 3,
name: '海尔冰箱',
price: 1000,
num: 1,
img: 'img/c.jpg'
}, {
id: 4,
name: '小米手机',
price: 1000,
num: 1,
img: 'img/d.jpg'
}, {
id: 5,
name: 'PPTV电视',
price: 1000,
num: 2,
img: 'img/e.jpg'
}]
}
},
template: `
<div class="cart">
<car-title :username="username"></car-title>
<car-list :list="list"></car-list>
<car-total :list="list"></car-total>
</div>`,
//注册子组件
components: {
'car-title': CarTitle,
'car-list': CarList,
'car-total': CarTotal
}
})
三.底部总价组件功能的实现
在底部总价组件中,接收父组件传递过来的数据,计算商品的总价,计算总价,我们使用计算属性来完成,因为商品的数量是变化的。
//底部组件
var CarTotal = {
props: ['list'], //获取父组件传递的数据
template: `<div class="total">
<span>总价:{{total}}</span>
<button>结算</button>
</div>`,
computed: {
total: function() {
var sum = 0;
this.list.forEach(function(item) {
sum += (item.num) * item.price;
});
return sum;
}
}
}
四.中间列表组件的功能实现
列表数据的动态显示
在父组件中将list数据传递给子组件,在组件中进行遍历展示
<car-list :list="list"></car-list>
子组件接收list数据,并遍历list展示数据
var CarList = {
props: ['list'], //获取父组件传递的数据
template: `<div>
<div class="item" v-for="(item,index) in list" :key="index">
<img :src="item.img" />
<div class="name">{{item.name}}</div>
<div class="change">
<a href="">-</a>
<input type="text" value="" class="num"/>
<a href="">+</a>
</div>
<div class="del">×</div>
</div>
</div>`,
}
注意: src前面加上冒号,注意模板要有一个根标签
五.删除按钮的实现
子组件的删除操作,要让父组件去做,因为数据是在父组件中绑定的,所以我们在子组件中,把当前要删除的条目的索引传递给父组件
- 给删除按钮绑定点击事件
<div class="del" @click="delItme(index)">×</div>
- 在methods中处理删除操作
methods: {
delItme(index) {
//把当前条目的索引传递给父组件
this.$emit('delete-cart', index);
}
}
- 父组件中,绑定自定义事件 delete-cart ,进行删除
<car-list @delete-cart="delEle" :list="list"></car-list>
methods:{
delEle(index){
this.list.splice(index,1);
}
}
六.商品数量的变更
- 在列表组件中,先把默认的商品数量展示出来,那这里我们不用 v-model 来绑定 而是 绑定 value属性,因为商品的数量是从父组件的传过来的,子组件不直接修改传递过来的数据,只做展示
<div class="change">
<a href="">-</a>
<input type="text" :value="item.num" class="num" $ref="in"/>
<a href="">+</a>
</div>
- 给输入框,绑定失去焦点事件,当用户在输入框输入了新的商品数量后,我们传递数据给父组件做数据更新。
<input @blur="inputNum(index,$event)" type="text" :value="item.num" class="num"/>
methods: {
inputNum(index) {
//alert(index);
this.$emit('change-num', {
index: index, //条目的索引
num: this.$refs.in.value, //获取输入框输入的值
type: 'change' //标记,表示是通过输入商品数量
});
}
}
- 在父组件中监听 change-num 自定义事件,根据子组件传递过来的数据,更新父组件中商品的数量
<car-list @delete-cart="delEle" @change-num="changeNum($event)" :list="list"></car-list>
changeNum(value) {
//console.log(value);
if (value.type == "change") {
//根据子组件传递过来的数据,更新父组件中商品的数量
this.list.forEach(function(item, index) {
if (index == value.index) {
item.num = value.num;
}
});
}
}
七.点击 + - 来改变商品数量
1.给 + - 绑定点击事件,改变商品的数量
<div class="change">
<a href="" @click.prevent='subNum(index)'>-</a>
<input @blur="inputNum(index)" type="text" :value="item.num" $ref="in" class="num"/>
<a href="" @click.prevent='addNum(index)'>+</a>
</div>
subNum(index){
this.$emit('change-num', {
index: index,
num: this.$refs.in.value,
type: 'sub' //表示是点击的-
});
},
addNum(index){
this.$emit('change-num', {
index: index,
num: this.$refs.in.value,
type: 'add' //表示是点击的+
});
},
- 父组件中监听自定义事件,根据传递过来数据中 type 类型分别来处理
<car-list @delete-cart="delEle" @change-num="changeNum($event)" :list="list"></car-list>
changeNum(value) {
//console.log(value);
if (value.type == "change") {
this.list.forEach(function(item, index) {
if (index == value.index) {
item.num = value.num;
}
});
} else if (value.type == "add") {
this.list.forEach(function(item, index) {
if (index == value.index) {
item.num += 1;
}
});
} else {
this.list.forEach(function(item, index) {
if (index == value.index) {
item.num -= 1;
if(item.num == 0){
alert("亲,商品的数量不能为0哦~");
item.num = 1;
}
}
});
}
}
完
四.最终代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style type="text/css">
.container {
padding: 0;
margin: 0;
}
.container .cart {
width: 300px;
/*background-color: lightgreen;*/
margin: auto;
}
.container .title {
background-color: lightblue;
height: 40px;
line-height: 40px;
text-align: center;
/*color: #fff;*/
}
.container .total {
background-color: #FFCE46;
height: 50px;
line-height: 50px;
text-align: right;
}
.container .total button {
margin: 0 10px;
background-color: #DC4C40;
height: 35px;
width: 80px;
border: 0;
}
.container .total span {
color: red;
font-weight: bold;
}
.container .item {
height: 55px;
line-height: 55px;
position: relative;
border-top: 1px solid #ADD8E6;
}
.container .item img {
width: 45px;
height: 45px;
margin: 5px;
}
.container .item .name {
position: absolute;
width: 90px;
top: 0;left: 55px;
font-size: 16px;
}
.container .item .change {
width: 100px;
position: absolute;
top: 0;
right: 50px;
}
.container .item .change a {
font-size: 20px;
width: 30px;
text-decoration:none;
background-color: lightgray;
vertical-align: middle;
}
.container .item .change .num {
width: 40px;
height: 25px;
}
.container .item .del {
position: absolute;
top: 0;
right: 0px;
width: 40px;
text-align: center;
font-size: 40px;
cursor: pointer;
color: red;
}
.container .item .del:hover {
background-color: orange;
}
#jiesuan:hover{
cursor: pointer;
}
</style>
</head>
<body>
<div id="app">
<div class="container">
<!-- 使用组件 -->
<my-car></my-car>
</div>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
//定义三个子组件:一定要先定义三个子组件,这样后面的父组件就可以取到了
var carTitle = {
props:['username'],
template: `<div class="title">{{username}}的商品</div>`,
};
var carList = {
props: ['list'],
template: `<div>
<div class="item" v-for="(elem,index) in list" :key="index">
<img :src="elem.img"/>
<div class="name">{{elem.name}}</div>
<div class="change">
<a href="" @click.prevent="subNum(index)">-</a>
<input @blur="inputNum(index)" type="text" class="num" v-model="elem.num" ref="in"/>
<a href="" @click.prevent="addNum(index)">+</a>
</div>
<div class="del" @click="delItem(index)">×</div>
</div>
</div>`,
methods: {
delItem(index) {
//把当前条目的索引传递给父组件
this.$emit('delete-cart', index);
},
inputNum(index) {
//alert(index);
this.$emit('change-num', {
index: index, //条目的索引
num: this.$refs.in.value, //获取输入框输入的值
type: 'change' //标记,表示是通过输入商品数量
});
},
subNum(index){
this.$emit('change-num', {
index: index,
num: this.$refs.in.value,
type: 'sub' //表示是点击的-
});
},
addNum(index){
this.$emit('change-num', {
index: index,
num: this.$refs.in.value,
type: 'add' //表示是点击的+
});
}
}
};
var carTotal = {
props: ['list'],
template: `<div class="total">
<span>总价:{{totalprice}}</span>
<button id="jiesuan">结算</button>
</div>`,
computed:{
totalprice(){
var sum = 0;
for(let i=0;i<this.list.length;i++){
sum += this.list[i].num * this.list[i].price;
}
return sum;
}
}
};
//定义父组件:
Vue.component('my-car', {
data: function () {
return {
username: '张劲雷',
list: [{
id: 1,
name: 'TCL彩电',
price: 1000,
num: 1,
img: 'img/a.jpg'
}, {
id: 2,
name: '机顶盒',
price: 1000,
num: 1,
img: 'img/b.jpg'
}, {
id: 3,
name: '海尔冰箱',
price: 1000,
num: 1,
img: 'img/c.jpg'
}, {
id: 4,
name: '小米手机',
price: 1000,
num: 1,
img: 'img/d.jpg'
}, {
id: 5,
name: 'PPTV电视',
price: 1000,
num: 2,
img: 'img/e.jpg'
}]
}
},
template: `<div class="cart">
<car-title :username="username"></car-title>
<car-list @delete-cart="delEle" @change-num="changeNum($event)" :list="list"></car-list>
<car-total :list="list"></car-total>
</div>`,
components: {
'car-title': carTitle,
'car-list': carList,
'car-total': carTotal
},
methods: {
delEle(index) {
this.list.splice(index, 1);
},
changeNum(value) {
//alert(value);
if (value.type == "change") {
//根据子组件传递过来的数据,更新父组件中商品的数量
this.list.forEach(function (item, index) {
if (index == value.index) {
item.num = value.num;
}
});
} else if (value.type == "add") {
this.list.forEach(function (item, index) {
if (index == value.index) {
item.num += 1;
}
});
} else {
this.list.forEach(function (item, index) {
if (index == value.index ) {
item.num -= 1;
if(item.num == 0){
alert("亲,商品的数量不能为0哦~");
item.num = 1;
}
}
})
}
}
}
});
var vm = new Vue({
el: '#app',
data: {
}
});
</script>
</body>
</html>