Vue2.js(Vue2及相关技术栈)

Vue是现在非常热门的前端框架技术,企业为了迅速的完成某个需求,或者完成某个要求,而框架就是提高开发效率的工具,例如如果让你写一个轮播图,使用原生的js就需要你自己写很久,而使用某些ui框架或者技术框架,就能使用几行代码快速的完成这个轮播图,能够大大的提高开发的效率。同时框架可以约束和规范代码,提高代码的可维护性和协作开发的效率,这个在我们进行团队开发的时候显得尤为重要。学习框架不一定会让你快乐的进行开发,但是不学框架一定不会很快的进行开发。当然并不是说学会了框架了以后,前面的一些原生的语言就不重要了,原生的语言和框架的学习是一个循序渐进的过程,基础不牢,地动山摇。同时框架并不是尽善尽美的,在遇到一些特殊的需求的时候,也需要我们使用原生语言来完成这些需求。

1.vue基础3

概念:Vue 是一个用于 构建用户界面的渐进式框架

构建页面:

构建页面

渐进式:vue+vueRouter+Vuex+vite+……..,vue的学习是循序渐进的,学完一个就能做出对应的东西来。

框架:上面有说明,能规范开发流程,大大的提高开发的效率。缺点就是需要动脑子去学,vue有一些语法规则需要我们去学习和练习。

创建vue实例

创建实例

1.准备容器:看vue管理的是哪一个容器

2.引入核心包(引入vue这个包)

开发版本:给程序员用的,开发环境版本,包含了有帮助的命令行警告,这个包比较大,内容比较丰富。

生产版本: 就是当你要把这个程序部署在服务器上的时候,可以切换成生产版本的这个vue包,比较小,但是缺少提示。

开发环境版本,包含了有帮助的命令行警告

1
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

生产环境版本,优化了尺寸和速度

1
2
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>

3.创建 Vue 实例 new Vue()

Vue是一个构造函数,里面包含了各个配置项,到后面一一跟大家讲解

4.渲染数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<body>
<!-- 1.准备容器:看vue管理的是哪一个容器 -->
<div id="app"> {{message}} {{age}} 双大括号里面就是渲染的数据</div>

<!-- 2.引入核心包(引入vue这个包) -->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>


<script>
// 3.创建 Vue 实例 new Vue()
var app = new Vue({
el: '#app', //确定管理的是哪个容器,这里管理的是上面的id为app的div
data: { // data这个配置项中配置的就是需要渲染的数据
message: 'Hello Vue!',
age:10
}
})

</script>

</body>

2.vue指令

2.1 插值表达式

插值表达式是Vue的模板语法

  • 1. 作用: 利用表达式进行插值,渲染到页面中

    表达式:是可以被求值的代码,JS引擎会将其计算出一个结果

  • 2. 语法:

    这样写的前提是:当前的容器被vue管理了, 并且data中有数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
     <div id="app"> 
    {{age}}

    {{p.age}}

    {{p.age>100?'老了':'没有'}}

    {{age+10}}
    </div>
  • 3.注意点

    注意点

2.2Vue的核心特性:响应式

响应式:数据一旦变化,视图就会自动更新(视图简单理解就是页面,只要数据发生改变了,用到这个数据的页面也会发生改变)

  • 如何访问修改?data中的数据, 最终会被添加到实例上

    data中的数据已经被处理成了响应式的数据

    ① 访问数据: “实例.属性名”

    ② 修改数据: “实例.属性名” = “值”

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 这里进行了实例化,存储在变量app中
    const app=new Vue({
    el:'#app',

    // data中的数据已经是响应式的数据了
    data:{
    name:'hello',
    age:10
    }
    })

    响应式的数据:数据一旦变化了,页面就自动更新,所以说,如果我们需要修改页面的话,我们只需要修改数据就可以,更专注于核心业务逻辑就可以。

    响应式


    2.3Vue 开发者工具

    vue2调试的时候会用上,到vue3这个开发者工具用的就相对来说就比较少了。

    • 安装

      (1)通过谷歌应用商店安装 (科学上网)

      (2)国内的一些插件网站(推荐) https://chrome.zzzmh.cn/index

      (3)谷歌浏览器安装插件,然后配置一下允许访问文件网址
      (4)重启一下浏览器,看控制台里面有没有vue


    2.4Vue指令

    Vue指令的合计:https://v2.cn.vuejs.org/v2/api/#%E6%8C%87%E4%BB%A4

    指令:带有 **v-**前缀的特殊标签属性,Vue 会根据不同的【指令】,针对标签实现不同的【功能】

    • v-html:

    作用:设置元素的 innerHTML

    语法:v-html = “表达式 “

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <body>

    <div id="app">
    <div v-html="inhtml"></div>
    </div>
    <!-- 2.引入核心包(引入vue这个包) -->
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

    <script>
    const app=new Vue({
    el:'#app',

    // data中的数据已经是响应式的数据了
    data:{
    inhtml:`<a href="www.baidu.com">到百度</a>`
    }
    })
    </script>
    </body>

    • v-show 与 v-if

      v-show

      本质上是通过控制css 的display:none 来控制显示与隐藏

      从实现效果可以看出DOM元素始终是存在的,v-show只是利用元素的display属性控制着元素的显示隐藏。

      1. 作用: 控制元素显示隐藏

      2. 语法: v-show = “表达式” 表达式值 true 显示, false 隐藏

      3. 原理: 切换 display:none 控制显示隐藏

      4. 场景: 频繁切换显示隐藏的场景

      v-if

      本质上是根据条件,控制元素的创建与移除

      从实现效果可以看出flag值为false时DOM元素被删除。

      1. 作用: 控制元素显示隐藏(条件渲染)

      2. 语法: v-if = “表达式” 表达式值 true 显示, false 隐藏

      3. 原理: 基于条件判断,是否 创建 或 移除 元素节点

      4. 场景: 要么显示,要么隐藏,不频繁切换的场景

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      <!DOCTYPE html>
      <html lang="en">
      <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <style>
      .box1 {
      width: 100px;
      height: 100px;
      background-color: yellowgreen;
      }
      </style>
      </head>
      <body>

      <div id="app">
      <div class="box1" v-show="istrue"></div>
      </div>
      <!-- 2.引入核心包(引入vue这个包) -->
      <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

      <script>
      const app=new Vue({
      el:'#app',

      // data中的数据已经是响应式的数据了
      data:{
      istrue:true
      }
      })
      </script>
      </body>
      </html>

      v-show与v-if 的面试题


    • v- v-else v-else-if

      1. 作用: 辅助 v-if 进行判断渲染

      2. 语法: v-else v-else-if = “表达式”

      3. 注意: 需要紧挨着 v-if 一起使用

      成绩判断案例


    • v-on

      1. 作用: 注册事件 = 添加监听 + 提供处理逻辑

      2. 语法:

      v-on

      ① v-on:事件名 = “内联语句”

      绑定任何的事件都是可以的,忘记了可以看 这里

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      <body>
      <!-- 1.准备容器:看vue管理的是哪一个容器 -->
      <div id="app">
      --- v-on:click="写一些比较简单的内联语句" ---
      <button v-on:click="num++">点击{{num}}</button>
      </div>

      <!-- 2.引入核心包(引入vue这个包) -->
      <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>


      <script>
      // 3.创建 Vue 实例 new Vue()
      var app = new Vue({
      el: '#app', //确定管理的是哪个容器,这里管理的是上面的id为app的div
      data: { // data这个配置项中配置的就是需要渲染的数据
      num:1111
      }
      })

      </script>

      </body>

      ② v-on:事件名 = “methods中的函数名

      内联的语句虽然方便,但是只适合简单的事件的处理,如果要进行比较复杂的功能就需要使用methods配置项

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      <body>
      <div id="app">
      <button v-on:click="add">点击{{num}}</button>
      </div>

      <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
      <script>

      var app = new Vue({
      el: '#app',
      data: {
      num:1111
      },
      // 这个配置项中放的都是方法
      methods: {
      // 方法
      add(){
      app.num++
      }
      },
      })
      </script>
      </body>

      注意:如果methods中的一些方法想使用data中的参数,需要从实例身上拿,data中的数据是挂载在实例上,如果实例的名称换了,从app换成别的,这个时候再使用app.数据就不行了,所以Vue让所有methods中函数的this都指向实例。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      <body>
      <div id="app">
      <button @click="add">点击{{num}}</button>
      </div>

      <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
      <script>

      var app = new Vue({
      el: '#app',
      data: {
      num:1111
      },
      // 这个配置项中放的都是方法
      methods: {
      // 方法
      add(){
      this.num++
      }
      },
      })
      </script>
      </body>

      3. 简写:@事件名

      1
      <button @click="num++">点击{{num}}</button>

      4.v-on 调用传参

      v-on

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      <body>
      <div id="app">
      <button @click="buy('我是传递过来的参数')">按钮</button>
      </div>

      <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
      <script>

      var app = new Vue({
      el: '#app',
      data: {
      num:1111
      },

      methods: {
      buy(a){
      console.log(a);
      }
      },
      })
      </script>
      </body>

      案例:自动贩卖机,有一百块钱,买可乐-3元,买咖啡-5元

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      <body>
      <div id="app">
      <h1>还剩{{money}}</h1>
      <button @click="buy(3)">买可乐-3</button>
      <button @click="buy(5)">买可乐-5</button>
      </div>

      <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
      <script>

      var app = new Vue({
      el: '#app',
      data: {
      money:100
      },
      methods: {
      buy(Bmoney){
      this.money-=Bmoney
      }
      },
      })
      </script>
      </body>

      • v-bond

      1. 作用: 动态的设置html的标签属性 → src url title …

      2. 语法: v-bind:属性名=”表达式”

      3. 注意: 简写形式 :属性名=”表达式”

      通过v-bind 来设置图片及其说明

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      <body>
      <div id="app">
      <img v-bind:src="imgurl" v-bind:alt="imgalt">
      </div>

      <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
      <script>

      var app = new Vue({
      el: '#app',
      data: {
      imgurl:"table2.jpg",
      imgalt:"我是图片的说明"
      },
      })
      </script>
      </body>

      简写形式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      <body>
      <div id="app">
      <img :src="imgurl" :alt="imgalt">
      </div>

      <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
      <script>

      var app = new Vue({
      el: '#app',
      data: {
      imgurl:"table2.jpg",
      imgalt:"我是图片的说明"
      },
      })
      </script>
      </body>

      使用v-bind做一个简单的轮播图

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      <body>
      <div id="app">
      <button @click='index--' v-show="index>0">上一张</button>
      <img :src="list[index]" :alt="imgalt">
      <button @click='index++' v-show="index<list.length-1">下一张</button>
      </div>

      <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
      <script>

      var app = new Vue({
      el: '#app',
      data: {
      list:[
      "1.jpg",
      "2.jpg",
      "3.jpg",
      ],
      // 第几张,list的下标
      index:0
      },
      })
      </script>
      </body>

      • v-for

      1. 作用: 基于数据循环, 多次渲染整个元素 → 数组、对象、数字…

      2. 遍历数组语法:

      v-for = “(item, index) in 数组” 数组有几项就生成几个元素

      item 每一项, index 下标 :key 当前元素的唯一标识

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      <body>
      <div id="app">

      <ul>
      <li v-for="(item,index) in list">{{item}} {{index}}</li>
      </ul>

      </div>

      <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
      <script>

      var app = new Vue({
      el: '#app',
      data: {
      list:[1,2,3,4]
      },
      })
      </script>
      </body>

      index 是可以省略的

案例:书架

书架案例

  • 案例模板

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    <!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>
    <body>

    <div id="app">
    <h3>小黑的书架</h3>
    <ul>
    <li>
    <span>《红楼梦》</span>
    <span>曹雪芹</span>
    <button>删除</button>
    </li>
    </ul>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/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: '罗贯中' }
    ]
    }
    })
    </script>
    </body>
    </html>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    <!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>
    <!-- 2.引入核心包(引入vue这个包) -->

    <div id="app">
    <h3>书架</h3>
    <ul>

    <!-- 遍历比较简单,item是每一项,index是遍历的那一项的下标 -->
    <li v-for="(item,index) in booksList" :key="item.id">
    <span>{{item.name}}</span>
    <span>{{item.author}}</span>
    {{item.id}}
    <!-- 删除功能,本质上就是删除数组的对应项 -->
    <!-- 可以通过注册点击事件,通过id来删除对应项 -->
    <button @click="deletew(item.id)">删除</button>
    </li>

    </ul>
    </div>
    <script src="1.js"></script>
    <script>
    const app = new Vue({
    el: '#app',

    // data中的数据已经是响应式的数据了
    data: {
    booksList: [
    { id: 1, name: '《001》', author: '张三' },
    { id: 2, name: '《002》', author: '李四' },
    { id: 3, name: '《003》', author: '王五' },
    { id: 4, name: '《004》', author: '赵六' }
    ]
    },
    methods: {
    deletew (id) {
    // filter() 能将数组中满足条件的元素过滤出来
    this.booksList= this.booksList.filter(function(item){
    return item.id!=id
    })

    // es6 写法
    this.booksList=this.booksList.filter((item)=>item.id!=id)
    }
    },
    })
    </script>
    </body>

    </html>

v-for中的key (重要)

给元素添加的唯一标识,便于Vue进行列表项的正确排序复用。

给元素加了key就相当于给当前的标签元素添加了一个唯一 的标识,也就是说给标签加了一个id!!! 是给标签加了一个id

标签是根据data中的数据渲染来的,每一个数据也都有自己的唯一的标识, 这个时候,标签有自己的唯一标识id,数据也有标识(这个标识是人工添加的 如上述数据的id),数据的唯一标识和标签的唯一标识是对应起来的。

根据vue的设计,如果数据发生了变化,例如第二个数据被删除了,vue 会根据数据的唯一标识找到对应的标签(因为数据的唯一标识是和标签的id是对应的所以能找到)然后删除对应的标签。

如果没有 key会怎么样

如果没哟key,就相当于标签是没有id的,就没办法和数据对应起来。这个时候如果删除了一个数据,vue底层是这样操作的:如渲染了 四个 li 标签,这四个标签都是没id的, 删除了第1个数据(这个数据默认是写在第一个li中的),这个时候vue会默认删除最后一个li(注意是最后一个,不是第一个),然后把第二个数据给第一个li,第三个数据给第二个li,第四个数据给第三个li,第一个li并没有被删除,仅仅只是数据被换了。

注意点:

  1. key 的值只能是 字符串 或 数字类型

  2. key 的值必须具有 唯一性

  3. 推荐使用 id 作为 key(唯一),不推荐使用 index 作为 key(会变化,不对应)

语法:

1
<li v-for="(item, index) in xxx" :key="唯一值">

  • v-model

数据和视图是同步的

**1.作用:**给表单元素使用,双向数据绑定→可以快速获取或设置表单元素内容

①数据变化→视图自动更新

②视图变化→数据自动更新

通过vue开发者工具验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!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>
<body>
<div id="app">

账号:<input type="text" v-model="username">
密码:<input type="text" v-model="password">
</div>



<script src="1.js"></script>

<script>
// 3.创建 Vue 实例 new Vue()
var app = new Vue({
el: '#app',
data: {
username:"",
password:""
}
})

</script>

</body>
</html>

通过v-mode 可以快速的设置表单中的元素或者内容

登录和重置功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<!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>

<body>
<div id="app">

账号:<input type="text" v-model="username">
密码:<input type="text" v-model="password">

<button @click="login">登录</button>
<button @click="reset">重置</button>
</div>



<script src="1.js"></script>

<script>
// 3.创建 Vue 实例 new Vue()
var app = new Vue({
el: '#app',
data: {
username: "",
password: ""
},
methods: {
login(){
if(this.username==="123"&&this.password==='123'){
alert("登录成功")
this.reset()
}else{
alert("账号或者密码错误")

this.reset()
}
},

reset(){
this.password=""
this.username=""
}
},
})

</script>

</body>

</html>

2.5综合案例

记事本

  • 列表渲染+删除功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<!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>记事本</title>
</head>
<body>

<!-- 主体区域 -->
<section id="app">
<!-- 输入框 -->
<header class="header">
<h1>小黑记事本</h1>
<input placeholder="请输入任务" class="new-todo" />
<button class="add">添加任务</button>
</header>
<!-- 列表区域 -->
<section class="main">
<ul class="todo-list">
<li class="todo" v-for="(item,index) in list">
<div class="view">
<span class="index">{{index+1}}.</span> <label>{{item.name}}</label>
<button class="destroy" @click="del(item.id)"></button>
</div>
</li>
</ul>
</section>
<!-- 统计和清空 -->
<footer class="footer">
<!-- 统计 -->
<span class="todo-count">合 计:<strong> 1 </strong></span>
<!-- 清空 -->
<button class="clear-completed">
清空任务
</button>
</footer>
</section>

<!-- 底部 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>

const app = new Vue({
el: '#app',
data: {
list:[
{id:1,name:'跑步1000m'},
{id:2,name:'跑步1000m'},
{id:3,name:'跑步1000m'}
]
},
methods: {
del(id){

this.list= this.list.filter((item)=>item.id!==id)

}
},
})

</script>
</body>
</html>

  • 添加功能

    1. 拿到输入框的内容
    2. 将内容添加到数组中, 往数组的最前面添加新的内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<!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>记事本</title>
</head>
<body>

<!-- 主体区域 -->
<section id="app">
<!-- 输入框 -->
<header class="header">
<h1>小黑记事本</h1>
<input v-model="todo" placeholder="请输入任务" class="new-todo" />
<button class="add" @click="add">添加任务</button>
</header>
<!-- 列表区域 -->
<section class="main">
<ul class="todo-list">
<li class="todo" v-for="(item,index) in list">
<div class="view">
<span class="index">{{index+1}}.</span> <label>{{item.name}}</label>
<button class="destroy" @click="del(item.id)"></button>
</div>
</li>
</ul>
</section>
<!-- 统计和清空 -->
<footer class="footer">
<!-- 统计 -->
<span class="todo-count">合 计:<strong> 1 </strong></span>
<!-- 清空 -->
<button class="clear-completed">
清空任务
</button>
</footer>
</section>

<!-- 底部 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>

const app = new Vue({
el: '#app',
data: {
list:[
{id:1,name:'跑步1000m'},
{id:2,name:'跑步1000m'},
{id:3,name:'跑步1000m'}
],
// 当前任务
todo:""
},
methods: {
del(id){

this.list= this.list.filter((item)=>item.id!==id)

},
add(){

if(this.todo){

// 添加内容
this.list.unshift({
id:this.list.length+100,
name:this.todo
})
// 清空数据
this.todo=''
}else{
alert("内容不能为空")
}

}
},
})

</script>
</body>
</html>

  • 合计+清空任务

1.合计就是当前数组的长度

2.清空就是清空数组就可以了

3.如果没有任务了就把清除任务就隐藏掉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<!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>记事本</title>
</head>

<body>

