Vue学习二:指令补充、computed计算属性、watch侦听器、案例:水果购物车
一、指令修饰符
通过"."指明一些指令后缀,不同后缀封装了不同的处理操作 → 简化代码
①按键修饰符
@keyup.enter → 键盘回车监听
②v-model修饰符
v-model.trim →去除首尾空格
v-model.number →转数字
③事件修饰符
@事件名.stop →阻止冒泡
@事件名.prevent →阻止默认行为
二、V-bind对样式控制的增强
操作class
语法 :class= "对象/数组"
①对象→键就是类名,值是布尔值。如果值为true,有这个类,否则没有这个类
使用场景:固定类名,来回切换是否使用
<div :class="{类名1:布尔值,类名2:布尔值}"></div>
②数组→数组中所有的类,都会添加到盒子上,本质就是一个class列表
使用场景:批量添加或删除类
<div :class="[类名1,类名2,类名3]"></div>
操作style
语法 :style= "样式属性"
使用场景:具体某个属性的动态设置
<div :style="{CSS属性名1:CSS属性值,CSS属性名2:CSS属性值}"></div>
案例进度条如下,使用layui的进度条

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="./vue.js"></script> <link rel="stylesheet" href="./layui/css/layui.css" media="all"> </head> <body> <div id="box01"> <div class="layui-progress" :style="{width:'300px',height:'50px'}"> <div class="layui-progress-bar" :style="{width:percent+'%',height:'50px'}"></div> </div> <button @click="percent = 25">进度25%</button> <button @click="percent = 50">进度50%</button> <button @click="percent = 75">进度75%</button> <button @click="percent = 100">进度100%</button> </div> <script> const app = new Vue({ el:'#box01', data:{ percent:0, } }) </script> <script src="./layui/layui.js" charset="utf-8"></script> <script> layui.use('element', function(){ var element = layui.element; }); </script> </body> </html>
三、v-model应用于其他表单元素
常见的表单元素都可以用v-model绑定关联→快速获取或设置表单元素的值,它会根据控件类型自动选取正确的方法来更新元素
四、计算属性
概念:基于现有的数据,计算出来的新属性。依赖的数据变化,自动重新计算。
语法:
①声明在computed配置项中,一个计算属性对应一个函数
②使用起来和普通属性一样使用{{计算属性名}}
案例如下

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="./vue.js"></script> <link rel="stylesheet" href="./layui/css/layui.css" media="all"> </head> <body> <div id="box01"> <table> <tr> <th>名字</th> <th>数量</th> </tr> <tr v-for="item in msg" :key="item.id"> <td>{{item.name}}</td> <td>{{item.num}}个</td> </tr> </table> <p>数量总计:{{totalNum}}个</p> </div> <script> const app = new Vue({ el:'#box01', data:{ msg:[ {id:1,name:'篮球',num:4}, {id:2,name:'足球',num:6}, {id:3,name:'排球',num:9} ] }, computed:{ totalNum(){ let totalnum = this.msg.reduce((sum,item)=>sum+item.num,0) return totalnum } } }) </script> <script src="./layui/layui.js" charset="utf-8"></script> </body> </html>
计算属性完整写法
计算属性默认的简写,只能读取访问,不能"修改"。
如果要"修改"→需要写计算属性的完整写法。
computed:{ 计算属性名(){ 一段代码逻辑(计算逻辑) return 结果 } } <!--完整计算属性写法--> computed:{ 计算属性名:{ get(){ 一段代码逻辑(计算逻辑) return 结果 } set(修改的值){ 一段代码逻辑(修改逻辑) } } }
成绩案例

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="./vue.js"></script> <link rel="stylesheet" href="./layui/css/layui.css" media="all"> <style> .tableStyle{ border:1px solid silver; width:400px; height: 200px; margin-left: 50px; text-align: center; } tr,td,th { border: 1px solid silver; align: center; } .red{ color: red; } </style> </head> <body> <div id="box01"> <table class="tableStyle"> <thead> <tr> <th>编号</th> <th>科目</th> <th>成绩</th> <th>操作</th> </tr> </thead> <tbody v-if="list.length > 0"> <tr v-for="(item,index) in list" :key="item.id"> <td>{{index+1}}</td> <td>{{item.subject}}</td> <td :class="{red:item.score < 60}">{{item.score}}</td> <td><a @click="del(item.id)" href="#">删除</a></td> </tr> </tbody> <tbody v-else> <tr> <td colspan="4">暂时无数据</td> </tr> </tbody> <tfoot> <tr> <td colspan="2">总分:{{totalScore}}</td> <td colspan="2">平均分:{{averageScore}}</td> </tr> </tfoot> </table> <form class="layui-form" action=""> <div class="layui-form-item"> <label class="layui-form-label">科目</label> <div class="layui-input-block"> <input v-model="subject" type="text" > </div> </div> <div class="layui-form-item"> <label class="layui-form-label">分数</label> <div class="layui-input-block"> <input v-model="score" type="number"> </div> </div> <div class="layui-form-item"> <div class="layui-input-block"> <button @click="add" class="layui-btn" lay-submit lay-filter="formDemo">添加</button> </div> </div> </form> </div> <script> const app = new Vue({ el:'#box01', data:{ subject:'', score:'', list:[ {id:1,subject:'语文',score:45}, {id:2,subject:'数学',score:65}, {id:3,subject:'英语',score:85}, {id:4,subject:'科学',score:90}, ] }, methods:{ del(id){ this.list = this.list.filter(item=>item.id != id) }, add(){ if(!this.subject){ alert("请输入科目") return } let score = parseFloat(this.score) if(score < 0 || score > 100){ alert("请输入正确的成绩") return } this.list.unshift({ id:+new Date(), subject:this.subject, score:score }) this.subject = '' this.score = '' } }, computed:{ totalScore(){ return this.list.reduce((sum,item)=>sum+item.score,0) }, averageScore(){ return this.list.reduce((sum,item)=>sum+item.score,0)/this.list.length } } }) </script> <script src="./layui/layui.js" charset="utf-8"></script> <script> //Demo layui.use('form', function(){ var form = layui.form; //监听提交 form.on('submit(formDemo)', function(data){ layer.msg(JSON.stringify(data.field)); return false; }); }); </script> </body> </html>
五、watch侦听器
作用:监视数据变化,执行一些业务逻辑或异步操作。
语法:
①简单写法→简单类型数据,直接监视
②完整写法→添加额外配置项
(1) deep: true对复杂类型深度监视
(2) immediate: true初始化立刻执行一次handler方法
watch:{
数据属性名(newValue,oldValue){
一些业务逻辑 或 异步操作
},
'对象.属性名'(newValue,oldValue){
一些业务逻辑 或 异步操作
}
}
案例如下

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="./vue.js"></script> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <link rel="stylesheet" href="./layui/css/layui.css" media="all"> <style> #box01{ margin: 50px 50px; } .translateBox01{ width:200px; height: 200px; background-color: antiquewhite; float: left; } .translateBox02{ width:200px; height: 200px; background-color:beige; float: left; } </style> </head> <body> <div id="box01"> <h3>中文翻译成意大利语</h3> <div> <textarea v-model="obj.words" placeholder="请输入你要翻译的内容" class="translateBox01"></textarea> </div> <div class="translateBox02"> {{result}} </div> </div> <script> const app = new Vue({ el:'#box01', data:{ obj:{ words:'' }, result:'' }, watch:{ 数据属性名(newValue,oldValue){ 一些业务逻辑 或 异步操作 }, '对象.属性名'(newValue,oldValue){ 一些业务逻辑 或 异步操作 } 'obj.words'(newValue){ clearTimeout(this.timer) this.timer = setTimeout(async ()=>{ const res = await axios({ url:'https://applet-base-api-t.itheima.net/api/translate', params:{ words:newValue } }) this.result = res.data.data console.log(res) },300) } } }) </script> <script src="./layui/layui.js" charset="utf-8"></script> </body> </html>
六、水果购物车
html代码

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> <script src="./vue.js"></script> <link rel="stylesheet" href="./layui/css/layui.css" media="all"> <link rel="stylesheet" href="css/shopCart.css" /> </head> <body> <div id="box01"> <table id="cartTable"> <thead> <tr> <th><label><input class="check-all check" type="checkbox"/> 全选</label></th> <th>商品</th> <th>单价</th> <th>数量</th> <th>小计</th> <th>操作</th> </tr> </thead> <tbody> <tr v-for="(item,index) in list" :key="item.id"> <td class="checkbox"> <input v-model="item.isChecked" class="check-one check" type="checkbox" /> </td> <td class="goods"><img :src="item.icon" alt="" /><span></span></td> <td class="price">{{item.price}}</td> <td class="count"> <button class="reduce" @click="item.num--" :disabled="item.num <= 1">-</button> <input v-model="item.num" class="count-input" type="text" /> <button class="add" @click="item.num++">+</button></td> <td class="subtotal">{{item.num * item.price}}</td> <td @click="del(item.id)" class="operation"><span class="delete">删除</span></td> </tr> </tbody> </table> <div class="foot" id="foot"> <label class="fl select-all"><input v-model="isAll" type="checkbox" class="check-all check"/> 全选</label> <div class="fr closing">结 算</div> <div class="fr total">合计:¥<span id="priceTotal">{{totalPrice}}</span></div> <div class="fr selected" id="selected">已选商品 <span id="selectedTotal">{{totalCount}}</span>件 <span class="arrow up">︽</span> <span class="arrow down">︾</span> </div> <div class="selected-view"> <div id="selectedViewList" class="clearfix"> </div> <span class="arrow">◆<span>◆</span></span> </div> </div> </div> <script> const app = new Vue({ el:'#box01', data:{ list:[ { id:1, icon:'./img/beautifulGirl01.PNG', isChecked:false, num:2, price:5999 }, { id:2, icon:'./img/beautifulGirl02.PNG', isChecked:true, num:6, price:3988 }, { id:3, icon:'./img/beautifulGirl03.PNG', isChecked:false, num:9, price:9999 }, { id:4, icon:'./img/beautifulGirl04.PNG', isChecked:false, num:2, price:2689 }, { id:5, icon:'./img/beautifulGirl05.PNG', isChecked:false, num:12, price:4599 }, ] }, methods:{ del(id){ this.list = this.list.filter(item => item.id != id) } }, computed:{ isAll:{ get(){ return this.list.every(item => item.isChecked) }, set(value){ this.list.forEach(item => item.isChecked = value) } }, totalCount(){ return this.list.reduce((sum,item)=> item.isChecked == true ? sum +=item.num:sum,0) }, totalPrice(){ return this.list.reduce((sum,item)=> item.isChecked == true ? sum +=item.num*item.price:sum,0) } } }) </script> <script src="./layui/layui.js" charset="utf-8"></script> </body> </html>
css样式

