四、绑定事件
1. 大麦网列表页
<div id="app">
<ul class="type">
<li>城市:</li>
<li@click="cityActive=index"v-for="(item,index) in citys":class="{active:cityActive===index}">{{item}}</li></ul>
<ul class="type">
<li>分类:</li>
<li@click="typeActive=index"v-for="(item,index) in types":class="{active:typeActive===index}">{{item}}</li>
</ul>
<div class="list">
<div class="item"v-for="item in showData"><img@click="gotoDetail(item.id)":src="item.verticalPic">
<div class="content"><div class="title">【{{item.cityname}}】 {{item.name}}</div>
<div>{{item.categoryname}}</div>
<div>{{item.cityname}} | {{item.venue}}</div>
<div>{{item.showtime}}</div>
<div class="price">{{item.price_str}} 元</div>
</div>
</div>
</div>
</div>
new Vue({el:'#app',// 定义数据
data(){
return{// 城市数组
citys:['全部','成都','北京','上海','杭州','温州','天津','上饶','深圳'],// 选中城市的高亮索引
cityActive:0,// 分类数组
types:['全部','音乐会','演唱会','话剧歌剧','展览休闲','舞蹈芭蕾','儿童亲子'],// 选中分类的高亮索引typeActive:0,// 定义一个商品数组(用于保存所有的商品信息)
resultData:[],// 定义一个商品数组(用于显示展示的商品信息)showData:[]}},// 定义方法
methods:{// 过滤数据的方法
filterData(){// 先根据城市筛选
if(this.cityActive===0){
this.showData=this.resultData
}
else{// 根据城市进行筛选
this.showData=this.resultData.filter(r=>r.cityname===this.citys[this.cityActive])}// 再根据分类筛选if(this.typeActive!==0){
this.showData=this.showData.filter(r=>r.categoryname===this.types[this.typeActive])}},// 跳转到详情页gotoDetail(id){
// 跳转到详情页window.location.href="./detail.html?id="+id}
},// 此时,我已经可以操作数据了
created(){axios.get("./data/data.json").then(({data:{pageData:{resultData}}})=>{this.resultData=resultDatathis.showData=resultData})},// 侦听器
watch:{// 侦听选中的城市索引
cityActive(){this.filterData()},// 侦听选中的分类的索引
typeActive(){this.filterData()}}})
2. 大麦网详情页
<div class="detail">
<img:src="item.verticalPic">
<div class="content">
<div class="title">【{{item.cityname}}】 {{item.name}}</div>
<div>{{item.categoryname}}</div>
<div>{{item.cityname}} | {{item.venue}}</div>
<div>{{item.showtime}}</div>
<div><span>票档:</span><span@click="priceActive=index"class="price":class="{active:priceActive===index}"v-for="(item,index) in prices">{{item}}</span></div><div><span>数量:</span>
<button@click="count--":disabled="count===1">-</button>
<input class="text"type="text":value="count"readonly>
<button@click="count++":disabled="count===6">+</button></div>
<div><span>合计:</span><span>{{totalPrice | toFixed2}}元</span></div>
</div>
</div>
newVue({el:'.detail',data(){
return{// 商品信息
item:{},// 票档
prices:[99,199,229,289,339],// 票档选中的索引
priceActive:0,// 购票数量
count:1}},// 页面挂载完毕的生命周期
mounted(){// 获取当前商品的id
let id=window.location.search.split('=')[1]// 根据id请求商品信息
axios.get("./data/data.json").then(({data:{pageData:{resultData}}})=>{// 数组的find方法,根据条件返回满足条件的第一个对象。
this.item=resultData.find(r=>r.id===id)})},// 计算属性
computed:{totalPrice(){return this.count*this.prices[this.priceActive]}},// 过滤器
filters:{toFixed2(val){return val.toFixed(2)}}})
3. v-model指令的详细用法
<divid="app"><div class="item"><span>姓名:</span><!-- v-model指令,绑定文本框的内容,实现双向数据绑定 --><input type="text"v-model="name">{{name}}</div>
<div class="item"><span>地址:</span>
<!-- v-model指令,绑定多行文本框的内容,实现双向数据绑定 -->
<text area cols="80"rows="4"v-model="address"></text area>{{address}}</div>
<div class="item"><span>是否同意:</span>
<!-- 单个复选框,通过v-model绑定一个布尔值 -->
<inputtype="checkbox"v-model="isOK">{{isOK}}</div>
<div class="item"><span>爱好:</span>
<!-- 多个复选框,通过v-model绑定到同一个数组 -->
<input type="checkbox"value="抽烟"v-model="hobbies">抽烟
<input type="checkbox" value="喝酒" v-model="hobbies">喝酒
<input type="checkbox" value="烫头" v-model="hobbies">烫头
<input type="checkbox" value="唱" v-model="hobbies">唱
<input type="checkbox"value="跳"v-model="hobbies">跳
<input type="checkbox"value="篮球"v-model="hobbies">篮球 {{hobbies}}</div>
<div class="item"><span>性别:</span>
<!-- 多个单选框,通过v-model绑定同一个数据 -->
<input type="radio"value="男"name="sex"v-model="sex">男
<input type="radio"value="女"name="sex"v-model="sex">女 {{sex}}</div>
<div class="item"><span>学历:</span>
<!-- 通过v-model可以给下拉框绑定一个属性 -->
<select v-model="xueli"><option value="小学">小学</option>
<option value="硕士">硕士</option>
<option value="博士">博士</option>
<option value="博士后">博士后</option></select>{{xueli}}</div>
<div class="item"><span>美食:</span>
<!-- 下拉框设置multiple属性后,就可以选择多个项 -->
<!-- 通过v-model可以给下拉框绑定一个数组 -->
<select v-model="meishi"multiple><option value="螃蟹">螃蟹</option><option value="龙虾">龙虾</option><option value="鸡腿">鸡腿</option><option value="牛排">牛排</option><option value="海鲜">海鲜</option></select>{{meishi}}</div>
<div class="item"><span>修饰符lazy</span>
<!-- v-model指令,添加.lazy修饰符,在文本框失去焦点后在更新数据 -->
<input type="text"v-model.lazy="msg">{{msg}}</div>
<div class="item"><span>修饰符number</span>
<!-- v-model指令,添加.number修饰符,在修改文本框内容时,会将修改后的内容转为number类型 --><input type="text"v-model.number="age">{{age+10}}</div>
<div class="item"><span>修饰符trim</span>
<!-- v-model指令,添加.trim修饰符,在修改文本框内容时,会忽略前后的空格 -->
<input type="text"v-model.trim="city"><span>长度:{{city.length}}</span></div></div>
let vm=new Vue({
el:'#app',
data:{name:'张三',address:'北京市朝阳区',// 用于表示是否同意
isOK:true,// 爱好数组
hobbies:["烫头","跳"],// 性别
sex:'女',// 学历
xueli:'博士',// 美食
meishi:[],// 消息
msg:'',//年龄
age:20,
city:'北京'},})
4. 绑定事件
<divid="app">
<!-- v-on:指令绑定事件,可以指定一个事件方法,事件方法要在methods里面定义。
指定事件方法时,如果没有给方法传递参数,默认会传递一个事件对象参数 -->
<button v-on:click="sayHi">Say Hi</button><br><br>
<!-- 如果我们传递了一个参数,还想再传递事件对象参数,就要通过$event关键字设置。 -->
<button v-on:click="sayHello('你好',$event)">Say Hello</button>
<br><br>
<!-- 如果事件处理的逻辑比较简单,可以直接在行内编写。 -->
<button v-on:click="name+='*'">修改name</button>{{name}}<hr>
<!-- @是v-on:的简写 --><!-- 通过.prevent事件修饰符,阻止默认行为 -->
<div class="a"@click="a"@contextmenu.prevent="cm">
<!-- 通过.stop事件修饰符,阻止事件冒泡 -->
<div class="b"@click.stop="b"></div></div>
<br>
<!-- 通过.once事件修饰符,让事件方法只执行一次 -->
<button@click.once="once">只触发一次</button>
<br><br>
<!-- 通过.self事件修饰符,控制事件在当前元素自身触发,不在内部元素身上触发 -->
<div class="c"@click.self="c"><div class="d"></div></div><br>
<!-- 默认情况下,手机的捕获模式是,从内部往外部挨个执行。
如果外部事件添加.capture修饰符,此时事件的不会模式就变成了,从外部外内部挨个执行。 -->
<div class="e"@click.capture="e">
<div class="f"@click="f"></div>
</div><br>
<!-- passive就是为了告诉浏览器,不用查询了,我们没用preventDefault阻止默认动作 -->
<div class="g"@scroll.passive="g">
<div class="h"></div></div></div>
new Vue({
el:'#app',
data(){return{name:'张三'}},
methods:{
sayHi(e){
console.log(e);
console.log('Hi');
},
sayHello(val,e){
console.log(val);
console.log(e);
},
a(){
alert('大家好!我是a')
},
b(){
// 通过事件对象,阻止事件冒泡
// e.stopPropagation();alert('大家好!我是b')},
cm(){
// 通过事件对象,阻止默认行为
// e.preventDefault();
console.log('哈哈');},
once(){alert('你好呀!')},
c(){alert('大家好!我是c')},
e(){alert('大家好!我是e')},
f(){alert('大家好!我是f')},
g(){console.log(11);}},})
五、深度响应式
1. 按键修饰符
<div id="app">
<p>
<label>搜索:</label>
<!-- Vue针对键盘事件,提供了按键修饰符
一共有9个按键修饰符,分别是:
.enter 是回车键
.tab 是tab键
.delete 是删除键和退格键
.esc 是退出键
.space 是空格键
.up 是上箭头
.down 是下箭头
.left 是左箭头
.right 是右箭头
-->
<input type="text"v-model="keywords"@keyup.enter="keydown">
<!-- 按键修饰符,也可以用按键码代替,注意:Vue3中取消了按键码 -->
<!-- <input type="text" v-model="keywords" @keyup.13="keydown"> -->
</p>
<p>{{content}}</p>
</div>
new Vue({
el:'#app',
// data选项,定义属性,该选项可以是一个对象,也可以是一个方法返回一个对象。
data:{
// 商品数组
goodses:['小米手机','华为电脑','苹果手表','尼康相机'],
// 搜索关键字
keywords:'',
// 搜索结果
content:''},
// methods选项,定义方法
methods:{
keydown(){
// 字符串的
includes()方法,用于检查字符串中是否包含指定的内容,包含返回truethis.content=this.goodses.find(g=>g.includes(this.keywords))}},})
2. 深度响应式
<div id="app">
<button@click="name='李四'">修改姓名</button>
<h2>{{name}}</h2>
<hr>
<button@click="obj.name='张飞'">修改姓名</button>
<button@click="addJob">添加工作属性</button>
<button@click="delAge">删除年龄属性</button>
<h2>{{obj}}</h2>
<hr>
<button@click="arr.push('可乐')">添加可乐</button>
<button@click="arr.splice(1,1,'榴莲')">通过方法修改元素</button>
<button@click="updateArr">通过下标修改元素</button>
<button@click="delArr">通过下标删除元素</button>
<h2>{{arr}}</h2>
</div>
// Vue实例,在初始化的时候,会将对象身上的所有数据,做响应式处理,
// 之后再向对象中添加属性,这些属性就不再具备响应式能力了。
// 针对数组,只能通过以下方法,才能实现响应式:push() pop() unshift() shift() splice() reverse() sort()
// 如何解决上面的问题?
// 方式1:通过Vue的set方法,更新指定的对象属性或数组成员;delete方法,删除指定对象的属性或数组的成员
// 方式2:通过Vue实例的$set方法,更新指定的对象属性或数组成员;$delete方法,删除指定对象的属性或数组的成员Vue.config.productionTip=falseletvm=newVue({el:"#app",data:{// 基本类型数据name:'张三',
// 对象数据obj:{name:'张杰',age:20,sex:'男'},
// 数组数据arr:['面包','饼干','薯片','巧克力']},methods:{
// 给对象添加工作属性的方法addJob(){
// 通过观察可以发现,我们可以给对象添加属性,但是添加后的属性,不具备响应式能力。
// this.obj.job='前端开发工程师'// set方法的参数分别是:指定的对象,对象的属性,属性值
// Vue.set(this.obj,'job','前端开发工程师')this.$set(this.obj,'job','前端开发工程师')},
// 删除对象身上年龄的方法delAge(){
// delete this.obj.age
// delete烦烦烦的参数分别是:指定的对象,对象的属性
// Vue.delete(this.obj,'age')this.$delete(this.obj,'age')},
// 修改数组身上的成员updateArr(){// this.arr[1] = '苹果'
// 这里set方法的参数分别是:指定的数组,数组的下标,对应的数据this.$set(this.arr,1,'苹果')},
// 根据下标删除数组元素delArr(){
// delete this.arr[1]
// 这里的delete方法的参数分别是:指定的数组,数组的下标this.$delete(this.arr,1)}},})
3. 小练习
<div id="app"><p>属性名称:<input type="text" v-model="pname">属性值:<input type="text"
v-model="val"><button@click="addProtory">添加</button></p>
<p>属性名称:<input type="text" v-model="pname2">
<button@click="delProtory">删除</button></p>
<div>{{obj}}</div></div>
new Vue({el:'#app',data:{obj:{name:'吴亦凡',age:30},
// 属性名pname:'',
// 属性值val:'',
// 删除时,用的属性名pname2:''},
methods:{
addProtory(){
this.$set(this.obj,this.pname,this.val)},
delProtory(){this.$delete(this.obj,this.pname2)}},})
4. 购物车
<div id="app">
<!-- 如果数组中有数据,显示购物车 -->
<table v-if="goodses.length>0">
<tr>
<th><inputtype="checkbox"v-model="isckAll">全选</th>
<th>商品名称</th><th>商品图片</th>
<th>商品单价</th><th>购买数量</th>
<th>小计</th>
<th>操作</th></tr>
<tr v-for="(item,index) in goodses":key="item.id"><td>
<input type="checkbox" v-model="item.isck"></td>
<td>{{item.name}}</td><td><img:src="item.img"></td>
<td>¥{{item.price | toFixed2}}</td>
<td>
<!-- :disabled绑定的值为true,按钮禁用 -->
<button@click="item.count--":disabled="item.count===1">-</button>
<input readonlytype="text":value="item.count"><button@click="item.count++":disabled="item.count===10">+</button>
</td><td>¥{{item.price*item.count | toFixed2}}</td>
<td><button@click="delGoods(index)">删除</button></td></tr>
<tr><tdcolspan="7"class="totalPrice"><span>总计:¥{{totalPrice | toFixed2}}</span>
<!-- 过滤器可以链式调用 -->
<span style="color:red;">${{totalPrice | toUS | toFixed2}}</span></td></tr></table>
<!-- 否则显示下面的div -->
<div class="empty" v-else>您的购物车空空如也</div></div>
new Vue({
el:"#app",
// 数据
data:{
// 商品数组
goodses:[{
// 商品编号id:'1001',
// 商品名称name:'小米手机',
// 商品图片img:'https://img.alicdn.com/bao/uploaded/i3/2279837698/O1CN01gkdsUP26jjYlI8HCS_!!2279837698.jpg',
// 商品单价price:1999,
// 购买数量count:3,
// 是否选中isck:false},
{id:'1002',name:'西门子冰箱',img:'https://img.alicdn.com/bao/uploaded/i4/2347095319/O1CN01xhxce31pA9MmYjHPc_!!2347095319.jpg',price:3999,count:2,isck:true},
{id:'1003',name:'索尼电视',img:'https://img.alicdn.com/bao/uploaded/i1/782731205/O1CN01o18KOx1KlvvaEIosx_!!0-item_pic.jpg',price:4999,count:1,isck:true},
{id:'1004',name:'联想电脑',img:'https://img.alicdn.com/bao/uploaded/i2/459462135/O1CN01yN7bD91RdsIyoddVW_!!459462135.jpg',price:5999,count:4,isck:false}]},
// 方法
methods:{
delGoods(index){if(confirm('是否确定删除?')){this.goodses.splice(index,1)}}},
// 计算属性
computed:{
// 表示是否全选
isckAll:{
// 返回结果
get(){
// 商品数组中所有的商品的状态为true时,返回truereturnthis.goodses.length>0&&this.goodses.every(g=>g.isck)},
// 修改结果
set(val){
// 循环所有的商品,设置所有商品的状态为最新的全选状态
this.goodses.forEach(g=>{g.isck=val});}},
// 表示总价totalPrice(){
/* let total = 0
for(let i=0;i<this.goodses.length;i++){
if(this.goodses[i].isck){
total += this.goodses[i].count * this.goodses[i].price
}
}
return total */lettotal=0this.goodses.forEach(g=>{if(g.isck){total+=g.price*g.count}})returntotal/*
return this.goodses.filter(g=>g.isck).map(g=>g.price*g.count).reduce((c,g)=>{
return c + g
},0) */
}},
// 过滤器
filters:{
// 数字保留两位小数
toFixed2(val){returnval.toFixed(2)},
// 转美金的方法
toUS(val){returnval/6.444}},
// 监听器
watch:{
// 监听totalPrice计算属性的值的变化
totalPrice(val){if(val>100000){alert('您确定消费的起吗?请理性消费!')}}}})
六、项目案例--课程管理
<div id="app"><div><span>课程名称:</span>
<!-- 文本框跟属性是双向绑定 -->
<input type="text" v-model="keywords">
<button@click="getSubjects">查询</button>
</div><hr>
<div class="main">
<table>
<thead>
<tr>
<th>编号</th><th>名称</th><th>课时</th><th>年级</th><th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(item,index) in subjects":key="index"><td>{{item.SubjectId}}</td><td>{{item.SubjectName}}</td>
<td>{{item.ClassHour}}</td><td>{{item.Grade.GradeName}}</td><td><button@click="getOne(item.SubjectId)">修改</button>
<button@click="del(item.SubjectId)">删除</button>
</td></tr></tbody><tfoot><tr><tdcolspan="5">
<!-- 当事件处理的代码比较简单时,直接将逻辑写到行内 --><button@click="pageIndex=1":disabled="pageIndex===1">首页</button>
<button@click="pageIndex--":disabled="pageIndex===1">上一页</button>
<span>{{pageIndex}}</span>
<span>/</span><span>{{totalPage}}</span><button@click="pageIndex++":disabled="pageIndex===totalPage">下一页</button><button@click="pageIndex=totalPage":disabled="pageIndex===totalPage">尾页</button>
<span>显示数量:</span><select v-model="pageSize">
<option value="5">5</option><option value="10">10</option><option value="15">15</option>
<option value="20">20</option></select></td></tr></t foot></table>
<div class="edit">
<div>课程名称:
<input type="text"v-model="subject.subjectName">
</div>
<div>课程课时:<input type="text"v-model="subject.classHour"></div><div>所属年级:
<select v-model="subject.gradeId"><option value="0">请选择年级</option>
<option v-for="(item,index) in grades":key="index":value="item.GradeId">{{item.GradeName}}</option></select></div><div>
<!-- 判断subject对象的subjectId属性,如果是空,就做添加;否则做修改。 -->
<button@click="add"v-if="subject.subjectId===''">添加</button>
<button@click="update"v-else>修改</button>
<button@click="clear">取消</button>
</div></div>
</div></div>
new Vue({
el:"#app",
// 定义属性
data:{
// 课程数组
subjects:[],
// 总数量count:0,
// 定义搜索关键字keywords:'',
// 定义每页显示数量pageSize:5,
// 定义当前页码pageIndex:1,
// 定义课程对象,用于添加和修改操作subject:{subjectId:'',subjectName:'',classHour:'',gradeId:'0'},
// 定义一个年级数组grades:[]},
// 定义方法methods:{
// 获取年级信息的方法
asyncgetGrades(){let{data}=awaitaxios.get('http://www.bingjs.com:81/Grade/GetAll')this.grades=data},// 获取课程信息的方法asyncgetSubjects(){
// 发送请求,获取课时信息l
et{data:{data,count}}=awaitaxios.get('http://www.bingjs.com:81/Subject/GetSubjectsConditionPages',{params:{
// 课程名称subjectName:this.keywords,
// 每页显示数量pageSize:this.pageSize,
// 当前页码pageIndex:this.pageIndex}})
// 获取课程数组this.subjects=data
// 获取总数量this.count=count},
// 清空课程信息的方法
clear(){
// 清空数据
this.subject={subjectId:'',subjectName:'',classHour:'',gradeId:'0'}},
// 添加课程的方法
asyncadd(){
// 添加前先做非空验证
if(this.subject.subjectName===''){returnalert('请输入课程名称')}elseif(this.subject.classHour===''){returnalert('请输入课时')}elseif(this.subject.gradeId==='0'){returnalert('请选择所属年级')}
// 发送请求,执行添加
let{data}=awaitaxios.post('http://www.bingjs.com:81/Subject/Add',this.subject)
// 返回true,表示添加成功
if(data){alert('添加成功!')
// 添加成功后,重新调用查询方法this.getSubjects()
// 清空数据this.clear()}else{alert('添加失败!')}},
// 根据课程编号查询课程信息asyncgetOne(subjectId){
// 根据课程编号,发送请求,获取对应的课程信息let{data}=awaitaxios.get('http://www.bingjs.com:81/Subject/GetSubjectById',{params:{subjectId}})
// 获取课程信息
this.subject={subjectId:data.SubjectId,subjectName:data.SubjectName,classHour:data.ClassHour,gradeId:data.GradeId}},
// 修改课程的方法asyncupdate(){
// 修改前先做非空验证if(this.subject.subjectName===''){returnalert('请输入课程名称')}elseif(this.subject.classHour===''){returnalert('请输入课时')}elseif(this.subject.gradeId==='0'){returnalert('请选择所属年级')}
// 发送请求,执行修改let{data}=awaitaxios.post('http://www.bingjs.com:81/Subject/Update',this.subject)
// 返回true,表示修改成功if(data){alert('修改成功!')
// 修改成功后,重新调用查询方法this.getSubjects()
// 清空数据this.clear()}else{alert('修改失败!')}},
// 删除课程的方法asyncdel(subjectId){
// 提示是否确定删除if(confirm('是否确定删除?')){
// 发送请求,删除数据let{data}=awaitaxios.post('http://www.bingjs.com:81/Subject/Delete',{subjectId})
// 返回true,表示删除成功if(data){alert('删除成功!')
// 删除成功后,重新调用查询方法this.getSubjects()}else{alert('删除失败!')}}}},
// 计算属性computed:{
// 总页数totalPage(){
// ceil方法,向上取整returnMath.ceil(this.count/this.pageSize)}},
// 侦听器watch:{
// 侦听pageSize的值是否发生变化pageSize(){
// 侦听到每页显示数量发生变化后,重新切换回第一页this.pageIndex=1
// 重新调用获取课程信息的方法this.getSubjects()},
// 侦听pageIndex的值是否发生变化pageIndex(){
// 重新调用获取课程信息的方法this.getSubjects()}},
// 在这个生命周期函数中,发送ajax请求,获取课程数据created(){
// 调用获取课程信息的方法 this.getSubjects()
// 调用获取年级信息的方法this.getGrades()},})