<!-- 主体区域 -->
<section id="app">
<!-- 输入框 -->
<header class="header">
<h1>小黑记事本</h1>
<input v-model="todo" placeholder="请输入任务" class="new-todo" />
<button class="add" @click="add">添加任务</button>
</header>
<!-- 列表区域 -->
<section class="main">
<ul class="todo-list">
<li class="todo" v-for="(item,index) in list">
<div class="view">
<span class="index">{{index+1}}.</span> <label>{{item.name}}</label>
<button class="destroy" @click="del(item.id)"></button>
</div>
</li>
</ul>
</section>
<!-- 统计和清空 -->
<footer class="footer" v-show="list.length>0">
<!-- 统计 -->
<span class="todo-count">合 计:<strong> {{list.length}} </strong></span>
<!-- 清空 -->
<button class="clear-completed" @click="clean">
清空任务
</button>
</footer>
</section>

<!-- 底部 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>

const app = new Vue({
el: '#app',
data: {
list: [
{ id: 1, name: '跑步1000m' },
{ id: 2, name: '跑步1000m' },
{ id: 3, name: '跑步1000m' }
],
// 当前任务
todo: ""
},
methods: {
del (id) {

this.list = this.list.filter((item) => item.id !== id)

},
add () {

if (this.todo) {

// 添加内容
this.list.unshift({
id: this.list.length + 100,
name: this.todo
})
// 清空数据
this.todo = ''
} else {
alert("内容不能为空")
}

},

clean(){
this.list=[]
}
},
})

</script>
</body>

</html>

2.6.v-bind样式控制

语法 :class = “对象/数组”

对象 → 键就是类名,值是布尔值。如果值为 true,有这个类,否则没有这个类

class样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>


<style>
.big{
height: 100px;
width: 100px;
background-color: yellowgreen;
}

.boxShadow{
box-shadow:4px 4px 4px rgb(83, 83, 83) ;
border: 2px solid #ccc;
}


</style>
</head>
<body>

<div id="app">
<div class="big" :class="{boxShadow:true}">
</div>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<script>
const app=new Vue({
el:'#app',

})
</script>
</body>
</html>

数组 → 数组中所有的类,都会添加到盒子上,本质就是一个 class 列表

box2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>


<style>
.big{
height: 100px;
width: 100px;
background-color: yellowgreen;
}

.boxShadow{
box-shadow:4px 4px 4px rgb(83, 83, 83) ;
border: 2px solid #ccc;
}


</style>
</head>
<body>

<div id="app">
<div :class="['big','boxShadow']"></div>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<script>
const app=new Vue({
el:'#app',

})
</script>
</body>
</html>

京东tab栏切换案例

源代码:样式+数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<!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>
<body>

<div id="app">
<ul>
<li><a class="active" href="#">京东秒杀</a></li>
<li><a href="#">每日特价</a></li>
<li><a href="#">品类秒杀</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: {

list: [
{ id: 1, name: '京东秒杀' },
{ id: 2, name: '每日特价' },
{ id: 3, name: '品类秒杀' }
]

}
})
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<body>

<div id="app">
<ul>
<li v-for="(item,index) in list" :key="item.id" @click="acitveIndex=index">
<a :class="{active:acitveIndex===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: {
// 第几个被激活
acitveIndex:0,
list: [
{ id: 1, name: '京东秒杀' },
{ id: 2, name: '每日特价' },
{ id: 3, name: '品类秒杀' }
]

}
})
</script>
</body>

2.7 v-model 应用于其他表单元素

常用的表单元素都能用v-model 来绑定和关联

  • 输入框 input:text

    文本输入框绑定的是一个字符串

    1
    2
    <!--文本输入框 -->
    姓名:<input type="text" v-modle="username">
  • 单选框checkbox

    文本输入框绑定的是一个布尔值

    1
    是否同意: <input type="checkbox" v-model="istrue">
  • 单选框 input:radio

    name:name一样的两个单选框会被分到一组里面。

    value:value是当前单选框的值,后续通过v-model 来获取这个值

    v-model:用于绑定data中的数据

    value是什么数据类型,data中绑定value的这个值就是什么类型的数据

    1
    2
    性别:<input type="radio" name="sex" value="1" v-model="sex">男
    <input type="radio" name="sex" value="0" v-model="sex">女
  • 下拉菜单 select

    select 中的v-model和option 是关联的,选中的option的value是什么, select中选v-model绑定的值就是什么

    select v-model 绑定的值和opction的值相关

    1
    2
    3
    4
    5
    班级
    <select v-model="userClass">
    <option value="2229">计专2229</option>
    <option value="22210">计专22210</option>
    </select>
  • 文本域 textarea

    1
    2
    描述:
    <textarea v-model="msg" cols="30" rows="10"></textarea>

3.指令修饰符

通过 “.” 指明一些指令 后缀,不同 后缀 封装了不同的处理操作

  • 按键修饰符

@keyup.enter

键盘回车监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!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">

<input type="text" @keyup.enter="fun">

<input type="text" @keyup="fun2">
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<script>
const app=new Vue({
el:'#app',
data:{

},
methods: {
fun(){
alert("回车事件触发")
},
// 原生js实现回车触发事件

fun2(e){
if(e.key==='Enter'){
this.fun()
}
}
},
})

</script>
</body>
</html>
  • v-model修饰符

    v-model.trim → 去除首尾空格

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <!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">

    <input type="text" v-model.trim="userName">

    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

    <script>
    const app=new Vue({
    el:'#app',
    data:{
    userName:''
    },

    })

    </script>
    </body>
    </html>

    v-model.number → 转数字

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    <!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">

    <input type="text" v-model.trim="userName">

    <input type="text" v-model.number="userName">

    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

    <script>
    const app=new Vue({
    el:'#app',
    data:{
    userName:'',
    password:''
    },

    })

    </script>
    </body>
    </html>
  • 事件修饰符

@事件名.stop → 阻止冒泡

事件冒泡:父元素有一个点击事件,子元素也有一个点击事件,触发子元素的点击事件会冒泡冒到父元素身上

正常的阻止事件冒泡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.f{
height: 200px;
width: 200px;
background-color: red;
}
.s{
height: 100px;
width: 100px;
background-color: rgb(0, 35, 131)
}
</style>
</head>
<body>

<div id="app">

<div @click="fun" class="f">

<div @click="fun1" class="s">

</div>
</div>

</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<script>
const app=new Vue({
el:'#app',
data:{
userName:'',
password:''
},
methods: {
fun(){
alert("父")
},
fun1(e){
e.stopPropagation();
alert("子")
}
},
})

</script>
</body>
</html>

事件修饰符.stop 阻止事件冒泡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.f{
height: 200px;
width: 200px;
background-color: red;
}
.s{
height: 100px;
width: 100px;
background-color: rgb(0, 35, 131)
}
</style>
</head>
<body>

<div id="app">

<div @click="fun" class="f">

<div @click.stop="fun1" class="s">

</div>
</div>

</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<script>
const app=new Vue({
el:'#app',
data:{
userName:'',
password:''
},
methods: {
fun(){
alert("父")
},
fun1(e){

alert("子")
}
},
})

</script>
</body>
</html>

@事件名.prevent → 阻止默认行为

原生js阻止元素默认行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<body>

<div id="app">
<a href="https://www.baidu.com" @click="fun">去百度</a>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<script>
const app=new Vue({
el:'#app',
data:{
userName:'',
password:''
},
methods: {
fun(e){
e.preventDefault();
alert("ddd")
}

},
})

</script>
</body>

.prevent 阻止默认行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<body>

<div id="app">
<a href="https://www.baidu.com" @click.prevent="fun">去百度</a>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<script>
const app=new Vue({
el:'#app',
data:{
userName:'',
password:''
},
methods: {
fun(){
alert("ddd")
}

},
})

</script>
</body>

4.计算属性

基于现有的属性,算出新的属性。(通过单价和购买数量算出总价),依赖的数据发生变化了以后计算属性也会自动计算。

  • 语法

    ① 声明在 computed 配置项中,一个计算属性对应一个函数

    ② 使用起来和普通属性一样使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    <!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>

    <body>

    <div id="app">
    {{money}}
    </div>
    <!-- 2.引入核心包(引入vue这个包) -->
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

    <script>
    const app = new Vue({
    el: '#app',
    data: {
    goods:[
    {name:"篮球",num:10,price:10},
    {name:"足球",num:10,price:10},
    {name:"乒乓球",num:10,price:10}
    ]
    },
    methods: {

    },

    computed:{
    money(){
    let Mmoney=0

    this.goods.forEach((item) => {
    Mmoney+=item.num
    });

    // for (let i = 0; i < this.goods.length; i++) {
    //Mmoney+=this.goods[i].num*this.goods[i].price
    //}

    return Mmoney
    }

    }
    })
    </script>
    </body>

    </html>

    计算属性特性:计算属性会对计算出来的结果缓存,再次使用直接读取缓存,依赖项变化了,会自动重新计算 → 并再次缓存

  • 计算属性完整的写法

    计算熟悉

    如果计算属性只需要获取的话就使用简写的形式,如果需要获取和设置就需要完整的写法

    get 中写的是获取的逻辑,如果需要用到计算属性就会调用get() 函数,执行里面的内容,将返回值作为求值的结果

    set中写的是 计算属性被修改以后的操作,会将被修改后的值传递给形参value,后续如何操作都是写在函数里面。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    <!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>

    <body>

    <div id="app">
    <input type="text" v-model="firstName">+ <input type="text" v-model="lastName">= {{name}}

    <button @click=changeName> 修改名字 </button>
    </div>
    <!-- 2.引入核心包(引入vue这个包) -->
    <script src="1.js"></script>

    <script>
    const app = new Vue({
    el: '#app',
    data: {
    firstName:'',
    lastName:''
    },
    methods: {
    changeName(){
    this.name='张三'
    }
    },

    computed:{
    name:{
    //被获取会调用get执行函数,将结果返回作为求值的值
    get(){
    return this.firstName+this.lastName

    },
    // 当赋值被修改的时候,将修改的值作为set 的形参,随你操作
    set(value){
    // value 是被修改以后的值
    this.firstName=value
    this.lastName=value
    }
    }

    },

    })
    </script>
    </body>

    </html>

    案例:成绩案例

    案例

    • 渲染

      1. 两个tbody ,当有数据的时候表格表格显示,否则暂无数据显示
      2. v-for渲染成绩
      3. 根据成绩决定是否加 red类(< 60 加red类 分数标红)
    1
    2
    3
    4
    5
    6
    7
    8
    <tbody v-if="list.length>0">
    <tr v-for="(item,index) in list" :key="item.id">
    <td>{{index+1}}</td>
    <td>{{item.subject}}</td>
    <td :class="{red:item.score<60}">{{item.score}}</td>
    <td><a href="#">删除</a></td>
    </tr>
    </tbody>
    • 删除

      注册事件,传递id,基于id,过滤掉要被删除的项,其他的保留

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <tbody v-if="list.length>0">
    <tr v-for="(item,index) in list" :key="item.id">
    <td>{{index+1}}</td>
    <td>{{item.subject}}</td>
    <td :class="{red:item.score<60}">{{item.score}}</td>
    <td><a href="#" @click="del(item.id)">删除</a></td>
    </tr>
    </tbody>

    methods: {
    del(id){
    this.list= this.list.filter(item=>item.id!=id)
    }
    },

    • 添加

    拿到用户输入的成绩,科目,组成一个对象,添加到数组中

    v-model.trim=”subject” .trim 去除前后空格

    v-model.number=”score” 转为数字类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    <!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 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>总分:246</span>
    <span style="margin-left: 50px">平均分:79</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.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="../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&& typeof(this.score.typeof)!=='number'&&this.score){
    this.list.push({
    id:+new Date(),
    subject:this.subject,
    score:this.score
    })
    }

    // 置空
    this.score=''
    this.subject=''
    }
    },

    })
    </script>
    </body>
    </html>


    • 统计总分&求平均分

      使用计算属性计算得到

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    <!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 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>总分 :{{sum}}</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.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="../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&& typeof(this.score.typeof)!=='number'&&this.score){
    this.list.push({
    id:+new Date(),
    subject:this.subject,
    score:this.score
    })
    }

    // 置空
    this.score=''
    this.subject=''
    }
    },

    computed:{
    sum(){
    let classSum=0
    this.list.forEach(item => {
    classSum+= item.score
    });
    return classSum
    },

    average(){

    if(this.list.length===0){
    return 0
    }
    return this.sum/this.list.length
    }

    }

    })
    </script>
    </body>
    </html>

5.watch 监听属性

监听数据的变化,一旦数据发生变化了就执行一些业务逻辑

  • 语法:简单写法

    watch

    data中的根级别的属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30

    <body>

    <div id="app">
    {{value}}
    <button @click="value++">++</button>
    </div>
    <!-- 2.引入核心包(引入vue这个包) -->
    <script src="1.js"></script>

    <script>
    const app = new Vue({
    el: '#app',
    data: {
    value:1
    },
    methods: {

    },

    // 监听属性 watch
    watch:{
    // 要监听的值 oldvalue 是以前的值 newValue是发生改变之后的值
    value( newValue,oldValue){
    console.log('新的值是'+newValue,'以前的值是'+oldValue);
    }
    }
    })
    </script>
    </body>

    监听对象中的子属性,直接对象.属性,但是一点要加“”

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    <!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>

    <body>

    <div id="app">
    {{value}}
    {{obj.objVaule}}
    <button @click="value++">++</button>
    <button @click="obj.objVaule++">++</button>
    </div>
    <!-- 2.引入核心包(引入vue这个包) -->
    <script src="1.js"></script>

    <script>
    const app = new Vue({
    el: '#app',
    data: {
    value:1,
    obj:{
    objVaule:1
    }
    },
    methods: {

    },

    // 监听属性 watch
    watch:{
    // 要监听的值 oldvalue 是以前的值 newValue是发生改变之后的值
    value( newValue,oldValue){
    console.log('新的值是'+newValue,'以前的值是'+oldValue);
    },

    'obj.objVaule'(oldValue,newValue){
    console.log('新的值是'+newValue,'以前的值是'+oldValue);
    }
    }
    })
    </script>
    </body>

    </html>

5.1监听属性的完整写法

监听属性

deep:true :深度监听,如果对象中有多个属性的话就可以监听到对象中的所有的属性

immediate: true 初始化立刻执行一次handler方法,也就是一进入页面,handle马上就会执行一次

handler: 数据变化后的执行函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<body>
<div id="app">
{{obj.objValue}}
{{obj.objValue2}}
<hr>
<button @click="obj.objValue++">++</button>
<button @click="obj.objValue2++">++</button>
</div>
<!-- 2.引入核心包(引入vue这个包) -->
<script src="1.js"></script>

<script>
const app = new Vue({
el: '#app',
data: {
value:1,
obj:{
objValue:1,
objValue2:2
}
},
methods: {

},

// 监听属性 watch
watch:{

obj:{
// 开启深度监听,能监听到对象中的每一个值的改变
deep:true,

// 一进入页面马上执行一次handle
immediate: true,
// 处理函数
handler(oldValue,newValue){
console.log(oldValue,newValue);
}
}

}
})
</script>
</body>

5.2水果案例

水果案例

  • 渲染
  1. 购物车主体和下面的空空如也,根据list中是否有数据来决定渲染谁

  2. v-for 遍历数据,渲染到页面上

    checkbox 是否选中对应下方isChecked

    img src 图片地址对应下发icon

    小计是单价X数量

  3. 当选中的时候,对应的列表项才有高亮的效果,使用:class的对象形式来写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
<!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 " :class="{active:item.isChecked}" v-for="(item,index) in fruitList" :key="item.id">
<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 class="decrease"> - </button>
<span class="my-input__inner">{{item.num}}</span>
<button class="increase"> + </button>
</div>
</div>
<div class="td">{{item.num*item.price}}</div>
<div class="td"><button>删除</button></div>
</div>
</div>
</div>
<!-- 底部 -->
<div class="bottom">
<!-- 全选 -->
<label class="check-all">
<input type="checkbox" />
全选
</label>
<div class="right-box">
<!-- 所有商品总价 -->
<span class="price-box">总价&nbsp;&nbsp;:&nbsp;&nbsp;¥&nbsp;<span class="price">24</span></span>
<!-- 结算按钮 -->
<button class="pay">结算( 6 )</button>
</div>
</div>
</div>
<!-- 空车 -->
<div class="empty" v-else>🛒空空如也</div>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
// 水果列表
fruitList: [
{
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,
},
],
},
})
</script>
</body>
</html>

  • 删除

    绑定事件,传id,根据id过滤

  • 点击加减修改数量

    给加减号绑定点击事件,传递id,根据id找到对应的数据改变该数据的num,进行加或者减

    减的过程中,如果已经小于0了,可以使用 disable 属性禁用掉button

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
<!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 " :class="{active:item.isChecked}" v-for="(item,index) in fruitList" :key="item.id">
<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 class="decrease" @click="sub(item.id)"> - </button>
<span class="my-input__inner">{{item.num}}</span>
<button class="increase" @click="add(item.id)"> + </button>
</div>
</div>
<div class="td">{{item.num*item.price}}</div>
<div class="td" @click="del(item.id)"><button>删除</button></div>
</div>
</div>
</div>
<!-- 底部 -->
<div class="bottom">
<!-- 全选 -->
<label class="check-all">
<input type="checkbox" />
全选
</label>
<div class="right-box">
<!-- 所有商品总价 -->
<span class="price-box">总价&nbsp;&nbsp;:&nbsp;&nbsp;¥&nbsp;<span class="price">24</span></span>
<!-- 结算按钮 -->
<button class="pay">结算( 6 )</button>
</div>
</div>
</div>
<!-- 空车 -->
<div class="empty" v-else>🛒空空如也</div>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
// 水果列表
fruitList: [
{
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)
},

add(id){
this.fruitList.forEach(item => {
if(item.id===id){
item.num++
}
});
},

sub(id){
this.fruitList.forEach(item=>{
if(item.id===id){
if(item.num>0){
item.num--
}

}
})
}
},
})
</script>
</body>
</html>

  • 全选,反选功能

使用计算属性,来动态的计算是否全选,给一个计算属性的值,如果列表每一项都勾选上了那就是全选否则就不是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
<!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 " :class="{active:item.isChecked}" v-for="(item,index) in fruitList" :key="item.id">
<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 class="decrease" @click="sub(item.id)"> - </button>
<span class="my-input__inner">{{item.num}}</span>
<button class="increase" @click="add(item.id)"> + </button>
</div>
</div>
<div class="td">{{item.num*item.price}}</div>
<div class="td" @click="del(item.id)"><button>删除</button></div>
</div>
</div>
</div>
<!-- 底部 -->
<div class="bottom">
<!-- 全选 -->
<label class="check-all">
<input type="checkbox" v-model="Alltrue" />
全选
</label>
<div class="right-box">
<!-- 所有商品总价 -->
<span class="price-box">总价&nbsp;&nbsp;:&nbsp;&nbsp;¥&nbsp;<span class="price">24</span></span>
<!-- 结算按钮 -->
<button class="pay">结算( 6 )</button>
</div>
</div>
</div>
<!-- 空车 -->
<div class="empty" v-else>🛒空空如也</div>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
// 水果列表
fruitList: [
{
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)
},

add(id){
this.fruitList.forEach(item => {
if(item.id===id){
item.num++
}
});
},

