文章目录
Vue概念
Vue 是一个用于构建用户界面
的渐进式
框架
。
- 构建用户界面:基于数据动态渲染页面
- 渐进式:循序渐进的学习
- 框架:一谈完整的项目解决方案,提升开发效率
Vue实例
创建Vue实例,初始化渲染
- 准备容器(Vue所管理的范围)
- 引包(官网)------开发版本/生产版本
- 创建Vue实例 new Vue()
- 指定配置项 el data=>渲染数据
- el 指定挂载点,选择器指定控制的是哪个盒子
- data提供数据
<body>
<div id="app">
<!-- 这里将来会编写一些用于渲染的代码逻辑 -->
{{ msg }}
</div>
<!-- 引入的是开发版本包,包含完整的注释和警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
// 一旦引入VueJS核心包,在全局环境,就有了Vue构造函数
const app = new Vue({
// 通过el配置选择器,指定Vue管理的是哪个盒子
el: '#app',
// 通过data提供数据
data: {
msg: '我是vue的第一个实例'
}
})
</script>
</body>
插值表达式
利用表达式进行插值,将数据渲染页面中
插值表达式的注意点:
- 使用的数据要存在
- 支持的是表达式,而非if…for语句
- 不能再标签属性里面使用
响应式
什么是响应式?
数据改变,视图自动更新
使用Vue开发---->更专注于业务核心逻辑
如何访问或修改数据?
data中的数据,最终会被添加到实例上
- 访问数据:“实例.属性名”
- 修改数据:“实例.属性名”=“值“
安装vue工具
Vue指令
指令就是带有v-前缀的特殊属性,不同属性对应不同的功能。
学习不同的指令——>解决不同业务场景需求
v-html
v-html="表达式"
——>动态设置元素innerHTML
<body>
<div id="app">
<div v-html="msg"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
msg: `<a href="https://www.csdn.net/">CSDN官网</a> `
}
})
</script>
</body>
v-show
- 控制元素显示隐藏
v-show="表达式"
,表达式值true显示,false隐藏- 原理:切换display:none来控制显示隐藏
- 场景:频繁切换显示隐藏的场景
v-if
- 控制元素的显示隐藏(条件渲染)
- 语法:
v-if="表达式"
,表达式值true显示,false隐藏- 原理:基于条件判断,是否创建或移除元素节点
- 场景:要么显示,要么隐藏,不频繁切换场景
v-show和v-if的区别
- v-show底层原理:切换css的display:none来控制显示隐藏(通常称为简单的显示和隐藏)
- v-if底层原理:根据判断条件控制元素的创建和移除(通常称为条件渲染)
<body>
<div id="app">
<div v-show="flag" class="box">
我是v-show控制的盒子
</div>
<div v-if="flag" class="box">
我是v-if控制的盒子
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
flag: false
}
})
</script>
</body>
v-else和v-else-if
- 辅助v-if进行判断 渲染
- 语法:
v-else
v-else-if="表达式"
- 注意:需要紧挨着v-if 一起使用
<body>
<div id="app">
<h1 v-if="gender===1">性别:男</h1>
<h1 v-else>性别:女</h1>
<p v-if="score>=90">成绩评定A</p>
<p v-else-if="score>=70&&score<90">成绩评定B</p>
<p v-else-if="score>=60&&score<70">成绩评定C</p>
<p v-else>成绩评定D</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
gender: 1,
score: 89
}
})
</script>
</body>
v-on
- 注册事件=添加监听+提供处理逻辑
- 语法:
- v-on:事件名=“内联语句"
- v-on:事件名=“Methods”中的函数名
- 简写:@事件名
- methods函数内的this指向Vue实例
<body>
<div id="app">
<button @click="count--">-</button>
<span>{{count}}</span>
<button @click="count++">+</button>
<hr>
<!-- 华丽的分割线 -->
<button @click="fn">显示与隐藏</button>
<h1 v-show="flag">VUE实例</h1>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
count: 100,
flag: true
},
methods: {
fn() {
console.log(this === app);
this.flag = !this.flag;
}
}
})
</script>
</body>
v-bind
- 动态设置html的标签属性——>src url title…
- v-bind:属性名=“表达式”
- 简写形式
:属性名="表达式"
<body>
<div id="app">
<img :src='imgUrl' :title='msg' alt="">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
imgUrl: './avator.jpg',
msg: `我是一个女生`
}
})
</script>
</body>
v-for
<body>
<div id="app">
<h1>小黑的书架</h1>
<ul>
<li v-for="(item,index) in booksList" :key="item.id">
<span>{{item.name}}</span>
<span>{{item.author}}</span>
<button @click="del(item.id)">删除</button>
</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
booksList: [
{ id: 1, name: '《红楼梦》', author: '曹雪芹' },
{ id: 2, name: '《西游记》', author: '吴承恩' },
{ id: 3, name: '《水浒传》', author: '施耐庵' },
{ id: 4, name: '《三国演义》', author: '罗贯中' }
]
},
methods: {
del(id) {
this.booksList = this.booksList.filter(item => item.id !== id)
}
}
})
</script>
</body>
**:key="item.id"
**的作用分析:
给元素添加唯一的表示,便于Vu进行列表项的正确排序复用
- key 的值只能是字符串或数字类型
- key值必须有唯一性
- 推荐使用id作为key(唯一),不推荐使用index作为key(会变化,不对应)
v-model
- 作用:给表单元素使用,双向数据绑定——>可以快速获取和设置表单元素内容
- 数据变化——>视图自动更新
- 视图变化——>数据自动更新
- 语法:v-model=‘变量’
小黑记事本案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>小黑记事本</title>
<link rel="stylesheet" href="./diray.css">
</head>
<body>
<section id="app">
<header class="header">
<h1>小黑记事本</h1>
<div class="header-input">
<input v-model="todoName" type="text" placeholder="请添加任务">
<button @click="add()" id="addThing">添加任务</button>
</div>
</header>
<section class="main">
<ul class="todo-list">
<li class="todo" v-for="(item,index) in list" :key="item.id">
<div class="view">
<span>{{index+1}}.</span>
<span>{{item.thing}}</span>
<span class="del" @click="del(item.id)">X</span>
</div>
</li>
</ul>
</section>
<footer class="footer" v-show="list.length>0">
<span class="todo-count">合计:<strong>{{list.length}}</strong></span>
<button class="clearAll" @click="clear()">
清空任务
</button>
</footer>
</section>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
document.addEventListener('keyup', function (e) {
if (e.keyCode === 13) {
app.add()
}
})
const app = new Vue({
el: '#app',
data: {
todoName: '',
list: [
{ id: 1, thing: '跑步3km' },
{ id: 2, thing: '游泳3km' },
{ id: 3, thing: '跳绳200次' },
]
},
methods: {
del(id) {
this.list = this.list.filter(item => item.id !== id)
},
add() {
if (this.todoName.trim() === '') {
alert('请输入任务名称')
return
}
this.list.unshift({
id: +new Date(),
thing: this.todoName
})
this.todoName = ''
},
clear() {
this.list = []
}
}
})
</script>
</body>
</html>
#app {
width: 500px;
margin: 0 auto;
color: #858080;
background-color: #ffffff;
padding: 10px 0;
box-shadow: 0px 0px 5px #a3a1a1;
}
#app header h1 {
text-align: center;
font-size: 38px;
font-weight: 100;
color: #b85959;
margin: 10px 0;
}
#app header .header-input {
width: 410px;
height: 40px;
margin: 0 auto;
padding: 20px 10px;
position: relative;
}
#app header .header-input input {
width: 400px;
height: 40px;
outline: none;
font-size: 16px;
border: none;
border: 1px solid #be2727;
border-radius: 8px;
text-indent: 10px;
}
#app header .header-input button {
width: 80px;
height: 44px;
position: absolute;
right: 10px;
top: 20px;
border: none;
color: #eee6e6;
background-color: #d15959;
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
cursor: pointer;
}
#app .main .todo-list {
margin: 0;
padding: 0;
list-style: none;
width: 430px;
margin: 0 auto;
}
#app .main .todo {
margin: 0 auto;
height: 50px;
width: 95%;
line-height: 50px;
border-bottom: 1px solid #c3bebe;
position: relative;
}
#app .main .todo span {
display: inline-block;
margin: 0 12px;
font-size: 18px;
}
#app .main .todo .del {
position: absolute;
right: 10px;
cursor: pointer;
font-weight: bolder;
display: none;
}
#app .main .todo .del:hover {
color: #c35c5c;
}
#app .main .todo:hover .del {
display: inline-block;
}
#app footer {
width: 410px;
padding: 10px 10px;
height: 30px;
margin: 0 auto;
font-size: 12px;
display: flex;
justify-content: space-between;
}
#app footer span {
display: inline-block;
height: 30px;
line-height: 30px;
}
#app footer button {
color: #858080;
border: none;
background-color: #ffffff;
cursor: pointer;
}
#app footer button:hover {
color: #a73535;
text-decoration: underline;
}
指令修饰符
通过.
指明一些指令后缀,不同后缀封装了不同的处理操作—>简化代码
按键修饰符
@keyup.enter —>键盘回车监听
v-model修饰符
v-model.trim —>去除首尾空格
v-model-number —>转数字
事件修饰符
@事件名.stop —>阻止冒泡
@事件名.prevent —>组织默认行为
v-bind对于样式控制的增强
操作class
语法::class="对象/数组"
对象—>键就是类名,值是布尔值。如果值为true,有这个类,否则没有这个类
<div class="box" :class="{类名1:布尔值,类名2:布尔值}"></div>
数组—>数组中所有的类,都会添加到盒子上,本质上就是一个class列表
<div class="box" :class="[类名1,类名2,类名3]"></div>
操作style
语法::style="样式对象"
<div class="box" :style="{css属性名1:css属性值1,css属性名2:css属性值2}"></div>
v-model应用于其他表单元素
常见的表单元素都可以用v-model绑定关联—>快速获取或设置表单的值,它会根据控件类型自动选取正确的方法来更新元素
输入框 input:text —>value
文本域 textarea —>value
复选框 input:checkbox —>checked
单选框 input:text —>checked
下拉菜单 select —>value
<body>
<div id="app">
<h1>小黑学习网</h1>
姓名:
<input type="text" v-model="username">
<br><br>
是否单身:
<input type="checked" v-model="IsSingle">
<br><br>
<!-- 理解:
1.name:给单选框加上name属性 可以分组->同一组互相会互斥
2.value:给单选框加上value属性,用于提交给后台的数据
结合Vue使用->v-model
-->
性别:
<input type="radio" name="sex" v-model="gender" value="1">男
<input type="radio" name="sex" v-model="gender" value="2">女
<br><br>
<!-- 理解:
1.option需要设置value值,提交给后台
2.select的value值,关联选中的option的value值
结合Vue使用->v-model
-->
所在城市:
<select v-model="city">
<option value="101">北京</option>
<option value="102">上海</option>
<option value="103">成都</option>
<option value="104">南京</option>
</select>
<br><br>
自我描述:
<textarea v-model="desc"></textarea>
<br><br>
<button>立即注册</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: '小黑',
IsSingle: true,
gender: 2,
city: 103,
desc: '爱好文学'
}
})
</script>
</body>
计算属性
**概念:**基于现有数据,计算出来的新属性。依赖的数据变化,自动重新计算。
语法:
- 声明在computed配置项中,一个计算属性对应一个函数
- 使用起来和普通属性一样使用{{计算属性名}}
计算属性—>可以将一段求值代码进行封装
computed计算属性 vs methods方法
computed计算属性
作用:封装了一段对于数据的处理,求得一个结果。
语法:
- 写在computed配置项中
- 作为属性,直接使用—>this.计算属性 {{计算属性}}
methods方法
作用:给实例提供一个方法,调用以处理业务逻辑
语法:
- 写在methods配置项中
- 作为方法,需要调用—>this.方法名() {{方法名()}} @事件名=“方法名”
computed计算属性有缓存特性(提升性能)
计算属性会对计算出来的结果缓存,再次使用直接读取缓存,依赖变化了,会自动重新计算—>并再次缓存
计算属性的完整写法
计算属性默认的简写,只能读取访问,不能“修改”。
如果要“修改”—>需要写计算属性的完整写法
computed:{
计算属性名:{
get(){
一段代码逻辑(计算逻辑)
return 结果
},
set(修改的值){
一段代码逻辑(修改逻辑)
}
}
}
watch监听器
作用:监视数据变化
语法:
- 简单写法:简单类型数据,直接监视
const app = new Vue({
el: '#app',
data: {
words: '苹果',
obj: {
words: '苹果'
}
},
watch: {
words(newValue, oldValue) {
// 一些业务逻辑 或者 异步操作
},
'obj.words'(newValue, oldValue) {
// 一些业务逻辑 或者 异步操作
}
}
})
- 完整写法:添加额外配置项(深度监视复杂类型,立刻执行)
watch: {
数据属性名:{
deep: true,//深度监视
immediate: true,//立刻执行,已进入页面就立刻执行
handler(newValue) {
console.log(newValue)
}
}
}
翻译小案例
- 接口传参
- 防抖优化
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>小小翻译器</title>
<style>
#app {
width: auto;
}
.translate {
display: flex;
}
.text {
width: 180px;
height: 150px;
font-size: 20px;
border: 1px solid #838383;
padding: 20px;
color: #2f2e2e;
}
.text textarea {
width: 95%;
height: 95%;
resize: none;
outline: none;
border: none;
font-size: 20px;
}
#outText {
border: none;
background-color: #e6e6e6;
}
</style>
</head>
<body>
<div id="app">
<span>翻译成的语言:</span>
<select v-model="obj.lang">
<option value="italy">意大利</option>
<option value="english">英语</option>
<option value="german">德语</option>
</select>
<div class="translate">
<div class="text">
<textarea type="text" placeholder="输入文本" v-model="obj.words" id="inText"></textarea>
</div>
<div class="text" id="outText">
<div type="text" placeholder="翻译">{{ result }}</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
result: '',
// timer: null,
//这个属性无需渲染,用来做防抖优化,可以直接需要使用时挂载到app对象身上
obj: {
lang: 'italy',
words: ''
}
},
watch: {
obj: {
deep: true,//深度监视
immediate: false,//立刻执行,已进入页面就立刻执行
handler(newValue) {
//防抖优化
clearTimeout(this.timer)
// this.timer 直接将timer属性挂载到app对象身上
this.timer = setTimeout(async () => {
const res = await axios({
//接口说明:
// 接口地址:https://applet-base-api-t.itheima.net/api/translate
// 接口是随机返回 一个字符串
// 请求方式:get
// 请求参数
// (1)words:需要被翻译的文本(必传)
// (1)lang:需要被翻译成的语言(可选)默认值 意大利
//
url: 'https://applet-base-api-t.itheima.net/api/translate',
//请求方式get可以省略不写
params: newValue
})
this.result = res.data.data
}, 900)
}
}
}
})
</script>
</body>
</html>
水果购物车案例
需求说明:
- 渲染功能
- 删除功能
- 修改个数
- 全选反选
- 统计选中的总价和总数量
- 持久化到本地
业务实现
- 渲染功能:v-if/v-show v-for :class
- 删除功能:点击传参 filter过滤覆盖原数组
- 修改个数:点击传参 find找对象
- 全选反选:计算属性computed 完整写法get/set
- 统计选中的总价和总数量:计算属性computed reduce条件求和
- 持久化到本地:watch监视,localStorage JSON.stringify JSON.parse
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>水果购物车</title>
<style>
.app-container {
width: 720px;
height: auto;
margin: 0 auto;
}
.banner {
width: 100%;
height: 100%;
}
.banner img {
width: 100%;
height: 100%;
}
.main .table {
width: 100%;
height: auto;
border-collapse: collapse;
}
.empty {
width: 100%;
height: 100px;
font-size: 24px;
color: #575757;
line-height: 100px;
text-align: center;
background-color: #f7f7f7;
}
.main .table .tr {
height: 100%;
display: flex;
border: 1px solid #e8e7e7;
justify-content: space-around;
text-align: center;
}
.main .table .tbody .active {
background-color: #f3f3f3;
}
.main .table .tr .th,
.td {
width: 120px;
height: 80px;
box-sizing: border-box;
text-align: center;
color: #575757;
}
.main .table .thead {
background-color: #f7f5f5;
height: 50px;
line-height: 50px;
}
.main .table .tbody .td {
line-height: 80px;
font-size: 16px;
}
.main .table .tbody .td img {
width: 80px;
height: 80px;
margin: 0 auto;
}
.main .table .tbody .td input {
cursor: pointer;
}
.main .table .tbody .number-op {
width: 120px;
height: 40px;
display: flex;
justify-content: center;
text-align: center;
margin: 20px auto;
box-shadow: 0 0 5px #818181;
}
.main .table .tbody .number-op .sub,
.add {
height: 100%;
line-height: 40px;
width: 40px;
background-color: #f1f1f1;
border: none;
outline: none;
cursor: pointer;
}
.main .table .tbody .number-op .number {
width: 55px;
height: 100%;
line-height: 40px;
background-color: #fefefe;
}
.main .table .tbody .tr .delelte button {
width: 60px;
height: 30px;
line-height: 30px;
background-color: #fc4f4f;
color: #FFF;
margin: 20px auto;
border-radius: 4px;
font-size: 12px;
outline: none;
border: none;
cursor: pointer;
}
.main .bottom {
width: 100%;
height: 60px;
line-height: 60px;
border: 1px solid #e8e7e7;
}
.main .bottom .checkAll,
.total {
width: 60%;
display: inline-block;
text-indent: 50px;
}
.main .bottom .total {
width: 25%;
display: inline-block;
}
.main .bottom .total span {
color: rgb(251, 135, 152);
font-size: 30px;
font-weight: bold;
}
.main .bottom button {
width: 80px;
height: 35px;
border: none;
background-color: #5689bc;
color: #fff;
border-radius: 4px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="app-container" id="app">
<!-- 顶部banner -->
<div class="banner"><img src="https://img.tukuppt.com/bg_grid/00/03/63/pd1Ab79ETa.jpg!/fh/350" alt=""></div>
<!-- 面包屑 -->
<div class="breadcrumb">主页/购物车</div>
<!-- 购物车主体 -->
<div class="main" v-if="fruitList.length>0">
<div class="table">
<!-- 头部 -->
<div class="thead">
<div class="tr">
<span class="th">选中</span>
<span class="th">图片</span>
<span class="th">单价</span>
<span class="th">个数</span>
<span class="th">小计</span>
<span class="th">操作</span>
</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 number-op">
<button class="sub" :disabled="item.num<=1" @click="sub(item.id)">-</button>
<div class="number">{{item.num}}</div>
<button class="add" @click="add(item.id)">+</button>
</div>
<div class="td">{{item.num*item.price }}</div>
<div class="td delelte"><button @click="del(item.id)">删除</button></div>
</div>
</div>
<!-- 底部 -->
<div class="bottom">
<div class="checkAll"><input type="checkbox" v-model="isAll">全选</div>
<div class="total">总价:¥<span>{{totalPrice}}</span></div>
<button>结算(<span>{{totalCount}}</span>)</button>
</div>
</div>
</div>
<!-- 空车 -->
<div class="empty" v-else>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-cart"
viewBox="0 0 16 16">
<path
d="M0 1.5A.5.5 0 0 1 .5 1H2a.5.5 0 0 1 .485.379L2.89 3H14.5a.5.5 0 0 1 .491.592l-1.5 8A.5.5 0 0 1 13 12H4a.5.5 0 0 1-.491-.408L2.01 3.607 1.61 2H.5a.5.5 0 0 1-.5-.5zM3.102 4l1.313 7h8.17l1.313-7H3.102zM5 12a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm7 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm-7 1a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm7 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2z" />
</svg> 空空如也
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
// fruitList: [
// {
// id: 1,
// // 草莓
// icon: 'https://bpic.588ku.com/element_origin_min_pic/19/03/07/e2a9f2049f8a27a802adc898a9a77263.jpg',
// isChecked: true,
// num: 2,
// price: 6,
// },
// {
// id: 2,
// //橘子
// icon: 'https://ts1.cn.mm.bing.net/th/id/R-C.cc043de924afe4c8e339ac9153fc6d0a?rik=idAViLjhl02Pkw&riu=http%3a%2f%2fimg95.699pic.com%2felement%2f40114%2f0063.png_860.png&ehk=%2bswafoP2gLwU6isZhlfWUgCDGnXxHt00KH1VlPG%2f3FI%3d&risl=&pid=ImgRaw&r=0',
// isChecked: true,
// num: 3,
// price: 6,
// },
// {
// id: 3,
// // 葡萄
// icon: 'https://tse2-mm.cn.bing.net/th/id/OIP-C.IE5_7TEOtuDLzOWoWmIevQHaFP?w=280&h=198&c=7&r=0&o=5&dpr=1.8&pid=1.7',
// isChecked: true,
// num: 2,
// price: 5,
// },
// {
// id: 4,
// // 西瓜
// icon: 'https://tse1-mm.cn.bing.net/th/id/OIP-C.j5_OeK8DBQLiq6PSHsh_bwHaHa?w=197&h=198&c=7&r=0&o=5&dpr=1.8&pid=1.7',
// isChecked: true,
// num: 2,
// price: 5,
// },
// {
// id: 5,
// //水蜜桃
// icon: 'https://tse1-mm.cn.bing.net/th/id/OIP-C.cJAO-NexLdDrjFm_TtOWvgHaHa?w=193&h=194&c=7&r=0&o=5&dpr=1.8&pid=1.7',
// isChecked: true,
// num: 8,
// price: 6,
// },
// {
// id: 6,
// // 苹果
// icon: 'https://tse4-mm.cn.bing.net/th/id/OIP-C.3sFMitLegnrn_6OOGf6drQHaHa?rs=1&pid=ImgDetMain',
// isChecked: true,
// num: 2,
// price: 6,
// },
// {
// id: 7,
// // 荔枝
// icon: 'https://tse1-mm.cn.bing.net/th/id/OIP-C.zy_tCKWr8TxhJbG_ZZpFbQHaHa?w=210&h=210&c=7&r=0&o=5&dpr=1.8&pid=1.7',
// isChecked: true,
// num: 2,
// price: 6,
// },
// ]
// 取数据的时候去的是本地浏览器保存的数据
fruitList: JSON.parse(localStorage.getItem('list')) || defaultArr,
},
methods: {
del(id) {
this.fruitList = this.fruitList.filter(item => item.id !== id)
},
add(id) {
const fruit = this.fruitList.find(item => item.id === id)
fruit.num++
},
sub(id) {
const fruit = this.fruitList.find(item => item.id === id)
fruit.num--
}
},
computed: {
// 完整写法:set+get
isAll: {
get() {
return this.fruitList.every(item => item.isChecked)
},
set(value) {
// 同步小选框和全选框的状态
this.fruitList.forEach(item => item.isChecked = value)
}
},
totalCount() {
return this.fruitList.reduce((sum, item) =>
item.isChecked ? sum + item.num : sum
, 0)
},
totalPrice() {
return this.fruitList.reduce((sum, item) =>
item.isChecked ? sum + item.num * item.price : sum
, 0)
}
},
watch: {
fruitList: {
deep: true,
handler(newValue) {
// 一旦修改,就将新数据存入浏览器本地中
localStorage.setItem('list', JSON.stringify(newValue))
}
}
}
})
</script>
</body>
</html>