个人名片:
😊作者简介:一个为了让更多人看见许舒雅的宝贝的小白先生
🤡个人主页:🔗 许舒雅的宝贝
🐼座右铭:深夜两点半的夜灯依旧闪烁,凌晨四点的闹钟不止你一个。
🎅学习目标: 坚持前端的学习进度,做一个全栈开发工程师
上篇文章没有写完的内容,在此进行补充。
目录
🌟 1.指令修饰符
👉 1.1按键修饰符
👉 1.2v-model修饰符
👉 1.3补充
🌟 2.v-bind对样式控制的增强-操作class
👉 2.1数组语法
👉 2.2对象语法
✍🏻 小案例:京东秒杀-tab栏切换导航高亮
🌟 3.v-bind对样式控制的增强---操作style
👉 3.1语法
👉 3.2代码练习
✍🏻 小案例:进度条案例
🌟 4.v-model在其他表单元素的使用
🌟 5.computed计算属性
👉 5.1概念
👉 5.2语法
👉 5.3注意
👉 5.4案例
🌟 6.computed计算属性和methods方法的区别
👉 6.1computed计算属性
👉 6.2methods计算属性
👉 6.3计算属性的优势
👉 6.4总结
🌟 7.计算属性的完整写法
✍🏻 综合案例:成绩单
🌟 8.watch侦听器
👉 8.1作用
👉 8.2语法
🎉 8.2.1简单写法案例1
🎉 8.2.2简单写法案例2
🎉 8.2.3完整写法案例1(deep的使用)
🎉 8.2.4完整写法案例2(immediate的使用)
👉 8.3总结
✍🏻 综合案例:购物车案例
🌟 1.指令修饰符
在Vue中,指令修饰符是用来改变指令行为的特殊后缀,通过在指令名称后添加一个点(.
)来使用。用来帮助开发者简化判断代码。
👉 1.1按键修饰符
①@keyup.enter:键盘回车监听
②@keyup.tab:触发当用户按下tab键
③@keyup.delete或@keyup.esc:触发当用户按下Delete键(捕获“删除”键和Esc键)
等等,还有.space,up,down,left,right等等修饰符,最常用的已经给大家标注出来啦。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./css/style.css" />
<title>记事本</title>
</head>
<body>
<!-- 主体区域 -->
<section id="app">
<!-- 输入框 -->
<header class="header">
<h1>小黑记事本</h1>
<input @keyup.enter="addTask()" v-model="message" placeholder="请输入任务" class="new-todo" />
<button class="add" @click="addTask()">添加任务</button>
</header>
<!-- 列表区域 -->
<section class="main">
<ul class="todo-list">
<li class="todo" v-for="(item,index) in taskList" :key="item.id">
<div class="view">
<span class="index">{{ index + 1 }}.</span> <label>{{ item.msg }}</label>
<button class="destroy" @click="del(item.id)"></button>
</div>
</li>
</ul>
</section>
<!-- 统计和清空 如果没有任务了,底部隐藏掉-->
<footer class="footer" v-show="taskList.length > 0">
<!-- 统计 -->
<span class="todo-count">合 计:<strong> {{ taskList.length }} </strong></span>
<!-- 清空 -->
<button class="clear-completed" @click="reset()">
清空任务
</button>
</footer>
</section>
<!-- 底部 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '',
taskList: [
{id: 1, msg: '吃饭'},
{id: 2, msg: '跑步'},
{id: 3, msg: '睡觉'},
]
},
methods: {
del(id){
this.taskList = this.taskList.filter(item => item.id !== id)
},
reset(){
this.taskList = []
},
addTask() {
// trim()函数去除字符串左右两边的空格,不去除字符串中的空格
if(this.message.trim() === ''){
alert('请输入任务内容')
return
}
this.taskList.unshift({
id: +new Date(),
msg: this.message
})
this.message = ''
}
},
})
</script>
</body>
</html>
用法就以keyup.enter作为案例来演示一下,这个很好理解,就是在某个地方我们使用了按键修饰符,那么当我们在这个地方使用键盘的按键之后,就会触发绑定的事件,就这么简单。
ok,我们现在思考一下怎么实现的?
当我们按下enter的时候,我们是不是可以拿到这个事件对象,事件对象中是不是有一个key,根据这个key来判断是否去触发事件不就行了。
我们可以在触发方法中加一行代码:console.log(e),方法参数传值e,上篇文章我们说了的,方法可以直接获取事件对象e。在页面F12查看控制台,触发事件观察事件对象的key。
👉 1.2v-model修饰符
①v-model.lazy:默认情况下,v-model在input事件中同步输入框的值与数据,使用.lazy修饰后,v-model则会变成在change事件中同步。
②v-model.number:当用户输入值时,.number修饰符确保输入值被转换为数值类型,如果输入值无法转换为数值,则为字符串。
③v-model.trim:使用.trim修饰符,会自动过滤用户输入的首尾空白字符。这里的.trim和和函数.trim()是不一样的,不要弄混了,虽然功能一样。
🚨 注意:这些修饰符都必须要和v-model结合使用,以满足不同的数据绑定需求。
👉 1.3补充
①@事件名.stop:阻止冒泡,冒泡的意思就是说比如父div包裹了一个子div,父子都有自己的触发事件,当我们点击子div的事件的时候,会出现冒泡到了父div。
②事件名.prevent:阻止默认行为,比如我们有a标签,这个是跳转链接的标签,当我们在这个标签上使用,就无法跳转到该链接。
🚨 注意:@事件名.stop.prevent —>可以连用 即阻止事件冒泡也阻止默认行为
<style>
.father {
width: 200px;
height: 200px;
background-color: pink;
margin-top: 20px;
}
.son {
width: 100px;
height: 100px;
background-color: skyblue;
}
</style>
<div id="app">
<h3>v-model修饰符 .trim .number</h3>
姓名:<input v-model="username" type="text"><br>
年纪:<input v-model="age" type="text"><br>
<h3>@事件名.stop → 阻止冒泡</h3>
<div @click="fatherFn" class="father">
<div @click="sonFn" class="son">儿子</div>
</div>
<h3>@事件名.prevent → 阻止默认行为</h3>
<a @click href="http://www.baidu.com">阻止默认行为</a>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: '',
age: '',
},
methods: {
fatherFn () {
alert('老父亲被点击了')
},
sonFn (e) {
// e.stopPropagation()
alert('儿子被点击了')
}
}
})
</script>
🌟 2.v-bind对样式控制的增强-操作class
为了方便开发者进行样式控制, Vue 扩展了 v-bind 的语法,可以针对 class 类名 和 style 行内样式进行控制 。
👉 2.1数组语法
<div> :class = "对象/数组">这是一个div</div>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./css/style.css" />
<title>demo</title>
</head>
<style>
.box {
width: 200px;
height: 200px;
background-color: #fff;
border: 1px solid #000;
}
.pink {
background-color: pink;
}
.big {
width: 500px;
height: 500px;
}
</style>
<body>
<div id="app">
<!-- <div class="box" :class="{pink: true}">小白先生</div>-->
<div class="box" :class="['pink']">小白先生</div>
</div>
<!-- 底部 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
}
})
</script>
</body>
</html>
测试结果
适用场景:批量添加或删除类
👉 2.2对象语法
当class动态绑定的是对象时,键就是类名,值就是布尔值,如果值是true,就有这个类,否则没有这个类
<div class="box" :class="{ 类名1: 布尔值, 类名2: 布尔值 }"></div>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./css/style.css" />
<title>demo</title>
</head>
<style>
.box {
width: 200px;
height: 200px;
background-color: #fff;
border: 1px solid #000;
}
.pink {
background-color: pink;
}
.big {
width: 500px;
height: 500px;
}
</style>
<body>
<div id="app">
<!-- <div class="box" :class="{pink: true}">小白先生</div>-->
<div class="box" :class="{pink: true,big: true}">小白先生</div>
</div>
<!-- 底部 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
}
})
</script>
</body>
</html>
测试结果
适用场景:一个类名,来回切换
✍🏻 小案例:京东秒杀-tab栏切换导航高亮
需求:当我们点击哪个导航栏时,哪个导航栏高亮。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./css/style.css" />
<title>demo</title>
</head>
<style>
* {
margin: 0;
padding: 0;
}
ul {
display: flex;
border-bottom: 2px solid #e01222;
padding: 0 10px;
}
li {
width: 100px;
height: 50px;
line-height: 50px;
list-style: none;
text-align: center;
}
li a {
display: block;
text-decoration: none;
font-weight: bold;
color: #333333;
}
li a.active {
background-color: #e01222;
color: #fff;
}
</style>
<body>
<div id="app">
<ul>
<li v-for="(item,index) in list" :key="item.id">
<a :class="{active:index===activeIndex}" @click="activeIndex = index " href="#">{{item.name}}</a>
</li>
</ul>
</div>
<!-- 底部 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
activeIndex: 0,
list: [
{ id: 1, name: '京东秒杀' },
{ id: 2, name: '每日特价' },
{ id: 3, name: '品类秒杀' }
]
}
})
</script>
</body>
</html>
测试结果:
思路很简单:
①根据数据适用v-for动态渲染数据
②准备一个下标,记录高亮的是哪一个
③基于下标动态切换class的类名
🌟 3.v-bind对样式控制的增强---操作style
👉 3.1语法
<div class="box" :style="{ CSS属性名1: CSS属性值, CSS属性名2: CSS属性值 }"></div>
👉 3.2代码练习
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./css/style.css" />
<title>demo2</title>
</head>
<style>
.box {
width: 200px;
height: 200px;
background-color: rgb(187, 150, 156);
}
</style>
<body>
<div id="app">
<div class="box" :style="{width: '400px'}"></div>
</div>
<!-- 底部 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
}
})
</script>
</body>
</html>
测试结果:
✍🏻 小案例:进度条案例
点击下面的百分比的按钮,即可生成相应的进度条。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./css/style.css" />
<title>demo2</title>
</head>
<style>
.progress {
height: 25px;
width: 400px;
border-radius: 15px;
background-color: #272425;
border: 3px solid #272425;
box-sizing: border-box;
margin-bottom: 30px;
}
.inner {
width: 50%;
height: 20px;
border-radius: 10px;
text-align: right;
position: relative;
background-color: #409eff;
background-size: 20px 20px;
box-sizing: border-box;
transition: all 1s;
}
.inner span {
position: absolute;
right: -20px;
bottom: -25px;
}
</style>
<body>
<div id="app">
<div class="progress">
<div class="inner" :style="{width:progress+'%'}">
<span>{{progress}}%</span>
</div>
</div>
<button @click="changeProgress(25)">设置25%</button>
<button @click="changeProgress(50)">设置50%</button>
<button @click="changeProgress(75)">设置75%</button>
<button @click="changeProgress(100)">设置100%</button>
</div>
<!-- 底部 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
progress:25
},
methods: {
changeProgress(num){
this.progress = num
}
}
})
</script>
</body>
</html>
测试结果:
🌟 4.v-model在其他表单元素的使用
常见的表单元素都可以用v-model绑定关联-->快速获取或设置表单元素的值,它会根据控件类型自动选取正确的方法来更新元素。
输入框 input:text ——> value
文本域 textarea ——> value
复选框 input:checkbox ——> checked
单选框 input:radio ——> checked
下拉菜单 select ——> value
...
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./css/style.css" />
<title>demo</title>
</head>
<body>
<div id="app">
<h3>个人信息主页</h3>
姓名:
<input type="text" v-model="username">
<br><br>
是否单身:
<input type="checkbox" v-model="isSingle">
<br><br>
<!--
前置理解:
1. name: 给单选框加上 name 属性 可以分组 → 同一组互相会互斥
2. value: 给单选框加上 value 属性,用于提交给后台的数据
并且通常value用具体的标识,一般不会传字符串文本的,一般是男是1,女是2 结合 Vue 使用 → v-model
-->
性别:
<input v-model="gender" type="radio" name="gender" value="1">男
<input v-model="gender" type="radio" name="gender" value="2">女
<br><br>
<!--
前置理解:
1. option 需要设置 value 值,提交给后台
option的value里面城市的编码一般是从后台获取的
2. select 的 value 值,关联了选中的 option 的 value 值
结合 Vue 使用 → v-model
-->
所在城市:
<select v-model="cityId">
<option value="101">北京</option>
<option value="102">上海</option>
<option value="103">成都</option>
<option value="104">南京</option>
</select>
<br><br>
<!-- 文本域和输入框是类似的 -->
自我描述:
<textarea v-model="desc"></textarea>
<button>立即注册</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: '',
isSingle: false,
// 标识女选中
gender: "2",
cityId: '102',
desc: ""
}
})
</script>
</body>
</html>
测试结果:
🌟 5.computed计算属性
👉 5.1概念
基于现有的数据,计算出新属性。依赖的数据变化,自动重新计算。
👉 5.2语法
①声明在computed配置项中,一个计算属性对应一个函数
②使用起来和普通属性一样使用{{计算属性名}}
👉 5.3注意
①computed配置项和data配置项是同级的
②computed中的计算属性虽然是函数的写法,但他依然是个属性
③computed中的计算属性不能和data中的属性同名
④使用computed中的计算属性和使用data中的属性是一样的用法
⑤computed中计算属性内部的this依然指向的是Vue实例
👉 5.4案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./css/style.css" />
<title>demo</title>
</head>
<style>
table {
border: 1px solid #000;
text-align: center;
width: 240px;
}
th,td {
border: 1px solid #000;
}
h3 {
position: relative;
}
</style>
<body>
<div id="app">
<h3>小黑的礼物清单</h3>
<table>
<tr>
<th>名字</th>
<th>数量</th>
</tr>
<tr v-for="(item, index) in list" :key="item.id">
<td>{{ item.name }}</td>
<td>{{ item.num }}个</td>
</tr>
</table>
<!-- 目标:统计求和,求得礼物总数 -->
<p>礼物总数:{{total}} 个</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
// 现有的数据
list: [
{ id: 1, name: '篮球', num: 1 },
{ id: 2, name: '玩具', num: 2 },
{ id: 3, name: '铅笔', num: 5 },
]
},
computed: {
total(){
let sum = 0;
this.list.forEach(item => {
sum += item.num;
});
return sum;
}
}
})
</script>
</body>
</html>
测试结果:
🌟 6.computed计算属性和methods方法的区别
👉 6.1computed计算属性
作用:封装了一段对于数据的处理,求得一个结果
语法:
①写在computed配置项中
②作为属性,直接使用
Ⅰjs中使用计算属性:this.计算属性
Ⅱ模板中使用计算属性:{{计算属性}}
👉 6.2methods计算属性
作用:给Vue实例提供一个方法,调用以处理业务逻辑
语法:
①写在methods配置项中
②作为方法调用
Ⅰjs中调用:this.方法名()
Ⅱ模板中调用{{方法名()}}或者@事件名="方法名"
👉 6.3计算属性的优势
①缓存特性(提升性能):计算属性会对计算出来的结果缓存,在此使用时直接读取缓存,依赖项变化了,会自动重新计算,并再次缓存。
②methods没有缓存特性
👉 6.4总结
①computed有缓存特性,methods没有缓存 ②当一个结果依赖其他多个值时,推荐使用计算属性
③当处理业务逻辑时,推荐使用methods方法,比如事件的处理函数
🌟 7.计算属性的完整写法
计算属性也是属性,能访问,应该也是可以修改的
计算属性默认的简写,只能读取访问,不能“修改”,如果需要修改,需要写计算属性的完整写法。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./css/style.css" />
<title>demo</title>
</head>
<body>
<div id="app">
姓:<input type="text" v-model="firstName"> +
名:<input type="text" v-model="lastName"> =
<span>{{totalName}}</span><br><br>
<button @click="changeName">改名卡</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
firstName: '刘',
lastName: '备'
},
computed: {
totalName:{
// (1) 当fullName计算属性,被获取求值时,执行get(有缓存,优先读缓存)
// 会将返回值作为,求值的结果
get(){
return this.firstName + this.lastName
},
// (2) 当fullName计算属性,被修改赋值时,执行set
// 修改的值,传递给set方法的形参
set(value){
this.firstName = value.slice(0,1)
this.lastName = value.slice(1)
},
}
},
methods: {
changeName(){
this.totalName = '吕小布'
}
}
})
</script>
</body>
</html>
computed的完整写法一般不常用,一般只作为计算,最终展示在页面上,不做修改操作,因为可以看出来,修改的操作还挺麻烦的。
✍🏻 综合案例:成绩单
功能描述:
1.渲染功能 2.删除功能 3.添加功能 4.统计总分,求平均分
思路分析:
1.渲染功能 v-for :key v-bind:动态绑定class的样式
2.删除功能 v-on绑定事件, 阻止a标签的默认行为
3.v-model的修饰符 .trim、 .number、 判断数据是否为空后 再添加、添加后清空文本框的数据
4.使用计算属性computed 计算总分和平均分的值
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./css/index.css" />
<title>Document</title>
</head>
<body>
<div id="app" class="score-case">
<div class="table">
<table>
<thead>
<tr>
<th>编号</th>
<th>科目</th>
<th>成绩</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<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.prevent="deleteData(item.id)" href="#">删除</a></td>
</tr>
</tbody>
<tbody v-if="list.length<=0">
<tr>
<td colspan="5">
<span class="none">暂无数据</span>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5">
<span>总分:{{ totalScope }}</span>
<span style="margin-left: 50px">平均分:{{ average }}</span>
</td>
</tr>
</tfoot>
</table>
</div>
<div class="form">
<div class="form-item">
<div class="label">科目:</div>
<div class="input">
<input
type="text"
placeholder="请输入科目"
v-model.trim="subject"
/>
</div>
</div>
<div class="form-item">
<div class="label">分数:</div>
<div class="input">
<input
type="text"
placeholder="请输入分数"
v-model.trim.number="score"
/>
</div>
</div>
<div class="form-item">
<div class="label"></div>
<div class="input">
<button class="submit" @click="addData()">添加</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
list: [
{ id: 1, subject: '语文', score: 20 },
{ id: 7, subject: '数学', score: 99 },
{ id: 12, subject: '英语', score: 70 },
],
subject: '',
score: ''
},
methods: {
deleteData(id){
this.list = this.list.filter(item => item.id !== id)
},
addData(){
if(!this.subject || !this.score){
alert('请输入数据!输入框不可为空!')
return
}
//unshift将元素把到数组的最前面,后面的元素一次往后移一位
this.list.unshift(
{
id: +new Date(),
subject: this.subject,
score: this.score
}
)
this.subject = ''
this.score = ''
}
},
computed: {
totalScope(){
return this.list.reduce((prev,item) => prev + Number(item.score),0)
},
average(){
return this.list.reduce((prev,item) => prev + Number(item.score),0) / this.list.length
}
}
})
</script>
</body>
</html>
样式文件css:
.score-case {
width: 1000px;
margin: 50px auto;
display: flex;
}
.score-case .table {
flex: 4;
}
.score-case .table table {
width: 100%;
border-spacing: 0;
border-top: 1px solid #ccc;
border-left: 1px solid #ccc;
}
.score-case .table table th {
background: #f5f5f5;
}
.score-case .table table tr:hover td {
background: #f5f5f5;
}
.score-case .table table td,
.score-case .table table th {
border-bottom: 1px solid #ccc;
border-right: 1px solid #ccc;
text-align: center;
padding: 10px;
}
.score-case .table table td.red,
.score-case .table table th.red {
color: red;
}
.score-case .table .none {
height: 100px;
line-height: 100px;
color: #999;
}
.score-case .form {
flex: 1;
padding: 20px;
}
.score-case .form .form-item {
display: flex;
margin-bottom: 20px;
align-items: center;
}
.score-case .form .form-item .label {
width: 60px;
text-align: right;
font-size: 14px;
}
.score-case .form .form-item .input {
flex: 1;
}
.score-case .form .form-item input,
.score-case .form .form-item select {
appearance: none;
outline: none;
border: 1px solid #ccc;
width: 200px;
height: 40px;
box-sizing: border-box;
padding: 10px;
color: #666;
}
.score-case .form .form-item input::placeholder {
color: #666;
}
.score-case .form .form-item .cancel,
.score-case .form .form-item .submit {
appearance: none;
outline: none;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 10px;
margin-right: 10px;
font-size: 12px;
background: #ccc;
}
.score-case .form .form-item .submit {
border-color: #069;
background: #069;
color: #fff;
}
测试结果:
这个代码可能不是最简单的和简短的,我只是为了练习之前学的一些内容,所以我是按照我的学习进度写的代码,大家可能有更好的解决方案的。
🌟 8.watch侦听器
👉 8.1作用
监视数据变化,执行一些业务逻辑或异步操作。
👉 8.2语法
①watch声明在data同级的配置项中
②简单写法:简单类型数据直接监视
③完整写法:添加额外的配置项
<script>
const app = new Vue({
el: "#app",
data: {
project: "语文",
obj: {
name: "张三",
},
},
watch: {
//该方法会在数据变化,触发执行
数据属性名(newValue, oldValue) {
一些业务逻辑或异步操作;
},
"对象.属性名"(newValue, oldValue) {
一些业务逻辑或异步操作;
},
},
});
</script>
🎉 8.2.1简单写法案例1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">
<textarea name="" v-model="project"></textarea>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
project: "语文",
},
watch: {
//该方法会在数据变化,触发执行
//newValue新值,oldValue老值(一般不用)
project(newValue, oldValue) {
console.log("变化前的值:", newValue, "变化后的值:", oldValue);
},
},
});
</script>
</html>
测试结果:
🎉 8.2.2简单写法案例2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">
<textarea name="" v-model="obj.project"></textarea>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
obj: {
project: "语文"
}
},
watch: {
//该方法会在数据变化,触发执行
//newValue新值,oldValue老值(一般不用)
"obj.project"(newValue, oldValue) {
console.log("变化前的值:", newValue, "变化后的值:", oldValue);
},
},
});
</script>
</html>
测试结果:
🎉 8.2.3完整写法案例1(deep的使用)
1.deep:true对复杂类型深度监视(监视对象中的所有属性)
2.immediate:true 初始化立刻执行一次handler方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">
<select v-model="obj.country">
<option value="italy">意大利</option>
<option value="english">英语</option>
<option value="german">德语</option>
</select>
<textarea name="" v-model="obj.project"></textarea>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
obj: {
project: "语文",
country: "english",
},
},
watch: {
//完整写法不是一个方法,是一个对象
obj: {
deep: true,
handler(newValue) {
console.log("对象被改变了", newValue);
},
},
},
});
</script>
</html>
测试结果:
🎉 8.2.4完整写法案例2(immediate的使用)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">
<select v-model="obj.country">
<option value="italy">意大利</option>
<option value="english">英语</option>
<option value="german">德语</option>
</select>
<textarea name="" v-model="obj.project"></textarea>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
obj: {
project: "语文",
country: "english",
},
},
watch: {
//完整写法不是一个方法,是一个对象
obj: {
deep: true,
immediate: true,
handler(newValue) {
console.log("对象被改变了", newValue);
},
},
},
});
</script>
</html>
测试结果:
👉 8.3总结
watch侦听器的写法有几种?
简单写法
watch: {
数据属性名 (newValue, oldValue) {
一些业务逻辑 或 异步操作。
},
'对象.属性名' (newValue, oldValue) {
一些业务逻辑 或 异步操作。
}
}
完整写法
watch: {// watch 完整写法
数据属性名: {
deep: true, // 深度监视(针对复杂类型)
immediate: true, // 是否立刻执行一次handler
handler (newValue) {
console.log(newValue)
}
}
}
✍🏻 综合案例:购物车案例
需求说明:
-
渲染功能
-
删除功能
-
修改个数
-
全选反选
-
统计 选中的 总价 和 总数量
-
持久化到本地
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./css/inputnumber.css" />
<link rel="stylesheet" href="./css/index.css" />
<title>购物车</title>
</head>
<body>
<div class="app-container" id="app">
<!-- 顶部banner -->
<div class="banner-box"><img src="./img/fruit.jpg" alt="" /></div>
<!-- 面包屑 -->
<div class="breadcrumb">
<span>🏠</span>
/
<span>购物车</span>
</div>
<!-- 购物车主体 -->
<div v-if="fruitList.length > 0" class="main">
<div class="table">
<!-- 头部 -->
<div class="thead">
<div class="tr">
<div class="th">选中</div>
<div class="th th-pic">图片</div>
<div class="th">单价</div>
<div class="th num-th">个数</div>
<div class="th">小计</div>
<div class="th">操作</div>
</div>
</div>
<!-- 身体 -->
<div class="tbody">
<div v-for="(item,index) in fruitList" :key="item.id" class="tr" :class="{active: item.isChecked}">
<div class="td"><input type="checkbox" v-model="item.isChecked" /></div>
<div class="td"><img :src="item.icon" alt="" /></div>
<div class="td">{{ item.price }}</div>
<div class="td">
<div class="my-input-number">
<button :disabled="item.num < 1 " @click="sub(item.id)" class="decrease"> - </button>
<span class="my-input__inner">{{ item.num }}</span>
<button @click="add(item.id)" class="increase"> + </button>
</div>
</div>
<div class="td">{{ item.num*item.price}}</div>
<div class="td"><button @click="del(item.id)">删除</button></div>
</div>
</div>
</div>
<!-- 底部 -->
<div class="bottom">
<!-- 全选 -->
<label class="check-all">
<input v-model="isAll" type="checkbox" />
全选
</label>
<div class="right-box">
<!-- 所有商品总价 -->
<span class="price-box">总价 : ¥ <span class="price">{{totalPrice}}</span></span>
<!-- 结算按钮 -->
<button class="pay">结算( {{ totalCount }} )</button>
</div>
</div>
</div>
<!-- 空车 -->
<div v-else class="empty">🛒空空如也</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const defaultArr = [{
id: 1,
icon: './img/火龙果.png',
isChecked: true,
num: 2,
price: 6,
},
{
id: 2,
icon: './img/荔枝.png',
isChecked: false,
num: 7,
price: 20,
},
{
id: 3,
icon: './img/榴莲.png',
isChecked: false,
num: 3,
price: 40,
},
{
id: 4,
icon: './img/鸭梨.png',
isChecked: true,
num: 10,
price: 3,
},
{
id: 5,
icon: './img/樱桃.png',
isChecked: false,
num: 20,
price: 34,
},
]
const app = new Vue({
el: '#app',
data: {
// 水果列表
fruitList: JSON.parse(localStorage.getItem('list')) || defaultArr,
},
computed: {
isAll: {
//获取计算属性
get() {
return this.fruitList.every(item => item.isChecked === true);
},
//设置计算属性并下发给小选项
//当你直接对复选框进行修改时,即修改计算属性,那么修改的值会传入到set的形参当中
set(value) {
this.fruitList.forEach(item => item.isChecked = value);
}
},
//统计水果总价
totalPrice() {
return this.fruitList.reduce((sum, item) => {
if (item.isChecked) {
sum = sum + item.num*item.price;
return sum;
} else {
return sum;
}
}, 0)
},
//统计水果总数量
totalCount() {
return this.fruitList.reduce((sum, item) => {
if (item.isChecked) {
sum = sum + item.num;
return sum;
} else {
return sum;
}
}, 0)
}
},
methods: {
//删除某一行水果
del: function (id) {
this.fruitList = this.fruitList.filter(item => item.id != id)
},
//将某一行的水果的数量增加
add: function (id) {
//1.找到对应id的水果
const fruit = this.fruitList.find(item => item.id === id);
//2.添加水果个数
fruit.num++
},
//减少水果数量
sub: function (id) {
//1.找到对应id的水果
const fruit = this.fruitList.find(item => item.id === id);
//2.减少水果个数
fruit.num--
}
},
watch:{
fruitList:{
deep:true,
handler(newValue){
//将变化后的newValue存入本地
localStorage.setItem('list',JSON.stringify(newValue))
}
}
}
})
</script>
</body>
</html>
css文件:
.app-container {
padding-bottom: 300px;
width: 800px;
margin: 0 auto;
}
@media screen and (max-width: 800px) {
.app-container {
width: 600px;
}
}
.app-container .banner-box {
border-radius: 20px;
overflow: hidden;
margin-bottom: 10px;
}
.app-container .banner-box img {
width: 100%;
}
.app-container .nav-box {
background: #ddedec;
height: 60px;
border-radius: 10px;
padding-left: 20px;
display: flex;
align-items: center;
}
.app-container .nav-box .my-nav {
display: inline-block;
background: #5fca71;
border-radius: 5px;
width: 90px;
height: 35px;
color: white;
text-align: center;
line-height: 35px;
margin-right: 10px;
}
.breadcrumb {
font-size: 16px;
color: gray;
}
.table {
width: 100%;
text-align: left;
border-radius: 2px 2px 0 0;
border-collapse: separate;
border-spacing: 0;
}
.th {
color: rgba(0, 0, 0, 0.85);
font-weight: 500;
text-align: left;
background: #fafafa;
border-bottom: 1px solid #f0f0f0;
transition: background 0.3s ease;
}
.th.num-th {
flex: 1.5;
}
.th {
text-align: center;
}
.th:nth-child(4),
.th:nth-child(5),
.th:nth-child(6),
.th:nth-child(7) {
text-align: center;
}
.th.th-pic {
flex: 1.3;
}
.th:nth-child(6) {
flex: 1.3;
}
.th,
.td {
position: relative;
padding: 16px 16px;
overflow-wrap: break-word;
flex: 1;
}
.pick-td {
font-size: 14px;
}
.main,
.empty {
border: 1px solid #f0f0f0;
margin-top: 10px;
}
.tr {
display: flex;
cursor: pointer;
border-bottom: 1px solid #ebeef5;
}
.tr.active {
background-color: #f5f7fa;
}
.td {
display: flex;
justify-content: center;
align-items: center;
}
.table img {
width: 100px;
height: 100px;
}
button {
outline: 0;
box-shadow: none;
color: #fff;
background: #d9363e;
border-color: #d9363e;
color: #fff;
background: #d9363e;
border-color: #d9363e;
line-height: 1.5715;
position: relative;
display: inline-block;
font-weight: 400;
white-space: nowrap;
text-align: center;
background-image: none;
border: 1px solid transparent;
box-shadow: 0 2px 0 rgb(0 0 0 / 2%);
cursor: pointer;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
touch-action: manipulation;
height: 32px;
padding: 4px 15px;
font-size: 14px;
border-radius: 2px;
}
button.pay {
background-color: #3f85ed;
margin-left: 20px;
}
.bottom {
height: 60px;
display: flex;
align-items: center;
justify-content: space-between;
padding-right: 20px;
border: 1px solid #f0f0f0;
border-top: none;
padding-left: 20px;
}
.right-box {
display: flex;
align-items: center;
}
.check-all {
cursor: pointer;
}
.price {
color: hotpink;
font-size: 30px;
font-weight: 700;
}
.price-box {
display: flex;
align-items: center;
}
.empty {
padding: 20px;
text-align: center;
font-size: 30px;
color: #909399;
}
.my-input-number {
display: flex;
}
.my-input-number button {
height: 40px;
color: #333;
border: 1px solid #dcdfe6;
background-color: #f5f7fa;
}
.my-input-number button:disabled {
cursor: not-allowed!important;
}
.my-input-number .my-input__inner {
height: 40px;
width: 50px;
padding: 0;
border: none;
border-top: 1px solid #dcdfe6;
border-bottom: 1px solid #dcdfe6;
}
.my-input-number {
position: relative;
display: inline-block;
width: 140px;
line-height: 38px;
}
.my-input-number span {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
.my-input-number .my-input {
display: block;
position: relative;
font-size: 14px;
width: 100%;
}
.my-input-number .my-input__inner {
-webkit-appearance: none;
background-color: #fff;
background-image: none;
border-radius: 4px;
border: 1px solid #dcdfe6;
box-sizing: border-box;
color: #606266;
display: inline-block;
font-size: inherit;
height: 40px;
line-height: 40px;
outline: none;
padding: 0 15px;
transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
width: 100%;
padding-left: 50px;
padding-right: 50px;
text-align: center;
}
.my-input-number .my-input-number__decrease,
.my-input-number .my-input-number__increase {
position: absolute;
z-index: 1;
top: 1px;
width: 40px;
height: auto;
text-align: center;
background: #f5f7fa;
color: #606266;
cursor: pointer;
font-size: 13px;
}
.my-input-number .my-input-number__decrease {
left: 1px;
border-radius: 4px 0 0 4px;
border-right: 1px solid #dcdfe6;
}
.my-input-number .my-input-number__increase {
right: 1px;
border-radius: 0 4px 4px 0;
border-left: 1px solid #dcdfe6;
}
.my-input-number .my-input-number__decrease.is-disabled,
.my-input-number .my-input-number__increase.is-disabled {
color: #c0c4cc;
cursor: not-allowed;
}
实现思路:
1.基本渲染: v-for遍历、:class动态绑定样式
2.删除功能 : v-on 绑定事件,获取当前行的id
3.修改个数 : v-on绑定事件,获取当前行的id,进行筛选出对应的项然后增加或减少
4.全选反选
必须所有的小选框都选中,全选按钮才选中 → every
如果全选按钮选中,则所有小选框都选中
如果全选取消,则所有小选框都取消选中
声明计算属性,判断数组中的每一个checked属性的值,看是否需要全部选
5.统计 选中的 总价 和 总数量 :通过计算属性来计算选中的总价和总数量
6.持久化到本地: 在数据变化时都要更新下本地存储 watch。
测试结果:
这篇文章就到这里了,下次见!
🥇原创不易,还希望各位大佬支持一下!
👍点赞,你的认可是我创作的动力 !
🌟收藏,你的青睐是我努力的方向!
✏️评论,你的意见是我进步的财富!