sub(id){
this.fruitList.forEach(item=>{
if(item.id===id){
if(item.num>0){
item.num--
}

}
})
}
},


computed:{

Alltrue:{
get(){
//必须所有的都选中, 全选才选中
//.every()方法接收一个回调函数作为参数,该函数对数组中的每个元素执行并检查istrue属性是否为true。只有当数组中所有元素的istrue属性都是true时,allTrue才会得到true。
return this.fruitList.every(obj => obj.isChecked === true)
},
set(value){
this.fruitList.forEach(item=>{
item.isChecked=value
})
}
}

}
})
</script>
</body>
</html>

  • 统计总数量(选中选中的总数)

    也是使用计算属性,根据是否选中和选中的个数进行累加

  • 统计总价

    计算属性,将选中的商品,价格X数量 累加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
<!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 " :class="{active:item.isChecked}" v-for="(item,index) in fruitList" :key="item.id">
<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 class="decrease" @click="sub(item.id)"> - </button>
<span class="my-input__inner">{{item.num}}</span>
<button class="increase" @click="add(item.id)"> + </button>
</div>
</div>
<div class="td">{{item.num*item.price}}</div>
<div class="td" @click="del(item.id)"><button>删除</button></div>
</div>
</div>
</div>
<!-- 底部 -->
<div class="bottom">
<!-- 全选 -->
<label class="check-all">
<input type="checkbox" v-model="Alltrue" />
全选
</label>
<div class="right-box">
<!-- 所有商品总价 -->
<span class="price-box">总价&nbsp;&nbsp;:&nbsp;&nbsp;¥&nbsp;<span class="price">{{totalPrice}}</span></span>
<!-- 结算按钮 -->
<button class="pay">结算( {{totalNum}} )</button>
</div>
</div>
</div>
<!-- 空车 -->
<div class="empty" v-else>🛒空空如也</div>
</div>
<script src="../vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
// 水果列表
fruitList: [
{
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)
},

add(id){
this.fruitList.forEach(item => {
if(item.id===id){
item.num++
}
});
},

sub(id){
this.fruitList.forEach(item=>{
if(item.id===id){
if(item.num>0){
item.num--
}

}
})
}
},


computed:{

Alltrue:{
get(){
return this.fruitList.every(obj => obj.isChecked === true)
},
set(value){
this.fruitList.forEach(item=>{
item.isChecked=value
})
}
},

// 选中的数量

totalNum(){
let totlal=0
this.fruitList.forEach(item=>{
if(item.isChecked){
totlal+=item.num
}
})

return totlal
},

// 总价totalprice
totalPrice(){

let totalPrice=0;
this.fruitList.forEach(item=>{
if(item.isChecked){
totalPrice+= item.num*item.price
}
})

return totalPrice
}

}
})
</script>
</body>
</html>

  • 持久化存储到本地

    持久化存储到本地的话,当用户下次一打开这个页面(程序)就能马上把用户以及选中的商品及商品数量马上就能加载出来,能够很好的加强用户的体验

    我们要监听所数据的变化,并且要监听深层次数据的变化,这个时候我们需要使用watch监听属性来监听深层次数据的变化

    将变化后的数据存储到本地

    存储数据到本地,从本地取数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    <!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 " :class="{active:item.isChecked}" v-for="(item,index) in fruitList" :key="item.id">
    <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 class="decrease" @click="sub(item.id)"> - </button>
    <span class="my-input__inner">{{item.num}}</span>
    <button class="increase" @click="add(item.id)"> + </button>
    </div>
    </div>
    <div class="td">{{item.num*item.price}}</div>
    <div class="td" @click="del(item.id)"><button>删除</button></div>
    </div>
    </div>
    </div>
    <!-- 底部 -->
    <div class="bottom">
    <!-- 全选 -->
    <label class="check-all">
    <input type="checkbox" v-model="Alltrue" />
    全选
    </label>
    <div class="right-box">
    <!-- 所有商品总价 -->
    <span class="price-box">总价&nbsp;&nbsp;:&nbsp;&nbsp;¥&nbsp;<span class="price">{{totalPrice}}</span></span>
    <!-- 结算按钮 -->
    <button class="pay">结算( {{totalNum}} )</button>
    </div>
    </div>
    </div>
    <!-- 空车 -->
    <div class="empty" v-else>🛒空空如也</div>
    </div>
    <script src="../vue.js"></script>
    <script>
    const defData=[
    {
    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'))||defData
    },

    methods: {
    del(id){
    this.fruitList= this.fruitList.filter(item=>item.id!==id)
    },

    add(id){
    this.fruitList.forEach(item => {
    if(item.id===id){
    item.num++
    }
    });
    },

    sub(id){
    this.fruitList.forEach(item=>{
    if(item.id===id){
    if(item.num>0){
    item.num--
    }

    }
    })
    }
    },


    computed:{

    Alltrue:{
    get(){
    return this.fruitList.every(obj => obj.isChecked === true)
    },
    set(value){
    this.fruitList.forEach(item=>{
    item.isChecked=value
    })
    }
    },

    // 选中的数量

    totalNum(){
    let totlal=0
    this.fruitList.forEach(item=>{
    if(item.isChecked){
    totlal+=item.num
    }
    })

    return totlal
    },

    // 总价totalprice
    totalPrice(){

    let totalPrice=0;
    this.fruitList.forEach(item=>{
    if(item.isChecked){
    totalPrice+= item.num*item.price
    }
    })

    return totalPrice
    }

    },


    watch:{
    // 监听数据的变化,如果数据发生变化了以后就直接存储到本地
    fruitList:{
    deep:true,
    handler(newValue){
    // 存储数据之前先把数据转成json形式的
    localStorage.setItem('list',JSON.stringify(newValue))
    }
    }

    }
    })
    </script>
    </body>
    </html>

6.Vue生命周期

vue的生命周期,就是vue从创建到销毁的全过程

vue生命周期

主要是四个生命周期阶段,创建,挂载,更新,销毁

  • 创建阶段

    创建实例,进行一些初始化的工作,将普通的数据做一些监听,做响应式的处理

  • 挂载阶段

    结合html 做渲染,页面已经渲染出来了

  • 更新阶段

    用户在使用的时候,就是处于更新阶段,这个阶段主要是修改数据,更改视图。

    vue监听到数据发生改变了以后,重新的去更新视图.

    更新阶段会进入一个循环,修改数据,更新视图,—修改数据更新视图

  • 销毁阶段

    销毁视图

6.1生命周期函数(钩子)

生命周期函数,就是在vue的声明周期中,自动调用的一些函数。

生命周期函数

  • 创建阶段—渲染阶段

    beforeCreate 阶段还没准备好响应式的数据

    created 阶段响应式的数据已准备好了,这个时候可以使用data中的数据了

    beforemount 这个阶段模板还没渲染好

    onmounted 这个阶段数据已经渲染到页面上了,可以进行DOM操作了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

// 1.创建阶段(准备数据)
beforeCreate() {
debugger
console.log('数据准备好之前');
},
created() {
debugger
console.log('响应式数据准备好之后',this.num);
},
// 挂载阶段(视图渲染)
beforeMount() {
debugger
console.log('视图渲染前');
},
mounted() {
debugger
console.log('视图渲染后');
},
  • 更新阶段—卸载阶段

    更新阶段是修改数据了以后,vue要修改视图,才会触发更新

    DOM的更新也是有过程的,分为更新前和更新后

    beforeUpdate:能拿到修改之前的DOM

    updated: 拿到更新后的DOM

    beforeDestroy: 卸载前,现在vue还能使用(通常在这一阶段清除掉一些定时器,延时器)还将用户当前的信息状态统一发送给后端

    destroy:卸载后,vue实例没了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

// 3.更新阶段
beforeUpdate() {
console.log('更新阶段,更新前');
},
updated() {
console.log('更新阶段,更新后');
},

// 4.卸载阶段
beforeDestroy() {
console.log('卸载前');
},
destroyed() {
console.log('卸载后');
},

案例:created 响应式数据准备好了,发送请求

axios参考地址 axios 参考地址

请求数据的网址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
<!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;
list-style: none;
}
.news {
display: flex;
height: 120px;
width: 600px;
margin: 0 auto;
padding: 20px 0;
cursor: pointer;
}
.news .left {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
padding-right: 10px;
}
.news .left .title {
font-size: 20px;
}
.news .left .info {
color: #999999;
}
.news .left .info span {
margin-right: 20px;
}
.news .right {
width: 160px;
height: 120px;
}
.news .right img {
width: 100%;
height: 100%;
object-fit: cover;
}
</style>
</head>
<body>

<div id="app">
<ul>
<li class="news">
<div class="left">
<div class="title">5G商用在即,三大运营商营收持续下降</div>
<div class="info">
<span>新京报经济新闻</span>
<span>2222-10-28 11:50:28</span>
</div>
</div>
<div class="right">
<img src="http://ajax-api.itheima.net/public/images/0.webp" alt="">
</div>
</li>

<li class="news">
<div class="left">
<div class="title">5G商用在即,三大运营商营收持续下降</div>
<div class="info">
<span>新京报经济新闻</span>
<span>2222-10-28 11:50:28</span>
</div>
</div>
<div class="right">
<img src="http://ajax-api.itheima.net/public/images/0.webp" alt="">
</div>
</li>

<li class="news">
<div class="left">
<div class="title">5G商用在即,三大运营商营收持续下降</div>
<div class="info">
<span>新京报经济新闻</span>
<span>2222-10-28 11:50:28</span>
</div>
</div>
<div class="right">
<img src="http://ajax-api.itheima.net/public/images/0.webp" alt="">
</div>
</li>
</ul>
</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>
// 接口地址:http://hmajax.itheima.net/api/news
// 请求方式:get
const app = new Vue({
el: '#app',
data: {
list: []
}
})
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<!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;
list-style: none;
}

.news {
display: flex;
height: 120px;
width: 600px;
margin: 0 auto;
padding: 20px 0;
cursor: pointer;
}

.news .left {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
padding-right: 10px;
}

.news .left .title {
font-size: 20px;
}

.news .left .info {
color: #999999;
}

.news .left .info span {
margin-right: 20px;
}

.news .right {
width: 160px;
height: 120px;
}

.news .right img {
width: 100%;
height: 100%;
object-fit: cover;
}
</style>
</head>

<body>

<div id="app">

<ul>
<li class="news" v-for="(item,index) in list" :key="item.id">
<div class="left">
<div class="title">{{item.title}}</div>
<div class="info">
<span>{{item.info}}</span>
<span>{{item.time}}</span>
</div>
</div>
<div class="right">
<img :src="item.img" alt="">
</div>
</li>


</ul>
</div>
<script src="1.js"></script>
<script src="axios.js"></script>
<script>
// 接口地址:http://hmajax.itheima.net/api/news
// 请求方式:get
const app = new Vue({
el: '#app',
data: {
list:[]
},
async created () {


this.list=await axios.get('http://hmajax.itheima.net/api/news')
.then(function (response) {
// 处理成功情况
return response.data.data

})
.catch(function (error) {
// 处理错误情况
console.log(error);
})
.finally(function () {
// 总是会执行
});


},
})
</script>
</body>

</html>

7.vue cli 脚手架

  • 传统开发模式和工程化开发模式

    1. 核心包传统开发模式:基于 html / css / js 文件,直接引入核心包,开发 Vue,就是使用原生的一些语言,加上引入一些包(不同的包有不同的功能),来开发程序,开发出来的程序直接用。缺点就是很多很多东西都需要使用原生的html css js去写

    2. 工程化开发模式:基于构建工具(例如:webpack ) 的环境中开发 Vue。

      使用最新的语法,开发代码, 用webpack去配置好了以后生成可运行的 代码

    脚手架

  • vue cli 脚手架

    Vue CLI 是 Vue 官方提供的一个全局命令工具。

    可以帮助我们快速创建一个开发 Vue 项目的标准化基础架子。webpack已经帮我们配置好了,不需要我们再配置了。

7.1 脚手架的安装及使用

一定要安装nodejs, 并且保证npm 的版本尽量保证为16.几 的这种稳定版本的,不然到时候代码写着写着就哭了。

软件的版本不是越高越好,版本过高或者过低都会出现版本冲突问题

1
npm install -g yarn

yarn 可以理解为是npm 非常哇塞的升级版,一般下载或者安装都相对路畅很多

npm 在使用前记得配置淘宝镜像

在一个新文件夹中,不要直接在桌面或者其他地方整,很不方便管理

全局安装需要很高的权限,需要在PowerShell 中输入命令

  1. 全局安装 (一次) :yarn global add @vue/cli 或 npm i @vue/cli -g

    npm i @vue/cli -g npm 是命令, i 是install下载的意识 @ vue/cli 是安装个啥玩意 -g 代表全局安装,也就是在电脑哪个地方都能用

    问题解决

  2. 查看 Vue 版本:vue –version

  3. 创建项目架子:vue create project-name(项目名-不能用中文)

  4. 启动项目: yarn serve 或 npm run serve(找package.json)

7.2 Vue 项目目录及运行

vue 项目目录

  • index.html

    放html 主要就是一个app容器,创建结构就是直接往app中渲染内容

  • App.vue

    根组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<!-- 引入组件 -->
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
name: 'App',
components: {
HelloWorld
}
}
</script>

<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

  • main.js 核心的入口文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 引入vue
import Vue from 'vue'

// 引入根组件
import App from './App.vue'

// 配置是生产环境还是开发环境
Vue.config.productionTip = false

new Vue({
// render: h => h(App), 基于App 创建结构
render: h => h(App),

// render:(createElement)=>{
// return createElement(App)
// }
}).$mount('#app')

// .$mount('#app') 配置管理哪个容器 管理 html中的app 容器
  • 运行流程(了解)

    运行流程

7.3 组件化开发

页面可以拆分成一个个组件,每个组件有着自己独立的结构、样式、行为。

所有组件之间的逻辑是相互独立的,哪里出问题了直接找对应的组件。如果组件需要复用的话也直接复用就可以。

组件

  • 普通组件

    一个一个的小组件

  • 根组件

    整个应用最上层的组件,包裹所有普通小组件

    根组件

  • 单文件的三个组成部分

    结构

APP.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<template>

<!-- 有且仅仅只有一个跟元素 也就是最外层不能有兄弟元素, vue2 vue3 就不存在了 -->
<div id="app">
我是结构
<button @click="fun">欢迎</button>
</div>
</template>

<script>


// export 导出的是配置项 以前怎么写这里就怎么写
export default {
methods:{
fun(){
alert("你好")
}
}

}
</script>

<style>
.app{
height: 100px;
width: 100px;
background-color: yellowgreen;
}

</style>

7.4 普通组件的注册和使用

  • 注册组件

    1. 局部注册:只能在注册的组件内使用

    2. 全局注册:所有组件内都能使用

  • 局部注册

    components: 在这个文件夹里面注册组件

components

  • 注册组件

    位置在components中,在该文件夹下面新建vue文件

    注意:这个文件的文件名很讲究,一定要使用小驼峰命名法

    例如:MyBody MyHead MyBody

    Myhead.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <template>
    <div class="header">
    <!-- -->
    </div>
    </template>

    <script>

    export default{

    }
    </script>


    <style>
    .header{
    height: 100px;
    widows: 450px;
    margin: 0 auto;
    background-color: rgb(142, 247, 182);
    border: 1px solid #ccc;
    border-radius: 10px;
    box-shadow: 5px 5px 5px #aaaaaa;
    }
    </style>

    主体的结构是就是那老三样

  • 引入和使用组件

    先引入组件,然后配置一下(告诉vue你引入的了组件),使用组件

    App.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    <template>
    <div class="App">
    <!--3.使用组件 -->
    <MyHeader></MyHeader>
    <MyBody></MyBody>
    </div>
    </template>

    <script>
    // 1.引入组件
    import MyHeader from './components/MyHeader.vue';
    import MyBody from './components/MyBody.vue';

    export default {
    // 2.注册组件
    components: {
    MyHeader: MyHeader,
    MyBody:MyBody
    },
    };
    </script>

    <style>
    .App {
    width: 300px;
    height: 500px;
    background-color: yellowgreen;
    margin: 0 auto;
    border: 1px solid #ccc;
    border-radius: 10px;
    box-shadow: 5px 5px 5px #aaaaaa;
    }
    </style>

7.5 组件的全局注册

全局注册:所有的组件都能使用

  • 全局注册方式

    1. 在components 中新建一个vue文件

      components\MyButton.vue

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      <template>
      <div>
      <button class="btn">按钮</button>
      </div>
      </template>

      <script>
      export default {

      };
      </script>

      <style>

      .btn{
      height: 30px;
      widows: 80px;
      background-color: yellowgreen;
      font-size: x-small;
      color: azure;
      box-shadow: 2px 2px 2px #ccc;
      border-radius: 5px;
      }
      </style>
    2. main.js中进行全局注册

      main.js

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      // 引入vue
      import Vue from 'vue'

      // 引入app.vue
      import App from './App.vue'

      // 引入要注册的组件
      import MyButton from './components/MyButton.vue'

      // 配置生产环境
      Vue.config.productionTip = false

      // 进行全局注册
      // Vue.component('组件名',组件)
      Vue.component('MyButton',MyButton)

      new Vue({
      render: h => h(App),
      }).$mount('#app')

    3. 使用

      App.vue

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      <template>
      <div class="App">
      <!--3.使用组件 -->
      <MyHeader>


      </MyHeader>
      <MyBody></MyBody>

      <!-- 直接使用 -->
      <MyButton></MyButton>

      </div>
      </template>

      <script>
      // 1.引入组件
      import MyHeader from './components/MyHeader.vue';
      import MyBody from './components/MyBody.vue';

      export default {
      // 2.注册组件
      components: {
      MyHeader: MyHeader,
      MyBody:MyBody
      },
      };
      </script>

      <style>
      .App {
      width: 300px;
      height: 500px;
      background-color: yellowgreen;
      margin: 0 auto;
      border: 1px solid #ccc;
      border-radius: 10px;
      box-shadow: 5px 5px 5px #aaaaaa;
      }
      </style>

7.6 综合案例

小兔仙

  • 根据模块来拆分组件
  • 根据设计图,把每一个组件写出来
  • 公用的组件继续进行拆分

7.7组件组成(三大部分)

三大组成

  • 组件样式冲突

    默认的样式会作用到全局

    /components/xxx.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <template>
    <div>
    轮播图
    <!-- -->
    </div>
    </template>

    <script>


    export default {


    }
    </script>

    <style>
    <!-- 这个默认的样式会作用到全局 -->
    div{
    height: 100px;
    width: 100px;
    background-color: yellowgreen;
    }
    </style>

    全局样式: 默认组件中的样式会作用到全局

    局部样式: 可以给组件加上 scoped 属性, 可以让样式只作用于当前组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <template>
    <div>
    轮播图
    <!-- -->
    </div>
    </template>

    <script>


    export default {


    }
    </script>

    <!-- 加了 scoped 以后 当前的样式只作用与当前的元素 -->
    <style dcoped>

    div{
    height: 100px;
    width: 100px;
    background-color: yellowgreen;
    }
    </style>
  • Data 函数
    /components/NumAdd.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    <template>
    <div>
    <button @click="num++">+</button>
    {{ num }}
    <button @click="num--">-</button>
    </div>
    </template>

    <script>


    export default {

    data(){
    return {
    num:10
    }
    }

    }
    </script>

    <!-- 加了 scoped 以后 当前的样式只作用与当前的元素 -->
    <style dcoped>

    div{
    height: 100px;
    width: 100px;
    background-color: yellowgreen;
    }
    </style>

    App.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <template>
    <div>


    <NumAdd></NumAdd>
    <NumAdd></NumAdd>

    </div>
    </template>

    <script>
    import NumAdd from './components/NumAdd.vue'

    export default{

    components:{
    NumAdd:NumAdd
    }
    }
    </script>

    <style>
    </style>

    为什么使用函数的形式:

    就是每次在使用在使用 组件的时候,都会调用Data函数,返回一个新的 数据,数据之间不会出现污染。

    使用同一份的效果

    /components/NumAdd.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    <template>
    <div>
    <button @click="num++">+</button>
    {{ num }}
    <button @click="num--">-</button>
    </div>
    </template>

    <script>

    const data ={num:0}
    export default {
    data:function(){
    return data
    }

    }
    </script>

    <!-- 加了 scoped 以后 当前的样式只作用与当前的元素 -->
    <style dcoped>

    div{
    height: 100px;
    width: 100px;
    background-color: yellowgreen;
    }
    </style>

8.组件通信

组件的数据是独立的,无法直接访问其他组件的数据。如果一个组件想用另外一个组件中的数据就需要使用到组件通信

  • 组件关系

    1. 父子关系

      A 组件包裹B 组件 A B 组件就是父子关系

    组件关系

    1. 非父子关系

      B C这种兄弟关系,或者更复杂的关系

8.1 父子组件通信

  1. 父组件通过 props 将数据传递给子组件

  2. 子组件利用 $emit 通知父组件修改更新

组件通信

  • 父组件 传值给子组件(重要)

    父组件在子组件的标签中,通过属性的方式传递值过去

App.vue

组件传值

子组件 通过props 接收值了以后直接使用

  • 子组件传递给父组件(重要)

Myson.vue

组件传值

传值给父组件

首先,给button 按钮添加一个点击事件,触发一个函数(没有什么好解释的)

