1. 指令补充
1.1 指令修饰符
1.1.1 按键修饰符
<!-- 指令修饰符: 通过"."指明一些指令后缀,不同后缀封装了不同的处理操作 → 简化代码 -->
<body>
<div id="app">
<!-- 1. 按键修饰符 -->
<h3>@keyup.enter → 监听键盘回车事件</h3>
<!-- <input v-model="username" type="text"> -->
<input type="text" @keyup="f1">
<input type="text" @keyup.enter="f2">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: ''
},
methods: {
f1(e) {
if (e.key === 'Enter') {
console.log('原来,按下回车键')
}
},
f2() {
console.log('指令修饰符,按下回车')
}
}
})
</script>
</body>
1.1.2 v-model 修饰符
1.1.3 事件修饰符
<!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">
<title>Document</title>
<style>
.father {
width: 200px;
height: 200px;
background-color: pink;
margin-top: 20px;
}
.son {
width: 100px;
height: 100px;
background-color: skyblue;
}
</style>
</head>
<body>
<div id="app">
<!-- 2. v-model修饰符 -->
<h3>v-model修饰符 .trim .number</h3>
<!-- v-model.trim 去除首尾空格 -->
姓名:<input v-model.trim="username" type="text"><br>
<!-- v-model.number 转数字 -->
年纪:<input v-model.number="age" type="text"><br>
<!-- 3. 事件修饰符 -->
<h3>@事件名.stop → 阻止冒泡</h3>
<div @click="fatherFn" class="father">
<div @click.stop="sonFn" class="son">儿子</div>
</div>
<h3>@事件名.prevent → 阻止默认行为</h3>
<a @click.prevent 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>
</body>
</html>
1.2 v-bind 对于样式操作的增强
1.2.1 操作 class
<!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">
<title>Document</title>
<style>
.box {
width: 200px;
height: 200px;
border: 3px solid #000;
font-size: 30px;
margin-top: 10px;
}
.pink {
background-color: pink;
}
.big {
width: 300px;
height: 300px;
}
</style>
</head>
<!--
v-bind 对于样式控制的增强
为了方便开发者进行样式控制,Vue扩展了v-bind的语法,可以针对class类名和style行内样式进行控制 。
-->
<!--
1. 操作class
语法 :class = "对象/数组"
1.1 对象 → 键就是类名,值是布尔值。如果值为 true,有这个类,否则没有这个类
<div class="box" :class="{ 类名1: 布尔值, 类名2: 布尔值 }"></div>
适用场景:一个类名,来回切换
1.2 数组 → 数组中所有的类,都会添加到盒子上,本质就是一个 class 列表
<div class="box" :class="[ 类名1, 类名2, 类名3 ]"></div>
适用场景:批量添加或删除类
-->
<body>
<div id="app">
<div class="box" :class="{pink:pinkFlag,big:bigFlag}">黑马程序员</div>
<div class="box" :class="['pink','big']">黑马程序员</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
pinkFlag: true,
bigFlag: false
}
})
</script>
</body>
</html>
案例1 - 京东秒杀 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">
<title>Document</title>
<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>
</head>
<!--
案例:京东秒杀 tab 导航高亮
核心思路:
1. 基于数据动态渲染 tab → v-for
2. 准备下标记录高亮的是哪一个 tab → activeIndex
3. 基于下标,动态控制 class 类名 → v-bind:class
-->
<body>
<div id="app">
<ul>
<!-- <li><a class="active" href="#">京东秒杀</a></li>
<li><a href="#">每日特价</a></li>
<li><a href="#">品类秒杀</a></li> -->
<li v-for="(item,index) in list" :key="item.id" @click="num=index">
<a href="#" :class="{active: num===index}">{{item.name}}</a>
</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
num: 0,
list: [
{ id: 1, name: '京东秒杀' },
{ id: 2, name: '每日特价' },
{ id: 3, name: '品类秒杀' }
]
}
})
</script>
</body>
</html>
1.2.2 操作 style
<!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">
<title>Document</title>
<style>
.box {
width: 200px;
height: 200px;
background-color: rgb(187, 150, 156);
}
</style>
</head>
<!--
2. 操作style
语法 :style = "样式对象"
<div class="box" :style="{ CSS属性名1: CSS属性值, CSS属性名2: CSS属性值 }"></div>
适用场景:某个具体属性的动态设置
-->
<body>
<div id="app">
<div class="box"></div>
<!-- 带中横杠的属性,要变小驼峰; 如 background-color → backgroundColor -->
<div class="box" :style="{width: w,height: h+'px',backgroundColor: myColor,}"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
w: '100px',
h: 100,
myColor: 'red',
}
})
</script>
</body>
</html>
案例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">
<title>Document</title>
<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>
</head>
<body>
<div id="app">
<div class="progress">
<div class="inner" :style="{width:num+'%'}">
<span>{{num}}%</span>
</div>
</div>
<button @click="num=25">设置25%</button>
<button @click="num=50">设置50%</button>
<button @click="num=75">设置75%</button>
<button @click="num=100">设置100%</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
num: 5,
}
})
</script>
</body>
</html>
1.3 v-model 应用于其他表单元素
<!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">
<title>Document</title>
<style>
textarea {
display: block;
width: 240px;
height: 100px;
margin: 10px 0;
}
</style>
</head>
<!--
v-model 应用于其他表单元素
v-model → 快速 获取 或 设置 表单元素的值
v-model 会根据 控件类型 自动选取 正确的方法 来更新元素
输入框 input:text → value
文本域 textarea → value
复选框 input:checkbox → checked
单选框 input:radio → checked
下拉菜单 select → value
-->
<body>
<div id="app">
<h3>小黑学习网</h3>
姓名:
<input type="text" v-model.trim="username">
<br><br>
是否单身:
<input type="checkbox" v-model="isSingle">
<br><br>
<!--
前置理解:
1. name: 给单选框加上 name 属性 可以分组 → 同一组互相会互斥
2. value: 给单选框加上 value 属性,用于提交给后台的数据
结合 Vue 使用 → v-model
-->
性别:
<input type="radio" name="gender" value="0" v-model="gender">男
<input type="radio" name="gender" value="1" v-model="gender">女
<br><br>
<!--
前置理解:
1. option 需要设置 value 值,提交给后台
2. select 的 value 值,关联了选中的 option 的 value 值
结合 Vue 使用 → v-model
-->
所在城市:
<select v-model="selCity">
<option value="bj">北京</option>
<option value="sh">上海</option>
<option value="cd">成都</option>
<option value="nj">南京</option>
</select>
<br><br>
自我描述:
<textarea></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: true,
gender: 1,
selCity: 'sh'
}
})
</script>
</body>
</html>
2. computed 计算属性
2.1 基础语法
<!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">
<title>Document</title>
<style>
table {
border: 1px solid #000;
text-align: center;
width: 240px;
}
th,
td {
border: 1px solid #000;
}
h3 {
position: relative;
}
</style>
</head>
<!--
计算属性 computed
概念:基于现有的数据,计算出来的新属性。依赖的数据变化,自动重新计算。
语法:
1. 声明在 computed 配置项中,一个计算属性对应一个函数
2. 使用起来和普通属性一样使用 {{ 计算属性名 }}
计算属性 → 可以将一段 求值的代码 进行封装
-->
<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>
<!-- 目标:统计求和,求得礼物总数 reduce? -->
<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() {
// arr.reduce(function(上一次值,当前值){}, 初始值)
return this.list.reduce((sum, item) => sum += item.num, 0)
},
}
})
</script>
</body>
</html>
2.2 计算属性 vs 方法
<!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">
<title>Document</title>
<style>
table {
border: 1px solid #000;
text-align: center;
width: 300px;
}
th,
td {
border: 1px solid #000;
}
h3 {
position: relative;
}
span {
position: absolute;
left: 145px;
top: -4px;
width: 16px;
height: 16px;
color: white;
font-size: 12px;
text-align: center;
border-radius: 50%;
background-color: #e63f32;
}
</style>
</head>
<!--
computed 计算属性 vs methods 方法
1. computed 计算属性:
作用:封装了一段对于数据的处理,求得一个结果。
作为属性,直接使用 → this.计算属性 {{ 计算属性 }}
2. methods 方法:
作用:给实例提供一个方法,调用以处理业务逻辑。
作为方法,需要调用 → this.方法名( ) {{ 方法名() }} @事件名="方法名"
缓存特性(提升性能):
计算属性会对计算出来的结果缓存,再次使用直接读取缓存,
依赖项变化了,会自动重新计算 → 并再次缓存
-->
<body>
<div id="app">
<h3>小黑的礼物清单🛒<span>{{ totalCount }}</span></h3>
<h3>小黑的礼物清单🛒<span>{{ fn() }}</span></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>礼物总数:{{ totalCount }} 个</p>
<p>礼物总数:{{ fn() }} 个</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: 3 },
{ id: 2, name: '玩具', num: 2 },
{ id: 3, name: '铅笔', num: 5 },
]
},
computed: {
totalCount() {
console.log('计算属性 computed')
let total = this.list.reduce((sum, item) => sum + item.num, 0)
return total
}
},
methods: {
fn() {
console.log('方法 methods')
let total = this.list.reduce((sum, item) => sum + item.num, 0)
return total
}
}
})
</script>
</body>
</html>
2.3 计算属性 完整写法
<!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">
<title>Document</title>
</head>
<!--
计算属性完整写法
计算属性默认的简写,只能读取访问,不能 "修改"。
如果要 "修改" → 需要写计算属性的完整写法。
computed: {
计算属性名: {
get() {
一段代码逻辑(计算逻辑)
return 结果
},
set(修改的值) {
一段代码逻辑(修改逻辑)
}
}
}
-->
<body>
<div id="app">
姓:<input type="text" v-model="firstName"><br>
名:<input type="text" v-model="secondName"><br>
<p>姓名:{{nameFn}}</p>
<button @click="fn">修改姓名</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
firstName: '',
secondName: ''
},
computed: {
nameFn: {
// 读取 - 依赖别的数据计算结果
get() {
return this.firstName + this.secondName
},
// 修改 - 形参自动获取修改后的结果
set(value) {
// this.firstName = value.substring(0, 1)
// this.secondName = value.substring(1)
this.firstName = value.slice(0, 1)
this.secondName = value.slice(1)
}
}
},
methods: {
fn() {
// this.nameFn = '宋江'
this.nameFn = '林俊杰'
}
},
})
</script>
</body>
</html>
2.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="./styles/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 v-if="list.length>0">
<!-- <tr>
<td>1</td>
<td>语文</td>
<td class="red">46</td>
<td><a href="#">删除</a></td>
</tr>
<tr>
<td>2</td>
<td>英语</td>
<td>80</td>
<td><a href="#">删除</a></td>
</tr>
<tr>
<td>3</td>
<td>数学</td>
<td>100</td>
<td><a href="#">删除</a></td>
</tr> -->
<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 href="#" @click="del(item.id)">删除</a></td>
</tr>
</tbody>
<tbody v-else>
<tr>
<td colspan="5">
<span class="none">暂无数据</span>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5">
<span>总分:{{total}}</span>
<!-- toFixed(数字):保留指定的小数位数(四舍五入) -->
<!-- <span style="margin-left: 50px">平均分:{{ (total/list.length).toFixed(2) || '0' }}</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="add">添加</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: {
del(id) {
this.list = this.list.filter(item => item.id !== id)
},
add() {
if (this.subject === '' || this.score === '') {
alert('请输入内容')
return
}
this.list.unshift({
id: +new Date(),
subject: this.subject,
score: this.score
})
this.subject = ''
this.score = ''
}
},
computed: {
total() {
return this.list.reduce((sum, item) => sum += item.score, 0)
},
average() {
// toFixed(数字):保留指定的小数位数(四舍五入); NaN时 toFixed会转为'NaN',字符串'NaN'隐式转换为true 逻辑中断效果未达到预期
// return (this.total / this.list.length).toFixed(2) || '0'
// 使用Math对象的方法来对数字进行四舍五入并保留两位小数 → Math.round(num * 100) / 100
return Math.round(this.total / this.list.length * 100) / 100 || 0
}
}
})
// || 逻辑中断
// 如果左侧的表达式为“假值”,则返回右侧的表达式;否则,返回左侧的表达式。
console.log(0 || 1) // 1
console.log(NaN || 0) // 0
console.log(undefined || NaN) // NaN
</script>
</body>
</html>
3. watch 侦听器
<!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" />
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-size: 18px;
}
#app {
padding: 10px 20px;
}
.query {
margin: 10px 0;
}
.box {
display: flex;
}
textarea {
width: 300px;
height: 160px;
font-size: 18px;
border: 1px solid #dedede;
outline: none;
resize: none;
padding: 10px;
}
textarea:hover {
border: 1px solid #1589f5;
}
.transbox {
width: 300px;
height: 160px;
background-color: #f0f0f0;
padding: 10px;
border: none;
}
.tip-box {
width: 300px;
height: 25px;
line-height: 25px;
display: flex;
}
.tip-box span {
flex: 1;
text-align: center;
}
.query span {
font-size: 18px;
}
.input-wrap {
position: relative;
}
.input-wrap span {
position: absolute;
right: 15px;
bottom: 15px;
font-size: 12px;
}
.input-wrap i {
font-size: 20px;
font-style: normal;
}
</style>
</head>
<!--
watch 侦听器(监视器)
作用:监视数据变化,执行一些 业务逻辑 或 异步操作。
语法:
1. 简单写法 → 简单类型数据,直接监视
watch: {
// 该方法会在数据变化时,触发执行
数据属性名(newValue, oldValue) {
一些业务逻辑 或 异步操作。
},
'对象.属性名'(newValue, oldValue) {
一些业务逻辑 或 异步操作。
}
}
2. 完整写法 → 添加额外配置项(深度监视复杂类型,立刻执行))
deep: true 对复杂类型深度监视
immediate: true 初始化立刻执行一次handler方法
watch: {
数据属性名: {
deep: true,
handler(newValue) {
console.log(newValue)
}
}
}
-->
<body>
<div id="app">
<!-- 条件选择框 -->
<div class="query">
<span>翻译成的语言:</span>
<select v-model="obj.language">
<option value="italy">意大利</option>
<option value="english">英语</option>
<option value="german">德语</option>
</select>
</div>
<!-- 翻译框 -->
<div class="box">
<div class="input-wrap">
<!-- <textarea v-model="words"></textarea> -->
<textarea v-model="obj.words"></textarea>
<span><i>⌨️</i>文档翻译</span>
</div>
<div class="output-wrap">
<div class="transbox">{{txt}}</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
// 接口地址:https://applet-base-api-t.itheima.net/api/translate
// 请求方式:get
// 请求参数:
// (1)words:需要被翻译的文本(必传)
// (2)lang: 需要被翻译成的语言(可选)默认值-意大利
// -----------------------------------------------
const app = new Vue({
el: '#app',
data: {
// words: '',
obj: { words: '苹果', language: 'italy' },
txt: '',
// timer: -1
},
// 具体讲解:(1) watch语法 (2) 具体业务实现
watch: {
// 1. 简单写法
/* words(newVal, oldVal) {
// console.log('words', newVal, oldVal)
}, */
/* 'obj.words'(newVal) {
// console.log('obj.words', newVal)
// this.timer → timer也是挂载到了Vue实例上,只不过是普通变量,不是响应式数据
clearTimeout(this.timer)
this.timer = setTimeout(async () => {
const res = await axios.get(
'https://applet-base-api-t.itheima.net/api/translate',
{ params: { words: this.obj.words } }
)
this.txt = res.data.data
}, 500)
}, */
// 2. 完整写法
// 语言变 重新发请求 监听整个对象
// obj(){}写法错误,简单数据可直接监听;复杂数据需完整写法
obj: {
// deep: true - 对复杂类型深度监视
deep: true,
// immediate: true - 初始化立刻执行一次handler方法
immediate: true,
handler(newVal) {
clearTimeout(this.timer)
this.timer = setTimeout(async () => {
const res = await axios.get(
'https://applet-base-api-t.itheima.net/api/translate',
{ params: { words: this.obj.words } }
)
this.txt = res.data.data
}, 500)
},
}
},
})
</script>
</body>
</html>
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/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 class="main" v-if="fruitList.length>0">
<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 class="tr" v-for="(item,index) in fruitList" :key="item.id" :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" class="decrease" @click="item.num--"> - </button>
<span class="my-input__inner">{{item.num}}</span>
<button class="increase" @click="item.num++"> + </button>
</div>
</div>
<div class="td">{{item.price*item.num}}</div>
<div class="td"><button @click="del(item.id)">删除</button></div>
</div>
</div>
</div>
<!-- 底部 -->
<div class="bottom">
<!-- 全选 -->
<label class="check-all">
<input type="checkbox" v-model="isSelectAll" />
全选
</label>
<div class="right-box">
<!-- 所有商品总价 -->
<span class="price-box">总价 : ¥ <span class="price">{{totalPrice}}</span></span>
<!-- 结算按钮 -->
<button class="pay">结算( {{totalCount}} )</button>
</div>
</div>
</div>
<!-- 空车 -->
<div class="empty" v-else>🛒空空如也</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
// 水果列表
fruitList: JSON.parse(localStorage.getItem('fruit')) || [
{
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,
},
],
},
methods: {
del(id) {
this.fruitList = this.fruitList.filter(item => item.id !== id)
}
},
// 计算属性 依赖别的数据计算
computed: {
// 1. 正确写法
/* totalCount() {
return this.fruitList.reduce((sum, item) => {
if (item.isChecked) {
sum += item.num
}
return sum
}, 0);
}, */
// 2. 化简写法
totalCount() {
return this.fruitList.reduce((sum, item) => {
return item.isChecked ? sum + item.num : sum
}, 0);
},
// 3. 错误写法及原因
/* totalCount() {
return this.fruitList.reduce((sum, item) => {
if (item.isChecked) {
// sum += item.num
// return sum
// 如果 item.isChecked 为 false,reduce 方法没有返回值,那么它会直接跳过这个 item。
// reduce 需要始终返回一个累加器,否则它会返回 undefined,导致最终的结果错误。
// 解决办法是:无论条件是否匹配,都需要返回 sum。
return sum += item.num
}
}, 0);
}, */
// 4. 答案写法
/* totalCount() {
return this.fruitList.reduce((sum, item) => {
if (item.isChecked) {
// 选中 → 需要累加
return sum + item.num
} else {
// 没选中 → 不需要累加
return sum
}
}, 0)
}, */
/* totalPrice() {
return this.fruitList.reduce((sum, item) => {
if (item.isChecked) {
sum += item.price * item.num
}
return sum
}, 0);
}, */
totalPrice() {
return this.fruitList.reduce((sum, item) => {
return item.isChecked ? sum + item.price * item.num : sum
}, 0);
},
isSelectAll: {
get() {
return this.fruitList.every(item => item.isChecked)
},
set(value) {
this.fruitList.forEach(item => item.isChecked = value)
}
}
},
watch: {
fruitList: {
deep: true,
handler(newVal) {
localStorage.setItem('fruit', JSON.stringify(newVal))
}
}
}
})
</script>
</body>
</html>