Vue基础
一、学习目标
- 能够说出
Vue的概念和作用
- 能够使用
@vue/cli脚手架工程化开发
- 能够熟练
Vue指令
- 能够了解
更新检测,key作用,虚拟DOM,diff算法
- 能够掌握
设置动态样式
- 能够掌握
过滤器,计算属性,监听器
- 能够完成
品牌管理案例
二、Vue基本概念
1. 为什么要学习Vue
更少的时间,干更多的活,开发网站速度快
1.1. 铺设li
- 原生js做法
<ul id="myUL"></ul>
<script>
let arr = ['春天','夏天','秋天','冬天']
let myUL = document.querySelector('#myUL')
for(let i = 0; i < arr.length; i++) {
let theLi = document.createElement('li')
theLi.innerHTML = arr[i]
myUL.appendChild(theLi)
}
</script>
<li v-for = "item in arr">{{item}}</li>
<script>
new Vue({
data: {
arr: ['春天','夏天','秋天','冬天']
}
})
</script>
1.2 企业里都使用Vue开发网站
2. Vue是什么
渐进式javascript框架,一套拥有自己规则的语法
官网地址: https://cn.vuejs.org/ (作者: 尤雨溪)
2.1 渐进式概念
(1)生活里的渐进式
渐进式:逐渐进步,想用什么就用什么,不必全都使用
(2)web里的渐进式
HTML能写网页,CSS能让网页更好看,JS赋予网页交互效果,jQ写的更快,Node可以提供动态数据
(3)Vue里的渐进式
Vue从基础开始,会遵循渐进向前学习,如下知识点可能你现在不明白,但是学完了整个Vue回头来看,会很有帮助
2.2 库和框架
库:封装的属性和方法(例如:jQuery)
框架:拥有自己的规则和元素,比库强大的多(例如:Vue)
3. Vue学习方式
- 传统开发模式:基于html文件开发Vue
- 工程化开发方式:在webpack环境中开发Vue,这里最推荐,企业常用的方式
3.1 Vue如何学
- 知识点自测最好做到了如指掌 - 做不到只能花30分钟去记住结论和公式
- 记住Vue指令作用,基础语法 - 弄一个字典(——映射关系)
- 在课上例子,练习,案例,作业,项目中,反复磨炼使用
- 学会查找问题的方式和解决方式(弄个报错总结.md,避免反复进坑)
三、@vue/cli脚手架
@vue/cli
是Vue官方提供的一个全局模块包(得到vue命令),此包用于创建脚手架
项目脚手架
是为了保证各施工过程顺利进行而搭设的工作平台
1. 脚手架好处
开箱即用
0配置webpack
- babel支持
- css.less支持
- 开发服务器支持
2. @vue/cli安装
- 全局安装命令
yarn global add @vue/cli
# OR
npm install -g @vue/cli
注意:如果半天没动静(95%都是网速问题)可以ctrl c
- 停止重新来
- 换一个网进行重来
- 查看
vue
脚手架版本
vue -V
总结:如果出现版本号就安装成功,否者失败
3. @vue/cli 创建项目
-
创建项目
# vue和create是命令, vuecli-demo是文件夹名 vue create vuecli-demo
-
选择模板
可以上下箭头选择,弄错了ctrl+c重来
准备用什么方式下载脚手架项目需要的依赖包
- 回车等待生成项目文件夹+文件+下载必须的第三方包
- 进入脚手架项目下,启动内置的热更新本地服务器
cd vuecli-demo
npm run serve
# or
yarn serve
只要看到绿色的,那你就成功了!(底层node+webpack热更新服务)
打开浏览器输入上述的地址
4. @vue/cli目录和代码分析
vuecil-demo # 项目目录
├── node_modules # 项目依赖的第三方包
├── public # 静态文件目录
├── favicon.ico# 浏览器小图标
└── index.html # 单页面的html文件(网页浏览的是它)
├── src # 业务文件夹
├── assets # 静态资源
└── logo.png # vue的logo图片
├── components # 组件目录
└── HelloWorld.vue # 欢迎页面vue代码文件
├── App.vue # 整个应用的根组件
└── main.js # 入口js文件
├── .gitignore # git提交忽略配置
├── babel.config.js # babel配置
├── package.json # 依赖包列表
├── README.md # 项目说明
└── yarn.lock # 项目包版本锁定和缓存地址
主要文件及含义
node_modules // 下都是下载的第三方包
public/index.html // 浏览器运行的网页
src/main.js // webpack打包的入口文件
src/App.vue // vue项目入口页面
package.json // 依赖包列表文件
5. @vue/cli 项目架构了解
目标:知道项目入口,以及代码执行顺序和引入关系
6. @vue/cli自定义配置
目标:项目中没有webpack.config.js文件,因为@vue/cli用的vue.config.js
src 并列处新建vue.config.js
/*覆盖webpack的配置*/
module.exports = {
devServer: { // 自定义服务配置
open: true, // 自动打开浏览器
port: 3000
}
}
7. eslint了解
目标:知道eslint的作用,和如何暂时关闭,它是一个
代码检查工具
例子:先在main.js随便声明个变量,但是不要使用
这时候你会发现,终端和页面都报错了
记住以后见到这样子的错误,证明你的代码不够严谨
- 方式1:手动解决掉错误,以后项目中会讲到如何自动解决
- 方式2:暂时关闭eslint检查(因为现在主要精力在学习Vue语法上)在vue.config.js中配置后重启服务
module.exports = {
// ... 其他配置
lintOnSave: false // 关闭eslint 检查
}
8. @vue/cli 单vue文件讲解
目标:单vue文件好处,独立作用域互不影响
- Vue推荐采用.vue文件来开发项目
- template里只能有一个根标签
- vue文件-独立模块-作用域互不影响
- style配合scoped属性,保证样式只针对当前template内标签生效
- vue文件配合webpack,把他们打包起来插入到index.html
<!-- template必须, 只能有一个根标签, 影响渲染到页面的标签结构 -->
<template>
<div>欢迎使用vue</div>
</template>
<!-- js相关 -->
<script>
export default {
name: 'App'
}
</script>
<!-- 当前组件的样式,设置scoped,可以保证样式只对当前的页面有效 -->
<style scoped>
</style>
最终:Vue文件配合webpack,把他们打包起来插入到index.html,然后在浏览器运行
9. @vue/cli 欢迎界面清理
目标:我们开始写我们自己的代码,无需欢迎页面
- src/App.vue默认有很多内容,可以全部删除留下框
- assets和components文件夹下的一切都删掉(不要默认的欢迎界面)
四、Vue指令
1. vue基础-插值表达式
目的:在dom标签中,直接插入内容
-
又叫:声明式渲染 /文本插值
-
语法:{{表达式}}
-
msg和obj是vue数据变量
-
要在js中data函数里声明
<template>
<div>
// 把值赋予到标签里
<h1>{{msg}}</h1>
<h1>{{obj.name}}</h1>
<h1>{{obj.age >= 18 ? '已成年':'未成年'}}</h1>
</div>
</template>
<script>
export default {
// 1. 变量在data函数return的对象上
data(){ // 格式固定定义vue数据之处
return { // key 相当于变量名
msg: 'Hello Vue',
obj: {
name: '刘德华',
age: 20
}
}
}
}
</script>
<style>
</style>
2. vue基础-MVVM设计模式
目的:转变思维,用数据驱动视图改变,操作dom的事,vue源码内干了
-
设计模式:是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结
-
演示:在上个代码基础上,在devtool工具改变M层的变量,观察V层(视图的自动同步)
- MVVM:一种软件架构模式,决定了写代码的思想和层次
- M:model数据模型 (data里定义)
- V: view视图 (html页面)
- VM: ViewModel视图模型(vue.js源码)
- MVVM通过数据双向绑定让数据自动地双向同步不需要操作DOM
- V(修改视图) -> M(数据自动同步)
- M(修改数据)-> V (视图自动同步)
- 在vue中,不推荐直接动手操作DOM
- 在vue中,通过数据驱动视图,不要想着怎么操作DOM,而是想着如何操作数据!!!(思想转变)
3. vue指令 v-bind
目标:给标签属性设置vue变量的值
vue指令,实质上就是特殊的html标签属性,特点:v-开头
每个指令,都有独立的作用
- 语法:
v-bind:属性名="vue变量"
- 简写:
:属性名="vue变量"
<!-- 语法:v-bind:原生标签属性名="vue变量" -->
<a v-bind:href="url">我是a标签</a>
<!-- 简写语法: :原生属性名="vue变量" -->
<img :src="imgSrc">
4. vue 指令 v-on
目标:给标签绑定事件
- 语法
- v-on:事件名=“要执行的
少量代码
” - v-on:事件名=“methods中的函数”
- v-on:事件名=“methods中的函数(实参)”
- v-on:事件名=“要执行的
- 简写:@事件名=“methods中的函数”
<template>
<div>
<p>你要购买的产品数量:{{count}}</p>
<!-- 1. 绑定事件 -->
<!-- 语法:v-on:事件名="要执行的少量代码" -->
<button v-on:click="count = count + 1">+1</button>
<!-- 语法:v-on:事件名="methods中的函数" -->
<button v-on:click="addbtn">+1</button>
<!-- 语法:v-on:事件名="methods中的函数(实参)" -->
<button v-on:click="addFn(10)">一次加10</button>
<!-- 简写:@事件名="methods中的函数" -->
<button @click="subFn">-1</button>
</div>
</template>
<script>
export default {
data () {
return {
count: 1
}
},
methods: { // this代表export default后面的组件对象(下属有data里return出来的属性)
addbtn() {
this.count++
},
addFn(num){
this.count += num
},
subFn() {
this.count--
}
}
}
</script>
<style>
</style>
总结:常用@事件名,给dom标签绑定事件,以及=右侧事件处理函数
5. vue指令 v-on事件对象
- 语法
- 无传参:通过形参直接接收
- 传参:通过$event指代事件对象传给事件处理函数
<template>
<div>
<a @click="one" href="http://www.baidu.com">百度</a>
<br>
<a @click="two(10,$event)" href="http://ww.taobao.com">淘宝</a>
</div>
</template>
<script>
export default {
methods: {
// 1. 事件触发,无传值,可以直接获取事件对象
one(e) {
e.preventDefault()
},
// 2. 事件触发,传值,需要手动传入$event
two(num,e) {
e.preventDefault()
}
}
}
</script>
<style>
</style>
6. vue指令 v-on修饰符
目标:在事件后面.修饰符,给事件带来更强大的功能
- 语法
- @事件名.修饰符=“methods里函数”
- 修饰符列表
- .stop 阻止事件冒泡
- .prevent 阻止默认行为
- .once 程序运行期间,只能触发一次事件处理函数
<template>
<div>
<!-- vue对事件进行了修饰设置,在事件后面,修饰符名即可使用更多功能 -->
<div @click="fatherFn">
<p @click.stop="oneFn">阻止事件冒泡</p>
<a href="www.baidu.com" @click.stop.prevent>去百度</a>
<button @click.once="btn">.once程序运行期间,只触发一次事件处理函数</button>
</div>
</div>
</template>
<script>
export default {
methods: {
fatherFn(){
console.log('fatcher-触发了click事件');
},
oneFn(){
console.log('p标签触发了click事件');
},
btn() {
console.log('1');
}
}
}
</script>
<style>
</style>
总结:修饰符给事件扩展额外功能
7. vue指令 v-on按键修饰符
目标:给键盘事件,添加修饰符,增强能力
- 语法:
- @keyup.enter 检测回车按键
- @keyup.esc 检测返回按键
- 更多修饰符: https://cn.vuejs.org/v2/guide/events.html
<template>
<div>
<!-- 1. 绑定键盘按下事件.enter回车 -->
<input type="text" @keydown.enter="enterFn">
<br>
<!-- 2. esc修饰符 取消键 -->
<input type="text" @keydown.esc="escFn">
</div>
</template>
<script>
export default {
methods: {
enterFn(){
console.log('用户按下了回车键');
},
escFn(){
console.log('用户按下了ESC键');
}
}
}
</script>
<style>
</style>
8. 练习-翻转世界
需求:点击按钮 - 把文字取反显示 - 在点击取反显示回来
提示:点击事件里,把Vue变量值挨个字母取反赋予回来
分析:先静后动
- 定义变量message: ‘Hello,World’
- 插值表达式赋予到dom上,准备按钮和文字
- 按钮绑定点击事件和函数
- 对message值用split拆分,返回数组
- 数组元素翻转用reverse方法
- 再把数组用join拼接成字符串赋予给message
- 因为Vue是MVVM设计模式,数据驱动视图,所以视图自动改变
正确代码:
<template>
<div>
<!-- 2. 插值表达式赋予到dom上 -->
<h1>{{message}}</h1>
<!-- 3.按钮绑定点击事件和函数 -->
<button @click="btn">翻转世界</button>
</div>
</template>
<script>
export default {
data () {
return {
// 1. 定义变量message: 'Hello,World'
message: 'Hello,World'
}
},
methods: {
btn() {
// 4. 先用split方法把字符串转为数组,再用reverse方法翻转数组,然后用join方法把数组转化为字符串
this.message = this.message.split('').reverse().join('')
}
}
}
</script>
<style>
</style>
总结:记住方法特点,多做需求,vue是数据变化视图自动更新,减少操作DOM时间,提高开发效率
9. vue指令 v-model
目标:把value属性和vue数据变量,双向绑定到一起
- 语法:v-model = “vue数据变量”
- 双向数据绑定
- 数据变化 -> 视图自动同步
- 视图变化 -> 数据自动同步
- 演示:用户名绑定 - vue内部是MVVM设计模式
<template>
<div>
<!-- v-model:是实现vue.js变量和表单标签value属性,双向绑定的指令 -->
<div>
<span>用户名:</span>
<input type="text" v-model="username">
</div>
<div>
<span>密码:</span>
<input type="password" v-model="password">
</div>
<div>
<span>来自于:</span>
<!-- 下拉菜单哟啊绑定在select上 -->
<select v-model="from">
<option value="北京市">北京</option>
<option value="南京市">南京</option>
<option value="天津市">天津</option>
</select>
</div>
<div>
<!-- (重要) 遇到复选框,v-model的变量值 非数组 - 关联的是复选框的checkout属性 数组 - 关联的是复选框的value属性 -->
<span>爱好:</span>
<input type="checkbox" v-model="hobby" value="干饭">干饭
<input type="checkbox" v-model="hobby" value="睡觉">睡觉
<input type="checkbox" v-model="hobby" value="打代码">打代码
</div>
<div>
<span>性别:</span>
<input type="radio" value="男" name="sex" v-model="gender">男
<input type="radio" value="女" name="sex" v-model="gender">女
</div>
<div>
<span>自我介绍</span>
<textarea v-model="intro"></textarea>
</div>
</div>
</template>
<script>
export default {
data () {
return {
username: '',
password: '',
from: '',
hobby: [],
gender: '',
intro: ''
}
}
}
</script>
<style>
</style>
总结:特别注意:v-model,在input[checkbox]的多选框状态
变量为非数组,则绑定的是checked的属性(true/false) 常用于:单个绑定使用
变量为数组,则绑定的是他们的value属性里的值 常用于:收集勾选了那些值
10. vue指令 v-model修饰符
目标:让v-model拥有更强大的功能
- 语法:
- v-model.修饰符=“vue数据变量”
- .number 以parseFloat转成数字类型
- .trim 去除首尾空白字符
- .lazu 在change时触发而非input时
- v-model.修饰符=“vue数据变量”
<template>
<div>
<div>
<!-- .number修饰符 以parseFolat转为数字型 -->
<span>年龄:</span>
<input type="number" v-model.number = "age">
</div>
<!-- .trim修饰符 去除首尾空白字符 -->
<div>
<span>人生格言:</span>
<input type="text" v-model.trim="motto">
</div>
<!-- .lazy修饰符 失去焦点内容改变(onchange)事件 -->
<div>
<span>自我介绍</span>
<textarea v-model.lazy="inro"></textarea>
</div>
</div>
</template>
<script>
export default {
data () {
return {
age: 0,
motto: '',
inro: ''
}
}
}
</script>
<style>
</style>
总结:v-model修饰符,可以对值进行预处理,非常高效好用
11. vue指令 v-text和v-html
目的:更新DOM对象的innerText/innerHTML
- 语法:
- v-text=“vue数据变量”
- v-html=“vue数据变量”
- 注意:会覆盖插值表达式
<template>
<div>
<p v-text="str"></p>
<p v-html="str"></p>
<!-- 注意:v-text或v-html会覆盖插值表达式 -->
<p v-html="str">{{10 + 20}}</p>
<p>{{10 + 20}}</p>
</div>
</template>
<script>
export default {
data () {
return {
str: '<span>我是一个span标签</span>'
}
}
}
</script>
<style>
</style>
总结:v-text把值当成普通的字符显示,v-html把值当做html解析
12. vue指令 v-show和v-if
目标:控制标签的隐藏或出现
- 语法:
- v-show=“vue变量”
- v-if=“vue变量”
- 原理
- v-show用的display:none隐藏(频繁切换使用)
- v-if 直接从DOM树上移除
- 高级
- v-else使用
<template>
<div>
<!-- v-show隐藏:采用display:none // 频繁切换 -->
<h1 v-show="isShow">v-show显示</h1>
<!-- v-if隐藏:采用从DOM树直接移除 // 移除 -->
<h1 v-if="isOK">v-if显示</h1>
<!-- v-if和v-else使用 -->
<p v-if="age >= 18">成年了</p>
<p v-else>未成年</p>
</div>
</template>
<script>
export default {
data () {
return {
isShow: true,
isOK: true,
age: 17
}
}
}
</script>
<style>
</style>
总结:使用v-show和v-if以及v-else指令,方便通过变量控制一套标签出现/隐藏
13. 案例 _折叠面板
目标:点击展开或收起时,把内容区域显示或隐藏
分析:
- 准备标签和样式,下载less模块和less-loader@5.0.0模块
- 熟悉下标签结构和样式,对应页面那一部分
- 按钮绑定点击事件
- 声明变量isShow来控制下面标签是否显示/隐藏
- 点击时,改变isShow的值,来影响页面效果
此案例使用了less语法,项目中下载模块
yarn add less-loader@5.0.0 less -D
<template>
<div id="app">
<h3>案例:折叠面板</h3>
<div>
<div class="title">
<h4>芙蓉楼送辛渐</h4>
<!-- 1. 绑定点击事件 -->
<span class="btn" @click="btn">
{{isShow ? '收起':'展开'}}
</span>
</div>
<!-- 2. v-show控制标签的显示和隐藏 -->
<div class="container" v-show="isShow">
<p>寒雨连江夜入吴,</p>
<p>平明送客楚山孤。</p>
<p>洛阳亲友如相问,</p>
<p>一片冰心在玉壶。</p>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
isShow: true
}
},
methods: {
btn(){
// 3. 点击时把值改成false
this.isShow = !this.isShow
}
}
}
</script>
<style lang="less">
body {
background-color: #ccc;
#app {
width: 400px;
margin: 20px auto;
background-color: #fff;
border: 4px solid blueviolet;
border-radius: 1em;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.5);
padding: 1em 2em 2em;
h3 {
text-align: center;
}
.title {
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid #ccc;
padding: 0 1em;
}
.title h4 {
line-height: 2;
margin: 0;
}
.container {
border: 1px solid #ccc;
padding: 0 1em;
}
.btn {
/* 鼠标改成手的形状 */
cursor: pointer;
}
}
}
</style>
14. vue指令 v-for
目标:列表渲染,所在标签结构,按照数据数量,循环生成
-
语法
- v-for=“(值,索引) in 目标结构”
- v-for=“值 in 目标结构”
-
目标结构:
- 可以遍历数组/对象/数字/字符串(可遍历结构)
-
注意:
v-for的临时变量名不能用到v-for范围外
<template>
<div id="app">
<div id="app">
<!-- v-for 把一组数据, 渲染成一组DOM -->
<!-- 口诀: 让谁循环生成, v-for就写谁身上 -->
<!-- 语法1:v-for"(值变量名,索引变量名) in 目标结构" -->
<p>学生姓名</p>
<ul>
<li v-for="(item,index) in arr" :key="index">
{{index}} - {{item}}
</li>
</ul>
<!-- 语法2: v-for="值变量名 in 目标结构" -->
<p>学生详细信息</p>
<ul>
<li v-for="obj in stuArr" :key="obj.id">
<span>{{obj.name}}</span>
<span>{{obj.sex}}</span>
<span>{{obj.hobby}}</span>
</li>
</ul>
<!-- 语法3:v-for="(value,key) in 对象" -->
<p>老师信息</p>
<div v-for="(value,key) in tObj" :key="value">
{{key}} -- {{value}}
</div>
<!-- 语法4:v-for"变量名 in 固定数字" -->
<div v-for="i in count" :key="i">
{{i}}
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
arr: ["小明", "小欢欢", "大黄"],
stuArr: [
{
id: 1001,
name: "孙悟空",
sex: "男",
hobby: "吃桃子",
},
{
id: 1002,
name: "猪八戒",
sex: "男",
hobby: "背媳妇",
},
],
tObj: {
name: "小黑",
age: 18,
class: "1期",
},
count: 10,
};
},
};
</script>
五、更新检测&key作用
1. v-for更新检测
目标:当v-for遍历的目标结构改变,vue触发v-for的更新
- 情况1:数组翻转
- 情况2 :数组截取
- 情况3:更新值
口诀:
数组变更的方法,就会导致v-for更新,页面更新
数组非变更方法,返回新数组,就不会导致v-for更新,可采用覆盖数组或this.$set()
<template>
<div>
<ul>
<li v-for="(val,index) in arr" :key="index">
{{val}}
</li>
</ul>
<button @click="revBtn">数组翻转</button>
<button @click="sliceBtn">截取前3个</button>
<button @click="updateBtn">更新第一个元素值</button>
</div>
</template>
<script>
export default {
data () {
return {
arr: [1,3,5,7,9]
}
},
methods: {
revBtn() {
// 1. 数组翻转可以让 v-for更新
this.arr.reverse()
},
sliceBtn() {
// 2. 数组slice方法不会造成v-for更新
// slice不会改变原数组
this.arr.slice(0,3)
},
updateBtn() {
// 3. 更新某个值的时候,v-for是检测不到的
this.arr[0] = 1000
}
}
}
</script>
<style>
</style>
- 这些方法会触发数组改变,v-for会检测到并更新页面
puch()
pop()
shift()
unshift()
splice()
sort()
reverse()
- 这些方法不会触发v-for更新
slice()
filter()
concat()
注意:vue不能检测到数组里面赋值的动作而更新,如果需要请使用vue.set()或者this.#set(),或者覆盖整个数组
总结:改变原数组的方法才能让v-for更新
2. v-for就地更新
v-for
的默认行为会尝试原地修改元素而不是移动他们。
这种虚拟DOM对比方式,可以提高性能-但是还不够高
3. 虚拟DOM
目标:了解虚拟DOM的概念
.vue文件中的template里写的标签,都是模板,都要被vue处理成虚拟DOM对象,才会渲染显示到真实DOM页面上
-
内存中生成一样的虚拟DOM结构(本质是个JS对象)
因为真实的DOM属性好几百个,没办法快速的字段那个属性改变了
比如template里标签结构
<template> <div id="box"> <p class="my_p"> 123 </p> </div> </template>
对应的虚拟DOM结构
const dom = { type: 'div', attributes: [{id: 'box'}], children: { type: 'p', attributes: [{class: 'my_p'}], text: '123' } }
-
以后vue数据更新
-
生成新的虚拟DOM结构
-
和旧的虚拟DOM结构对比
-
利用diff算法,找不同,只更新变化的部分(重绘/回流)到页面 - 也叫补丁
-
好处1:提高了更新DOM的性能(不用把页面全删除重新渲染)
-
好处2:虚拟DOM只包含必要的属性(没有真实DOM上百个属性)
总结:虚拟DOM保存在内存中,只记录dom关键字信息,配合diff算法提高DOM更新的性能
在内存中比较差异,然后给真实DOM打补丁更新上
4. diff算法
vue用diff算法,新虚拟dom,和旧的虚拟dom比较
情况1:根元素变了,删除重建
旧虚拟DOM
<div id="box">
<p class="my_p">123 </p>
</div>
新虚拟DOM
<ul id="box">
<li class="my_p">123</li>
</ul>
情况2:根元素没变,属性改变,元素复用,更新属性
就虚拟DOM
<div id="box">
<p class="my_p">123</p>
</div>
新虚拟DOM
<div id="myBox" title="标题">
<p class="my_p">123</p>
</div>
5. diff算法-key
情况3:根元素没变,子元素没变,元素内容改变
无key - 就地更新
v-for不会移动DOM,而是尝试复用,就地更新,如果需要v-for一定DOM,你需要用特殊attributekey
来提供一个排序提示
<template>
<div>
<ul>
<li v-for="item in arr">
{{item}}
</li>
</ul>
<button @click="arrFn">下标位置为1新增一个</button>
</div>
</template>
<script>
export default {
data () {
return {
arr: ['老大','老二','老三']
}
},
methods: {
arrFn() {
this.arr.splice(1,0,'新来的')
}
}
}
</script>
<style>
</style>
旧虚拟DOM结构和新虚拟DOM结构对比过程
性能不高,从第二个li往后都更新了
有key - 值为索引
- 还是就地更新
因为新旧虚拟DOM对比,key存在就复用此标签更新内容,如果不存在就直接建立一个新的
<template>
<div>
<ul>
<li v-for="(str,index) in arr" :key="index">
{{str}}
<input type="text">
</li>
</ul>
<button @click="arrFn">下标位置为1新增一个</button>
</div>
</template>
<script>
export default {
data () {
return {
arr: ['老大','老二','老三']
}
},
methods: {
arrFn() {
this.arr.splice(1,0,'新来的')
}
}
}
</script>
<style>
</style>
- v-for先循环产生新的DOM结构,key是连续的,和数据对应
- 然后比较新旧DOM结构,找到区别,打补丁到页面上最后补一个li,然后从第二个往后,都要更新内容
口诀:key的值有id用id,没id用索引
有key -值为id
key的值只能是唯一不重复的,字符串或数值
v-for不会移动DOM,你需要用特殊attributekey
来提供一个排序提示
新DOM里数据的key存在,去旧的虚拟DOM结构里找到key标记的标签,复用标签
新DOM里数据的key存在,去旧的虚拟DOM结构里没有找到key标签的标签,创建
旧DOM结构的key,在新的DOM结构里没有了,则移除key所在的标签
<template>
<div>
<ul>
<li v-for="obj in arrStr" :key="obj.id">
{{obj.name}}
<input type="text">
</li>
</ul>
<button @click="arrFn">下标位置为1新增一个</button>
</div>
</template>
<script>
export default {
data () {
return {
arr: ['老大','老二','老三'],
arrStr: [
{
id:50,
name: '老大'
},
{
id: 31,
name: '老二'
},
{
id: 10,
name: '老三'
}
]
}
},
methods: {
arrFn() {
this.arrStr.splice(1,0,{
id:19,
name: '新来的'
})
}
}
}
</script>
<style>
</style>
总结:不用key也不影响功能(就地更新),添加key可以提高更新的性能
6.阶段小结
- v-for什么时候会更新页面呢?
- 数组采用更新的方法,才导致v-for更新页面
- vue是如何提高更新性能的?
- 采用虚拟DOM+diff算法提高性能
- 虚拟DOM是什么?
- 本质是保存dom关键信息的JS对象
- diff算法如何比较新旧虚拟DOM?
- 根元素改变,删除当前DOM数重建
- 根元素未变,属性改变,更新属性
- 根元素未变,子元素/内容改变
- 无key,就地更新/有key,按key比较
7. 动态class
目标:用v-bind给标签class设置动态的值
- 语法:
- :class=“{类名:布尔值}”
<template>
<div>
<!-- 语法::class="{类名:布尔值}",使用场景:vue变量控制标签是否应该有类名 -->
<div :class="{div_str:true}">动态class</div>
</div>
</template>
<script>
export default {
data () {
return {
isOk: true
}
}
}
</script>
<style scoped>
.div_str {
width: 200px;
height: 200px;
background-color: pink;
color: red;
text-align: center;
line-height: 200px;
}
</style>
总结:就是把类名保存在vue变量中赋予给标签
8. 动态style
目标:给标签动态设置style的值
- 语法
- :style=“css属性:值”
<template>
<div>
<p :style="{backgroundColor: colorStr}">动态style</p>
</div>
</template>
<script>
export default {
data () {
return {
colorStr: 'red'
}
}
}
</script>
<style>
</style>
总结:动态style的key都是css属性名
9. 案例-品牌管理
9.1 品牌管理(铺)
目标:数据铺设
-
需求1:把默认数据显示到表格上
-
需求2:注意价格超过100的,都用红色字体标记出来
-
细节:
- 先铺设静态页面
- 此案例使用bootstrap,需要下载,并导入到工程main.js中
- 用v-for配合默认数据,把数据默认铺设到表格上显示
- 直接在标签上,大于100价格,动态设置red类名
图示:
-
因为案例使用了bootstrpa,工程化开发,模块化用npm/yarn下载引入使用
yarn add bootstrap
-
在main.js引入bootstrap
import 'bootstrap/dist/css/bootstrap.css'
-
代码
<template> <div id="app"> <div class="container"> <!-- 顶部框模块 --> <div class="form-group"> <div class="input-group"> <h4>品牌管理</h4> </div> </div> <!-- 数据表格 --> <table class="table table-bordered table-hover mt-2"> <thead> <tr> <th>编号</th> <th>资产名称</th> <th>价格</th> <th>创建时间</th> <th>操作</th> </tr> </thead> <tbody> <tr v-for="obj in list" :key="obj.id"> <td>{{obj.id}}</td> <td>{{obj.name}}</td> <!-- 如果价格超过100,就有red这个类 --> <td :class="{red: obj.price > 100}">{{obj.price}}</td> <td>{{obj.time}}</td> <td><a href="#" >删除</a></td> </tr> </tbody> <!-- <tfoot > <tr> <td colspan="5" style="text-align: center">暂无数据</td> </tr> </tfoot> --> </table> <!-- 添加资产 --> <form class="form-inline"> <div class="form-group"> <div class="input-group"> <input type="text" class="form-control" placeholder="资产名称" /> </div> </div> <div class="form-group"> <div class="input-group"> <input type="text" class="form-control" placeholder="价格" /> </div> </div> <!-- 阻止表单提交 --> <button class="btn btn-primary">添加资产</button> </form> </div> </div> </template> <script> export default { data() { return { name: "", // 名称 price: 0, // 价格 list: [ { id: 100, name: "外套", price: 199, time: new Date('2010-08-12')}, { id: 101, name: "裤子", price: 34, time: new Date('2013-09-01') }, { id: 102, name: "鞋", price: 25.4, time: new Date('2018-11-22') }, { id: 103, name: "头发", price: 19900, time: new Date('2020-12-12') } ], }; }, }; </script> <style > .red{ color: red; } </style>
9.2 品牌管理(增)
目标:数据新增
- 需求1:实现表单数据新增进表格功能
- 需求2:判断用户输入是否为空提示
- 分析:
- 添加资产按钮,绑定点击事件
- 给表单v-model绑定vue变量收集用户输入内容
- 添加数组到数组中
- 判断用户内容是否符合规定
<template>
<div id="app">
<div class="container">
<!-- 顶部框模块 -->
<div class="form-group">
<div class="input-group">
<h4>品牌管理</h4>
</div>
</div>
<!-- 数据表格 -->
<table class="table table-bordered table-hover mt-2">
<thead>
<tr>
<th>编号</th>
<th>资产名称</th>
<th>价格</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="obj in list" :key="obj.id">
<td>{{obj.id}}</td>
<td>{{obj.name}}</td>
<!-- 如果价格超过100,就有red这个类 -->
<td :class="{red: obj.price > 100}">{{obj.price}}</td>
<td>{{obj.time}}</td>
<td><a href="#" >删除</a></td>
</tr>
</tbody>
<!--
<tfoot >
<tr>
<td colspan="5" style="text-align: center">暂无数据</td>
</tr>
</tfoot>
-->
</table>
<!-- 添加资产 -->
<form class="form-inline">
<div class="form-group">
<div class="input-group">
<input
type="text"
class="form-control"
placeholder="资产名称"
v-model="name"
/>
</div>
</div>
<div class="form-group">
<div class="input-group">
<input
type="text"
class="form-control"
placeholder="价格"
v-model.number="price"
/>
</div>
</div>
<!-- 4. 阻止表单提交 -->
<!-- 2. 给按钮绑定点击事件 -->
<button class="btn btn-primary" @click.prevent="btnAdd">添加资产</button>
</form>
</div>
</div>
</template>
<script>
export default {
data() {
return {
name: "", // 名称
price: 0, // 价格
list: [
{ id: 100, name: "外套", price: 199, time: new Date('2010-08-12')},
{ id: 101, name: "裤子", price: 34, time: new Date('2013-09-01') },
{ id: 102, name: "鞋", price: 25.4, time: new Date('2018-11-22') },
{ id: 103, name: "头发", price: 19900, time: new Date('2020-12-12') }
],
};
},
methods: {
// 3.把值以对象的形式插入list
btnAdd () {
// 5. 判断是否为空
if(this.name.trim() === '' || this.price === 0) {
return alert('不能为空!')
}
this.list.push({
id: this.list[this.list.length - 1].id + 1,
name: this.name,
price: this.price,
time: new Date()
})
}
}
};
</script>
<style >
.red{
color: red;
}
</style>
9.3 品牌管理(删)
目标:数据删除
- 需求1:点击删除的a标签,删除数据
- 需求2:删除没数据了要提示暂无数据的tfoot
- 分析
- a标签绑定点击事件
- 给事件方法传id
- 通过id,找到对应数据删除
- 删除光了要让tfoot显示
- 删除光了再新增,有bug(id值问题)需要修复
<template>
<div id="app">
<div class="container">
<!-- 顶部框模块 -->
<div class="form-group">
<div class="input-group">
<h4>品牌管理</h4>
</div>
</div>
<!-- 数据表格 -->
<table class="table table-bordered table-hover mt-2">
<thead>
<tr>
<th>编号</th>
<th>资产名称</th>
<th>价格</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="obj in list" :key="obj.id">
<td>{{obj.id}}</td>
<td>{{obj.name}}</td>
<!-- 如果价格超过100,就有red这个类 -->
<td :class="{red: obj.price > 100}">{{obj.price}}</td>
<!-- 3.使用过滤器 -->
<td>{{obj.time | formatDate}}</td>
<td><a href="#" @click="delFn(obj.id)">删除</a></td>
</tr>
</tbody>
<tfoot v-if="list.length === 0">
<tr>
<td colspan="5" style="text-align: center">暂无数据</td>
</tr>
</tfoot>
</table>
<!-- 添加资产 -->
<form class="form-inline">
<div class="form-group">
<div class="input-group">
<input
type="text"
class="form-control"
placeholder="资产名称"
v-model="name"
/>
</div>
</div>
<div class="form-group">
<div class="input-group">
<input
type="text"
class="form-control"
placeholder="价格"
v-model.number="price"
/>
</div>
</div>
<!-- 4. 阻止表单提交 -->
<!-- 2. 给按钮绑定点击事件 -->
<button class="btn btn-primary" @click.prevent="btnAdd">添加资产</button>
</form>
</div>
</div>
</template>
<script>
// 1. 导入下载模块
import moment from 'moment'
// 2. 定义过滤器
export default {
data() {
return {
name: "", // 名称
price: 0, // 价格
list: [
{ id: 100, name: "外套", price: 199, time: new Date('2010-08-12')},
{ id: 101, name: "裤子", price: 34, time: new Date('2013-09-01') },
{ id: 102, name: "鞋", price: 25.4, time: new Date('2018-11-22') },
{ id: 103, name: "头发", price: 19900, time: new Date('2020-12-12') }
],
};
},
methods: {
// 3.把值以对象的形式插入list
btnAdd () {
// 5. 判断是否为空
if(this.name.trim() === '' || this.price === 0) {
return alert('不能为空!')
}
// 解决bug:无数组新增list没有数据,id需要给一个固定值
let id = this.list.length > 0 ? this.list[this.list.length - 1].id + 1 : 100
this.list.push({
id: id,
name: this.name,
price: this.price,
time: new Date()
})
},
delFn(id) {
// 通过id找到这条数据在数组中下标
let index = this.list.findIndex(obj => obj.id === id)
this.list.splice(index,1)
}
},
filters: {
formatDate(val) {
return moment(val).format('YYYY-MM-DD')
}
}
};
</script>
<style >
.red{
color: red;
}
</style>
六、过滤器
1. 过滤器的定义使用
目的:转换格式,过滤器就是一个函数,传入值返回处理后的值
过滤器只能用在,插值表达式和v-bind表达式
- Vue中的过滤器场景
- 字母转大写,输入"hello",输出"HELLO"
- 字符串翻转,输入"hello,world",输出"dlrow,olleh"
- 语法
- Vue.filter(“过滤器”,(值) => {return “返回处理后的值”})
- filters:{过滤器的名字:(值) => {return “返回处理后的值”}}
- 例子:
- 全局定义字符都大写的过滤器
- 局部定义字符串翻转的过滤器
<template>
<div>
<p>原来的样子:{{msg}}</p>
<!-- 过滤器的使用语法:{{值 | 过滤器文字}} -->
<p>使用翻转过滤器:{{msg | reverse}}</p>
<p :title="msg | toUp">鼠标长停留</p>
</div>
</template>
<script>
export default {
data () {
return {
msg: 'Hello,World'
}
},
// 方式2:局部过滤器
// 只能在当前vue文件内使用
// 语法: filters: {过滤器名字 (val) {return 处理后的值}}
filters: {
toUp (val) {
return val.toUpperCase()
}
}
}
</script>
<style>
</style>
总结:把值转成另一种形式,使用过滤器,Vue3用函数替代了过滤器
全局注册最好在main.js中注册,一出注册到处使用
2. 过滤器-传参和多过滤器
目标:可同时使用多个过滤器,或者给过滤器传参
- 语法:
- 过滤器传参:vue变量 | 过滤器(实参)
- 多个过滤器:vue变量 | 过滤器1 | 过滤器2
<template>
<div>
<p>原来的样子:{{msg}}</p>
<!-- 给过滤器传参语法:vue变量 | 过滤器名(值) -->
<p>使用翻转过滤器:{{msg | reverse('|')}}</p>
<!-- 多个过滤器使用语法:vue变量 | 过滤器1 | 过滤器2 -->
<p :title="msg | toUp | reverse('|')">鼠标长停留</p>
</div>
</template>
<script>
export default {
data () {
return {
msg: 'Hello,World'
}
},
// 方式2:局部过滤器
// 只能在当前vue文件内使用
// 语法: filters: {过滤器名字 (val) {return 处理后的值}}
filters: {
toUp (val) {
return val.toUpperCase()
}
}
}
</script>
<style>
</style>
总结:过滤器可以传参,还可以对某个过滤器结果,后面在使用一个过滤器
3. 案例-品牌管理(时间格式优化)
目标:复制上个案例,在此基础上,把表格里的时间利用过滤器+moment模块,格式化成YYYY-MM-DD格式
图示:
-
下载moment处理日期的第三方工具模块
moment官方文档http://momentjs.cn/docs/#/displaying/
yarn add moment
-
定义过滤器,把时间用moment模块格式化,返回我们想要的格式
// 目标: 处理时间 // 1. 下载moment模块 import moment from 'moment' // 2. 定义过滤器, 编写内部代码 filters: { formatDate (val){ return moment(val).format('YYYY-MM-DD') } } <!-- 3. 使用过滤器 --> <td>{{ obj.time | formatDate }}</td>
七、计算属性
1. 计算属性- computed
目标:一个数据,依赖另外一些数据计算而来的结果
-
语法:
computed: { "计算属性名" () { return "值" } }
-
需求:
-
求2两个数的和显示到页面上
<template> <div> <p>a+b的结果是:{{num}}</p> </div> </template> <script> export default { data () { return { a: 10, b: 20 } }, computed: { num() { return this.a + this.b } } } </script> <style> </style>
注意:计算属性也是vue数据变量,所以不要和data里重名,用法和data相同
-
总结:一个数据,依赖另为一些数据计算而来的结果
2. 计算属性-缓存
目标:计算属性是基于他们的依赖项的值结果进行缓存的,只要依赖的变量不变,都直接从缓存取结果
<template>
<div>
<p>{{reverseMessage}}</p>
<p>{{reverseMessage}}</p>
<p>{{reverseMessage}}</p>
<p>{{getMessage()}}</p>
<p>{{getMessage()}}</p>
<p>{{getMessage()}}</p>
</div>
</template>
<script>
export default {
data () {
return {
msg: 'Hello,World'
}
},
methods: {
getMessage(){
console.log('函数执行了');
return this.msg.split('').reverse().join('')
}
},
computed: {
reverseMessage(){
console.log('计算属性执行了');
return this.msg.split('').reverse().join('')
}
}
}
</script>
<style>
</style>
总结:计算属性根据依赖变量结果缓存,依赖变化重新计算结果存入缓存,比普通方法性能更高
3. 案例-品牌管理(总价和均价)
目标:基于之前的案例,完成总价和均价的计算效果
<tr style="background-color: #EEE">
<td>统计:</td>
<td colspan="2">总价钱为: {{ allPrice }}</td>
<td colspan="2">平均价: {{ svgPrice }}</td>
</tr>
<script>
// 目标: 总价和均价显示
// 1. 末尾补tr - 显示总价和均价
export default {
// ...源代码省略
// 2. 计算属性
computed: {
allPrice(){
// 3. 求总价
return this.list.reduce((sum, obj) => sum += obj.price, 0)
},
avgPrice(){
// 4. 求均价 - 保留2位小数
return (this.allPrice / this.list.length).toFixed(2)
}
}
}
</script>
总结:总价来源于所有数据计算而来的结果,故采用计算属性
4. 计算属性-完整写法
目标:计算属性也是变量,如果想要直接赋值,需要使用完整写法
-
语法:
computed: { "属性名" :{ set(值){ }, get() { return "值" } } }
-
需求:
- 计算属性给v-mode使用
<template>
<div>
<div>
<span>姓名:</span>
<input type="text" v-model="full">
</div>
</div>
</template>
<script>
// 问题: 给计算属性赋值 - 需要setter
// 解决:
/*
完整语法:
computed: {
"计算属性名" (){},
"计算属性名": {
set(值){
},
get(){
return 值
}
}
}
*/
export default {
computed: {
full: {
// 给full赋值触发set方法
set(val){
console.log(val)
},
// 使用full的值触发get方法
get(){
return "张三"
}
}
}
}
</script>
<style>
</style>
总结:想要给计算属性赋值,需要使用set方法
5. 案例-小选影响全选
目标:小选框都选中(手选),全选自动选中
- 需求:小选框都选中(手选),全选自动选中
分析:
- 先静态后动态,从md拿到静态标签和数据
- 循环生成复选框和文字,对象的c属性和小选项框的选中状态,用v-model双向绑定
- 定义isAll计算属性,值通过小选框们统计c属性状态得来
<template>
<div>
<span>全选:</span>
<!-- v-model关联选中状态 -->
<input type="checkbox" v-model="isAll"/>
<button>反选</button>
<ul>
<li v-for="(obj,index) in arr" :key="index">
<!-- 对象.c关联选中状态 -->
<input type="checkbox" v-model="obj.c"/>
<span>{{obj.name}}</span>
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
arr: [
{
name: "猪八戒",
c: false,
},
{
name: "孙悟空",
c: false,
},
{
name: "唐僧",
c: false,
},
{
name: "白龙马",
c: false,
},
],
};
},
computed: {
isAll() {
// every口诀:查找数组里"不符合"条件,直接原地返回false
return this.arr.every(obj => obj.c === true)
}
}
};
</script>
6. 案例-全选影响小选
目标:全选影响小选
- 需求1:获取到全状态-改装isAll计算属性
- 需求2:全选状态同步给所有小选框
分析:
- isAll改成完整写法,set里获取到全选框,勾选的状态值
- 遍历数据数组,赋给所有小选框v-model关联的属性
<script>
export default {
// ...其他代码
// 5. 计算属性-isAll
computed: {
isAll: {
set(val){
// 7. 全选框 - 选中状态(true/false)
this.arr.forEach(obj => obj.c = val)
},
get(){
// 6. 统计小选框状态 -> 全选状态
// every口诀: 查找数组里"不符合"条件, 直接原地返回false
return this.arr.every(obj => obj.c === true)
}
}
}
};
</script>
7. 案例-反选
目标:反选功能
- 需求:点击反选,让所有小选框,各自取相反勾选状态
分析:
- 小选框的勾选状态,在对象c属性
- 遍历所有对象,把对象的c属性取反赋予回去即可
<button @click="btn">反选</button>
<script>
export default {
// ...其他代码省略
methods: {
btn(){
// 8. 让数组里对象的c属性取反再赋予回去
this.arr.forEach(obj => obj.c = !obj.c)
}
}
};
</script>
八、侦听器
目标:可以侦听data/computed属性值改变
-
语法:
watch: { "被侦听的属性名"(newVal,oldVal) { } }
<template>
<div>
<input type="text" v-model="name">
</div>
</template>
<script>
export default {
data () {
return {
name: ''
}
},
// 目标:侦听到name值的改变
/*
语法:watch: {
变量名(newVal,oldVal){
// 变量名对应值改变这里自动触发
}
}
*/
watch: {
// newVal:当前最新值
// oldVal:上一刻值
name(newVal,oldVal) {
console.log(newVal,oldVal);
}
}
}
</script>
<style>
</style>
2. 侦听器=深度侦听和立即执行
目标:侦听复杂类型,或者立即执行侦听函数
-
语法:
watch:{ "要侦听的属性名"{ immediate:true, //立即执行 deep:true, //深度侦听复杂类型内变化 handler (newVal,oldVal) { } } }
<template>
<div>
<input type="text" v-model="user.name">
<input type="text" v-model="user.age">
</div>
</template>
<script>
export default {
data () {
return {
user: {
name: '',
age: 0
}
}
},
// 目标:侦听对象
/*
语法:watch: {
变量名:{
handler(newVal,oldVal){
},
deep: true,
immediate: true
}
*/
watch: {
// newVal:当前最新值
// oldVal:上一刻值
user: {
handler(newVal,oldVal){
console.log(newVal,oldVal);
},
deep: true,
immediate: true
}
}
}
</script>
<style>
</style>
总结:immediate立即侦听,deep深度侦听,handler固定方法触发
3. 案例-品牌管理(数据缓存)
目标:侦听list变化,同步到浏览器本地
- 需求:把品牌管理的数据实时同步到本地缓存
分析:
- 在watch侦听list变化的时候,把最新的数组list转成JSON字符串存入到localStorage本地
- data里默认把list变量从本地取值,如果取不到给个默认值的空数组
效果:新增/删除-刷新页面-数据还在
<script>
import moment from "moment";
export default {
data() {
return {
name: "", // 名称
price: 0, // 价格
// 3. 本地取出缓存list
list: JSON.parse(localStorage.getItem('pList')) || [],
};
},
// ...其他代码省略
watch: {
list: {
handler(){
// 2. 存入本地
localStorage.setItem('pList', JSON.stringify(this.list))
},
deep: true
}
}
};
</script>
4. 品牌管理(完整版)
<template>
<div id="app">
<div class="container">
<!-- 顶部框模块 -->
<div class="form-group">
<div class="input-group">
<h4>品牌管理</h4>
</div>
</div>
<!-- 数据表格 -->
<table class="table table-bordered table-hover mt-2">
<thead>
<tr>
<th>编号</th>
<th>资产名称</th>
<th>价格</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="obj in list" :key="obj.id">
<td>{{obj.id}}</td>
<td>{{obj.name}}</td>
<!-- 如果价格超过100,就有red这个类 -->
<td :class="{red: obj.price > 100}">{{obj.price}}</td>
<td>{{obj.time | FormData}}</td>
<td><a href="#" @click="delFn(obj.id)">删除</a></td>
</tr>
<tr style="background-color: #EEE">
<td>统计:</td>
<td colspan="2">总价钱为:{{allprice}}</td>
<td colspan="2">平均价:{{avgprice}}</td>
</tr>
</tbody>
<tfoot v-if="list.length === 0">
<tr>
<td colspan="5" style="text-align: center">暂无数据</td>
</tr>
</tfoot>
</table>
<!-- 添加资产 -->
<form class="form-inline">
<div class="form-group">
<div class="input-group">
<input
type="text"
class="form-control"
placeholder="资产名称"
v-model="name"
/>
</div>
</div>
<div class="form-group">
<div class="input-group">
<input
type="text"
class="form-control"
placeholder="价格"
v-model.number="price"
/>
</div>
</div>
<!-- 4. 阻止表单提交 -->
<!-- 2. 给按钮绑定点击事件 -->
<button class="btn btn-primary" @click.prevent="btnAdd">添加资产</button>
</form>
</div>
</div>
</template>
<script>
//时间格式化包
import moment from 'moment'
export default {
data() {
return {
name: "", // 名称
price: 0, // 价格
list: JSON.parse(localStorage.getItem('pList')) || [],
};
},
methods: {
// 3.把值以对象的形式插入list
btnAdd () {
// 5. 判断是否为空
if(this.name.trim() === '' || this.price === 0) {
return alert('不能为空!')
}
// 解决bug:无数组新增list没有数据,id需要给一个固定值
let id = this.list.length > 0 ? this.list[this.list.length - 1].id + 1 : 100
this.list.push({
id: id,
name: this.name,
price: this.price,
time: new Date()
})
},
delFn(id) {
// 通过id找到这条数据在数组中下标
let index = this.list.findIndex(obj => obj.id === id)
this.list.splice(index,1)
}
},
filters: {
FormData(val) {
return moment(val).format('YYYY-MM-DD')
}
},
computed: {
// 计算总价
allprice() {
return this.list.reduce((sum,obj) => sum += obj.price,0)
},
// 计算平均价
avgprice() {
return (this.allprice / this.list.length).toFixed(2)
}
},
watch: {
// 侦听器list
list: {
handler(){
// 2. 存入本地
localStorage.setItem('pList',JSON.stringify(this.list))
},
deep:true
}
}
};
</script>
<style >
.red{
color: red;
}
</style>