在这个函数fun 里面,使用实例对象身上的方法 $emit ,emit 是发射的意思,也就相当于是emit 发射了一个事件,并携带了一个参数。(三体里面红岸基地,叶文洁向宇宙广播消息一样,只不过这里是广播给父组件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div>
<button @click="fun">按钮</button>
</div>
</template>

<script>


export default{

methods:{
fun(){
this.$emit("sonValue","我是子组件传过来的值")
}
}

}
</script>

父组件,@事件名 来监听这个事件这个事件,事件名就是传过来的第一个参数。

紧接着去绑定一个事件处理函数,这个事件处理函数接收一个参数,这个参数就是,emim传过来的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template>
<div>
<!-- 以给组件添加属性的方式传值 -->
<!-- :属性='值的名字' -->
<MySon @sonValue="getSonvalue"></MySon>

<p>{{msg}}</p>
</div>
</template>

<script>


import MySon from './components/MySon.vue';
export default{
data(){
return{
msg:''
}
},
components:{
MySon:MySon
},
methods:{
getSonvalue(value){
this.msg=value
}
}

}
</script>

<style>
</style>

8.2 prop

Prop 定义:组件上注册的一些自定义属性,作用是向子组件传递数据

可以传递任意数量的prop ,可以传递任意类型的prop

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<template>
<div>
<!-- 以给组件添加属性的方式传值 -->
<!-- :属性='值的名字' -->
<MySon
:name="name"
:age='age'
:car="car"
:like="like"

></MySon>


</div>
</template>

<script>


import MySon from './components/MySon.vue';
export default{
data(){
return{
name:"张三",
age:18,
car:{
color:'red',
type:'suv',
},
like:['写代码','学习']
}
},
components:{
MySon:MySon
},
methods:{
getSonvalue(value){
this.msg=value
}
}

}
</script>

<style>
</style>

son.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>
{{name}}
{{ age }}
{{ car }}
{{ like }}
</div>
</template>

<script>


export default{

props:['name','car','like','age']

}
</script>

8.3prop 校验

prop 的数据不能乱传,就比如说人的年龄必须是1-3位数的数字类型的数据

prop校验

数据名: 数据类型

1
2
3
4
5
6
7
export default{

props:{
age:Number
}

}

不满足要求会报错

校验


细致校验

数据校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
props:{
age:{
// 类型校验
type:Number,
// 是否必填
required:true,
// 默认值 没传就是默认值
default:0,

// 自定义校验 如果return 了true 就通过了校验 否则就没通过校验
validator(value){
//value 就是传过来的值

if(value>=0&&value<=200){
return true
}else{
return false
}
}


}
}

8.3 单向数据流

prop的数据是父组件提供的,父组件传递的数据子组件不能直接改

单向数据流:父级 prop 的数据更新,会向下流动,影响子组件。这个数据流动是单向的。

如果直接在子元素中修改prop传过来的数据会报错:当前的数据是prop传过来的,不能修改

修改

son.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<template>
<div>
{{ age }}
<button @click="age++">++</button>
</div>
</template>

<script>


export default{

props:{
age:{
// 类型校验
type:Number,
// 非空校验 必须要传这个
required:true,
// 默认值 没传就是默认值
default:0,

// 自定义校验 如果return 了true 就通过了校验 否则就没通过校验
validator(value){
//value 就是传过来的值

if(value>=0&&value<=200){
return true
}else{
return false
}
}


}
}

}
</script>

8.4 子元素修改父元素数据

本质上还是子元素通知父元素要修改数据,并且把要修改的数据传递给父元素,在父元素里面进行修改

子元素修改

son.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<template>
<div>
<!-- 子元素身上的点击事件 通过这个点击事件触发一个函数 -->
<button @click="changeData">++</button>
</div>
</template>

<script>


export default{

methods:{
changeData(){
// 通过emit 广播 发射事件,并携带表达式
this.$emit('changeData',this.age+1)
}
},

props:{
age:{
// 类型校验
type:Number,
// 非空校验 必须要传这个
required:true,
// 默认值 没传就是默认值
default:0,

// 自定义校验 如果return 了true 就通过了校验 否则就没通过校验
validator(value){
//value 就是传过来的值

if(value>=0&&value<=200){
return true
}else{
return false
}
}


}
}

}
</script>

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<template>
<div>
<!-- 父组件监听事件 并且触发函数 -->
<MySon :age=age @changeData="changeAge"></MySon>


</div>
</template>

<script>


import MySon from './components/MySon.vue';
export default{
data(){
return{

age:18,

}
},
components:{
MySon:MySon
},

methods:{

// 触发函数 拿到想要更新的值 把更新后的值给
changeAge(value){
this.age=value
}
}


}
</script>

<style>
</style>

8.5 小黑记事本—组件实现

小黑记事本


  • 组件拆分

/components/xxx.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<!-- 输入框 -->
<header class="header">
<h1>小黑记事本</h1>
<input placeholder="请输入任务" class="new-todo" />
<button class="add">添加任务</button>
</header>
</template>

<script>
export default {
data () {
return {

}
}
}
</script>

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template>
<!-- 主体区域 -->
<section id="app">
<TodoeHader></TodoeHader>
<TodoMain></TodoMain>
<TodoFooter></TodoFooter>

</section>
</template>

<script>
import TodoFooter from './components/TodoFooter.vue';
import TodoeHader from './components/TodoHeader.vue';
import TodoMain from './components/TodoMain.vue';

export default {
data () {
return {

}
},

components:{
TodoFooter,
TodoeHader,
TodoMain
}
}
</script>

<style>

</style>


  • 页面渲染

数据三个子组件都需要使用,所以数据存储在父组件中,通过父传子的方式,将数据传递给子元素,然后子元素进行渲染

App.vue

提供数据,然后传给子元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<template>
<!-- 主体区域 -->
<section id="app">
<TodoeHader></TodoeHader>
<!-- 父传子,将数据给子元素 -->
<TodoMain :list="list"></TodoMain>
<TodoFooter></TodoFooter>

</section>
</template>

<script>
import TodoFooter from './components/TodoFooter.vue';
import TodoeHader from './components/TodoHeader.vue';
import TodoMain from './components/TodoMain.vue';

export default {
data () {
return {

// 父组件中定义数据
list:[
{id:1,name:'吃饭'},
{id:2,name:'喝水'},
{id:3,name:'睡觉'}
]

}
},

components:{
TodoFooter,
TodoeHader,
TodoMain
}
}
</script>

<style>

</style>

main.vue

接收数据,稍微渲染一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template>
<!-- 列表区域 -->
<section class="main">
<ul class="todo-list">
<li class="todo" v-for="(item,index) in list" :key="item.id">
<div class="view">
<span class="index">{{ index +1}}.</span> <label>{{ item.name }}</label>
<button class="destroy"></button>
</div>
</li>
</ul>
</section>
</template>

<script>
export default {
data () {
return {

}
},

props:{
list:{
typeof:Array,
default:[
{id:1,name:'吃饭'},
{id:2,name:'喝水'},
{id:3,name:'睡觉'}
]
}
}
}
</script>

  • 添加功能

    1.收集表单数据,

    2.回车或者点击的时候,将数据发送给父元素

header.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
<!-- 输入框 -->
<header class="header">
<h1>小黑记事本</h1>
<input placeholder="请输入任务" class="new-todo" @keyup.enter="add" v-model="todo"/>
<button class="add" @click="add">添加任务</button>
</header>
</template>

<script>
export default {
data () {
return {
todo:null
}
},

methods:{
add(){
this.$emit('addList',{id:this.todo.length,name:this.todo})
this.todo=null
}
}
}
</script>

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<template>
<!-- 主体区域 -->
<section id="app">
<TodoeHader @addList="addList"></TodoeHader>
<!-- 父传子,将数据给子元素 -->
<TodoMain :list="list"></TodoMain>
<TodoFooter></TodoFooter>

</section>
</template>

<script>
import TodoFooter from './components/TodoFooter.vue';
import TodoeHader from './components/TodoHeader.vue';
import TodoMain from './components/TodoMain.vue';

export default {
data () {
return {

// 父组件中定义数据
list:[
{id:1,name:'吃饭'},
{id:2,name:'喝水'},
{id:3,name:'睡觉'}
]

}
},

components:{
TodoFooter,
TodoeHader,
TodoMain
},

methods:{
addList(value){
this.list.push(value)
}
}
}
</script>

<style>

</style>


  • 删除任务

    在子组件中删除,通知父组件改数据

    TodoMain.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    <template>
    <!-- 列表区域 -->
    <section class="main">
    <ul class="todo-list">
    <li class="todo" v-for="(item, index) in list" :key="item.id">
    <div class="view">
    <span class="index">{{ index + 1 }}.</span>
    <label>{{ item.name }}</label>
    <button class="destroy" @click="del(item.id)"></button>
    </div>
    </li>
    </ul>
    </section>
    </template>

    <script>
    export default {
    data() {
    return {};
    },

    props: {
    list: {
    typeof: Array,
    default: [
    { id: 1, name: '吃饭' },
    { id: 2, name: '喝水' },
    { id: 3, name: '睡觉' },
    ],
    },
    },

    methods: {
    del(id) {

    this.$emit('delItem',this.list.filter(item=>item.id!=id))
    },
    },
    };
    </script>

    App.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    <template>
    <!-- 主体区域 -->
    <section id="app">
    <TodoeHader @addList="addList"></TodoeHader>
    <!-- 父传子,将数据给子元素 -->
    <TodoMain :list="list" @delItem="delItem"></TodoMain>
    <TodoFooter></TodoFooter>

    </section>
    </template>

    <script>
    import TodoFooter from './components/TodoFooter.vue';
    import TodoeHader from './components/TodoHeader.vue';
    import TodoMain from './components/TodoMain.vue';

    export default {
    data () {
    return {

    // 父组件中定义数据
    list:[
    {id:1,name:'吃饭'},
    {id:2,name:'喝水'},
    {id:3,name:'睡觉'}
    ]

    }
    },

    components:{
    TodoFooter,
    TodoeHader,
    TodoMain
    },

    methods:{
    addList(value){
    this.list.push(value)
    },
    delItem(value){
    this.list=value
    }
    }
    }
    </script>

    <style>

    </style>


  • 底部合计功能

将数据传递给子元素,子元素计算数量。

  • 清空

    数据子传父

    foot.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    <template>
    <!-- 统计和清空 -->
    <footer class="footer">
    <!-- 统计 -->
    <span class="todo-count">合 计:<strong> {{ list.length }} </strong></span>
    <!-- 清空 -->
    <button class="clear-completed" @click="clean">
    清空任务
    </button>
    </footer>
    </template>

    <script>
    export default {
    data () {
    return {

    }
    },

    props:{
    list:Array
    },

    methods:{
    clean(){
    this.$emit('clean',[])
    }
    }
    }
    </script>

    App.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    <template>
    <!-- 主体区域 -->
    <section id="app">
    <TodoeHader @addList="addList"></TodoeHader>
    <!-- 父传子,将数据给子元素 -->
    <TodoMain :list="list" @delItem="delItem"></TodoMain>
    <TodoFooter :list="list" @clean="clean"></TodoFooter>

    </section>
    </template>

    <script>
    import TodoFooter from './components/TodoFooter.vue';
    import TodoeHader from './components/TodoHeader.vue';
    import TodoMain from './components/TodoMain.vue';

    export default {
    data () {
    return {

    // 父组件中定义数据
    list:[
    {id:1,name:'吃饭'},
    {id:2,name:'喝水'},
    {id:3,name:'睡觉'}
    ]

    }
    },

    components:{
    TodoFooter,
    TodoeHader,
    TodoMain
    },

    methods:{
    addList(value){
    this.list.push(value)
    },
    delItem(value){
    this.list=value
    },
    clean(value){
    this.list=value
    }
    }
    }
    </script>

    <style>

    </style>

8.6 全局事件总线–event bus

非父子组件之间的建议的数据传递

说白了就是一个类似于广播台, A组件在广播台订阅内容,B组在广播台发布内容。这个时候就相当于B把数据发送给了A

事件总线

实现:

  1. 创建一个事件总线(创建一个广播台)

/src/utils/EventBus.js

1
2
3
4
5
6
// 1.创建一个谁都能访问到的时间总线
import Vue from "vue";

const Bus=new Vue()

export default Bus
  1. 接收数据方,添加监听

/MyA.vue(接收数据方)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>

</template>

<script>
import Bus from '../utils/EventBus';
export default {
data () {
return {

}
},

// 在接受方进行监听,就是订阅信息

created(){
// 进行监听,Msg就是监听的事件(也就是你订阅的内容),函数里面能接收到内容
Bus.$on('Msg',(Value)=>{

})
}
}
</script>

/B.vue(发布数据方)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template>
<div class="box">

<button @click="send">发布消息</button>
</div>

</template>

<script>
import Bus from '../utils/EventBus';
export default {
data() {
return {};
},
methods: {

// 2.消息发送放,触发事件的方式来传递参数
send(){
Bus.$emit('Msg','我是数据 ,发送方的数据')

}
},
};
</script>


<style>

.box{
height: 100px;
width: 100px;
background-color: yellowgreen;
}
</style>

其他组件也可以监听事件, 然后发布方一发布,接收方都能收到

这种全局事件总线,一般来说就是在一些简单的数据传递的时候可以用,复杂的数据发布还是使用 VueX

8.7跨层级组件通信

provide & inject 作用:跨层级共享数据。在爷爷组件提供数据,孙子组件就可以直接去使用

跨层级数据通信

  • 父组件通过provide提供数据

    App.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    export default {
    // 父组件中共享数据
    provide(){
    return {
    msg:this.msg,
    list:this.list
    }
    },
    data () {
    return {

    // 父组件中定义数据
    list:[
    {id:1,name:'吃饭'},
    {id:2,name:'喝水'},
    {id:3,name:'睡觉'}
    ],
    msg:"我是App组件的数据"

    }
    },
  • 子孙组件通过inject 使用

    /son.vue

    1
    2
    3
     export default {

    inject:['list','Fmsg'],
  • 注意:

    复杂数据类型的数据是响应式的,简单数据类型的数据是非响应式的

    List 是复杂的 响应式的数据 msg 是简单的,非响应式的数据

    1
    2
    3
    4
    5
    6
    list:[
    {id:1,name:'吃饭'},
    {id:2,name:'喝水'},
    {id:3,name:'睡觉'}
    ],
    msg:"我是App组件的数据"

9.vue 获取设置DOM元素

9.1 ref 和$refs

作用:利用 ref 和 $refs 可以用于 获取 dom 元素, 或 组件实例

特点:查找范围 → 当前组件内 (更精确稳定),如果不是在挡墙组件内的话可能出现污染,例如:APP父组件上有一个class 为box的div,子组件Head 上也有一个class为box 的div,这个时候在子组件上使用doc.qs() 找iclass 为box的元素,可能会找到父组件身上。所以需要查找范围为当前的组件。

  • 获取DOM元素

    给目标标签 – 添加 ref 属性

    ref

    当当前的DOM元素渲染出来了以后通过this.$refs.xxxx来获取DOM元素

    refs

MyHead.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div class="header">
<!-- 设置元素的ref -->
<div class="box" ref="box">我是子盒子</div>
</div>
</template>

<script>

export default{
mounted(){

// this.$refs.box 通过ref 找到对应的元素
this.$refs.box.style.color='red'
}
}
</script>


<style>

</style>
  • ref 和$refs 获取组件实例

目标组件添加 ref 属性

refs

恰当时机, 通过 this.$refs.xxx, 获取目标组件,就可以调用组件对象里面的方法

ref

父组件调用子组件身上的方法

MyHeader.vue子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template>
<div class="header">
用户名:<input type="text" v-model="userNumber">
密码:<input type="text" v-model="passWord">
</div>
</template>

<script>

export default{

data(){
return{
userNumber:'',
passWord:''
}
},

methods:{

reset(){
this.userNumber=''
this.passWord=''
}

}

}
</script>


<style>

</style>

App.vue 父组件 (通过ref 调用子组件实例上的方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<template>
<div class="App">

<MyHeader ref="Myheader"> </MyHeader>

<button @click="reset">父组件重置</button>

</div>
</template>

<script>
// 1.引入组件
import MyHeader from './components/MyHeader.vue';

export default {


methods:{
reset(){
this.$refs.Myheader.reset()
}
},
// 2.注册组件
components: {
MyHeader: MyHeader,
},
};
</script>

<style>

</style>


父组件获取子组件身上的数据

子组件 MyHeader.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<template>
<div class="header">
用户名:<input type="text" v-model="userNumber">
密码:<input type="text" v-model="passWord">
</div>
</template>

<script>

export default{

data(){
return{
userNumber:'',
passWord:''
}
},

methods:{
// 子组件这里提供一个方法,把数据返回出去
getValueS(){
return{
userNumber:this.userNumber,
passWord:this.passWord
}
},
reset(){
this.userNumber=''
this.passWord=''
}

}

}
</script>


<style>

</style>

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<template>
<div class="App">

<MyHeader ref="Myheader"> </MyHeader>

<button @click="reset">父组件重置</button>

<button @click="getValue"> 父组件获取数据</button>

</div>
</template>

<script>
// 1.引入组件
import MyHeader from './components/MyHeader.vue';

export default {


methods:{

reset(){
this.$refs.Myheader.reset()
},

getValue(){
console.log(this.$refs.Myheader.getValueS());
}
},
// 2.注册组件
components: {
MyHeader: MyHeader,
},
};
</script>

<style>

</style>

9.2 $nextTick

nextTick底层原理就是就是把异步的任务推到执行栈里面,当执行栈执行了这个异步任务了以后就能获取到最新的DOM 了

作用:获取更新后的DOM,等 DOM 更新后, 才会触发执行站此方法里的函数体。

语法:

nextTick

  • 案例:点击编辑,input输入框自动获取焦点

    自动获取焦点

    自动获取焦点 第一次获取不到的原因就是 当数据更新了以后,不会立即更新DOM,而是会把任务压到任务栈里面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    <template>
    <div class="header">

    <button @click="text">编辑</button>
    用户名:<input type="text" v-show="isshow" ref="inp">
    </div>
    </template>

    <script>

    export default{

    data(){
    return{
    isshow:false
    }
    },

    methods:{
    // 点击编辑了以后,input输入框显示出来,并且自动获取焦点
    text(){
    // input 输入框显示出来
    this.isshow=true

    // 自动获取焦点 第一次获取不到的原因就是 当数据更新了以后,不会立即更新DOM,而是会把任务压到任务栈里面
    // console.log(this.$refs.inp);
    // this.$refs.inp.focus()

    this.$nextTick(()=>{
    console.log(this.$refs.inp);
    this.$refs.inp.focus()
    })
    }
    }

    }
    </script>


    <style>

    </style>

10.自定义指令

v-html v-model v-for 。。。这些都是vue给我们准备好的指令。

自定义指令:自己定义的指令, 可以封装一些 dom 操作, 扩展额外功能

在页面加载的时候,需要元素自动获取焦点,我们可以用我们之前所学的ref 来做

1
this.$refs.inp.focus()

但是,如果有很多个页面,每个页面进去了,都需要input输入框获取焦点,就需要我们重复去写上面的代码

这个时候就可以使用自定义指令,自定义指令可以封装一些Dom操作,扩展额外的功能

自定义指令

inserted(el) 这个el就是指令所绑定的元素(如我们在输入框中使用的这个指令,el就是这个输入框)

当元素被插入到页面中的时候, 马上就会执行指令所对应的内容

  • 全局注册指令(任何一个组件都能使用,是挂载在Vue上的)\

    定义一个自定义指令

    main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 引入vue
import Vue from 'vue'

// 引入app.vue
import App from './App.vue'

// 引入要注册的组件
import MyButton from './components/MyButton.vue'

// 配置生产环境
Vue.config.productionTip = false

// 'focus'自定义指令名称
Vue.directive('focus',{

// inserted 指令所在的元素被插入到页面中的时候就会执行

// 使用指令的元素
inserted(el){
// 对元素的操作
el.focus()

}
})

// 进行全局注册
// Vue.component('组件名',组件)
Vue.component('MyButton',MyButton)

new Vue({
render: h => h(App),
}).$mount('#app')

使用自定义指令

head.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div class="header">

<!-- 使用自定义指令 -->
用户名:<input type="text" v-focus>
</div>
</template>

<script>

export default{




}
</script>


<style>

</style>

  • 局部注册指令

    局部注册的指令只能在当前的组件使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<template>
<div class="header">

<!-- 使用自定义指令 -->
<!-- 用户名:<input type="text" v-focus> -->

密码 <input type="text" v-myFocus>
</div>
</template>

<script>

export default{

directives:{
// 指令名 指令的配置项
myFocus:{
inserted(el){
el.focus()
}
}
}



}
</script>


<style>

</style>

10.1 自定义指令值

通过拿到自定义指令的值,来做对应的事情

自定义指令值

① v-指令名 = “指令值” ,通过 等号 可以绑定指令的值

② 通过 binding.value 可以拿到指令的值

③ 通过 update 钩子,可以监听指令值的变化,进行dom更新操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template>
<div class="header">
<div v-color="color" @click="color='yellow'">组件</div>
</div>
</template>
<script>

export default{
data(){
return{
color:'red'
}
},

directives:{
color:{
//el 谁使用 自定义指令, el就是谁, binding是自定义指令传过来的值
inserted(el,binding){
el.style.color=binding.value
},
update(el,binding){
el.style.color=binding.value
}
}
}

}
</script>
<style>
</style>

10.2 v-loading指令封装

在初始阶段, 发送请求,数据还没回来,页面处于空白状态, 这个时候需要我们在页面上面加一些东西,例如加一个loading的效果

  1. 本质 loading 效果就是一个蒙层,盖在了盒子上

  2. 数据请求中,开启loading状态,添加蒙层

  3. 数据请求完毕,关闭loading状态,移除蒙层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
<template>
<div class="main">
<div class="box">
<ul>
<li v-for="item in list" :key="item.id" class="news">
<div class="left">
<div class="title">{{ item.title }}</div>
<div class="info">
<span>{{ item.source }}</span>
<span>{{ item.time }}</span>
</div>
</div>

<div class="right">
<img :src="item.img" alt="">
</div>
</li>
</ul>
</div>
</div>
</template>

<script>
// 安装axios
import axios from 'axios'

// 接口地址:http://hmajax.itheima.net/api/news
// 请求方式:get
export default {
data () {
return {
list: []
}
},
async created () {
// 1. 发送请求获取数据
const res = await axios.get('http://hmajax.itheima.net/api/news')

setTimeout(() => {
// 2. 更新到 list 中
this.list = res.data.data
}, 2000)
}
}
</script>

<style>
/* 伪类 - 蒙层效果 */
.loading:before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #fff url('./loading.gif') no-repeat center;
}

/* .box2 {
width: 400px;
height: 400px;
border: 2px solid #000;
position: relative;
} */

.box {
width: 800px;
min-height: 500px;
border: 3px solid orange;
border-radius: 5px;
position: relative;
}
.news {
display: flex;
height: 120px;
width: 600px;
margin: 0 auto;
padding: 20px 0;
cursor: pointer;
}
.news .left {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
padding-right: 10px;
}
.news .left .title {
font-size: 20px;
}
.news .left .info {
color: #999999;
}
.news .left .info span {
margin-right: 20px;
}
.news .right {
width: 160px;
height: 120px;
}
.news .right img {
width: 100%;
height: 100%;
object-fit: cover;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
<template>
<div class="main">

<!-- 1.通过v-loading 来控制是否有 加载效果 isLoading如果为true 就是加载效果 -->
<div class="box" v-loading="isLoading">
<ul>
<li v-for="item in list" :key="item.id" class="news">
<div class="left">
<div class="title">{{ item.title }}</div>
<div class="info">
<span>{{ item.source }}</span>
<span>{{ item.time }}</span>
</div>
</div>

<div class="right">
<img :src="item.img" alt="">
</div>
</li>
</ul>
</div>
</div>
</template>

<script>
// 安装axios npm i axios
import axios from 'axios'

// 接口地址:http://hmajax.itheima.net/api/news
// 请求方式:get
export default {
data () {
return {
list: [],
isLoading:true
}
},
async created () {
// 1. 发送请求获取数据
const res = await axios.get('http://hmajax.itheima.net/api/news')

setTimeout(() => {
// 2. 更新到 list 中
this.list = res.data.data

// 已经有数据了 所以取消loading效果

this.isLoading=false
}, 2000)
},

// 通过自定义指令,能拿到isLoading 的值
directives:{
loading:{

inserted(el,binding){

// 如果值为true 就有对应的加载效果
if(binding.value){
el.classList.add('loading')
}else{
// 否则就没有
el.classList.remove('loading')
}
},

//
update(el,binding){
if(binding.value){
el.classList.add('loading')
}else{
// 否则就没有
el.classList.remove('loading')
}
}
}
}


}
</script>

<style>
/* 伪类 - 蒙层效果 */
.loading:before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #fff url('./assets/logo.png') no-repeat center;
}

/* .box2 {
width: 400px;
height: 400px;
border: 2px solid #000;
position: relative;
} */

.box {
width: 800px;
min-height: 500px;
border: 3px solid orange;
border-radius: 5px;
position: relative;
}
.news {
display: flex;
height: 120px;
width: 600px;
margin: 0 auto;
padding: 20px 0;
cursor: pointer;
}
.news .left {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
padding-right: 10px;
}
.news .left .title {
font-size: 20px;
}
.news .left .info {
color: #999999;
}
.news .left .info span {
margin-right: 20px;
}
.news .right {
width: 160px;
height: 120px;
}
.news .right img {
width: 100%;
height: 100%;
object-fit: cover;
}
</style>

11.插槽

插槽可以让组件内部的结构支持自定义

插槽

11.1默认插槽

  1. 组件内需要定制的结构部分,改用占位 (挖坑)

  2. 使用组件时, 标签内部, 传入结构替换slot (填坑)

插槽

header.vue(子组件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<template>
<div class="dialog">
<div class="dialog-header">
<h3>友情提示</h3>
<span class="close">✖️</span>
</div>


<div class="dialog-content">
<!-- 在这个地方挖了一个坑 -->
<slot></slot>
</div>
<div class="dialog-footer">
<button>取消</button>
<button>确认</button>
</div>

</div>
</template>

<script>
export default {
data () {
return {


}

}
}
</script>

<style scoped>
* {
margin: 0;
padding: 0;
}
.dialog {
width: 470px;
height: 230px;
padding: 0 25px;
background-color: #ffffff;
margin: 40px auto;
border-radius: 5px;
}
.dialog-header {
height: 70px;
line-height: 70px;
font-size: 20px;
border-bottom: 1px solid #ccc;
position: relative;
}
.dialog-header .close {
position: absolute;
right: 0px;
top: 0px;
cursor: pointer;
}
.dialog-content {
height: 80px;
font-size: 18px;
padding: 15px 0;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
}
.dialog-footer button {
width: 65px;
height: 35px;
background-color: #ffffff;
border: 1px solid #e1e3e9;
cursor: pointer;
outline: none;
margin-left: 10px;
border-radius: 3px;
}
.dialog-footer button:last-child {
background-color: #007acc;
color: #fff;
}
</style>


App.vue 父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

<template>
<div class="App">

<MyHeader ref="Myheader">
<!-- 在这里填东西进去 -->
<p>你确认退出吗</p>
</MyHeader>



</div>
</template>

<script>
// 1.引入组件
import MyHeader from './components/MyHeader.vue';

export default {


methods:{

reset(){
this.$refs.Myheader.reset()
},

getValue(){
console.log(this.$refs.Myheader.getValueS());
}
},
// 2.注册组件
components: {
MyHeader: MyHeader,
},
};
</script>

<style>

</style>

11.2 默认插槽默认值(后备内容)

封装组件时,可以为预留的 slot 插槽写一些默认的内容

  • 语法: 在 标签内,放置内容, 作为默认显示内容
  • 外部使用组件时,不传东西,则slot会显示后备内容
  • 外部使用组件时,传东西了,则slot整体会被换掉
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div class="dialog">
<div class="dialog-header">
<h3>友情提示</h3>
<span class="close">✖️</span>
</div>
<div class="dialog-content">
<!-- 在这个地方挖了一个坑 -->
<slot> 我是默认内容</slot>
</div>
<div class="dialog-footer">
<button>取消</button>
<button>确认</button>
</div>

</div>

默认内容

1
2
3
<MyHeader ref="Myheader"> 
放在默认插槽中的值
</MyHeader>

有值

没有传值就是使用默认的值,如果有值就使用传过来的值

11.3具名插槽

一个组件有多个部分需要外部传入,进行定制

  • 语法

    1. 多个slot使用name属性区分名字
    2. template配合v-slot:名字来分发对应标签

    具名插槽

  • 简写形式

    简写形式

header.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div class="dialog">
<div class="dialog-header">
<h3>
<slot name="title"></slot>
</h3>
<span class="close">✖️</span>
</div>
<div class="dialog-content">
<slot name="content"></slot>
</div>
<div class="dialog-footer">
<button>取消</button>
<button>确认</button>
</div>

</div>
</template>

APP.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div class="App">

<MyHeader ref="Myheader">
<template v-slot:title>
我是标题
</template>
<template v-slot:content>
我是内容
</template>
</MyHeader>
</div>
</template>

简写形式

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<div class="App">
<MyHeader ref="Myheader">
<template #title>
我是标题
</template>
<template #content>
我是内容
</template>
</MyHeader>
</div>
</template>

11.4 作用域插槽

定义 slot 插槽的同时, 是可以传值的。给 插槽 上可以 绑定数据,将来 使用组件时可以用。(也就是说子组件可以通过给插槽绑定数据,从而将数据给到父组件)

  • 使用步骤

作用域插槽

  • 小案例

    需求: 数据存储在父组件,子组件负责渲染数据,当点击删除的时候,子组件通过作用域插槽,把当前项传递给父组件,父组件删除对应数据。

    插槽

    table.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    <template>
    <table class="my-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. name}}</td>
    <td>{{ item.age }}</td>
    <td>
    <!-- 1. 给 slot 标签, 以 添加属性的方式传值 可以添加多个 -->
    <slot :rowitem="item" :test="测试用的数据"></slot>
    <!-- 2. 所有添加的属性, 都会被收集到一个对象中
    {
    rowitem:{id:xxx , name:xxx,age:xxx},
    text:测试用的数据
    }
    -->
    </td>
    </tr>

    </tbody>
    </table>
    </template>

    <script>
    export default {
    props:{
    list:Array
    }
    }
    </script>

    <style scoped>
    .my-table {
    width: 450px;
    text-align: center;
    border: 1px solid #ccc;
    font-size: 24px;
    margin: 30px auto;
    }
    .my-table thead {
    background-color: #1f74ff;
    color: #fff;
    }
    .my-table thead th {
    font-weight: normal;
    }
    .my-table thead tr {
    line-height: 40px;
    }
    .my-table th,
    .my-table td {
    border-bottom: 1px solid #ccc;
    border-right: 1px solid #ccc;
    }
    .my-table td:last-child {
    border-right: none;
    }
    .my-table tr:last-child td {
    border-bottom: none;
    }
    .my-table button {
    width: 65px;
    height: 35px;
    font-size: 18px;
    border: 1px solid #ccc;
    outline: none;
    border-radius: 3px;
    cursor: pointer;
    background-color: #ffffff;
    margin-left: 5px;
    }
    </style>

    App.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    <template>
    <div>
    <!-- 父组件通过 子传父 把数据个子组件 -->
    <MyTable :list="list">
    <!-- 在template中, 通过 ` #插槽名= "obj" ` 接收,默认插槽名为 default -->
    <template #default="obj">
    <!-- 父组件能拿到子组件通过插槽传递过来的 值,直接使用即可 -->
    <button @click="del(obj.rowitem.id)">删除</button>
    </template>

    </MyTable>

    <MyTable :list="list">
    <button>查看</button>
    </MyTable>
    </div>
    </template>

    <script>
    import MyTable from './components/MyTable.vue'
    export default {
    data () {
    return {
    list: [
    { id: 1, name: '张小花', age: 18 },
    { id: 2, name: '孙大明', age: 19 },
    { id: 3, name: '刘德忠', age: 17 },
    ],
    list2: [
    { id: 1, name: '赵小云', age: 18 },
    { id: 2, name: '刘蓓蓓', age: 19 },
    { id: 3, name: '姜肖泰', age: 17 },
    ]
    }
    },
    components: {
    MyTable
    },

    methods:{
    del(id){
    this.list= this.list.filter(item=>item.id!=id)
    }
    }
    }
    </script>

    作用域插槽就是在插槽上以属性的方式去传值, 所有的属性都会添加到一个对象中去。 组件使用的时候能进行接收,接收了以后就可以随意使用

11.5 综合案例

综合案例

素材代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
<template>
<div class="table-case">
<table class="my-table">
<thead>
<tr>
<th>编号</th>
<th>图片</th>
<th>名称</th>
<th width="100px">标签</th>
</tr>
</thead>
<tbody>
<tr>
<td>101</td>
<td>
<img
src="https://yanxuan-item.nosdn.127.net/f8c37ffa41ab1eb84bff499e1f6acfc7.jpg"
/>
</td>
<td>梨皮朱泥三绝清代小品壶经典款紫砂壶</td>
<td>
<div class="my-tag">
<!-- <input
class="input"
type="text"
placeholder="输入标签"
/> -->
<div class="text">茶具</div>
</div>
</td>
</tr>
<tr>
<td>101</td>
<td>
<img
src="https://yanxuan-item.nosdn.127.net/f8c37ffa41ab1eb84bff499e1f6acfc7.jpg"
/>
</td>
<td>梨皮朱泥三绝清代小品壶经典款紫砂壶</td>
<td>
<div class="my-tag">
<input class="input" type="text" placeholder="输入标签" />
<!-- <div class="text">{{ value }}</div> -->
</div>
</td>
</tr>
</tbody>
</table>
</div>
</template>

<script>
export default {
name: 'TableCase',
data() {
return {
goods: [
{
id: 101,
picture:
'https://yanxuan-item.nosdn.127.net/f8c37ffa41ab1eb84bff499e1f6acfc7.jpg',
name: '梨皮朱泥三绝清代小品壶经典款紫砂壶',
tag: '茶具',
},
{
id: 102,
picture:
'https://yanxuan-item.nosdn.127.net/221317c85274a188174352474b859d7b.jpg',
name: '全防水HABU旋钮牛皮户外徒步鞋山宁泰抗菌',
tag: '男鞋',
},
{
id: 103,
picture:
'https://yanxuan-item.nosdn.127.net/cd4b840751ef4f7505c85004f0bebcb5.png',
name: '毛茸茸小熊出没,儿童羊羔绒背心73-90cm',
tag: '儿童服饰',
},
{
id: 104,
picture:
'https://yanxuan-item.nosdn.127.net/56eb25a38d7a630e76a608a9360eec6b.jpg',
name: '基础百搭,儿童套头针织毛衣1-9岁',
tag: '儿童服饰',
},
],
};
},
};
</script>

<style scoped>
.table-case {
width: 1000px;
margin: 50px auto;
}
.table-case img {
width: 100px;
height: 100px;
object-fit: contain;
vertical-align: middle;
}

.my-table img {
width: 100px;
height: 100px;
object-fit: contain;
vertical-align: middle;
}
.my-table th {
background: #f5f5f5;
border-bottom: 2px solid #069;
}

.my-table td {
border-bottom: 1px dashed #ccc;
}

.my-table td,
th {
text-align: center;
padding: 10px;
transition: all 0.5s;
&.red {
color: red;
}
}
.my-table .none {
height: 100px;
line-height: 100px;
color: #999;
}
.my-table {
width: 100%;
border-spacing: 0;
}
.my-tag {
cursor: pointer;
}

.my-tag .input {
appearance: none;
outline: none;
border: 1px solid #ccc;
width: 100px;
height: 40px;
box-sizing: border-box;
padding: 10px;
color: #666;
}
</style>

案例步骤:

  • 创建tag组件,进行组件的初始化

  • 双击显示输入框

    用一个变量来控制是显示输入框还是文字,通过v-show 或者v-if 来控制具体是显示哪一个

    双击文字的时候,控制显示输入框,并让输入框自动获取焦点

    mytag.vue (通过调用输入框身上的focus()方法实现 )

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    <template>
    <div class="my-tag">
    <input
    v-if="isWrite"
    class="input"
    type="text"
    placeholder="输入标签"
    ref="inp"
    />

    <!-- 双击文字显示输入框 -->
    <div v-else class="text" @dblclick="IsWrite" >茶具</div>
    </div>
    </template>

    <script>
    export default {
    data(){
    return{
    // 是否显示输入框
    isWrite:false
    }
    },

    methods:{
    IsWrite(){
    // 显示输入框
    this.isWrite=true

    // 自动获取焦点
    this.$nextTick(()=>{
    this.$refs.inp.focus()
    })

    }
    }
    }
    </script>

    <style scoped>

    </style>

    通过自定义指令实现

    main.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import Vue from 'vue'
    import App from './App.vue'

    Vue.config.productionTip = false


    // 定义一个全局自定义指令
    Vue.directive('focus',{
    inserted(el){
    el.focus()
    }
    })
    new Vue({
    render: h => h(App),
    }).$mount('#app')

    Mytag.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    <template>
    <div class="my-tag">
    <input
    v-if="isWrite"
    class="input"
    type="text"
    placeholder="输入标签"
    v-focus
    />

    <!-- 双击文字显示输入框 -->
    <div v-else class="text" @dblclick="IsWrite" >茶具</div>
    </div>
    </template>

    <script>
    export default {
    data(){
    return{
    // 是否显示输入框
    isWrite:false
    }
    },

    methods:{
    IsWrite(){
    // 显示输入框
    this.isWrite=true

    }
    }
    }
    </script>

    <style scoped>
    </style>
  • 失去焦点自动隐藏输入框

    控制显示输入框或文字的那个变量即可

    myTag.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    <template>
    <div class="my-tag">
    <input
    v-if="isWrite"
    class="input"
    type="text"
    placeholder="输入标签"
    v-focus
    @blur="isWrite=false"
    />

    <!-- 双击文字显示输入框 -->
    <div v-else class="text" @dblclick="IsWrite" >茶具</div>
    </div>
    </template>

    <script>
    export default {
    data(){
    return{
    // 是否显示输入框
    isWrite:false
    }
    },

    methods:{
    IsWrite(){
    // 显示输入框
    this.isWrite=true

    }
    }
    }
    </script>

    <style scoped>

    </style>
  • 回显标签信息

    父组件(App.vue) 遍历渲染数据

    当前项传递给子组件,子组件接收了以后,在双击文字的时候,将数据给到输入框

  • 内容修改,回车 → 修改标签信息

    给input 绑定键盘提起事件,同时用enter 修饰一下,只有当按下的是回车的时候触发修改函数

    修改函数里面,通过子传父,把当前用户输入的信息传递过去

    父组件接收了以后,直接修改源数据

    APP.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    <template>
    <div class="table-case">
    <table class="my-table">
    <thead>
    <tr>
    <th>编号</th>
    <th>图片</th>
    <th>名称</th>
    <th width="100px">标签</th>
    </tr>
    </thead>
    <tbody>
    <tr v-for="(item) in goods" :key="item.id">
    <td>{{ item.id }}</td>
    <td>
    <img
    :src="item.picture"
    />
    </td>
    <td>{{ item.name }}</td>
    <td>
    <MyTag :tagName="item" @changeV="changeV"></MyTag>
    </td>
    </tr>
    </tbody>
    </table>
    </div>
    </template>

    <script>
    import MyTag from '@/components/MyTag.vue'
    export default {
    name: 'TableCase',
    data() {
    return {
    goods: [
    {
    id: 101,
    picture:
    'https://yanxuan-item.nosdn.127.net/f8c37ffa41ab1eb84bff499e1f6acfc7.jpg',
    name: '梨皮朱泥三绝清代小品壶经典款紫砂壶',
    tag: '茶具',
    },
    {
    id: 102,
    picture:
    'https://yanxuan-item.nosdn.127.net/221317c85274a188174352474b859d7b.jpg',
    name: '全防水HABU旋钮牛皮户外徒步鞋山宁泰抗菌',
    tag: '男鞋',
    },
    {
    id: 103,
    picture:
    'https://yanxuan-item.nosdn.127.net/cd4b840751ef4f7505c85004f0bebcb5.png',
    name: '毛茸茸小熊出没,儿童羊羔绒背心73-90cm',
    tag: '儿童服饰',
    },
    {
    id: 104,
    picture:
    'https://yanxuan-item.nosdn.127.net/56eb25a38d7a630e76a608a9360eec6b.jpg',
    name: '基础百搭,儿童套头针织毛衣1-9岁',
    tag: '儿童服饰',
    },
    ],
    };
    },
    components:{
    MyTag
    },
    methods:{
    changeV(value){
    this.goods.forEach(item=>{
    if(item.id===value.id){
    item.tag=value.tag
    }
    })
    }
    }
    };
    </script>

    <style scoped>


    </style>

    Mytag.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    <template>
    <div class="my-tag">
    <input
    v-if="isWrite"
    class="input"
    type="text"
    placeholder="输入标签"
    v-focus
    v-model="inpValue"
    @blur="isWrite=false"
    @keyup.enter="changeValue"
    />

    <!-- 双击文字显示输入框 -->
    <div v-else class="text" @dblclick="IsWrite">{{tagName.tag}}</div>
    </div>
    </template>

    <script>
    export default {

    props:{
    tagName:Object
    },
    data(){
    return{
    // 是否显示输入框
    isWrite:false,
    inpValue:''
    }
    },

    methods:{
    IsWrite(){
    // 显示输入框
    this.isWrite=true

    // 把tagName 给到输入框
    this.inpValue=this.tagName.tag
    },
    changeValue(){

    this.$emit('changeV',{
    id: this.tagName.id,
    picture:this.tagName.picture,
    name: this.tagName.name,
    tag: this.inpValue,
    })
    this.isWrite=false
    }
    }
    }
    </script>

    <style scoped>
    </style>
  • 表格组件的拆分

    把表格单独的拆分出来,引入的组件,方法都需要重新写

    table.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    <template>
    <div>
    <table class="my-table">
    <thead>
    <tr>
    <th>编号</th>
    <th>图片</th>
    <th>名称</th>
    <th width="100px">标签</th>
    </tr>
    </thead>
    <tbody>
    <tr v-for="(item,index) in goods" :key="item.id">
    <td>{{ index+1 }}</td>
    <td>
    <img
    :src="item.picture"
    />
    </td>
    <td>{{ item.name }}</td>
    <td>


    <MyTag :item="item" @changeV='chengData'></MyTag>

    </td>
    </tr>
    <!-- <tr>
    <td>101</td>
    <td>
    <img
    src="https://yanxuan-item.nosdn.127.net/f8c37ffa41ab1eb84bff499e1f6acfc7.jpg"
    />
    </td>
    <td>梨皮朱泥三绝清代小品壶经典款紫砂壶</td>
    <td>
    <div class="my-tag">
    <input class="input" type="text" placeholder="输入标签" />
    <div class="text">{{ value }}</div>
    </div>
    </td>
    </tr> -->
    </tbody>
    </table>
    </div>
    </template>

    <script>
    import MyTag from '@/components/MyTag'
    export default {
    components:{
    MyTag
    },
    props:{
    goods:Array
    },
    methods:{
    chengData(value){

    this.$emit('changeData',value)
    // this.goods.forEach(item=>{
    // if(item.id===value.id){
    // item.tag=value.tag

    // }
    // })

    // console.log(this.goods);
    }
    }
    };
    </script>

    <style>

    .my-table img {
    width: 100px;
    height: 100px;
    object-fit: contain;
    vertical-align: middle;
    }
    .my-table th {
    background: #f5f5f5;
    border-bottom: 2px solid #069;
    }

    .my-table td {
    border-bottom: 1px dashed #ccc;
    }

    .my-table td,
    th {
    text-align: center;
    padding: 10px;
    transition: all 0.5s;
    &.red {
    color: red;
    }
    }
    .my-table .none {
    height: 100px;
    line-height: 100px;
    color: #999;
    }
    .my-table {
    width: 100%;
    border-spacing: 0;
    }

    </style>

    tag.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    <template>
    <div class="my-tag" @dblclick="showText">
    <input
    class="input"
    type="text"
    placeholder="输入标签"
    v-if="isShowinp"
    v-focus='isFocus'
    @blur="isShowinp=false"
    v-model="inputValue"
    @keydown.enter="changeValue"
    />
    <div class="text" v-else>{{ item.tag }}</div>
    </div>
    </template>

    <script>


    export default {

    props:{
    item:Object
    },

    data(){
    return {
    isShowinp:false,
    isFocus:false,
    inputValue:''
    }
    },

    methods:{
    showText(){
    this.isShowinp=true
    // 获取焦点
    this.isFocus=true
    // 将数据给input
    this.inputValue=this.item.tag
    },
    changeValue(){
    this.$emit('changeV',
    {
    id: this.item.id,
    picture:this.item.picture,
    name: this.item.name,
    tag: this.inputValue
    })
    this.isShowinp=false
    }
    },
    directives:{
    focus:{
    inserted(el,binding){
    if(binding.value){
    el.focus()
    }
    }
    }
    }
    }
    </script>

    <!-- 加了 scoped 以后 当前的样式只作用与当前的元素 -->
    <style dcoped>
    .my-tag {
    cursor: pointer;
    }

    .my-tag .input {
    appearance: none;
    outline: none;
    border: 1px solid #ccc;
    width: 100px;
    height: 40px;
    box-sizing: border-box;
    padding: 10px;
    color: #666;
    }

    </style>

    App.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    <template>
    <div class="table-case">
    <MyTable :goods="goods" @changeData="changeData"></MyTable>
    </div>
    </template>

    <script>
    // import MyTag from './components/MyTag.vue'
    import MyTable from '@/components/MyTable'

    export default {


    data() {
    return {
    goods: [
    {
    id: 101,
    picture:
    'https://yanxua清n-item.nosdn.127.net/f8c37ffa41ab1eb84bff499e1f6acfc7.jpg',
    name: '梨皮朱泥三绝代小品壶经典款紫砂壶',
    tag: '茶具',
    },
    {
    id: 102,
    picture:
    'https://yanxuan-item.nosdn.127.net/221317c85274a188174352474b859d7b.jpg',
    name: '全防水HABU旋钮牛皮户外徒步鞋山宁泰抗菌',
    tag: '男鞋',
    },
    {
    id: 103,
    picture:
    'https://yanxuan-item.nosdn.127.net/cd4b840751ef4f7505c85004f0bebcb5.png',
    name: '毛茸茸小熊出没,儿童羊羔绒背心73-90cm',
    tag: '儿童服饰',
    },
    {
    id: 104,
    picture:
    'https://yanxuan-item.nosdn.127.net/56eb25a38d7a630e76a608a9360eec6b.jpg',
    name: '基础百搭,儿童套头针织毛衣1-9岁',
    tag: '儿童服饰',
    },
    ],
    };
    },

    components:{
    // MyTag:MyTag,
    MyTable:MyTable
    },

    methods:{
    changeData(value){
    this.goods.forEach(item=>{
    if(item.id===value.id){
    item.tag=value.tag
    }
    })
    }
    }
    };
    </script>

    <style scoped>
    .table-case {
    width: 1000px;
    margin: 50px auto;
    }
    .table-case img {
    width: 100px;
    height: 100px;
    object-fit: contain;
    vertical-align: middle;
    }

    .my-table img {
    width: 100px;
    height: 100px;
    object-fit: contain;
    vertical-align: middle;
    }
    .my-table th {
    background: #f5f5f5;
    border-bottom: 2px solid #069;
    }

    .my-table td {
    border-bottom: 1px dashed #ccc;
    }

    .my-table td,
    th {
    text-align: center;
    padding: 10px;
    transition: all 0.5s;
    &.red {
    color: red;
    }
    }
    .my-table .none {
    height: 100px;
    line-height: 100px;
    color: #999;
    }
    .my-table {
    width: 100%;
    border-spacing: 0;
    }

    </style>

12.路由

12.1 SPA单页面应用

单页面应用(SPA): 所有功能在 一个html页面 上实现

单页面

单页面应用程序,之所以开发效率高,性能高,用户体验好,最大的原因就是:页面按需更新

12.2路由概念

路由就是路径和组件的对应关系

路由

12.3VueRouter基本配置

vueRouter

作用:修改地址栏路径时,切换显示匹配的组件

说明:Vue 官方的一个路由插件,是一个第三方包

官网:https://v3.router.vuejs.org/zh/


VueRouter 的使用

5个基础步骤 (固定)

① 下载: 下载 VueRouter 模块到当前工程,版本3.6.5

1
npm install vue-router@3.6.5

② 引入

1
import VueRouter from 'vue-router' 

③ 安装注册

Vue.use(vue 相关的插件)

1
Vue.use(VueRouter)

④ 创建路由对象

1
const router = new VueRouter()

⑤ 注入,将路由对象注入到new Vue实例中,建立关联

1
2
3
4
5
6
7
new Vue({

render: h => h(App),

router

}).$mount('#app')

main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
Vue.config.productionTip = false
Vue.use(VueRouter)
const router = new VueRouter()

// 定义一个全局自定义指令
Vue.directive('focus',{
inserted(el){
el.focus()
}
})
new Vue({
render: h => h(App),
router
}).$mount('#app')

运行起来了以后能看到#/就代表成功了


准备组件,配置路由规则

router

  1. 在/scr/views文件夹中新建三个组件,组件里面写满东西

    vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <template>
    <div>
    <ul>
    <li>发现音乐</li>
    <li>发现音乐</li>
    <li>发现音乐</li>
    <li>发现音乐</li>
    </ul>
    </div>
    </template>

    <script>

    export default {
    //配置组件名称,这样就不会出现说组件名要由多个单词组成的这种报错
    name:"FindMusic"

    }
    </script>

    <style scoped>


    </style>
  2. main.js中配置路由规则

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    import Vue from 'vue'
    import App from './App.vue'
    import VueRouter from 'vue-router'
    import Find from './views/Find.vue';
    import My from './views/My.vue';
    import Friends from './views/Friends.vue';
    Vue.config.productionTip = false
    Vue.use(VueRouter)
    const router = new VueRouter({
    // routes 路由规则们
    routes:[
    {path:'/find',component:Find},
    {path:'/my',component:My},
    {path:'/friends',component:Friends}
    ]
    })

    // 定义一个全局自定义指令
    Vue.directive('focus',{
    inserted(el){
    el.focus()
    }
    })
    new Vue({
    render: h => h(App),
    router
    }).$mount('#app')

    配置导航,配置路由出口(路径匹配的组件显示的位置)

    App.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <template>
    <div class="table-case">
    <div>
    <ul>
    <li><a href="#/find">find</a></li>
    <li><a href="#/my">my</a></li>
    <li><a href="#/friends">friends</a></li>
    </ul>
    </div>
    <!-- 路由出口 -->
    <router-view></router-view>
    </div>
    </template>

    <script>

    export default {


    }
    </script>

    <style scoped>


    </style>

12.4views 和companies区别

views

views:用于大的页面的展示

companies:小的组件,经常用于复用的组件

本质上都是vue文件,只不过用途稍有不同,本质上都是为了方便维护

12.5路由模块封装

所有的路由配置都放在main.js中是不太合适的,需要将路由拆分出来,不仅能减少main.js中的代码,同时能更好的维护router,就是将所有的路由配置都写到 src/router/index.js中

/scr/router/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 引入相关组件
import Find from './views/Find.vue';
import My from './views/My.vue';
import Friends from './views/Friends.vue';

// 引入vue 和vuerouter
import VueRouter from 'vue-router'
import Vue from 'vue'
// 组件初始化
Vue.use(VueRouter)

// 创建路由对象
const router = new VueRouter({
// routes 路由规则们
routes:[
{path:'/find',component:Find},
{path:'/my',component:My},
{path:'/friends',component:Friends}
]
})

// 导出
export default router

main.js 引入 router

main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Vue from 'vue'
import App from './App.vue'
import router from './router/index.js';

// 引入
Vue.config.productionTip = false


new Vue({
render: h => h(App),
// 使用
router
}).$mount('#app')

12.6声明式导航

vue-router 提供了一个全局组件 router-link (取代 a 标签)

① 能跳转,配置 to 属性指定路径(必须) 。本质还是 a 标签 ,to 无需 #

② 能高亮,默认就会提供高亮类名,可以直接设置高亮样式(底层是封装了一个a:active)

router

APP.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div class="table-case">
<router-link to="/my">我的</router-link>
<router-link to="/friends">朋友</router-link>
<router-link to="/find">发现</router-link>
<!-- 路由出口 -->
<router-view></router-view>
</div>
</template>

<script>

export default {


}
</script>

<style scoped>


</style>

高亮本质上就是 a:acitve 这个结构伪类

rou

所以说找到对应的类名,加上指定的样式就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div class="table-case">
<router-link to="/my">我的</router-link>
<router-link to="/friends">朋友</router-link>
<router-link to="/find">发现</router-link>
<!-- 路由出口 -->
<router-view></router-view>
</div>
</template>

<script>

export default {
}
</script>

<style scoped>
.router-link-exact-active{
background-color: yellowgreen;
line-height: 50px;
text-align: center;
}
</style>

两个类名的区别
两个类名区别

12.7 声明导航-跳转传参

在跳转路由的时候,进行传参

跳转传参

home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input type="text">
<button>搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<router-link to="/search?key=黑马程序员">黑马程序员</router-link>
<router-link to="/search?key=前端培训">前端培训</router-link>
<router-link to="/search?key=如何成为前端大牛">如何成为前端大牛</router-link>
</div>
</div>
</template>

<script>
export default {
name: 'FindMusic'
}
</script>

search.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div class="search">
<!-- 拿到query参数 -->
<p>搜索关键字: {{ $route.query.key }} </p>
<p>搜索结果: </p>
<ul>
<li>.............</li>
<li>.............</li>
<li>.............</li>
<li>.............</li>
</ul>
</div>
</template>

<script>
export default {
name: 'MyFriend',
created () {
// 在created中,获取路由参数
// this.$route.query.参数名 获取
console.log(this.$route.query.key);
}
}
</script>

动态路由传参:在跳转路由时, 进行传值

/search/:words 表示,必须要传参数。如果不传参数,也希望匹配,可以加个可选符 “?”

动态路由传参

这里的words就有点类似于形式参数,跳转的时候哦 传的是什么,word的值就是什么

/scr/router/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import Home from '@/views/Home'
import Search from '@/views/Search'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化

// 创建了一个路由对象
const router = new VueRouter({
routes: [
// 配置动态路由
{ path: '/home/:words?', component: Home },
{ path: '/search/:words?', component: Search }
]
})

export default router

/scr/home.vue(跳转方)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div class="home">
<div class="logo-box"></div>
<div class="search-box">
<input type="text">
<button>搜索一下</button>
</div>
<div class="hot-link">
热门搜索:
<router-link to="/search/黑马程序员">黑马程序员</router-link>
<router-link to="/search/前端培训">前端培训</router-link>
<router-link to="/search/如何成为前端大牛">如何成为前端大牛</router-link>
</div>
</div>
</template>

<script>
export default {
name: 'FindMusic'
}
</script>

scr/seacrh.vue(跳转过去的那一方)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div class="search">
<!-- 拿到query参数 -->
<p>搜索关键字: {{ $route.params.word}} </p>
<p>搜索结果: </p>
<ul>
<li>.............</li>
<li>.............</li>
<li>.............</li>
<li>.............</li>
</ul>
</div>
</template>

<script>
export default {
name: 'MyFriend',
created () {
// 在created中,获取路由参数
// this.$route.query.参数名 获取
console.log(this.$route.params.words);
}
}
</script>

12.8路由重定向

网页打开, url 默认是 / 路径,未匹配到组件时,会出现空白

重定向 → 匹配path后, 强制跳转path路径

语法: { path: 匹配路径, redirect: 重定向到的路径 },

1
2
3
4
5
6
7
8
9
10
11
// 创建了一个路由对象
const router = new VueRouter({
routes: [
// 配置动态路由
{path:'/',redirect:'/home'},
{ path: '/home/:words?', component: Home },
{ path: '/search/:words?', component: Search }
]
})

export default router

12.9 404

当前路径没有匹配的路径时,给一个提示页面

位置:配在路由最后

语法:path: “*” (任意路径) – 前面不匹配就命中最后这个

/scr/router/index.js

1
2
3
4
5
6
7
8
9
10
11
// 创建了一个路由对象
const router = new VueRouter({
routes: [
// 配置动态路由
{path:'/',redirect:'/home'},
{ path: '/home/:words?', component: Home },
{ path: '/search/:words?', component: Search },
// 路由404 会匹配所有路径
{ path: '*',component:NotFind}
]
})

一般这种情况在 404 或者notFind 页面直接写一个按钮,一点击就能回到首页就行

12.10 router、routes、route的区别

路由,其实就是指向的意思,当我点击页面上的home按钮时,页面中就要显示home的内容,如果点击页面上的about 按钮,页面中就要显示about 的内容。Home按钮 => home 内容, about按钮 => about 内容,也可以说是一种映射. 所以在页面上有两个部分,一个是点击部分,一个是点击之后,显示内容的部分。

点击之后,怎么做到正确的对应,比如,我点击home 按钮,页面中怎么就正好能显示home的内容。这就要在js 文件中配置路由。

路由中有三个基本的概念 route, routes, router。

1, route,它是一条路由,由这个英文单词也可以看出来,它是单数, Home按钮 => home内容, 这是一条route, about按钮 => about 内容, 这是另一条路由。

2, routes 是一组路由,把上面的每一条路由组合起来,形成一个数组。[{home 按钮 =>home内容 }, { about按钮 => about 内容}]

3, router 是一个机制,相当于一个管理者,它来管理路由。因为routes 只是定义了一组路由,它放在哪里是静止的,当真正来了请求,怎么办? 就是当用户点击home 按钮的时候,怎么办?这时router 就起作用了,它到routes 中去查找,去找到对应的 home 内容,所以页面中就显示了 home 内容。

1.router:路由器对象(new的路由器对象),包含一些操作路由的功能函数,来实现编程式导航。一般指的是在任何组件内访问路由。如:路由编程式导航的$router.push()

1
this.$router.push('/路径?参数名1=参数值1&参数2=参数值2')

2.routes:指创建vue-router路由实例的配置项。用来配置多个route路由对象

1
2
3
4
5
6
7
8
9
10
const router = new VueRouter({
routes: [
// 配置动态路由
{path:'/',redirect:'/home'},
{ path: '/home/:words?', component: Home },
{ path: '/search/:words?', component: Search },
// 路由404 会匹配所有路径
{ path: '*',component:NotFind}
]
})

3.route:指路由对象表示当前激活的路由的状态信息。如:this.$route指的是当前路由对象,path/meta/query/params

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div class="search">
<!-- 拿到query参数 -->
<p>搜索关键字: {{ $route.params.words}} </p>
<p>搜索结果: </p>
<ul>
<li>.............</li>
<li>.............</li>
<li>.............</li>
<li>.............</li>
</ul>
</div>
</template>

<script>
export default {
name: 'MyFriend',
created () {
// 在created中,获取路由参数
// this.$route.query.参数名 获取
console.log(this.$route.params.words);
}
}
</script>

12.11 编程式导航

使用js代码来进行路由跳转

  • 简写形式

编程式导航

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template>
<div class="search">

<button @click="goHome">去首页</button>
<!-- 拿到query参数 -->
<p>搜索关键字: {{ $route.params.words}} </p>
<p>搜索结果: </p>
<ul>
<li>.............</li>
<li>.............</li>
<li>.............</li>
<li>.............</li>
</ul>
</div>
</template>

<script>
export default {
name: 'MyFriend',
methods:{
goHome(){
this.$router.push('/home')
}
},

}
</script>
  • 完整写法(需要给路由起名字)

编程式路由

/scr/router/index.js

给路由配置名字

1
2
3
4
5
6
7
8
9
10
11
12
// 创建了一个路由对象
const router = new VueRouter({
routes: [
// 配置动态路由
{path:'/',redirect:'/home'},
{ name:'home',path: '/home/:words?', component: Home },
{ name:'search',path: '/search/:words?', component: Search },
// 路由404 会匹配所有路径
{ path: '*',component:NotFind}
]
})

路由跳转

1
2
3
4
5
6
7
8
methods:{
goHome(){
this.$router.push({
name:'home'
})
}
},

12.12 路由传参

① path 路径跳转传参 (query传参)

传参语法:

1
this.$router.push('/路径?参数名1=参数值1&参数2=参数值2')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
export default {
name: 'MyFriend',
data(){
return{
msg:'我是msg'
}
},
methods:{
goHome(){
this.$router.push(`/home?key=${this.msg}`)
}
},

}
</script>

接收语法:$route.query.xxx

1
2
3
4
5
6
7
8
9
10
11
<template>
<div class="home">
{{ $route.query.key }}
<div class="hot-link">
热门搜索:
<router-link to="/search/黑马程序员">黑马程序员</router-link>
<router-link to="/search/前端培训">前端培训</router-link>
<router-link to="/search/如何成为前端大牛">如何成为前端大牛</router-link>
</div>
</div>
</template>
1
2
3
4
5
6
7
export default {
name: 'FindMusic',
mounted(){
console.log(this.$route.query.key);
}
}
</script>

path 路径跳转传参 (动态路由传参)

传:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div class="search">

<button @click="goHome">去首页2</button>

</div>
</template>

<script>
export default {
name: 'MyFriend',
data(){
return{
msg:'我是msg'
}
},
methods:{
goHome(){
this.$router.push(`/home/${this.msg}`)
}
},

}
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div class="home">
{{ $route.params.words }}

</div>
</template>

<script>
export default {
name: 'FindMusic',
mounted(){
console.log(this.$route.params.words);
}
}
</script>

② name 命名路由跳转传参 (query传参)

路由传参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<template>
<div class="search">

<button @click="goHome">去首页2</button>

</div>
</template>

<script>
export default {
name: 'MyFriend',
data(){
return{
msg:'我是msg'
}
},
methods:{
goHome(){
this.$router.push({
name:'home',
query:{
msg:this.msg
}
})
}
},

}
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div class="home">
{{ $route.query.msg }}

</div>
</template>

<script>
export default {
name: 'FindMusic',
mounted(){
console.log(this.$route.query.msg);
}
}
</script>

name 命名路由跳转传参 (动态路由传参)

路由传参

传:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<template>
<div class="search">

<button @click="goHome">去首页2</button>

</div>
</template>

<script>
export default {
name: 'MyFriend',
data(){
return{
msg:'我是msg'
}
},
methods:{
goHome(){
this.$router.push({
name:'home',
params:{
msg:this.msg
}
})
}
},

}
</script>

收:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div class="home">
{{ $route.params.msg }}

</div>
</template>

<script>
export default {
name: 'FindMusic',
mounted(){
console.log(this.$route.params.msg);
}
}
</script>

12.13 案例:面经

面经案例

  • 配置路由

    先配置一级路由arcicleDeltail, layout

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    import ArticleDetail from '@/views/ArticleDetail.vue';
    import Layout from '@/views/Layout.vue';
    Layout
    import Vue from 'vue'
    import VueRouter from "vue-router";

    ArticleDetail
    Vue.use(VueRouter)

    const router = new VueRouter({
    routes: [

    // 首页
    {
    path:'/',
    component:Layout
    },

    {
    path:"/detail",
    component:ArticleDetail
    }

    ]
    })

    export default router
  • 配置二级路由

    /router/index.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    const router = new VueRouter({
    routes: [

    // 首页
    {
    path:'/',
    component:Layout,
    // 配置二级路由
    // 在alyout 中准备路由出口
    children:[
    {path:'/article',component:Article},
    {path:'/collect',component:Collect},
    {path:'/like',component:Like},
    {path:'/user',component:User}
    ]
    },

    {
    path:"/detail",
    component:ArticleDetail
    }

    ]
    })

    /layout.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <template>
    <div class="h5-wrapper">
    <div class="content">
    <router-view></router-view>
    </div>
    <nav class="tabbar">
    <a href="#/article">面经</a>
    <a href="#/collect">收藏</a>
    <a href="#/like">喜欢</a>
    <a href="#/user">我的</a>
    </nav>
    </div>
    </template>
  • 导航高亮

    a 标签换成router-link 结合高亮的类名来实现高亮的效果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <template>
    <div class="h5-wrapper">
    <div class="content">
    <router-view></router-view>
    </div>
    <nav class="tabbar">

    <router-link to="/article">面经</router-link>
    <router-link to="/collect">收藏</router-link>
    <router-link to="/like">喜欢</router-link>
    <router-link to="/user">我的</router-link>
    </nav>
    </div>
    </template>
  • 首页请求渲染(Article.vue中的数据的渲染)

    按下axios 用axios 发送请求 拿到数据之后渲染数据

    1
    npm install axios

    请求数据,渲染数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    <template>
    <div class="article-page">
    <div class="article-item" v-for="(item, index) in list" :key="item.id">
    <div class="head">
    <img :src="item.creatorAvatar " alt="" />
    <div class="con">
    <p class="title">{{ index+1 }}{{ item.stem }}</p>
    <p class="other">{{item.creatorName}}</p>
    </div>
    </div>
    <div class="body">
    {{ item.creatorName }}
    </div>
    <div class="foot">点赞 {{ likeCount }} | 浏览 {{ item.views }}</div>
    </div>
    </div>
    </template>

    <script>
    // 请求地址: https://mock.boxuegu.com/mock/3083/articles
    // 请求方式: get
    import axios from 'axios'
    export default {
    name: 'ArticlePage',
    data () {
    return {
    list:null
    }
    },
    async created(){

    await axios.get('https://mock.boxuegu.com/mock/3083/articles')
    .then(response => {
    this.list=response.data.result.rows
    console.log(this.list);
    })
    .catch(error => {
    console.error('Error:', error);
    });
    }
    }
    </script>

    <style lang="less" scoped>

    </style>
  • 跳转到详情页

    跳转到首页,并且将当前的这一项的id传过去,可以有很多方式进行传参

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    <template>
    <div class="article-page">
    <div class="article-item" v-for="(item, index) in list" :key="item.id" @click="toDetail(item.id)">
    <div class="head">
    <img :src="item.creatorAvatar " alt="" />
    <div class="con">
    <p class="title">{{ index+1 }}{{ item.stem }}</p>
    <p class="other">{{item.creatorName}}</p>
    </div>
    </div>
    <div class="body">
    {{ item.creatorName }}
    </div>
    <div class="foot">点赞 {{ likeCount }} | 浏览 {{ item.views }}</div>
    </div>
    </div>
    </template>

    <script>
    // 请求地址: https://mock.boxuegu.com/mock/3083/articles
    // 请求方式: get
    import axios from 'axios'
    export default {
    name: 'ArticlePage',
    data () {
    return {
    list:null
    }
    },

    methods:{
    toDetail(id){
    this.$router.push(
    {
    path:"/detail",
    query:{
    id:id
    }
    }
    )
    }
    },
    async created(){

    await axios.get('https://mock.boxuegu.com/mock/3083/articles')
    .then(response => {
    this.list=response.data.result.rows
    console.log(this.list);
    })
    .catch(error => {
    console.error('Error:', error);
    });
    }
    }
    </script>

    detail 详情页接收参数

    1
    2
    3
    mounted(){
    console.log(this.$route.query.id);
    }

    返回

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <template>
    <div class="article-detail-page">
    <nav class="nav"><span class="back" @click="$router.back()">&lt;</span> 面经详情</nav>
    <header class="header">
    <h1>百度前端面经</h1>
    <p>2022-01-20 | 315 浏览量 | 44 点赞数</p>
    <p>
    <img
    src="http://teachoss.itheima.net/heimaQuestionMiniapp/%E5%AE%98%E6%96%B9%E9%BB%98%E8%AE%A4%E5%A4%B4%E5%83%8F%402x.png"
    alt=""
    />
    <span>青春少年</span>
    </p>
    </header>
    <main class="body">
    虽然百度这几年发展势头落后于AT, 甚至快被京东赶上了,毕竟瘦死的骆驼比马大,
    面试还是相当有难度和水准的, 一面.....
    </main>
    </div>
    </template>
  • 详情页数据渲染

    请求数据,渲染数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    <template>
    <div class="article-detail-page">
    <nav class="nav">
    <span class="back" @click="$router.back()">&lt;</span> 面经详情
    </nav>
    <header class="header">
    <h1>{{list.stem}}</h1>
    <p>2{{list.createdAt}} | {{ list.views}} 浏览量 | {{ list.likeCount }}点赞数</p>
    <p>
    <img
    :src="list.creatorAvatar"
    alt=""
    />
    <span>{{ list.creatorName }}</span>
    </p>
    </header>
    <main class="body">
    {{list.content }}
    </main>
    </div>
    </template>

    <script>
    // 请求地址: https://mock.boxuegu.com/mock/3083/articles/:id
    // 请求方式: get

    import axios from 'axios'
    export default {
    name: 'ArticleDetailPage',
    data() {
    return {
    list:null
    };
    },

    async created() {

    await axios
    .get(`https://mock.boxuegu.com/mock/3083/articles/${this.$route.query.id}`)
    .then((response) => {
    this.list = response.data.result;
    })
    .catch((error) => {
    console.error('Error:', error);
    });
    },
    };
    </script>

    <style lang="less" scoped>

    </style>

13.vuex

Vuex 是vue 的 状态管理工具,状态就是数据。就是一个公共的仓库,一些公用的数据都可以放在xuex这个大仓库中。

  • 使用场景

    ①某个数据在很多个组件来使用(个人信息)

    ②多个组件共同维护一份数据(购物车)

  • 优势

    ①共同维护一份数据,数据集中化管理

    ②响应式

    ③操作简洁(vuex提供了一些辅助函数)

13.1 搭建vuex

创建三个组件, 目录如下

1
2
3
4
|-components
|--Son1.vue
|--Son2.vue
|-App.vue

App.vue在入口组件中引入 Son1 和 Son2 这两个子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<template>
<div id="app">
<h1>根组件</h1>
<input type="text">
<Son1></Son1>
<hr>
<Son2></Son2>
</div>
</template>

<script>
import Son1 from './components/Son1.vue'
import Son2 from './components/Son2.vue'

export default {
name: 'app',
data: function () {
return {

}
},
components: {
Son1,
Son2
}
}
</script>

<style>
#app {
width: 600px;
margin: 20px auto;
border: 3px solid #ccc;
border-radius: 3px;
padding: 10px;
}
</style>

main.js

1
2
3
4
5
6
7
8
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
render: h => h(App)
}).$mount('#app')

Son1.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template>
<div class="box">
<h2>Son1 子组件</h2>
从vuex中获取的值: <label></label>
<br>
<button>值 + 1</button>
</div>
</template>

<script>
export default {
name: 'Son1Com'
}
</script>

<style lang="css" scoped>
.box{
border: 3px solid #ccc;
width: 400px;
padding: 10px;
margin: 20px;
}
h2 {
margin-top: 10px;
}
</style>

Son2.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template>
<div class="box">
<h2>Son2 子组件</h2>
从vuex中获取的值:<label></label>
<br />
<button>值 - 1</button>
</div>
</template>

<script>
export default {
name: 'Son2Com'
}
</script>

<style lang="css" scoped>
.box {
border: 3px solid #ccc;
width: 400px;
padding: 10px;
margin: 20px;
}
h2 {
margin-top: 10px;
}
</style>

安装配置vueX

vuex

1.安装 vuex

安装vuex与vue-router类似,vuex是一个独立存在的插件,如果脚手架初始化没有选 vuex,就需要额外安装。

1
yarn add vuex@3 或者 npm i vuex@3

2.新建 store/index.js 专门存放 vuex

​ 为了维护项目目录的整洁,在src目录下新建一个store目录其下放置一个index.js文件。 (和 router/index.js 类似)

vuex

3.创建仓库 store/index.js

1
2
3
4
5
6
7
8
9
10
11
12
// 导入 vue
import Vue from 'vue'
// 导入 vuex
import Vuex from 'vuex'
// vuex也是vue的插件, 需要use一下, 进行插件的安装初始化
Vue.use(Vuex)

// 创建仓库 store
const store = new Vuex.Store()

// 导出仓库
export default store

4 在 main.js 中导入挂载到 Vue 实例上

1
2
3
4
5
6
7
8
9
10
import Vue from 'vue'
import App from './App.vue'
import store from './store'

Vue.config.productionTip = false

new Vue({
render: h => h(App),
store
}).$mount('#app')

测试:随意一个组件打印this.$store瞅瞅

1
2
3
created(){
console.log(this.$store);
},

13.2 state状态

State提供唯一的公共数据源,所有共享的数据都要统一放到Store中的State中存储。

  • 存储数据

state

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 导入 vue
import Vue from 'vue'
// 导入 vuex
import Vuex from 'vuex'
// vuex也是vue的插件, 需要use一下, 进行插件的安装初始化
Vue.use(Vuex)

// 创建仓库 store
const store = new Vuex.Store({
state:{
msg:"store中的数据"
}
})

// 导出仓库
export default store
  • 使用数据

store

直接使用

1
<h1>{{$store.state.msg}}</h1>

组件中使用

1
console.log(this.$store.state.msg);

js中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
import Vue from 'vue'
import App from './App.vue'
import store from './store'

Vue.config.productionTip = false

//js中使用store
console.log(store.state.msg);

new Vue({
render: h => h(App),
store
}).$mount('#app')

13.3辅助函数mapstate

mapState是辅助函数,帮助我们把store中的数据映射到 组件的计算属性中, 它属于一种方便的用法。 就是帮我们把state中的数据,定义在组件中的计算属性中

mapstate

  • 使用方式:

    map

    1.导入mapState (mapState是vuex中的一个函数)

    1
    import { mapState } from 'vuex'

    2,采用数组形式引入state属性

    1
    mapState(['msg']) 

    上面代码的最终得到的是 类似于

    1
    2
    3
    count () {
    return this.$store.state.msg
    }

    3.利用展开运算符将导出的状态映射给计算属性

    1
    2
    3
    computed: {
    ...mapState(['msg'])
    }
    1
    <div> state的数据:{{ msg }}</div>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    <template>
    <div id="app">
    <!-- 3. 使用 -->
    <h1>{{msg}}</h1>
    <h1>根组件</h1>
    <input type="text">
    <Son1></Son1>
    <hr>
    <Son2></Son2>
    </div>
    </template>

    <script>
    import Son1 from './components/Son1.vue'
    import Son2 from './components/Son2.vue'

    // 1. 引入
    import {mapState} from 'vuex';

    export default {

    computed:{
    // 2.隐射
    ...mapState(['msg'])
    },
    name: 'app',
    data: function () {
    return {

    }
    },
    created(){
    console.log(this.$store);
    console.log(this.$store.state.msg);
    },
    components: {
    Son1,
    Son2
    }
    }
    </script>

    <style>
    #app {
    width: 600px;
    margin: 20px auto;
    border: 3px solid #ccc;
    border-radius: 3px;
    padding: 10px;
    }
    </style>