* { margin: 0; padding: 0; } a { color: #666; text-decoration: none; } body { padding: 20px; color: #666; } .fl { float: left; } .fr { float: right; } table { border-collapse: collapse; border-spacing: 0; border: 0; text-align: center; width: 937px; } th, td { border: 1px solid #CADEFF; } th { background: #e2f2ff; border-top: 3px solid #a7cbff; height: 30px; } td { padding: 10px; color: #444; } tbody tr:hover { background: RGB(238, 246, 255); } .checkbox { width: 60px; } .goods { width: 300px; } .goods span { width: 180px; margin-top: 20px; text-align: left; float: left; } .price { width: 130px; } .count { width: 90px; } .count .add, .count input, .count .reduce { float: left; margin-right: -1px; position: relative; z-index: 0; } .count .add, .count .reduce { height: 23px; width: 17px; border: 1px solid #e5e5e5; background: #f0f0f0; text-align: center; line-height: 23px; color: #444; } .count .add:hover, .count .reduce:hover { color: #f50; z-index: 3; border-color: #f60; cursor: pointer; } .count input { width: 50px; height: 15px; line-height: 15px; border: 1px solid #aaa; color: #343434; text-align: center; padding: 4px 0; background-color: #fff; z-index: 2; } .subtotal { width: 150px; color: red; font-weight: bold; } .operation { width: 80px; } .operation span:hover, a:hover { cursor: pointer; color: red; text-decoration: underline; } img { width: 100px; height: 80px; /*border: 1px solid #ccc;*/ margin-right: 10px; float: left; } .foot { width: 935px; margin-top: 10px; color: #666; height: 48px; border: 1px solid #c8c8c8; background-color: #eaeaea; background-image: linear-gradient(RGB(241, 241, 241), RGB(226, 226, 226)); position: relative; z-index: 8; } .foot div, .foot a { line-height: 48px; height: 48px; } .foot .select-all { width: 100px; height: 48px; line-height: 48px; padding-left: 5px; color: #666; } .foot .closing { border-left: 1px solid #c8c8c8; width: 100px; text-align: center; color: #000; font-weight: bold; background: RGB(238, 238, 238); cursor: pointer; } .foot .total { margin: 0 20px; cursor: pointer; } .foot #priceTotal, .foot #selectedTotal { color: red; font-family: "Microsoft Yahei"; font-weight: bold; } .foot .selected { cursor: pointer; } .foot .selected .arrow { position: relative; top: -3px; margin-left: 3px; } .foot .selected .down { position: relative; top: 3px; display: none; } .show .selected .down { display: inline; } .show .selected .up { display: none; } .foot .selected:hover .arrow { color: red; } .foot .selected-view { width: 935px; border: 1px solid #c8c8c8; position: absolute; height: auto; background: #ffffff; z-index: 9; bottom: 48px; left: -1px; display: none; } .show .selected-view { display: block; } .foot .selected-view div { height: auto; } .foot .selected-view .arrow { font-size: 16px; line-height: 100%; color: #c8c8c8; position: absolute; right: 330px; bottom: -9px; } .foot .selected-view .arrow span { color: #ffffff; position: absolute; left: 0px; bottom: 1px; } #selectedViewList { padding: 20px; margin-bottom: -20px; } #selectedViewList div { display: inline-block; position: relative; width: 100px; height: 80px; border: 1px solid #ccc; margin: 10px; } #selectedViewList div span { display: none; color: #ffffff; font-size: 12px; position: absolute; top: 0px; right: 0px; width: 60px; height: 18px; line-height: 18px; text-align: center; background: RGBA(0, 0, 0, .5); cursor: pointer; } #selectedViewList div:hover span { display: block; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY