今天用vue写了一个类似购物车功能,涉及到很多基础,其实是为了巩固基础,适合基础比较薄弱的猿们,就两个vue文件,一个子组件一个父组件,页面是这样的:
ok,首先我们先用vue cli3新建一个项目,在view文件夹下新建一个vue文件,这就是父组件,我们起名shopCar.vue,然后配置路由,让路径Home重定向到shopCar,
在shopCar中,我们首先先把样式写好:
<div>
<h1>增加课程</h1>
<div>
<label for="name">课程名称</label>
<input id="name" type="text" v-model="courseInfo.name">
</div>
<div>
<label for="">课程价格</label>
<input type="text" v-model="courseInfo.price">
</div>
<button @click="addCourse">增加课程</button>
</div>
<div>
这是最上面的input框的部分,为什么要用label标签呢?如果我用了label标签,将for后面的和id名字起得的相同,label for=“name” 和 input id=“name”,当我点击label的时候,光标会自动定位到input框中,感兴趣的可以试一下^^。
我v-model绑定的是data中定义的数据,那我们就先要在data中定义好:
data() {
return {
courseInfo: {
name:"",
price:"",
},
}
},
接下来我们实现点击添加按钮然后将输入框中输入的数据添加到课程列表中,那我们先在data中写一个数组对象,如下:
courseList:[
{
id:0,
name:"语文",
price:100
},
{
id:1,
name:"数学",
price:200
}
],
然后我们在上面继续写课程列表的样式,
<div>
<h1>课程列表</h1>
<table>
<tr>
<td>名称</td>
<td>价格</td>
<td>操作</td>
</tr>
<tr v-for="(item,index) in courseList" :key="item.id">
<td>{{item.name}}</td>
<td>{{item.price}}</td>
<td>
<button @click="addToCar(item,index)">增加到购物车</button>
</td>
</tr>
</table>
</div>
ok,接下来写点击添加课程名称和课程价格的方法
addCourse() {
this.courseList.push(
{
id:this.courseList.length,
name:this.courseInfo.name,
price:parseInt(this.courseInfo.price),
}
)
},
这样每次点击添加的时候课程列表就增加了一条,其实就是数组的push方法,
接下来就是逻辑稍微复杂点的加入购物车功能了,这是子组件,我们在view文件夹下再新建一个文件components,在components下新建一个vue文件,起名shopping,那我们在shopping中写购物车的样式,代码:
<template>
<div>
我是购物车
<table>
<tr>
<td>勾选</td>
<td>课程名称</td>
<td>课程数量</td>
<td>数量</td>
<td>价格</td>
</tr>
<tr v-for="(item,index) in courseList" :key="item.id">
<td>
<input type="checkbox" v-model="item.isActive"/>
</td>
<td>{{item.name}}</td>
<td>{{item.price}}</td>
<td>
<button @click="minut(item,index)">-</button>
{{item.num}}
<button @click="add(item,index)">+</button>
</td>
<td>{{item.num*item.price}}</td>
</tr>
<tr>
<td></td>
<td>{{buy}}/{{this.courseList.length}}</td>
<td>{{totalPrice}}</td>
</tr>
</table>
</div>
</template>
那我就先把样式全写了,来依次解释一下:
courseList是从父组件传过来的,我在父组件的data中定义了一个空数组
父组件:
<template>
<shopping :courseList="courseList"></shopping>//子组件
</template>
<script>
import Shopping from "你是什么路径就写什么路径"
export default{
data(){
return{
courseList:[]
}
}
}
</script>
子组件:
props:["courseList"],//接收父组件传过来的值
然后在子组件中,遍历courseList,将数据渲染上去。接着就是点击事件,minut和add,这边涉及到一个知识点,就是vue单项数据流,不应该更改父组件传过来的值,所以,我们最好将处理方法由父组件来处理,那么我们就要用到$emit,父传子,将值传到父组件中处理,代码:
methods:{
minut(item,index){
if(item.num<1){//当数量小于1的时候,删除那条数据
if(window.confirm('是否移除课程')){
this.$emit('parentDelete',index)//将index的值传过去
}
}else{
item.num-=1
this.$emit('parentMinut',index,item)
}
},
add(item,index){
item.num += 1;
this.$emit("parentAdd",index,item)
}
},
父组件:
<shopping :courseList="courseList"
@parentDelete="parentDelete"
@parentMinut="parentMinut"
@parentAdd="parentAdd"></shopping>
methods:{
// 当子组件$emit传多个参数的时候,父组件以数组的形式接收
parentMinut(){//子组件传给父组件的值和方法
this.$set(this.courseList,arguments[1],arguments[0])//就是item和index
},
parentDelete(){
this.courseList.splice(arguments[0],1) //item
},
parentAdd(){
this.$set(this.courseList,arguments[1],arguments[0])
}
}
上面是子组件的方法,arguments是什么意思呢?就如同我上面写的:当子组件
e
m
i
t
传
多
个
参
数
的
时
候
,
父
组
件
以
数
组
的
形
式
接
收
,
然
后
我
怎
么
传
的
参
数
顺
序
,
在
父
组
件
中
可
以
用
下
标
来
表
示
,
比
如
t
h
i
s
.
emit传多个参数的时候,父组件以数组的形式接收,然后我怎么传的参数顺序,在父组件中可以用下标来表示,比如 this.
emit传多个参数的时候,父组件以数组的形式接收,然后我怎么传的参数顺序,在父组件中可以用下标来表示,比如this.emit(‘parentAdd’,index,item),
arguments[0]就是index,
arguments[1]就是item,
so,are you understand?
好的,接下来我们就来做增加到购物车的功能,当我点击添加到购物车的按钮的时候,底下的购物车内要有勾选、课程名称、课程价格、数量、总价,那我们来写添加到购物车的方法:
addToCar(item,index){ //当我点击追加到购物车
let myItem={ //首先用一个对象接收各属性
...item //展开运算符
}
let isFlagItem=this.courseList.find(x=>x.id==item.id);
if(isFlagItem){ //如果id相同
isFlagItem.num+=1;
this.$set(this.courseList,index,isFlagItem) //更改视图
}else{ //否则就新增,默认数量为一
myItem.num=1;
myItem.isActive = true//checkbox默认是true,默认添加了属性active
this.courseList.push(myItem)
}
},
ok,这里有四点:1、展开运算符
2、更改视图
3、find
4、自动添加属性
首先是展开运算符,我三个。。。其实就包含了id,name,price等属性,然后就是$set,当vue的data里边声明或者已经赋值过的对象或者数组(数组里边的值是对象)时,向对象中添加新的属性,如果更新此属性的值,是不会更新视图的。那这个时候就要
调用方法: Vue.set( target , key , value)
target: 要更改的数据源(可以是一个对象或者数组)
key 要更改的具体数据 (索引)
value 重新赋的值
this.$set(target,key,value),
然后是find方法,它是js的方法,
find() 方法为数组中的每个元素都调用一次函数执行:
当数组中的元素在测试条件时返回 true 时, find() 返回符合条件的元素,之后的值
不会再调用执行函数。
如果没有符合条件的元素返回 undefined
注意: find() 对于空数组,函数是不会执行的。
注意: find() 并没有改变数组的原始值。
当添加的科目的id与已经存在的科目的id相同的时候,购物车中的科目数量就加1,否则就重新添加一个数据,数量为1,然后会自动增加一个属性isActive,在courseList中,也会随着父传子传给子组件。
接下来,再增加一个功能,就是统计当勾选的时候,所有勾选的科目的数量以及总的价格,类似于购物车结算,那我们用computed计算属性来处理,computed是依赖发生改变时会重新取值,这样就能实时监控了,代码如下:
computed:{
buy(){
let list=this.courseList.filter(item=>item.isActive)//过滤数组中checkbox是true的选项
return list.length;//返回这个数组的长度
},
totalPrice(){
let num=0;
this.courseList.forEach(item => {
if(item.isActive){//如果checkbox为true,
num+=item.num*item.price
}
});
return num
}
}
第一个buy方法就是返回我勾选的课程的数组长度,通过过滤的方法,第二个方法就是根据勾选实时计算总价。接下来是完整代码:
父组件:
<template>
<div>
<div>
<h1>增加课程</h1>
<div>
<label for="name">课程名称</label>
<input id="name" type="text" v-model="courseInfo.name"/>
</div>
<div>
<label for="">课程价格</label>
<input type="text" v-model="courseInfo.price"/>
</div>
<button @click="addCourse">增加课程</button>
</div>
<div>
<h1>课程列表</h1>
<table>
<tr>
<td>名称</td>
<td>价格</td>
<td>操作</td>
</tr>
<tr v-for="(item,index) in courseInfo" :key="item.id">
<td>{{item.name}}</td>
<td>{{item.price}}</td>
<td>
<button @click="addToCar(item,index)">增加到购物车</button>
</td>
</tr>
</table>
</div>
<!--将子组件的方法都拿到父组件来操作 -->
<shop :courseList="courseList" @parentDelete="parentDelete" @parentMinut="parentMinut"
@parentAdd="parentAdd"></shop>
</div>
</template>
<script>
import Shop from "./components/shop"
export default{
name:"shopCar",
components:{
Shop
},
data(){
return {
courseInfo:[
{
id:1,
name:"语文",
price:100
},
{
id:2,
name:"数学",
price:200
},
{
id:3,
name:"英语",
price:300
}
],
courseList:[]
}
},
methods:{
addCourse(){
this.courseInfo.push({
id:this.courseInfo.length,
name:this.courseInfo.name,
price:parseInt(this.courseInfo.price)
})
console.log(this.courseInfo)
},
addToCar(item,index){ //当我点击追加到购物车
let myItem={ //首先用一个对象接收各属性
...item //展开运算符
}
let isFlagItem=this.courseList.find(x=>x.id==item.id);
if(isFlagItem){ //如果id相同
isFlagItem.num+=1;
this.$set(this.courseList,index,isFlagItem) //更改视图
}else{ //否则就新增,默认数量为一
myItem.num=1;
myItem.isActive = true//checkbox默认是true,默认添加了属性active
this.courseList.push(myItem)
}
},
// 当子组件$emit传多个参数的时候,父组件以数组的形式接收
parentMinut(){//子组件传给父组件的值和方法
this.$set(this.courseList,arguments[1],arguments[0])//就是item和index
},
parentDelete(){
this.courseList.splice(arguments[0],1) //item
},
parentAdd(){
this.$set(this.courseList,arguments[1],arguments[0])
}
}
}
</script>
<style>
</style>
子组件
<template>
<div>
我是购物车
<table>
<tr>
<td>勾选</td>
<td>课程名称</td>
<td>课程数量</td>
<td>数量</td>
<td>价格</td>
</tr>
<tr v-for="(item,index) in courseList" :key="item.id">
<td>
<input type="checkbox" v-model="item.isActive"/>
</td>
<td>{{item.name}}</td>
<td>{{item.price}}</td>
<td>
<button @click="minut(item,index)">-</button>
{{item.num}}
<button @click="add(item,index)">+</button>
</td>
<td>{{item.num*item.price}}</td>
</tr>
<tr>
<td></td>
<td>{{buy}}/{{this.courseList.length}}</td>
<td>{{totalPrice}}</td>
</tr>
</table>
</div>
</template>
<script>
export default{
name:"shop",
props:["courseList"],
methods:{
minut(item,index){
if(item.num<1){//当数量小于1的时候,删除那条数据
if(window.confirm('是否移除课程')){
this.$emit('parentDelete',index)//将index的值传过去
}
}else{
item.num-=1
this.$emit('parentMinut',index,item)
}
},
add(item,index){
item.num += 1;
this.$emit("parentAdd",index,item)
}
},
computed:{
buy(){
let list=this.courseList.filter(item=>item.isActive)//过滤数组中checkbox是true的选项
return list.length;//返回这个数组的长度
},
totalPrice(){
let num=0;
this.courseList.forEach(item => {
if(item.isActive){//如果checkbox为true,
num+=item.num*item.price
}
});
return num
}
}
}
</script>
<style>
</style>