13.4 mutations

vue中 不能直接在组件中修改仓库中的数据,类似于props接收过来的数据,能用,但是不能自己直接进行修改,如果要进行修改的话,需要将修改的要求提交到store 在store中进行修改。

错误写法(不会报错,现在貌似也能成功,但是不保证不出问题)

1
this.$store.state.msg++ (错误写法)

mutations的使用

mutations

  • 提供方法(用于修改state中的数据)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 创建仓库 store
const store = new Vuex.Store({
state:{
msg:11
},

// 提供修改数据的方法
mutations:{
// state 就是上方的state
addMsg(state){
state.msg++
}
}
})
  • 调用方法
1
2
3
4
5
6
7
8
9
10
created(){
// 提交方法,让这个方法去修改数据
this.$store.commit('addMsg')
},

methods:{
add(){
this.$store.commit('addMsg')
}
},

练习:

修改数据

13.5mutaiton 传参

传参

  • 接收参数
1
2
3
4
5
6
7
8
9
10
11
// 提供修改数据的方法
mutations:{
// state 就是上方的state
addMsg(state){
state.msg++
},
addNum(state,n){
state.msg+=n
}
}
})
  • 传参
1
2
3
4
5
methods:{
add(){
this.$store.commit('addNum',100)
}
},
  • 多个参数,包装成一个对象传递

mutations


案例:实时更新数据

实时更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<template>
<div id="app">
<h1>{{msg}}</h1>
<h1>根组件</h1>

<!-- 绑定 value 添加输入事件 或者change 事件-->
<input type="text" :value="msg" @input="handlechange">
<Son1></Son1>
<hr>
<Son2></Son2>

<button @click="add">++</button>
</div>
</template>

<script>
import Son1 from './components/Son1.vue'
import Son2 from './components/Son2.vue'

// 1. 引入
import {mapState} from 'vuex';

export default {

computed:{
// 2.隐射
...mapState(['msg'])
},
name: 'app',
data: function () {
return {

}
},
created(){

},

methods:{
add(){
this.$store.commit('addNum',100)
},

// 实时获取数据提交给mutation
handlechange(e){
alert(e.target.value)
this.$store.commit('changeMsg',+e.target.value)
}
},
components: {
Son1,
Son2
}
}
</script>

<style>
</style>

store.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 导入 vue
import Vue from 'vue'
// 导入 vuex
import Vuex from 'vuex'
// vuex也是vue的插件, 需要use一下, 进行插件的安装初始化
Vue.use(Vuex)

// 创建仓库 store
const store = new Vuex.Store({
state:{
msg:11
},

// 提供修改数据的方法
mutations:{
// state 就是上方的state
addMsg(state){
state.msg++
},
addNum(state,n){
state.msg+=n
},

changeMsg(state,n){
state.msg=n
}
}
})

// 导出仓库
export default store

13.6 mapMutations

mapMutations和mapState很像,它把位于mutations中的方法提取了出来,我们可以将它导入methods 方法中直接使用

map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
methods:{
add(){
this.$store.commit('addNum',100)
},

// 实时获取数据提交给mutation
handlechange(e){
alert(e.target.value)
this.$store.commit('changeMsg',+e.target.value)
},
// 使用解构赋值的方式,直接解构到methods中
...mapMutations(['addMsg','changeMsg'])


},

13.7 acitons

state是存放数据的,mutations是同步更新数据 (便于监测数据的变化, 更新视图等, 方便于调试工具查看变化),

actions则负责进行异步操作

actons

store.js

1
2
3
4
5
6
7
8
9
10
11
12
13
actions:{
// acions 中用于处理异步,不能直接操作state 如果要直接操作state,还是需要commit mutations

// content 是执行上下文 (先理解为 store仓库),n是携带的参数()可以是别的名字
setAsyncCount(context,n){
// 一般都是发送网络请求, 这里发请求,拿到数据了以后提交给 state
setTimeout(() => {

// 提交mutations
context.commit('changeMsg',n)
}, 1000);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 导入 vue
import Vue from 'vue'
// 导入 vuex
import Vuex from 'vuex'
// vuex也是vue的插件, 需要use一下, 进行插件的安装初始化
Vue.use(Vuex)

// 创建仓库 store
const store = new Vuex.Store({
state:{
msg:11
},

// 提供修改数据的方法
mutations:{
// state 就是上方的state
addMsg(state){
state.msg++
},
addNum(state,n){
state.msg+=n
},

changeMsg(state,n){
state.msg=n
}
},

actions:{
// acions 中用于处理异步,不能直接操作state 如果要直接操作state,还是需要commit mutations

// content 是执行上下文 (先理解为 store仓库),n是携带的参数()可以是别的名字
setAsyncCount(context,n){
// 一般都是发送网络请求, 这里发请求,拿到数据了以后提交给 state
setTimeout(() => {

// 提交mutations
context.commit('changeMsg',n)
}, 1000);
}
}
})

// 导出仓库
export default store

调用actions

xxx.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  <div class="box">
<h2>Son1 子组件</h2>
从vuex中获取的值: <label></label>
<br>
<button @click="handlechange">一秒后+100</button>
</div>
</template>

<script>
export default {
name: 'Son1Com',

methods:{
handlechange(){
// 调用acitons 并传参
this.$store.dispatch('setAsyncCount',1000)
}
}
}
</script>

13.8 mapActions

mapActions 是把位于 actions中的方法提取了出来,映射到组件methods中,直接通过this.acitons名来调用acitons

mapactions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<div class="box">
<h2>Son2 子组件</h2>
从vuex中获取的值:<label></label>
<br />
<button @click="handleChangeValue">一秒后改1000</button>
</div>
</template>

<script>
import {mapActions} from 'vuex';
export default {
name: 'Son2Com',
methods:{
...mapActions(['setAsyncCount']),
handleChangeValue(){
this.setAsyncCount(1000)
}

}
}
</script>

13.9 getters

除了state之外,有时我们还需要从state中筛选出符合条件的一些数据,这些数据是依赖state的,此时会用到getters,

简单来说就是属于 store的计算属性

例如,state中定义了list,为1-10的数组

1
2
3
state: {
list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}

组件中,需要显示所有大于5的数据,正常的方式,是需要list在组件中进行再一步的处理,但是getters可以帮助我们实现它

getters

1.定义getters

1
2
3
4
5
getters: {
// getters函数的第一个参数是 state
// 必须要有返回值
filterList: state => state.list.filter(item => item > 5)
}

2.使用getters

  • 原始方式-$store
1
<div>{{ $store.getters.filterList }}</div>
  • 辅助函数 - mapGetters
1
2
3
computed: {
...mapGetters(['filterList'])
}
1
<div>{{ filterList }}</div>

完整代码

store.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// 导入 vue
import Vue from 'vue'
// 导入 vuex
import Vuex from 'vuex'
// vuex也是vue的插件, 需要use一下, 进行插件的安装初始化
Vue.use(Vuex)

// 创建仓库 store
const store = new Vuex.Store({
state:{
msg:11,
list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
},

// 提供修改数据的方法
mutations:{
// state 就是上方的state
addMsg(state){
state.msg++
},
addNum(state,n){
state.msg+=n
},

changeMsg(state,n){
state.msg=n
}
},

actions:{
// acions 中用于处理异步,不能直接操作state 如果要直接操作state,还是需要commit mutations

// content 是执行上下文 (先理解为 store仓库),n是携带的参数()可以是别的名字
setAsyncCount(context,n){
// 一般都是发送网络请求, 这里发请求,拿到数据了以后提交给 state
setTimeout(() => {

// 提交mutations
context.commit('changeMsg',n)
}, 1000);
}
},

getters:{

// 定义一个 筛选出大于5的函数
filterBigfive(state){
return state.list.filter(item=>item>5)
}
}
})

// 导出仓库
export default store

xxx.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<template>
<div class="box">
<!-- 普通使用 -->
{{$store.getters.filterBigfive}}

<!-- 使用mapgetters 后 -->
{{filterBigfive}}
<button @click="handlechange">一秒后+100</button>
</div>
</template>

<script>
import {mapGetters} from 'vuex';
export default {
name: 'Son1Com',

methods:{
handlechange(){
// 调用acitons 并传参
this.$store.dispatch('setAsyncCount',1000)
}
},

computed:{
...mapGetters(['filterBigfive'])
}
}
</script>

<style lang="css" scoped>
.box{
border: 3px solid #ccc;
width: 400px;
padding: 10px;
margin: 20px;
}
h2 {
margin-top: 10px;
}
</style>

小结

小结

13.10 分模块module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

所以就将vuex 中的state 拆分成多个模块,每一个模块都有对应 mutations action getter,最后将所有的模块集中在一起暴露

user

定义两个模块 usersetting

user中管理用户的信息状态 userInfo modules/user.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const state = {
userInfo: {
name: 'zs',
age: 18
}
}

const mutations = {}

const actions = {}

const getters = {}

export default {
state,
mutations,
actions,
getters
}

setting中管理项目应用的 主题色 theme,描述 desc, modules/setting.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const state = {
theme: 'dark'
desc: '描述真呀真不错'
}

const mutations = {}

const actions = {}

const getters = {}

export default {
state,
mutations,
actions,
getters
}

store/index.js文件中的modules配置项中,注册这两个模块

1
2
3
4
5
6
7
8
9
import user from './modules/user'
import setting from './modules/setting'

const store = new Vuex.Store({
modules:{
user,
setting
}
})

使用模块中的数据, 可以直接通过模块名访问 $store.state.模块名.xxx => $store.state.setting.desc

1
{{$store.state.user.userName}}

尽管已经分模块了,但其实子模块的状态,还是会挂到根级别的 state 中,属性名就是模块名

img

13.11获取模块中的数据

尽管已经分模块了,但其实子模块的状态,还是会挂到根级别的 state 中,属性名就是模块名

img

如果想要通过mapstate获取模块中的数据,就需要学一些新的语法

模块数据

  • 直接映射(拿到的是一整个大对象)

    1
    2
    3
    4
    5
    6
      computed:{
    // 2.隐射
    ...mapState(['msg']),

    ...mapState(['user'])
    },

    使用

    1
    {{user.userName}}
  • 子模块的映射 mapMutations(‘模块名’, [‘xxx’]) - 需要开启命名空间

    user.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    const state = {

    userName:"我是user"
    };
    const mutations = {};
    const actions = {};
    const getters = {};

    export default {

    // 开启命名空间
    namespaced: true,
    state,
    mutations,
    actions,
    getters,
    };

    映射

    1
    2
    // 访问user 模块下面的 userName
    ...mapState('user',['userName'])

    使用

    1
    {{userName}}

    getters

    访问模块中的getters中的数据

    1
    2
    3
    4
    5
    6
    7
    const getters = {

    // state 指的是当前模块的state
    add(state){
    return state.userName+"我是getters拼接上的内容"
    }
    };

    直接隐射访问(比较特殊)

    getters 的访问比较奇怪,它的属性名非常的特殊

    1
    {{$store.getters['user/add']}}

    通过mapGetters访问射 mapGetters(‘模块名’, [‘xxx’])

    1
    ...mapGetters('user',['add'])
    1
    {{ add }}

    访问模块中的mutations

    mutations

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    const state = {

    userName:"我是user"
    };
    const mutations = {
    changeUserName(state,n){
    state.userName=n
    }

    };
    const actions = {};
    const getters = {

    // state 指的是当前模块的state
    add(state){
    return state.userName+"我是getters拼接上的内容"
    }
    };

    export default {

    // 开启命名空间
    namespaced: true,
    state,
    mutations,
    actions,
    getters,
    };

    使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    methods:{

    // 解构到methds中
    ...mapMutations('user',['changeUserName']),

    changeName(){
    this.changeUserName("我是新的名字")
    }

    },

    使用子模块中的actions

    actions

    1
    2
    3
    4
    5
    6
    7
    8
    const actions = {

    time(context,newValue){
    setTimeout(() => {
    context.commit('changeUserName',newValue)
    }, 100);
    }
    };

    结构赋值给methods 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    methods:{

    // 解构到methds中
    ...mapMutations('user',['changeUserName']),

    // 结构
    ...mapActions('user',['time']),


    // 使用
    changeName(){
    this.time("actions 改的新名字")
    }

    },

13.12 购物车案例

购物车案例

① 请求动态渲染购物车,数据存 vuex

② 数字框控件 修改数据

③ 动态计算 总价和总数量

  • 创建一个模块仓库
    vuexmodel

moduls/carts.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export default{
// 开启命名空间
namespaced: true,
state(){
return{

// 购物车数据
list:[]
}
},
mutations:{

},

actions:{},

getters:{},
}

store/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Vue from 'vue'
import Vuex from 'vuex'

// 引入
import cart from "@/store/modules/carts.js"

Vue.use(Vuex)

export default new Vuex.Store({
state: {
},
getters: {
},
mutations: {
},
actions: {
},

// 挂载
modules: {
cart
}
})


前端mock 一些数据

json sever 官网

mock

  1. 安装全局工具 json-server (全局工具仅需要安装一次)
1
yarn global add json-server 或 npm i json-server  -g
  1. 代码根目录新建一个 db 目录

    josn

    myapp/db/index.json

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    {
    "cart": [
    {
    "id": 100001,
    "name": "低帮城市休闲户外鞋天然牛皮COOLMAX纤维",
    "price": 128,
    "count": 1,
    "thumb": "https://yanxuan-item.nosdn.127.net/3a56a913e687dc2279473e325ea770a9.jpg"
    },
    {
    "id": 100002,
    "name": "网易味央黑猪猪肘330g*1袋",
    "price": 39,
    "count": 10,
    "thumb": "https://yanxuan-item.nosdn.127.net/d0a56474a8443cf6abd5afc539aa2476.jpg"
    },
    {
    "id": 100003,
    "name": "KENROLL男女简洁多彩一片式室外拖",
    "price": 128,
    "count": 2,
    "thumb": "https://yanxuan-item.nosdn.127.net/eb1556fcc59e2fd98d9b0bc201dd4409.jpg"
    },
    {
    "id": 100004,
    "name": "云音乐定制IN系列intar民谣木吉他",
    "price": 589,
    "count": 1,
    "thumb": "https://yanxuan-item.nosdn.127.net/4d825431a3587edb63cb165166f8fc76.jpg"
    }
    ]
    }
  2. 将资料 index.json 移入 db 目录

  3. 进入 db 目录,执行命令,启动后端接口服务 (使用–watch 参数 可以实时监听 json 文件的修改)

    1
    2
    cd db
    json-server --watch index.json
  4. 访问地址拿到数据

    1
    http://localhost:3000/cart

购物车

carts.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import axios from 'axios';
export default{
// 开启命名空间
namespaced: true,
state(){
return{

// 购物车数据
list:[]
}
},
mutations:{
updata(state,value){
state.list=value
}
},

actions:{
getList(context){
axios.get('http://localhost:3000/cart')
.then(response => {
console.log(response);

context.commit('updata',response)
})
.catch(error => {
console.error('Error:', error);
});
}

},

getters:{},
}

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Vue from 'vue'
import Vuex from 'vuex'

// 引入
import cart from "@/store/modules/carts.js"

Vue.use(Vuex)

export default new Vuex.Store({
state: {
},
getters: {
},
mutations: {
},
actions: {
},

// 挂载
modules: {
cart
}
})

xxx.vue使用测试

1
2
3
4
5
6
7
created(){
this.getList()
},

methods:{
...mapActions('cart',["getList"])
}

请求数据,渲染页面

APP.vue

1
2
3
4
5
6
7
8
9

methods:{
...mapActions('cart',["getList"])
},

created(){
// 请求数据
this.getList()
},

APP.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<template>
<div class="app-container">
<!-- Header 区域 -->
<cart-header></cart-header>

<!-- 商品 Item 项组件 -->
<cart-item v-for="(item) in list" :key="item.id" :items="item"> </cart-item>
<!-- Foote 区域 -->
<cart-footer></cart-footer>
</div>
</template>

<script>
import CartHeader from '@/components/cart-header.vue'
import CartFooter from '@/components/cart-footer.vue'
import CartItem from '@/components/cart-item.vue'

import { mapActions,mapState } from 'vuex';

export default {
name: 'App',
components: {
CartHeader,
CartFooter,
CartItem
},

methods:{
...mapActions('cart',["getList"])
},

created(){
// 请求数据
this.getList()
},

computed:{
...mapState('cart',['list'])
}


}
</script>

<style lang="less" scoped>
.app-container {
padding: 50px 0;
font-size: 14px;
}
</style>

cartitem.vue 接收数据 渲染内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<template>
<div class="goods-container">
<!-- 左侧图片区域 -->
<div class="left">
<img :src="items.thumb" alt="">
</div>
<!-- 右侧商品区域 -->
<div class="right">
<!-- 标题 -->
<div class="title">{{ items.name }}</div>
<div class="info">
<!-- 单价 -->
<span class="price">{{ items.price }}</span>
<div class="btns">
<!-- 按钮区域 -->
<button class="btn btn-light">-</button>
<span class="count">{{ items.count }}</span>
<button class="btn btn-light">+</button>
</div>
</div>
</div>
</div>
</template>

<script>
export default {
name: 'CartItem',
methods: {

},
props:{
items:{
type:Array
}
}
}
</script>

<style lang="less" scoped>
.goods-container {
display: flex;
padding: 10px;
+ .goods-container {
border-top: 1px solid #f8f8f8;
}
.left {
.avatar {
width: 100px;
height: 100px;
}
margin-right: 10px;
}
.right {
display: flex;
flex-direction: column;
justify-content: space-between;
flex: 1;
.title {
font-weight: bold;
}
.info {
display: flex;
justify-content: space-between;
align-items: center;
.price {
color: red;
font-weight: bold;
}
.btns {
.count {
display: inline-block;
width: 30px;
text-align: center;
}
}
}
}
}

.custom-control-label::before,
.custom-control-label::after {
top: 3.6rem;
}
</style>