admin管理员组

文章数量:1531792

什么是VUE?

vue是一个用于构建用户界面的渐进式框架

vue是一个数据驱动的渐进式框架

一.第一个vue实例

1.1步骤

1.1.1 构建用户界面(前端页面)—>创建VUE实例,初始化渲染.

  1. 准备容器,即div标签容器或者其他标签作为容器
  2. 导包,导包后,在全局环境下,就有了Vue构造函数,即可new Vue()
  3. 创建vue实例 new Vue()
  4. 指定配置项 --> 渲染数据 

      ①el指定挂载点,即vue实例管理哪一个容器

      ②data提供数据

以上两个配置项,实现了在哪一个容器展示数据

<!-- 容器  -->
<div id="app">
    {{msg}}
</div>

<!-- 本地导入 -->
<script src="../day01/files/vue.js" ></script>
<!-- 在线导入 -->
<!-- <script src="https://cdn.jsdelivr/npm/vue@2.7.16/dist/vue.js"></script> -->
<script>
    const vue =new Vue({
        el : '#app',
        data : {
            msg : '明天会更好!'
        }
    })
</script>

2.插值表达式 {{ 表达式 }}

  • 插值表达式是一种Vue的模板语法
  • 作用:利用表达式进行插值,渲染到页面中
  • 注意点
  • ①插值语法中使用到的数据在data中必须存在
  • ②插值语法中支持的是表达式,(表达式是必定求得结果) 而不是语句,即不能在插值表达式中使用  if  等语句
  • ③不能在标签属性中使用插值表达式

2.1插值语法报错信息

1.当使用的数据qq在data中不存在时,控制台报错

Property or method "qq" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. 

属性或方法“qq”未在实例上定义,但在渲染过程中被引用。通过初始化该属性,确保该属性是被动的,无论是在数据选项中,还是对于基于类的组件

2.当在插值表达式中使用了if语句,页面无法渲染,控制台报错

Error compiling template:

avoid using JavaScript keyword as property name: "if"

编译模板时出错:

避免使用JavaScript关键字作为属性名称:“if”

3.当插值表达式使用在标签属性上时,控制台报错

Error compiling template:

title="{{msg}}": Interpolation inside attributes has been removed. Use v-bind or the colon shorthand instead. For example, instead of <div id="{{ val }}">, use <div :id="val">.

编译模板时出错:

title=“{{msg}}”:属性内部的插值已被删除。请改用v-bind或冒号简写。例如,使用<div:id=“val”>而不是<div id=“{{val}}”>。

2.2 vue的核心属性:响应式 :当数据发生变化的时候,视图会自动更新

如何访问或者修改data中的数据呢?

data中的数据最终都会添加到vue实例上的:vue实例就是 const vue = new Vue({})

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

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

浏览器控制台数据访问和修改

2.3vue调试工具的安装

2.4 Vue指令

vue会根据不同的指令,针对标签实现不同的功能

指令:带有v-前缀的特殊标签属性

  • v-html : 设置元素的innerHTML,解析标签 语法:v-html = “表达式”
<!-- 容器  -->
<div id="app">
    <div v-html="msg"></div>
</div>

<!-- 本地导入 -->
<script src="../day01/files/vue.js" ></script>
<!-- 在线导入 -->
<!-- <script src="https://cdn.jsdelivr/npm/vue@2.7.16/dist/vue.js"></script> -->
<script>
    const vue =new Vue({
        el : '#app',
        data : {
            msg : '<h1>明天会更好!</h1>',
            count : '100'
        }
    })
</script>

1. v-show和v-if

代码实现
<!-- 容器  -->
<div id="app">
    <div v-show="tag">这式一个v-show控制显示的盒子</div>
    <div v-if="tag">这式一个v-if控制显示的盒子</div>
</div>

<!-- 本地导入 -->
<script src="../day01/files/vue.js" ></script>
<!-- 在线导入 -->
<!-- <script src="https://cdn.jsdelivr/npm/vue@2.7.16/dist/vue.js"></script> -->
<script>
    const vue =new Vue({
        el : '#app',
        data : {
            msg : '<h1>明天会更好!</h1>',
            count : '100',
            tag : true
        }
    })
</script>

效果:表达式值为flase

效果:表达式值为true

2.v-else 和v-if-else

  • v-else和v-else-if都是辅助v-if进行判断渲染的,v-if必须存在
  • v-else是没有表达式的,v-else-if才有表达式

<!-- 容器  -->
<div id="app">
    <div v-if="good">我会变得更好 </div>
    <div >
        <li v-if="point > 90">成绩A</li>
        <li v-else-if="point > 80">成绩B</li>
        <li v-else-if="point >70">成绩C</li>
        <li v-else>成绩D</li>
    </div>
</div>

<!-- 本地导入 -->
<script src="../day01/files/vue.js" ></script>
<!-- 在线导入 -->
<!-- <script src="https://cdn.jsdelivr/npm/vue@2.7.16/dist/vue.js"></script> -->
<script>
    const vue =new Vue({
        el : '#app',
        data : {
            // 如果值为null,默认为flase
            good : '',
            point : '75'
        }
    })
</script>
当在使用v-else和v-else-if时,没有v-if会报错

 Error compiling template:

v-else-if="point > 80" used on element <li> without corresponding v-if.

3  |      <div>
4  |          <!-- <li v-if="point > 90">成绩A</li> -->
5  |          <li v-else-if="point > 80">成绩B</li>
   |              ^^^^^^^^^^^^^^^^^^^^^^
6  |          <li v-else-if="point >70">成绩C</li>
7  |          <li v-else="">成绩D</li>

编译模板时出错:

在元素<li>上使用的v-else-if=“point>80”,没有相应的v-if。

编译报错的时候,可以去检查语法是否正确

2.v-on:事件名 = "内联语句”

代码
<!-- 容器  -->
<div id="app">
    <button v-on:click="count--">-</button>
    <span>{{count}}</span>
    <button v-on:click="count++">+</button>
</div>

<!-- 本地导入 -->
<script src="../day01/files/vue.js" ></script>
<!-- 在线导入 -->
<!-- <script src="https://cdn.jsdelivr/npm/vue@2.7.16/dist/vue.js"></script> -->
<script>
    const vue =new Vue({
        el : '#app',
        data : {
            count : '100',

        }
    })
</script>

如果少了事件名,会报错

 v-on without argument expects an Object value

没有参数的v-on需要Object值

v-on:事件名 = "methods配置项中对应于的方法名"
代码
<!-- 容器  -->
<div id="app">
    <button v-on:click="show">控制显示与隐藏</button>
    <div v-show="tag">显示</div>
</div>

<!-- 本地导入 -->
<script src="../day01/files/vue.js" ></script>
<script>
    const vue =new Vue({
        el : '#app',
        data : {
            tag : true,
        },
        methods : {
            show(){
                // vue.tag = !vue.tag
                this.tag = !this.tag
            }
        }
    })
</script>

重要知识点

  • methods配置项是无法通过属性名直接使用data中的数据的,因为data配置项和methods配置项是平级的,作用域是局部的
  • methods中该如何使用data配置项中的数据的?data配置是挂载在vue实例中的,可以通过Vue实例.属性名来使用,即vue.tag =!vue.tag
  • methods配置项中 this 指向的是 Vue实例的 ,即可通过 this.属性名调用data中的属性 ,即 this.tag = ! this.tag

当没有通过实例去调用data中的数据时,会报错

Error in v-on handler: "ReferenceError: tag is not defined"

v-on处理程序中出错:“ReferenceError:标记未定义”

v-on 调用传参   格式 : @click(参数1,参数2,其他参数)
<!-- 容器  -->
<div id="app">

    <div style="width: 500px;height: 250px;margin: 0 auto;background-color: pink;text-align: center; ">
    小闭售卖机
    <br>
    <button @click="buy(10)">牛奶10元</button>
    <br>
    <button @click="buy(7)">小米粥7元</button>
    <br>
    <span>余额:{{account}}</span>
    </div>

</div>

<!-- 本地导入 -->
<script src="../day01/files/vue.js" ></script>
<script>
    const vue =new Vue({
        el : '#app',
        data : {
            account : "100",
        },
        methods : {
            buy(price){
                this.account = this.account - price
            }
        }
    })
</script>

当在methods配置中,定义了一个函数,函数在调用处传递了实际参数,但在函数中却没有使用形式参数接收,却在函数体中使用,会报错

Error in v-on handler: "ReferenceError: price is not defined"

v-on处理程序中的错误:“ReferenceError:未定义价格”

3. v-bind

代码

<!-- 容器  -->
<div id="app">
    <button v-show="index > 0" v-on:click="index--" style="width: 200px;height: 200px;">上一页</button>
    <br>
    <div style="width: 500px;height: 500px;">
        <img v-bind:src="list[index]"></img>
    </div>
    <br>
    <button v-show="index < list.length-1" v-on:click="index++" style="width: 200px;height: 200px;">下一页</button>

</div>

<!-- 本地导入 -->
<script src="../day01/files/vue.js" ></script>
<script>
    const vue =new Vue({
        el : '#app',
        data : {
            index : 0,

            list : [
                'img/001.webp',
                'img/002.webp',
                'img/003.webp',
                'img/004.webp',
            ]

        },
    })
</script>

4. v-for

代码

<!-- 容器  -->
<div id="app">
    <ul>
        <li v-for="(item,index) in list">
            {{item}}
        </li>
    </ul>

</div>

<!-- 本地导入 -->
<script src="../day01/files/vue.js" ></script>
<script>
    const vue =new Vue({
        el : '#app',
        data : {
            index : 0,

            list : [
                '香蕉',
                '菠萝',
                '苹果'
            ]

        },
    })
</script>

案例:

<!-- 容器  -->
<div id="app">
    <ul>
        <li v-for="(item,index) in list " :key="item.id">
            <span>{{item.name}}</span>
            <span> <button v-on:click="del(item.id)">删除</button></span>
        </li>
    </ul>

</div>

<!-- 本地导入 -->
<script src="../day01/files/vue.js" ></script>
<script>
    const vue =new Vue({
        el : '#app',
        data : {
            list : [
                {id:1,name:'《红楼梦》',author:'曹雪芹'},
                {id:2,name:'《西游记》',author:'吴承恩'},
                {id:3,name:'《水浒传》',author:'施耐盐'},
                {id:4,name:'《三国演义》',author:'罗贯中'},
            ]

        },
        methods:{
            del(id){
                /* function(item){
                    return item.id !==id
                }
                在前端JavaScript中,数组通常有一个 filter() 方法,
                它会创建满足条件的所有元素的新数组
                */
                this.list = this.list.filter(item => item.id !== id)
            }
        }
    })
</script>

v-for中的key

有key和没key的区别,可以通过css样式来观察

没有key时,删除红楼梦,发现样式还保留着(底层并没有删除这个li)

有key时,删除红楼梦,样式跟着被删除

5 . v-model

变量在data配置项中定义

代码

<!-- 容器  -->
<div id="app">
    <form action="#">
        <span> 账户:<input v-model="username" type="text" /></span><br>
        <span> 密码:<input v-model="password" type="password"/></span><br>
        <span><button>登录</button></span>
        <span><button @click="reset">重置</button></span>
    </form>
</div>

<!-- 本地导入 -->
<script src="../day01/files/vue.js" ></script>
<script>
    const vue =new Vue({
        el : '#app',
        data : {
            username : '',
            password : '',
        },
        methods:{
            reset(){
                this.username='',
                this.password=''
            }
        }
    })
</script>

6.指令修饰符

vue的指令修饰符本质是对事件对象做了封装

当指令修饰符使用错误时,如 正确语法 v-on:click.prevent = "函数名"   错误的用成了  v-on.prevent:click="函数名"  控制台报错

v-on without argument does not support modifiers

没有参数的v-on不支持修饰符

7.v-bind操作class

其中以数组形式绑定类名,类名需要加上单引号  v-bind:class ="['类名1','类名1']"

代码

<!-- 容器  -->
<div id="app">

    <div v-bind:class="{box:true,yelloCollor:true}">
        <span v-bind:class="{yelloCollor:true,fontSize:true}">这是v-bind以对象形式操作CSS样式</span>
    </div>
    <div v-bind:class="['boxArr','boxArrColor']">
        <span v-bind:class="['fontSize','yelloCollor']">这是v-bind以数组形式操作CSS样式</span>
    </div>
</div>

<!-- 本地导入 -->
<script src="../day01/files/vue.js" ></script>
<script>
    const vue =new Vue({
        el : '#app',
        data : {
            username : '',
            password : '',
        },
        methods:{
            reset(){
                this.username='',
                this.password=''
            }
        }
    })
</script>

<style>
.yelloCollor {
    background-color: yellow;
}
.fontSize {
    font-size: 25px;
}
.box {
    width: 700px;
    height: 800px;
}
.boxArr{
    width: 500px;
    height: 600px;
}
.boxArrColor{
    background-color: aqua;
}

</style>

案列:京东秒杀tab导航高亮

案例:代码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }
    ul {
      display: flex;
      border-bottom: 2px solid #e01222;
      padding: 0 10px;
    }
    li {
      width: 100px;
      height: 50px;
      line-height: 50px;
      list-style: none;
      text-align: center;
    }
    li a {
      display: block;
      text-decoration: none;
      font-weight: bold;
      color: #333333;
    }
    li a.active {
      background-color: #e01222;
      color: #fff;
    }

  </style>
</head>
<body>

  <div id="app">
    <ul>
      <li v-for="(item,index) in list" :key="item.id" v-on:click="activeIndex = index">
        <a v-bind:class="{active:index===activeIndex}" href="#">{{item.name}}</a>
      </li>
    </ul>
  </div>
  <script src="./vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        activeIndex : 0,
        list: [
          { id: 1, name: '京东秒杀' },
          { id: 2, name: '每日特价' },
          { id: 3, name: '品类秒杀' }
        ]

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

8.v-bind操作style

v-bind绑定style时,css属性名和css属性值要遵守js语法规范

  •  background-color    要写成    ' background-color'    或者     backgroundColor

  • CSS属性值要加上英文单引号

  • v-bind:style="{ ' background-color' : 'red' }"

案例 代码

<!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">
    <div class="progress">
      <div class="inner" v-bind:style="{width:percent+'%',}">
        <span>{{percent}}%</span>
      </div>
    </div>
    <button  @click="percent=25">设置25%</button>
    <button  @click="percent=50">设置50%</button>
    <button  @click="percent=75">设置75%</button>
    <button  @click="percent=100">设置100%</button>
  </div>
  <script src="https://cdn.jsdelivr/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        percent : 0,
      }
    })
  </script>
</body>
<style>
  .progress {
    height: 25px;
    width: 400px;
    border-radius: 15px;
    background-color: #272425;
    border: 3px solid #272425;
    box-sizing: border-box;
    margin-bottom: 30px;
  }
  .inner {
    width: 50%;
    height: 20px;
    border-radius: 10px;
    text-align: right;
    position: relative;
    background-color: #409eff;
    background-size: 20px 20px;
    box-sizing: border-box;
    transition: all 1s;
  }
  .inner span {
    position: absolute;
    right: -20px;
    bottom: -25px;
  }
</style>

</html>

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

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    textarea {
      display: block;
      width: 240px;
      height: 100px;
      margin: 10px 0;
    }
  </style>
</head>
<body>

  <div id="app">
    <h3>小黑学习网</h3>

    姓名:
      <input type="text" v-model="username"> 
      <br><br>

    是否单身:
      <input type="checkbox" v-model="isSingle"> 
      <br><br>

    <!-- 
      前置理解:
        1. name:  给单选框加上 name 属性 可以分组 → 同一组互相会互斥
        2. value: 给单选框加上 value 属性,用于提交给后台的数据
      结合 Vue 使用 → v-model
    -->
    性别: 
      <input v-model="gender" type="radio" name="gender" value="1">男
      <input v-model="gender" type="radio" name="gender" value="2">女
      <br><br>

    <!-- 
      前置理解:
        1. option 需要设置 value 值,提交给后台
        2. select 的 value 值,关联了选中的 option 的 value 值
      结合 Vue 使用 → v-model
    -->
    所在城市:
      <select v-model="cityId">
        <option value="1">北京</option>
        <option value="2">上海</option>
        <option value="3">成都</option>
        <option value="4">南京</option>
      </select>
      <br><br>

    自我描述:
      <textarea v-model="desc"></textarea> 

    <button>立即注册</button>
  </div>
  <script src="https://cdn.jsdelivr/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        username:'',
        isSingle:true,
        gender:'1',
        cityId:'1',
        desc:'',

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

10. 计算属性

  • 基于现有的数据(属性),计算出新的属性,如果依赖的数据发生变化,计算出来的新属性会重新计算   如下:已有属性数据 1,2,5 计算出新的属性数据 8
  • 计算属性的声明实在computed配置项中,一个计算属性对应一个函数,计算设计到逻辑处理,逻辑处理一般需要函数
  • 计算属性的用法与普通属性的用法一样  {{ 计算属性名 }}
  • 计算属性名本质上是一个属性,使用时(注意不是调用)不需要(),计算属性名就是computed配置项中的函数名

代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    table {
      border: 1px solid #000;
      text-align: center;
      width: 240px;
    }
    th,td {
      border: 1px solid #000;
    }
    h3 {
      position: relative;
    }
  </style>
</head>
<body>

  <div id="app">
    <h3>小黑的礼物清单</h3>
    <table>
      <tr>
        <th>名字</th>
        <th>数量</th>
      </tr>
      <tr v-for="(item, index) in list" :key="item.id">
        <td>{{ item.name }}</td>
        <td>{{ item.num }}个</td>
      </tr>
    </table>

    <!-- 目标:统计求和,求得礼物总数 -->
    <p>礼物总数:{{getSum}}个</p>
  </div>
  <script src="https://cdn.jsdelivr/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        // 现有的数据
        list: [
          { id: 1, name: '篮球', num: 1 },
          { id: 2, name: '玩具', num: 2 },
          { id: 3, name: '铅笔', num: 5 },
        ]
      },
      computed:{
        getSum(){
         let total = this.list.reduce((sum,item) =>sum+item.num ,0)
         return total;
        }
      }
    })
  </script>
</body>
</html>

计算属性与方法的区别

计算属性的完整写法

  • get()  获取计算属性
  • set()   设置计算属性的值,影响到基于现有数据

代码

<!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  v-model="firstname" type="text">+
    名:<input  v-model="lastname" type="text">={{fullName}}
    <button v-on:click="edit">修改姓名</button>
  </div>
  <script src="https://cdn.jsdelivr/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        firstname:'皇甫',
        lastname :'圣华'
      },
      computed: {
        fullName:{
          get(){
            return this.firstname + this.lastname
          },
          set(value){
            this.firstname = value.slice(0,1)
            this.lastname = value.slice(1)
          }
        }

      },
      methods: {
        edit(){
          this.fullName = '刘德华'
        }
      }
    })
  </script>
</body>
</html>
  • 当给计算属性重新赋值时,这个值会默认传到set(value)中使用value接收值
  • 如  :  给计算属性重新赋值  =>   this.fullName = '刘德华'  ,  而在set(value)中的value的值就是刘德华

问题:当给计算属性设置值的时候,并没有set(),控制台报错

 Computed property "fullName" was assigned to but it has no setter.

已将计算属性“fullName”分配给,但它没有setter。

成绩案例:

  • filter:详解 http://t.csdnimg/61oOH
  • reduce :详解 http://t.csdnimg/xY8Hk
<!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" v-bind:key="item.id">
              <td>{{index + 1}}</td>
              <td>{{item.subject}}</td>
              <td v-bind:class="{red : item.score < 60 }">{{item.score}}</td>
              <td><a v-on:click.prevent="del(item.id)" href="https://www.baidu/index.htm">删除</a></td>
            </tr>
          </tbody>
          <tbody v-else>
            <tr>
              <td colspan="5">
                <span class="none">暂无数据</span>
              </td>
            </tr>
          </tbody>

          <tfoot>
            <tr>
              <td colspan="5">
                <span>总分:{{totalScore}}</span>
                <span style="margin-left: 50px">平均分:{{averageScore}}</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" v-on: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){
              alert("科目不能为空")
              return
            }
            if(typeof this.score !== 'number'){
              alert("请正确输入成绩")
              return
            }
             this.list.unshift({
              id : +new Date(),
              subject : this.subject,
              score : this.score,
            })
            
            this.subject = ''
            this.score = ''

          }
        },
        computed : {
          totalScore(){
            // reduce((sum阶段性求和结果,item每一项) =>每次算完的结果, 起始值)
            return this.list.reduce((sum,item) =>sum+item.score,0)
          },
          averageScore(){
            // toFixed 保留两位小数
            return (this.totalScore / this.list.length).toFixed(2)
          }
        }
      })
    </script>
  </body>
</html>

11.watch 侦听器(监视器)

  • 当被监视的属性或者数据发生变化时,自动执行一些义务逻辑的操作


代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
        font-size: 18px;
      }
      #app {
        padding: 10px 20px;
      }
      .query {
        margin: 10px 0;
      }
      .box {
        display: flex;
      }
      textarea {
        width: 300px;
        height: 160px;
        font-size: 18px;
        border: 1px solid #dedede;
        outline: none;
        resize: none;
        padding: 10px;
      }
      textarea:hover {
        border: 1px solid #1589f5;
      }
      .transbox {
        width: 300px;
        height: 160px;
        background-color: #f0f0f0;
        padding: 10px;
        border: none;
      }
      .tip-box {
        width: 300px;
        height: 25px;
        line-height: 25px;
        display: flex;
      }
      .tip-box span {
        flex: 1;
        text-align: center;
      }
      .query span {
        font-size: 18px;
      }

      .input-wrap {
        position: relative;
      }
      .input-wrap span {
        position: absolute;
        right: 15px;
        bottom: 15px;
        font-size: 12px;
      }
      .input-wrap i {
        font-size: 20px;
        font-style: normal;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <!-- 条件选择框 -->
      <div class="query">
        <span>翻译成的语言:</span>
        <select>
          <option value="italy">意大利</option>
          <option value="english">英语</option>
          <option value="german">德语</option>
        </select>
      </div>

      <!-- 翻译框 -->
      <div class="box">
        <div class="input-wrap">
          <textarea v-model="words"></textarea>
          <textarea v-model="obj.wordsObj"></textarea>
          <span><i>⌨️</i>文档翻译</span>
        </div>
        <div class="output-wrap">
          <div class="transbox">mela</div>
        </div>
      </div>
    </div>
    <script src="https://cdn.jsdelivr/npm/vue@2/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr/npm/axios/dist/axios.min.js"></script>
    <script>
      // 接口地址:https://applet-base-api-t.itheima/api/translate
      // 请求方式:get
      // 请求参数:
      // (1)words:需要被翻译的文本(必传)
      // (2)lang: 需要被翻译成的语言(可选)默认值-意大利
      // -----------------------------------------------
      
      const app = new Vue({
        el: '#app',
        data: {
          words: '',
          obj : {
            //需要有引号包裹
            'obj.wordsObj' : ''
          }
        },
        watch : {
          //data属性的监视,通常只需要newValue的值
          words(newValue,oldValue){
            console.log(newValue+" 前面的是新值,后面的是旧值" + oldValue);
          },
          //对象属性的监视
          'obj.wordsObj'(newValue,oldValue){
            console.log(newValue+" 前面的是新值,后面的是旧值" + oldValue);
          }
        },
        // 具体讲解:(1) watch语法 (2) 具体业务实现
      })
    </script>
  </body>
</html>

watch监视属性的完整写法

  • 当需要去监视一整个对象的属性时,以  被监视属性名(新值,旧值){}  实现监视效果,显然需要写多个属性监视器,十分不便
  • 现在通过监视一个对象来达到监视对象全部属性的效果,只需要配置两个配置项 ,注意写法: watch : {被监视的对象名 : { 
  •                                               deep : true
  •                                               handler(新值,旧值){  }                     }
  •                             }
  • 对象中的任何一个属性发生变化,都会被监视到,handler函数就会执行
  • immediate配置项,只要配置了,立刻执行一次handler函数,不需要属性变化

案例:购物车
<!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="http://autumnfish/static/fruit.jpg" alt="" /></div>
      <!-- 面包屑 -->
      <div class="breadcrumb">
        <span>🏠</span>
        /
        <span>购物车</span>
      </div>
      <!-- 购物车主体 -->
      <div class="main"v-if="this.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  v-for="(item,index) in fruitList" v-bind:key="item.id"  class="tr " v-bind:class="{active : item.isChecked}">
              <div class="td"><input v-model="item.isChecked" type="checkbox" checked /></div>
              <div class="td"><img  v-bind:src="item.icon" alt="" /></div>
              <div class="td">{{item.price}}</div>
              <div class="td">
                <div class="my-input-number">
                  <!-- v-bind:disabled="item.num <= 1"  动态控制按钮失效的 -->
                  <button v-bind:disabled="item.num <= 1" 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.price * item.num}}</div>
              <div class="td"><button @click="del(item.id)">删除</button></div>
            </div>

          </div>
        </div>
        <!-- 底部 -->
        <div class="bottom">
          <!-- 全选 -->
          <label class="check-all">
            <input type="checkbox" v-model="isAll" />
            全选
          </label>
          <div class="right-box">
            <!-- 所有商品总价 -->
            <span class="price-box">总价&nbsp;&nbsp;:&nbsp;&nbsp;¥&nbsp;<span class="price">{{totalPrice}}</span></span>
            <!-- 结算按钮 -->
            <button class="pay">结算( {{totalCount}} )</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){
            // 1. 根据id找到数组中相对应的项 ->find
            const fruit = this.fruitList.find(item => item.id === id)
            fruit.num++
          },
          sub(id){
            // 1. 根据id找到数组中相对应的项 ->find
            const fruit = this.fruitList.find(item => item.id === id)
            fruit.num--
          }
        },
        computed:{
          isAll:{
            get(){
              // 必循每一项的小选框都选中,全选按钮才选中 --every
              return this.fruitList.every(item => item.isChecked === true)
            },
            set(valvue){
              // 循环遍数组中的每一个项,让每一个项的isChecked 重新赋值
              // this.fruitList.forEach(item =>item.isChecked = valvue);
              this.fruitList.forEach(element => {
                element.isChecked =valvue
              });
            },
          },
            ///
            totalCount(){
              return this.fruitList.reduce((sum,item) => {
                if(item.isChecked){
                  //选中 
                  return sum + item.num 
                }else{
                  return sum
                }
              },0)
            },
            
            totalPrice(){
              return this.fruitList.reduce((sum,item) => {
                if(item.isChecked){
                  return sum + item.num * item.price
                }else{
                  return sum
                }
              },0)
            }
        },
        watch : {
          fruitList : {
            deep : true,
            handler (newValue) {
              localStorage.setItem('list',JSON.stringify(newValue))
            }
          }
        }


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

二.生命周期

生命周期总结:四个阶段八个函数

  • 创建阶段和挂载阶段只执行一次,更新阶段执行多次,在更新阶段,当数据发生变化时,就会去更新视图,这是一个循环的过程

四个阶段

八个函数

  • beforeCreate是还没有进行创建阶段,类似于站在创建阶段前的门口
  • created是已经响应完数据了
  • 生命周期函数是与data配置项同级的

代码

<!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>{{ title }}</h3>
    <div>
      <button @click="count--">-</button>
      <span>{{ count }}</span>
      <button @click="count++">+</button>
    </div>
  </div>
  <script src="https://cdn.jsdelivr/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        count: 100,
        title: '计数器'
      },
      // 1. 创建阶段(准备数据)
      beforeCreate () {
        console.log('beforeCreate 响应式数据准备好之前', this.count)
      },
      created () {
        console.log('created 响应式数据准备好之后', this.count)
        // this.数据名 = 请求回来的数据
        // 可以开始发送初始化渲染的请求了
      },

      // 2. 挂载阶段(渲染模板)
      beforeMount () {
        console.log('beforeMount 模板渲染之前', document.querySelector('h3').innerHTML)
      },
      mounted () {
        console.log('mounted 模板渲染之后', document.querySelector('h3').innerHTML)
        // 可以开始操作dom了
      },

      // 3. 更新阶段(修改数据 → 更新视图)
      beforeUpdate () {
        console.log('beforeUpdate 数据修改了,视图还没更新', document.querySelector('span').innerHTML)
      },
      updated () {
        console.log('updated 数据修改了,视图已经更新', document.querySelector('span').innerHTML)
      },

      // 4. 卸载阶段
      beforeDestroy () {
        console.log('beforeDestroy, 卸载前')
        console.log('清除掉一些Vue以外的资源占用,定时器,延时器...')
      },
      destroyed () {
        console.log('destroyed,卸载后')
      }
    })
  </script>
</body>
</html>

控制台截图

案例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- CSS only -->
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
    />
    <style>
      .red {
        color: red!important;
      }
      .search {
        width: 300px;
        margin: 20px 0;
      }
      .my-form {
        display: flex;
        margin: 20px 0;
      }
      .my-form input {
        flex: 1;
        margin-right: 20px;
      }
      .table > :not(:first-child) {
        border-top: none;
      }
      .contain {
        display: flex;
        padding: 10px;
      }
      .list-box {
        flex: 1;
        padding: 0 30px;
      }
      .list-box  a {
        text-decoration: none;
      }
      .echarts-box {
        width: 600px;
        height: 400px;
        padding: 30px;
        margin: 0 auto;
        border: 1px solid #ccc;
      }
      tfoot {
        font-weight: bold;
      }
      @media screen and (max-width: 1000px) {
        .contain {
          flex-wrap: wrap;
        }
        .list-box {
          width: 100%;
        }
        .echarts-box {
          margin-top: 30px;
        }
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div class="contain">
        <!-- 左侧列表 -->
        <div class="list-box">

          <!-- 添加资产 -->
          <form class="my-form">
            <input v-model.trim="name" type="text" class="form-control" placeholder="消费名称" />
            <input v-model.number="price" type="text" class="form-control" placeholder="消费价格" />
            <button @click="add" type="button" class="btn btn-primary">添加账单</button>
          </form>

          <table class="table table-hover">
            <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 :class="{ red: item.price > 500 }">{{ item.price.toFixed(2) }}</td>
                <td><a @click="del(item.id)" href="javascript:;">删除</a></td>
              </tr>
            </tbody>
            <tfoot>
              <tr>
                <td colspan="4">消费总计: {{ totalPrice.toFixed(2) }}</td>
              </tr>
            </tfoot>
          </table>
        </div>
        
        <!-- 右侧图表 -->
        <div class="echarts-box" id="main"></div>
      </div>
    </div>
    <script src="https://cdn.jsdelivr/npm/echarts@5.4.0/dist/echarts.min.js"></script>
    <script src="https://cdn.jsdelivr/npm/vue@2/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr/npm/axios/dist/axios.min.js"></script>
    <script>
      /**
       * 接口文档地址:
       * https://www.apifox/apidoc/shared-24459455-ebb1-4fdc-8df8-0aff8dc317a8/api-53371058
       * 
       * 功能需求:
       * 1. 基本渲染
       *    (1) 立刻发送请求获取数据 created
       *    (2) 拿到数据,存到data的响应式数据中
       *    (3) 结合数据,进行渲染 v-for
       *    (4) 消费统计 => 计算属性
       * 2. 添加功能
       *    (1) 收集表单数据 v-model
       *    (2) 给添加按钮注册点击事件,发送添加请求
       *    (3) 需要重新渲染
       * 3. 删除功能
       *    (1) 注册点击事件,传参传 id
       *    (2) 根据 id 发送删除请求
       *    (3) 需要重新渲染
       * 4. 饼图渲染
       *    (1) 初始化一个饼图 echarts.init(dom)  mounted钩子实现
       *    (2) 根据数据实时更新饼图 echarts.setOption({ ... })
       */
      const app = new Vue({
        el: '#app',
        data: {
          list: [],
          name: '',
          price: ''
        },
        computed: {
          totalPrice () {
            return this.list.reduce((sum, item) => sum + item.price, 0)
          }
        },
        created () {
          // 将以下的代码封装进getList方法重
          // const res = await axios.get('https://applet-base-api-t.itheima/bill', {
          //   params: {
          //     creator: '小黑'
          //   }
          // })
          // this.list = res.data.data

          this.getList()
        },
        mounted () {
          this.myChart = echarts.init(document.querySelector('#main'))
          this.myChart.setOption({
            // 大标题
            title: {
              text: '消费账单列表',
              left: 'center'
            },
            // 提示框
            tooltip: {
              trigger: 'item'
            },
            // 图例
            legend: {
              orient: 'vertical',
              left: 'left'
            },
            // 数据项
            series: [
              {
                name: '消费账单',
                type: 'pie',
                radius: '50%', // 半径
                data: [
                  // { value: 1048, name: '球鞋' },
                  // { value: 735, name: '防晒霜' }
                ],
                emphasis: {
                  itemStyle: {
                    shadowBlur: 10,
                    shadowOffsetX: 0,
                    shadowColor: 'rgba(0, 0, 0, 0.5)'
                  }
                }
              }
            ]
          })
        },

        methods: {
          async getList () {
            const res = await axios.get('https://applet-base-api-t.itheima/bill', {
              params: {
                creator: '小黑'
              }
            })
            this.list = res.data.data

            // 更新图表
            this.myChart.setOption({
              // 数据项
              series: [
                {
                  // data: [
                  //   { value: 1048, name: '球鞋' },
                  //   { value: 735, name: '防晒霜' }
                  // ]
                  data: this.list.map(item => ({ value: item.price, name: item.name}))
                }
              ]
            })
          },
          async add () {
            if (!this.name) {
              alert('请输入消费名称')
              return
            }
            if (typeof this.price !== 'number') {
              alert('请输入正确的消费价格')
              return
            }

            // 发送添加请求
            const res = await axios.post('https://applet-base-api-t.itheima/bill', {
              creator: '小黑',
              name: this.name,
              price: this.price
            })
            // 重新渲染一次
            this.getList()

            this.name = ''
            this.price = ''
          },
          async del (id) {
            // 根据 id 发送删除请求
            const res = await axios.delete(`https://applet-base-api-t.itheima/bill/${id}`)
            // 重新渲染
            this.getList()
          }
        }
      })
    </script>
  </body>
</html>

三.工程化开发和脚手架

1.脚手架 vue-cli

2. 标准化目录

注意:使用vscode打开工程化的项目时,规范化:在根目录打开  -->把跟文件拖进vscode中

根组件的介绍
<template>
  <div class="App">
    <div class="box" @click="fn"></div>
  </div>
</template>

<script>
// 导出的是当前组件的配置项
// 里面可以提供 data(特殊) methods computed watch 生命周期八大钩子
export default {
  created () {
    console.log('我是created')
  },
  methods: {
    fn () {
      alert('你好')
    }
  }
}
</script>

<style lang="less">
/* 让style支持less
   1. 给style加上 lang="less"
   2. 安装依赖包 less less-loader
      yarn add less less-loader -D (开发依赖)
*/
.App {
  width: 400px;
  height: 400px;
  background-color: pink;
  .box {
    width: 100px;
    height: 100px;
    background-color: skyblue;
  }
}
</style>

main.js的介绍
// 文件核心作用:导入App.vue,基于App.vue创建结构渲染index.html
// 1. 导入 Vue 核心包
import Vue from 'vue'

// 2. 导入 App.vue 根组件
import App from './App.vue'

// 提示:当前处于什么环境 (生产环境 / 开发环境)
Vue.config.productionTip = false

// 3. Vue实例化,提供render方法 → 基于App.vue创建结构渲染index.html
new Vue({
  // el: '#app', 作用:和$mount('选择器')作用一致,用于指定Vue所管理容器
  // render: h => h(App),
  render: (createElement) => {
    // 基于App创建元素结构
    return createElement(App)
  }
}).$mount('#app')

3. 组件化开发

4. 组件的三个部分

扩展

当在style标签中,有嵌套的css写法时,style的lang设置为less

  • 需要安装依赖包
  • yarn add less less-loader -D            -----  -D  开发环境
  • 嵌套css 
  •     <style lang="less">
  •            .APP{
  •                 .box {
  •                  }                    
  •             } 
  •    <style>
  • }

5.组件的注册

1.组件的局部注册

标签补齐快捷键设置 : tab建  :设置 -->搜 trigger  on tab  勾选

代码 组件的局部组测

<template>
  <div id="app">
    <!-- 头 -->
    <Header></Header>
    <!-- 体 -->
    <Main></Main>
    <!-- 低 -->
    <HmFooter></HmFooter>
  </div>
</template>

<script>
import HmHeader from './components/HmHeader.vue'
import HmMain from './components/HmMain.vue'
import HmFooter from './components/HmFooter.vue'

export default {
  name: 'App',
  components: {
    Header: HmHeader,
    Main: HmMain,
    // 同名,可以缩写
    HmFooter 
    
  }

}
</script>

<style>

</style>

2.组件的全局注册

  • 在main.js中注册,一个只能注册一个全局组件
  • Vueponent(‘组件名’, 导入的组件对象

四.组件之间的通信

1.组件三大组成部分的说明

  • 组件的默认样式是全局样式,会影响到其他组件的样式,实际中,每一个组件都应该有自己独立的样式,也就是局部样式,局部样式是在 <style scoped></style> 标签上加上scoped

2.组件中的data配置项必须是一个函数 ,只有这样,才能每次执行data方法时,才会得到一个新的数据对象,同一个组件生成的多个数据对象才不会互相影响

视图名.vue的配置项是一个对象

3.组件通信

1.父传子

  1. 在父组件里面的子组件标签中,以添加属性的方式传值
  2. 在子组件中,通过props进行接收
  3. 最后在子组件中渲染使用
代码
app.vue根组件
<template>
  <div id="app">
    <!-- 1.给组件标签,添加属性方式 赋值 -->
    <son v-bind:title="mytitle"></son>
  </div>
</template>

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

export default {
  name: 'App',

  data() {
    return {
      mytitle : '组件通信 : 父传子'
    }
  },
  components: {
    Son : Son
  }
}
</script>

<style>

</style>
son.vue子组件
<template>
  <div class="son" style="border:3px solid #000;margin:10px">
    <!-- 3.直接使用props的值 -->
    我是Son组件 {{title}}
  </div>
</template>

<script>
export default {
  name: 'Son-Child',
  // 2.通过props来接受,数组中的函数名要与被绑定的属性名相同。 <son v-bind:title="mytitle"></son>
  props : ['title']
}
</script>

<style>

</style>

2.子传父

son.vue子组件
<template>
  <div class="son" style="border:3px solid #000;margin:10px">
    我是Son组件 {{title}}
    <!--  1. 绑定一个函数,通过函数来触发$emit -->
    <button v-on:click="changeFn">子组件向父组件通信</button>
  </div>
</template>

<script>
export default {
  name: 'Son-Child',
  props: ['title'],
  methods: {
    changeFn() {
      // 2. 通过this.$emit() 向父组件发送通知  第一个参数是自定义名称,这个名称在父组件中绑定使用,
      // 第二参数是要修改的新值
      this.$emit("changTitle",'工业大学')
    }
  }
}
</script>

<style>

</style>
app.vue父组件
<template>
  <div id="app">
    <!-- 3. 绑定一个函数来接收,而绑定的事件不在是click这类事件,而是子组件中自定义名称-->
    <son v-bind:title="mytitle" v-on:changTitle="handleChange"></son>
  </div>
</template>

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

export default {
  name: 'App',

  data() {
    return {
      mytitle : '组件通信 : 父传子'
    }
  },
  methods: {
    // 4.newTitle 就是修改的新值 --> 工业大学
    handleChange(newTitle) {
      this.mytitle = newTitle
    }
  },
  components: {
    Son : Son
  }
}
</script>

<style>

</style>

注意点
  • vm.$emit("自定义的事件名" , "修改的值") 第一个参数:自定义事件名在父组件中作为绑定事件名使用,而第二个参数修改的值:就是子组件向父组件传递的数据 

3.props     

 property属性

代码
app.vue
<template>
  <div id="app">
    <!-- 3. 绑定一个函数来接收,而绑定的事件不在是click这类事件,而是子组件中自定义名称-->
    <UserInfo  
         :username="username" 
         :age="age" 
         :isSingle="isSingle" 
         :car="car" 
         :hobby="hobby">
    </UserInfo>
  </div>
</template>

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

export default {
  name: 'App',

  data() {
    return {
      username: '小帅',
      age: 28,
      isSingle: true,
      car: {
        brand: '宝马',
      },
      hobby: ['篮球', '足球', '羽毛球'],
    }
    },
  components: {
    UserInfo : UserInfo
  }
}
</script>

<style>

</style>
UserInfo.vue
<template>
  <div class="userinfo">
    <h3>我是个人信息组件</h3>
    <div>姓名:{{username}}</div>
    <div>年龄:{{age}}</div>
    <div>是否单身:{{isSingle}}</div>
    <div>座驾:{{car.brand}}</div>
    <div>兴趣爱好:{{hobby.join('、')}}</div>
  </div>
</template>

<script>
export default {
  props:['username','age','isSingle','car','hobby']
}
</script>

<style>
.userinfo {
  width: 300px;
  border: 3px solid #000;
  padding: 20px;
}
.userinfo > div {
  margin: 20px 10px;
}
</style>
props校验
  • 校验的属性名 : 校验的数据类型        -->  类型校验,校验传入值的类型是否符合校验的类型。

app.vue代码
<template>
  <div class="app">
    <BaseProgress :w="width" @WEmitEvent="receiveWEmitEvent"></BaseProgress>
  </div>
</template>

<script>
import BaseProgress from './components/BaseProgress.vue'
export default {
  data() {
    return {
      width: 50,
    }
  },
  components: {
    BaseProgress:BaseProgress, 
  },
  methods: {
    receiveWEmitEvent(newValue) {
      this.width = newValue
    }
  }
}
</script>

<style>
</style>

BaseProgress.vue代码
<template>
  <div class="base-progress">
    <div class="inner" :style="{ width: w + '%' }">
      <span>{{ w }}%</span><br><br>
      <button @click="changeW">修改</button>
    </div>
  </div>
</template>

<script>
export default {

  // 1.基础写法(类型校验)
  // props: {
  //   w: Number,
  // },

  // 2.完整写法(类型、默认值、非空、自定义校验)
  props: {
    w: {
      type: Number,
      required: true,
      default: 0,
      //  validator(val)中的参数val是传过来的值
      validator(val) {
        // console.log(val)
        if (val >= 100 || val <= 0) {
          // 自定义错误提示
          console.error('传入的范围必须是0-100之间')
          //  return false 表示校验不通过
          return false
        } else {
           //  return true 表示校验通过
          return true
        }
      },
    },
  },
  methods: {
    changeW() {
      this.$emit("WEmitEvent",30)
    }
  }
}
</script>

<style scoped>
.base-progress {
  height: 26px;
  width: 400px;
  border-radius: 15px;
  background-color: #272425;
  border: 3px solid #272425;
  box-sizing: border-box;
  margin-bottom: 30px;
}
.inner {
  position: relative;
  background: #379bff;
  border-radius: 15px;
  height: 25px;
  box-sizing: border-box;
  left: -3px;
  top: -2px;
}
.inner span {
  position: absolute;
  right: 0;
  top: 26px;
}
</style>
prop和data的区别

App.vue父组件的代码
<template>
  <div class="app">
    <BaseCount :count="count" @changeCount="handleChange"></BaseCount>
  </div>
</template>

<script>
import BaseCount from './components/BaseCount.vue'
export default {
  components:{
    BaseCount
  },
  data(){
    return {
      count:100
    }
  },
  methods:{
    handleChange(newVal){
      // console.log(newVal);
      this.count = newVal
    }
  }
}
</script>

<style>

</style>
BaseCount.vue子组件的代码
<template>
  <div class="base-count">
    <button @click="handleSub">-</button>
    <span>{{ count }}</span>
    <button @click="handleAdd">+</button>
  </div>
</template>

<script>
export default {
  // 1.自己的数据随便修改  (谁的数据 谁负责)
  // data () {
  //   return {
  //     count: 100,
  //   }
  // },
  // 2.外部传过来的数据 不能随便修改
  props: {
    count: {
      type: Number,
    },
  },
  methods: {
    handleSub() {
      // count不能++,因为count本质上是父组件的数据
      this.$emit('changeCount', this.count - 1)
    },
    handleAdd() {
      this.$emit('changeCount', this.count + 1)
    },
  },
}
</script>

<style>
.base-count {
  margin: 20px;
}
</style>

综合案例

app.vue父组件

<template>
  <!-- 主体区域 -->
  <section id="app">
    <TodoHeader @add="handleAdd"></TodoHeader>
    <TodoMain :list="list" @del="handelDel"></TodoMain>
    <TodoFooter :list="list" @clear="clear"></TodoFooter>
  </section>
</template>

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

// 渲染功能:
// 1.提供数据: 提供在公共的父组件 App.vue
// 2.通过父传子,将数据传递给TodoMain
// 3.利用 v-for渲染

// 添加功能:
// 1.手机表单数据  v-model
// 2.监听事件(回车+点击都要添加)
// 3.子传父,讲任务名称传递给父组件 App.vue
// 4.进行添加 unshift(自己的数据自己负责)
// 5.清空文本框输入的内容
// 6.对输入的空数据 进行判断

// 删除功能
// 1.监听事件(监听删除的点击) 携带id
// 2.子传父,讲删除的id传递给父组件的App.vue
// 3.进行删除filter(自己的数据 自己负责)

// 底部合计:父传子  传list 渲染
// 清空功能:子传父  通知父组件 → 父组件进行更新
// 持久化存储:watch深度监视list的变化 -> 往本地存储 ->进入页面优先读取本地数据
export default {
  data() {
    return {
      list: JSON.parse(localStorage.getItem('list')) || [
        { id: 1, name: '打篮球' },
        { id: 2, name: '看电影' },
        { id: 3, name: '逛街' },
      ],
    }
  },
  components: {
    TodoHeader,
    TodoMain,
    TodoFooter,
  },
  watch: {
    list: {
      deep: true,
      handler(newVal) {
        // 持久化到本地,将js对象转成json对象
        localStorage.setItem('list', JSON.stringify(newVal))
      },
    },
  },
  methods: {
    handleAdd(todoName) {
      // console.log(todoName)
      this.list.unshift({
        id: +new Date(),
        name: todoName,
      })
    },
    handelDel(id) {
      // console.log(id);
      this.list = this.list.filter((item) => item.id !== id)
    },
    clear() {
      this.list = []
    },
  },
}
</script>

<style>
</style>

TodoHeader.vue

<template>
   <!-- 输入框 -->
  <header class="header">
    <h1>小黑记事本</h1>
    <input placeholder="请输入任务" class="new-todo" v-model="todoName" @keyup.enter="handleAdd"/>
    <button class="add" @click="handleAdd">添加任务</button>
  </header>
</template>

<script>
export default {
  data(){
    return {
      todoName:''
    }
  },
  methods:{
    handleAdd(){
      // console.log(this.todoName)
      this.$emit('add',this.todoName)
      this.todoName = ''
    }
  }
}
</script>

<style>

</style>

TodoMain.vue

<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="handleDel(item.id)"></button>
        </div>
      </li>
    </ul>
  </section>
</template>

<script>
export default {
  props: {
    list: {
      type: Array,
    },
  },
  methods: {
    handleDel(id) {
      this.$emit('del', id)
    },
  },
}
</script>

<style>
</style>

TodoFooter.vue

<template>
  <!-- 统计和清空 -->
  <footer class="footer">
    <!-- 统计 -->
    <span class="todo-count"
      >合 计:<strong> {{ list.length }} </strong></span
    >
    <!-- 清空 -->
    <button class="clear-completed" @click="clear">清空任务</button>
  </footer>
</template>

<script>
export default {
  props: {
    list: {
      type: Array,
    },
  },
  methods:{
    clear(){
      this.$emit('clear')
    }
  }
}
</script>

<style>
</style>

4.非父子通信

4.1 event bus事件总线

代码
1. 新建 utils/EventBus.js 文件代码   
// 事件总线,创建一个Vue实例

import Vue from 'vue'

const Bus  =  new Vue()

export default Bus
 2. App.vue 代码
<template>
  <div class="app">
    <BaseA></BaseA>
    <BaseB></BaseB>
    <BaseC></BaseC>
  </div>
</template>

<script>
import BaseA from './components/BaseA.vue'
import BaseB from './components/BaseB.vue'
import BaseC from './components/BaseC.vue'
export default {
  components:{
    BaseA,
    BaseB,
    BaseC
  }
}
</script>

<style>

</style>
3. BaseB.vue发送方的代码
<template>
  <div class="base-b">
    <div>我是B组件(发布方)</div>
    <button @click="sendMsgFn">发送消息</button>
  </div>
</template>

<script>
import Bus from '../utils/EventBus'
export default {
  methods: {
    sendMsgFn() {
      // vm.$emit() 第一个参数是自定义事件名,在接收方时,要一一对应
      // vm.$emit() 第二个参数是传输的数据,在接收方时,使用形参接收
      Bus.$emit('sendMsg', '今天天气不错,适合旅游')
    },
  },
}
</script>

<style scoped>
.base-b {
  width: 200px;
  height: 200px;
  border: 3px solid #000;
  border-radius: 3px;
  margin: 10px;
}
</style>
4. BaseA.vue 接收方的代码
<template>
  <div class="base-b">
    <div>我是B组件(发布方)</div>
    <button @click="sendMsgFn">发送消息</button>
  </div>
</template>

<script>
import Bus from '../utils/EventBus'
export default {
  methods: {
    sendMsgFn() {
      // vm.$emit() 第一个参数是自定义事件名,在接收方时,要一一对应
      // vm.$emit() 第二个参数是传输的数据,在接收方时,使用形参接收
      Bus.$emit('sendMsg', '今天天气不错,适合旅游')
    },
  },
}
</script>

<style scoped>
.base-b {
  width: 200px;
  height: 200px;
  border: 3px solid #000;
  border-radius: 3px;
  margin: 10px;
}
</style>
5. BaseC.vue 接收方的代码
<template>
  <div class="base-c">
    我是C组件(接受方)
    <p>{{msg}}</p>  
  </div>
</template>

<script>
import Bus from '../utils/EventBus'
export default {
  data() {
    return {
      msg: '',
    }
  },
  created() {
    Bus.$on('sendMsg', (msg) => {
      // console.log(msg)
      this.msg = msg
    })
  },
}
</script>

<style scoped>
.base-c {
  width: 200px;
  height: 200px;
  border: 3px solid #000;
  border-radius: 3px;
  margin: 10px;
}
</style>

事件总线是一个一对多的关系,一个组件发布信息,其他组件只要正确接收,都能收到传输的数据

4.2 跨层级传输数据,如爷孙,祖孙等等

  • provide : 提供;规定;供应;给予  -->  作为数据的发送者
  • inject : 添加,增加 --> 作为数据的接收者
1. app.vue 代码
<template>
  <div class="app">
    我是APP组件
    <button @click="change">修改数据</button>
    <SonA></SonA>
    <SonB></SonB>
  </div>
</template>

<script>
import SonA from './components/SonA.vue'
import SonB from './components/SonB.vue'
export default {

  provide() {
    return {
      // 简单类型 是非响应式的,this.color就是一个普通的值
      color: this.color,
      // 复杂类型 是响应式的,this.userInfo,是一个对象
      userInfo: this.userInfo,
    }
  },

  data() {
    return {
      color: 'pink',
      userInfo: {
        name: 'zs',
        age: 18,
      },
    }
  },
  methods: {
    change() {
      this.color = 'red'
      this.userInfo.name = 'ls'
    },
  },
  components: {
    SonA,
    SonB,
  },
}
</script>

<style>
.app {
  border: 3px solid #000;
  border-radius: 6px;
  margin: 10px;
}
</style>
2. SonA.vue代码
<template>
  <div class="SonA">我是SonA组件
    <GrandSon></GrandSon>
  </div>
</template>

<script>
import GrandSon from '../components/GrandSon.vue'
export default {
  components:{
    GrandSon
  }
}
</script>

<style>
.SonA {
  border: 3px solid #000;
  border-radius: 6px;
  margin: 10px;
  height: 200px;
}
</style>
3. SonB.vue代码
<template>
  <div class="SonB">
    我是SonB组件
  </div>
</template>

<script>
export default {

}
</script>

<style>
.SonB {
  border: 3px solid #000;
  border-radius: 6px;
  margin: 10px;
  height: 200px;
}
</style>
4. GrandSon.vue 是 SonA.vue 的子组件 
<template>
  <div class="grandSon">
    我是GrandSon
    {{ color }} -{{ userInfo.name }} -{{ userInfo.age }}
  </div>
</template>

<script>
export default {
  // 接收者,color等变量名要与provide中声明的一致
  inject: ['color', 'userInfo'],
}
</script>

<style>
.grandSon {
  border: 3px solid #000;
  border-radius: 6px;
  margin: 10px;
  height: 100px;
}
</style>

5.v-model 的详解

6.表单类组件实现封装(父子组件之间的双向绑定)

  • v-model只能使用在自己的数据上实现双向绑定

App.vue的代码

<template>
  <div class="app">
    <!-- 父组件向子组件传递数据 -->
    <BaseSelect v-bind:cityId="selectId" v-on:cityFun="handleChange"></BaseSelect>
  </div>
</template>

<script>
import BaseSelect from './components/BaseSelect.vue'
export default {
  data() {
    return {
      selectId: '102',
    }
  },
  components: {
    BaseSelect,
  },
  methods: {
    handleChange(val) {
      console.log(val);
      this.selectId = val
    }
    
  }
}
</script>

<style>
</style>

BaseSelect.vue的代码

<template>
  <div>
    <select v-bind:value="cityValue" v-on:change="selectCity">
      <option value="101">北京</option>
      <option value="102">上海</option>
      <option value="103">武汉</option>
      <option value="104">广州</option>
      <option value="105">深圳</option>
    </select>
  </div>
</template>

<script>

export default {
  props: ['cityId'],
  data() {
    return {
      cityValue: this.cityId,
    }
  },
  methods: {
    selectCity(e) {
     this.$emit("cityFun",e.target.value) 
    }
  }
}
</script>

<style>
</style>

7.表单类组件实现封装(父子组件之间的双向绑定)

1.当满足子组件使用props 的value(value)接收父组件传递过来的数据和自定义事件触发名是 input时,可以在父组件中使用v-model简化代码

App.vue代码

<template>
  <div class="app">
    <!-- 1.当子组件中props中使用value接收,触发事件名input(this.$emit("input",e.target.value) -->
     <!-- 可以在父组件中使用v-model实现简写 -->
    <BaseSelect v-model="selectId"></BaseSelect>
  </div>
</template>

<script>
import BaseSelect from './components/BaseSelect.vue'
export default {
  data() {
    return {
      selectId: '102',
    }
  },
  components: {
    BaseSelect,
  },
  methods: {
    handleChange(val) {
      console.log(val);
      this.selectId = val
    }
    
  }
}
</script>

<style>
</style>

BaseSelect.vue的代码

<template>
  <div>
    <select v-bind:value="value" v-on:change="selectCity">
      <option value="101">北京</option>
      <option value="102">上海</option>
      <option value="103">武汉</option>
      <option value="104">广州</option>
      <option value="105">深圳</option>
    </select>
  </div>
</template>

<script>

export default {
  props: {
    value : String
  },
  methods: {
    selectCity(e) {
     this.$emit("input",e.target.value) 
    }
  }
}
</script>

<style>
</style>

8.总结

4.3 sync修饰符

  • sync 可以自定义props配置项中的属性名

App.vue

<template>
  <div class="app">
    <button @click="openDialog">退出按钮</button>
    <BaseDialog v-bind:visible.sync="isShow"></BaseDialog>
  </div>
</template>

<script>
import BaseDialog from "./components/BaseDialog.vue"
export default {
  data() {
    return {
      isShow : false,
    }
  },
  methods: {
    openDialog() {
      this.isShow=true
    }
  },
  components: {
    BaseDialog,
  },
}
</script>

<style>
</style>

BaseDialog.vue代码

<template>
  <div class="base-dialog-wrap" v-show="visible">
    <div class="base-dialog">
      <div class="title">
        <h3>温馨提示:</h3>
        <button class="close" v-on:click="closeDialog">x</button>
      </div>
      <div class="content">
        <p>你确认要退出本系统么?</p>
      </div>
      <div class="footer">
        <button>确认</button>
        <button>取消</button>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    visible : Boolean,
  },
  methods: {
    closeDialog() {
      // 必须写成 update:visible 
      // visible 是自定义的属性名,是props配置项中的属性名
      this.$emit("update:visible",false)
    }
  }

}
</script>

<style scoped>
.base-dialog-wrap {
  width: 300px;
  height: 200px;
  box-shadow: 2px 2px 2px 2px #ccc;
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  padding: 0 10px;
}
.base-dialog .title {
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 2px solid #000;
}
.base-dialog .content {
  margin-top: 38px;
}
.base-dialog .title .close {
  width: 20px;
  height: 20px;
  cursor: pointer;
  line-height: 10px;
}
.footer {
  display: flex;
  justify-content: flex-end;
  margin-top: 26px;
}
.footer button {
  width: 80px;
  height: 40px;
}
.footer button:nth-child(1) {
  margin-right: 10px;
  cursor: pointer;
}
</style>

4.4 ref和$refs (获取dom和组件实例)

  •      document.querySelector 会查找项目中所有的元素,$refs只会在当前组件查找盒子

4.4.1 ref和 $refs 实现获取dom元素

App.vue

<template>
  <div class="app">
    <!-- 获取dom元素 -->
    <BaseChart></BaseChart>
  </div>
</template>

<script>
import BaseChart from './components/BaseChart.vue'
export default {
  components:{
    BaseChart
  }
}
</script>

<style>
.base-chart-box {
  width: 200px;
  height: 100px;
}
</style>

BaseChart.vue

<template>
  <!-- ref="自定义名称" -->
  <div ref="mychart" class="base-chart-box">子组件</div>
</template>

<script>
import * as echarts from 'echarts'

export default {
  mounted() {
    // 基于准备好的dom,初始化echarts实例
    // 获取到ref对应的dom元素 
    const myChart = echarts.init(this.$refs.mychart)
    // 绘制图表
    myChart.setOption({
      title: {
        text: 'ECharts 入门示例',
      },
      tooltip: {},
      xAxis: {
        data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子'],
      },
      yAxis: {},
      series: [
        {
          name: '销量',
          type: 'bar',
          data: [5, 20, 36, 10, 10, 20],
        },
      ],
    })
  },
}
</script>

<style scoped>
.base-chart-box {
  width: 400px;
  height: 300px;
  border: 3px solid #000;
  border-radius: 6px;
}
</style>
4.4.2 ref和$refs实现获取组件实例

App.vue

<template>
  <div class="app">
    <!-- 获取组件的实例,就可以调用该组件的方法。注意组件实例是在mounted之后 -->
    <BaseForm ref="BaseForm"></BaseForm>
    <button v-on:click="handleGet">获取数据</button>
    <button v-on:click="handleReset">重置数据</button>
  </div>
</template>

<script>
import BaseForm from './components/BaseForm.vue'
export default {
  components: {
    BaseForm,
  },
  methods: {
    handleGet() {
      console.log(this.$refs.BaseForm);
      console.log(this.$refs.BaseForm.getFormData());
     
    },
    handleReset() {
      console.log(this.$refs.BaseForm.resetFormData);
      this.$refs.BaseForm.resetFormData()
    }
  }
}
</script>

<style>
</style>

BaseForm.vue代码

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

<script>
export default {
  data() {
    return {
      username: 'admin',
      password: '123456',
    }
  },
  methods: {
    getFormData() {
      return {
        username: this.username,
        password : this.password
      }
    },
    resetFormData() {
      this.username = ''
      this.password = ''
      console.log('重置表单数据成功');
    },
  }
}
</script>

<style scoped>
.app {
  border: 2px solid #ccc;
  padding: 10px;
}
.app div{
  margin: 10px 0;
}
.app div button{
  margin-right: 8px;
}
</style>

4.5 Vue异步更新和$nextTick

App.vue代码

<template>
  <div class="app">
    <div v-if="isShowEdit">
      <input type="text" v-model="editValue" ref="inp" />
      <button>确认</button>
    </div>
    <div v-else>
      <span>{{ title }}</span>
      <button v-on:click="handleEdit">编辑</button>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      title: '大标题',
      isShowEdit: false,
      editValue: '',
    }
  },
  methods: {
    handleEdit() {
      this.isShowEdit = true
      // 函数体,在dom元素更新后,执行焦点
      this.$nextTick( () => {
        this.$refs.inp.focus()
      })
    }
  },
}
</script>

<style>
</style>

5.1.自定义指令

5.1.1 全局注册指令

main.js文件中,配置全局指令

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false
// // 1. 全局注册指令
// Vue.directive('focus', {
//   // inserted 会在 指令所在的元素,被插入到页面中时触发
//   inserted (el) {
//     // el 就是指令所绑定的元素
//     // console.log(el);
//     el.focus()
//   }
// })

Vue.directive('focus', {
  inserted(el) {
    el.focus()
  }
})

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

App.vue文件中使用自定义指令v-focus

<template>
  <div>
    <h1>自定义指令</h1>
    <!-- v-focus 自定义指令 -->
    <input v-focus ref="inp" type="text">
  </div>
</template>

<script>
export default {
  // mounted () {
  //   this.$refs.inp.focus()
  // }
  

}
</script>

<style>

</style>

5.1.2 局部自定义指令

在组件内部中定义,也只能在该组件内部使用

App.vue代码

<template>
  <div>
    <h1>自定义指令</h1>
    <input v-focus ref="inp" type="text">
  </div>
</template>

<script>
export default {
  // mounted () {
  //   this.$refs.inp.focus()
  // }
  
  // 2. 局部注册指令
  directives: {
    // 指令名:指令的配置项
    focus: {
      inserted (el) {
        el.focus()
      }
    }
  }
}
</script>

<style>

</style>

5.1.3 自定义指令的值

App.vue的代码

<template>
  <div>
    <h1 v-color="color1">指令的值1测试</h1>
    <h1 v-color="color2">指令的值2测试</h1>
  </div>
</template>

<script>
export default {
  data () {
    return {
      color1: 'red',
      color2: 'orange'
    }
  },
  directives: {
    color: {
      // 1. inserted 提供的是元素被添加到页面中时的逻辑
      inserted (el, binding) {
        // console.log(el, binding.value);
        // binding.value 就是指令的值,拿到的是 v-color="color1" 的color1
        el.style.color = binding.value
      },
      // 2. update 指令的值修改的时候触发,提供的值变化后,dom更新的逻辑
      update (el, binding) {
        console.log('指令的值修改了');
        el.style.color = binding.value
      }
    }
  }
}
</script>

<style>

</style>

5.1.4 自定义封装的loading指令

App.vue

<template>
  <div class="main">
    <!-- 自定义指令v-loading -->
    <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 class="box2" v-loading="isLoading2"></div>
  </div>
</template>

<script>
// 导入axios库用于发送HTTP请求
// 安装axios =>  yarn add axios
import axios from 'axios'

// 定义组件,包含数据获取和自定义指令
export default {
  data () {
    return {
      // 初始时列表为空,加载状态为真
      list: [],
      isLoading: true,
    }
  },
  async created () {
    // 创建时发送请求获取新闻数据
    // 1. 发送请求获取数据
    const res = await axios.get('http://hmajax.itheima/api/news')
    // 模拟延迟,模拟数据加载过程
    setTimeout(() => {
      // 更新列表数据并关闭加载状态
      // 2. 更新到 list 中,用于页面渲染 v-for
      this.list = res.data.data
      this.isLoading = false
    }, 2000)
  },
  directives: {
    // 自定义v-loading指令,用于元素的加载动画控制
    loading: {
      inserted (el, binding) {
        // 根据绑定的值决定是否添加加载状态类
        binding.value ? el.classList.add('loading') : el.classList.remove('loading')
      },
      update (el, binding) {
        // 更新时同样根据绑定的值控制加载状态
        binding.value ? el.classList.add('loading') : el.classList.remove('loading')
      }
    }
  }
}
</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>

5.2 插槽

  • 插槽可让封装好的组件实现一些自定义的效果
  • 封装好的组件,实现结构自定义使用插槽

App.vue代码

<template>
  <div>
    <!-- 2. 在使用组件时,组件标签内填入内容 -->
    <MyDialog>
      <div>你确认要删除么</div>
    </MyDialog>

    <MyDialog>
      <p>你确认要退出么</p>
    </MyDialog>
  </div>
</template>

<script>
import MyDialog from './components/MyDialog.vue'
export default {
  data () {
    return {

    }
  },
  components: {
    MyDialog
  }
}
</script>

<style>
body {
  background-color: #b3b3b3;
}
</style>

MyDialog.vue

<template>
  <div class="dialog">
    <div class="dialog-header">
      <h3>友情提示</h3>
      <span class="close">✖️</span>
    </div>

    <div class="dialog-content">
      <!-- 1. 在需要定制的位置,使用slot占位 -->
       <!-- <MyDialog>
            <p>你确认要退出么</p>
            </MyDialog> 中的<p>你确认要退出么</p>会在插槽的位置展示-->
      <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>

截图效果

5.2.1 插槽-后备内容

App.vue

<template>
  <div>
    <!-- 2. 在使用组件时,组件标签内填入内容 -->
    <MyDialog></MyDialog>

    <MyDialog>
      <p>你确认要退出么</p>
    </MyDialog>
  </div>
</template>

<script>
import MyDialog from './components/MyDialog.vue'
export default {
  data () {
    return {

    }
  },
  components: {
    MyDialog
  }
}
</script>

<style>
body {
  background-color: #b3b3b3;
}
</style>

MyDialog.vue

<template>
  <div class="dialog">
    <div class="dialog-header">
      <h3>友情提示</h3>
      <span class="close">✖️</span>
    </div>

    <div class="dialog-content">
      <!-- 1. 在需要定制的位置,使用slot占位 -->
       <!-- 2.后备内容 ,没有值显示时,显示插槽中的默认内容 -->
      <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>

效果图

5.2.2 具名插槽

App.vue代码

<template>
  <div>
    <MyDialog>
      <template v-slot:head>
        <div>具名插槽</div></template>
      <template v-slot:content>具名插槽的内容</template>
      <template v-slot:footer>
        <div>
          <button>确定</button>
          <button>取消</button>
        </div>
      </template>
    </MyDialog>
  </div>
</template>

<script>
import MyDialog from './components/MyDialog.vue'
export default {
  data () {
    return {

    }
  },
  components: {
    MyDialog
  }
}
</script>

<style>
body {
  background-color: #b3b3b3;
}
</style>

MyDialog.vue代码

<template>
  <div class="dialog">
    <div class="dialog-header">
      <slot name="head">标题</slot>
    </div>

    <div class="dialog-content">
      <slot name="content">内容</slot>
    </div>
    <div class="dialog-footer">
      <slot name="footer">按钮</slot>
    </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>

效果图

5.2.3 作用域插槽

  • obj是一个自定义的对象名
  • 作用域插槽是可以传值的

App.vue代码

<template>
  <div>
    <!-- v-bind 绑定数据 将list数据传递到MyTable.vue组件中通过props接收数据 -->
    <MyTable :data="list">
      <!-- 3. 通过template #插槽名="变量名" 接收 ,插槽没有名字,默认插槽名是default obj是一个对象-->
      <template #default="obj">
        <!--  通过插槽row接收数据 -->
        <button @click="del(obj.row.id)">
          删除
        </button>
      </template>
    </MyTable>
    
    <MyTable :data="list2">
      <template #default="{ row }">
        <button @click="show(row)">查看</button>
      </template>
    </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 },
      ]
    }
  },
  methods: {
    del (id) {
      this.list = this.list.filter(item => item.id !== id)
    },
    show (row) {
      // console.log(row);
      alert(`姓名:${row.name}; 年纪:${row.age}`)
    }
  },
  components: {
    MyTable
  }
}
</script>

MyTable.vue代码

<template>
  <table class="my-table">
    <thead>
      <tr>
        <th>序号</th>
        <th>姓名</th>
        <th>年纪</th>
        <th>操作</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="(item, index) in data" :key="item.id">
        <td>{{ index + 1 }}</td>
        <td>{{ item.name }}</td>
        <td>{{ item.age }}</td>
        <td>
          <!-- 1. 给slot标签,添加属性的方式传值  row是自定义属性名 msg是自定义属性名 -->
          <slot :row="item" msg="测试文本"></slot>

          <!-- 2. 将所有的属性,添加到一个对象中 -->
          <!-- 
             {
               row: { id: 2, name: '孙大明', age: 19 },
               msg: '测试文本'
             }
           -->
        </td>
      </tr>
    </tbody>
  </table>
</template>

<script>
export default {
  props: {
    data: 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>

效果图

5.3 案列 : 商品列表

main.js代码:封装focus指令

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

// 封装全局指令 focus
Vue.directive('focus', {
  // 指令所在的dom元素,被插入到页面中时触发
  inserted (el) {
    el.focus()
  }
})


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

MyTable.vue代码

<template>
  <table class="my-table">
    <thead>
      <tr>
        <slot name="head"></slot>
      </tr>
    </thead>
    <tbody>
      <tr v-for="(item, index) in data" :key="item.id">
        <slot name="body" :item="item" :index="index" ></slot>
      </tr>
    </tbody>
  </table>
</template>

<script>
export default {
  props: {
    data: {
      type: Array,
      required: true
    }
  }
};
</script>

<style lang="less" scoped>

.my-table {
  width: 100%;
  border-spacing: 0;
  img {
    width: 100px;
    height: 100px;
    object-fit: contain;
    vertical-align: middle;
  }
  th {
    background: #f5f5f5;
    border-bottom: 2px solid #069;
  }
  td {
    border-bottom: 1px dashed #ccc;
  }
  td,
  th {
    text-align: center;
    padding: 10px;
    transition: all .5s;
    &.red {
      color: red;
    }
  }
  .none {
    height: 100px;
    line-height: 100px;
    color: #999;
  }
}

</style>

MyTag.vue代码

<template>
  <div class="my-tag">
    <input
      v-if="isEdit"
      v-focus
      ref="inp"
      class="input"
      type="text"
      placeholder="输入标签"
      :value="value"
      @blur="isEdit = false"
      @keyup.enter="handleEnter"
    />
    <div 
      v-else
      @dblclick="handleClick"
      class="text">
      {{ value }}
    </div>
  </div>
</template>

<script>
export default {
  props: {
    value: String
  },
  data () {
    return {
      isEdit: false
    }
  },
  methods: {
    handleClick () {
      // 双击后,切换到显示状态 (Vue是异步dom更新)
      this.isEdit = true
      
      // // 等dom更新完了,再获取焦点
      // this.$nextTick(() => {
      //   // 立刻获取焦点
      //   this.$refs.inp.focus()
      // })
    },
    handleEnter (e) {
      // 非空处理
      if (e.target.value.trim() === '') return alert('标签内容不能为空')

      // 子传父,将回车时,[输入框的内容] 提交给父组件更新
      // 由于父组件是v-model,触发事件,需要触发 input 事件
      this.$emit('input', e.target.value)
      // 提交完成,关闭输入状态
      this.isEdit = false
    }
  }
}
</script>

<style lang="less" scoped>
.my-tag {
  cursor: pointer;
  .input {
    appearance: none;
    outline: none;
    border: 1px solid #ccc;
    width: 100px;
    height: 40px;
    box-sizing: border-box;
    padding: 10px;
    color: #666;
    &::placeholder {
      color: #666;
    }
  }
}
</style>

App.vue代码

<template>
  <div class="table-case">
    <MyTable :data="goods">
      <template #head>
        <th>编号</th>
        <th>名称</th>
        <th>图片</th>
        <th width="100px">标签</th>
      </template>

      <template #body="{ item, index }">
        <td>{{ index + 1 }}</td>
        <td>{{ item.name }}</td>
        <td>
          <img
            :src="item.picture"
          />
        </td>
        <td>
          <MyTag v-model="item.tag"></MyTag>
        </td>
      </template>
    </MyTable>
  </div>
</template>

<script>
// my-tag 标签组件的封装
// 1. 创建组件 - 初始化
// 2. 实现功能
//    (1) 双击显示,并且自动聚焦
//        v-if v-else @dbclick 操作 isEdit
//        自动聚焦:
//        1. $nextTick => $refs 获取到dom,进行focus获取焦点
//        2. 封装v-focus指令

//    (2) 失去焦点,隐藏输入框
//        @blur 操作 isEdit 即可

//    (3) 回显标签信息
//        回显的标签信息是父组件传递过来的
//        v-model实现功能 (简化代码)  v-model => :value 和 @input
//        组件内部通过props接收, :value设置给输入框

//    (4) 内容修改了,回车 => 修改标签信息
//        @keyup.enter, 触发事件 $emit('input', e.target.value)

// ---------------------------------------------------------------------

// my-table 表格组件的封装
// 1. 数据不能写死,动态传递表格渲染的数据  props
// 2. 结构不能写死 - 多处结构自定义 【具名插槽】
//    (1) 表头支持自定义
//    (2) 主体支持自定义

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

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

</style>

效果图

  • 安装less : npm install less@4 --save-dev
  • 安装less-loader : npm install less-loader@7 --save-dev
  • 要根据构建工具版本选择合适的版本

5.3 单页应用程序

5.4 路由

5.4.1 VueRoute

版本对应关系 (Vue VueRouter  VueX)

  • Vue2     VueRouter3.x        Vuex3.x
  • Vue3      VueRouter4.x        Vuex4.x

VueRouter也可以使用npm下载

官网:安装 | Vue Router (vuejs)

npm install vue-router@4

5.4.2 在main.js文件中,配置全局路由

// 导入Vue框架
import Vue from 'vue'
// 导入App组件,这是应用程序的入口组件
import App from './App.vue'

// 导入VueRouter来实现路由管理
import VueRouter from 'vue-router'

// 关闭Vue的生产提示,优化生产环境性能
Vue.config.productionTip = false

// 使用VueRouter来启用路由功能
Vue.use(VueRouter)

// 创建路由实例
const router = new VueRouter()

// 创建Vue实例,并挂载到ID为app的元素上
new Vue({
  // 指定渲染函数,用于生成App组件
  render: h => h(App),
  // 设置路由对象
  router: router
}).$mount('#app')

5.4.5 路由规则的配置

main.js代码

import Vue from 'vue'
import App from './App.vue'

// 路由的使用步骤 5 + 2
// 5个基础步骤
// 1. 下载 v3.6.5
// 2. 引入
// 3. 安装注册 Vue.use(Vue插件)
// 4. 创建路由对象
// 5. 注入到new Vue中,建立关联

// 2个核心步骤
// 1. 建组件(views目录),配规则
// 2. 准备导航链接,配置路由出口(匹配的组件展示的位置) 
import Find from './views/Find'
import My from './views/My'
import Friend from './views/Friend'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化

const router = new VueRouter({
  //  routes 路由规则们
  // route 一条路由规则 是一个对象 { path: 路径, component: 组件 }
  routes: [
    { path: '/find', component: Find },
    { path: '/my', component: My },
    { path: '/friend', component: Friend },
  ]
})

Vue.config.productionTip = false

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

App.vue代码

<template>
  <div>
    <div class="footer_wrap">
      <a href="#/find">发现音乐</a>
      <a href="#/my">我的音乐</a>
      <a href="#/friend">朋友</a>
    </div>
    <div class="top">
      <!-- 路由出口 → 匹配的组件所展示的位置 -->
      <router-view></router-view>
    </div>
  </div>
</template>

<script>
export default {};
</script>

<style>
body {
  margin: 0;
  padding: 0;
}
.footer_wrap {
  position: relative;
  left: 0;
  top: 0;
  display: flex;
  width: 100%;
  text-align: center;
  background-color: #333;
  color: #ccc;
}
.footer_wrap a {
  flex: 1;
  text-decoration: none;
  padding: 20px 0;
  line-height: 20px;
  background-color: #333;
  color: #ccc;
  border: 1px solid black;
}
.footer_wrap a:hover {
  background-color: #555;
}
</style>

My.vue代码

<template>
  <div>
    <p>我的音乐</p>
    <p>我的音乐</p>
    <p>我的音乐</p>
    <p>我的音乐</p>
  </div>
</template>

<script>
export default {
  // name: 'MyMusic' 避免语法规则错误
  name: 'MyMusic'
}
</script>

<style>

</style>

Find.vue代码

<template>
  <div>
    <p>发现音乐</p>
    <p>发现音乐</p>
    <p>发现音乐</p>
    <p>发现音乐</p>
  </div>
</template>

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

<style>

</style>

Friend.vue代码

<template>
  <div>
    <p>我的朋友</p>
    <p>我的朋友</p>
    <p>我的朋友</p>
    <p>我的朋友</p>
  </div>
</template>

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

<style>

</style>

效果图

5.4.5 组件存放目录问题

5.4.3 路由模块的封装

main.js代码

import Vue from 'vue'
import App from './App.vue'
// 导入封装在router/index.js中的router对象
import router from './router/index'

Vue.config.productionTip = false

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

router/index.js中的代码

// 导入Find视图组件
import Find from '@/views/Find'
// 导入My视图组件
import My from '@/views/My'
// 导入Friend视图组件
import Friend from '@/views/Friend'
// 导入Vue对象
// 这里使用到Vue,需要导入Vue对象
import Vue from 'vue'
// 导入VueRouter对象
import VueRouter from 'vue-router'

// 使用VueRouter插件
Vue.use(VueRouter) // VueRouter插件初始化

// 创建VueRouter实例
// 创建了一个路由对象
const router = new VueRouter({
  // 配置路由规则
  // routes 路由规则们
  // route  一条路由规则 { path: 路径, component: 组件 }
  routes: [
    // 配置找到页面的路由
    { path: '/find', component: Find },
    // 配置我的页面的路由
    { path: '/my', component: My },
    // 配置朋友页面的路由
    { path: '/friend', component: Friend },
  ]
})

// 导出router实例供其他地方使用
export default router

5.5 声明式编程导航/导航链接

  • <router-link to="/路由规则中配置的路径">

router/index.js中的代码

import Find from '@/views/Find'
import My from '@/views/My'
import Friend from '@/views/Friend'

import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化

// 创建了一个路由对象
const router = new VueRouter({
  // routes 路由规则们
  // route  一条路由规则 { path: 路径, component: 组件 }
  routes: [
    { path: '/find', component: Find },
    { path: '/my', component: My },
    { path: '/friend', component: Friend },
  ]
})

export default router

main.js代码

import Vue from 'vue'
import App from './App.vue'
import router from './router/index'

Vue.config.productionTip = false

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

App.vue代码

<template>
  <div>
    <!-- 底部导航栏,包含应用中的主要功能入口 -->
    <div class="footer_wrap">
      <!-- 发现音乐入口 -->
      <router-link to="/find">发现音乐</router-link>
      <!-- 我的音乐入口 -->
      <router-link to="/my">我的音乐</router-link>
      <!-- 朋友入口 -->
      <router-link to="/friend">朋友</router-link>
    </div>
    <!-- 路由视图,用于展示根据路由动态切换的组件 -->
    <div class="top">
      <router-view></router-view>
    </div>
  </div>
</template>

<script>
export default {};
</script>

<style>
/* 为整个页面设置根元素的样式,确保页面内容全屏显示 */
body {
  margin: 0;
  padding: 0;
}

/* 定义底部导航栏的样式 */
.footer_wrap {
  position: relative;
  left: 0;
  top: 0;
  display: flex;
  width: 100%;
  text-align: center;
  background-color: #333;
  color: #ccc;
}

/* 定义导航栏链接的样式 */
.footer_wrap a {
  flex: 1;
  text-decoration: none;
  padding: 20px 0;
  line-height: 20px;
  background-color: #333;
  color: #ccc;
  border: 1px solid black;
}

/* 当路由链接激活时,应用不同的背景颜色 */
/* router-link-active 是css类名,表示当前活跃的链接  高亮色为紫色*/ 
.footer_wrap a.router-link-active {
  background-color: purple;
}

/* 鼠标悬停在链接上时,改变背景颜色 */
.footer_wrap a:hover {
  background-color: #555;
}
</style>

5.5.1 精确匹配和模糊匹配

5.5.1 定制类名

router/index.js代码

import Find from '@/views/Find'
import My from '@/views/My'
import Friend from '@/views/Friend'

import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化

// 创建了一个路由对象
const router = new VueRouter({
  // routes 路由规则们
  // route  一条路由规则 { path: 路径, component: 组件 }
  routes: [
    { path: '/find', component: Find },
    { path: '/my', component: My },
    { path: '/friend', component: Friend },
  ],
  // link自定义高亮类名,定制高亮类名 
  linkActiveClass: 'active', // 配置模糊匹配的类名
  linkExactActiveClass: 'exact-active' // 配置精确匹配的类名
})

export default router

App.js代码

<template>
  <div>
    <div class="footer_wrap">
      <router-link to="/find">发现音乐</router-link>
      <router-link to="/my">我的音乐</router-link>
      <router-link to="/friend">朋友</router-link>
    </div>
    <div class="top">
      <!-- 路由出口 → 匹配的组件所展示的位置 -->
      <router-view></router-view>
    </div>
  </div>
</template>

<script>
export default {};
</script>

<style>
body {
  margin: 0;
  padding: 0;
}
.footer_wrap {
  position: relative;
  left: 0;
  top: 0;
  display: flex;
  width: 100%;
  text-align: center;
  background-color: #333;
  color: #ccc;
}
.footer_wrap a {
  flex: 1;
  text-decoration: none;
  padding: 20px 0;
  line-height: 20px;
  background-color: #333;
  color: #ccc;
  border: 1px solid black;
}
/* 
  router-link-active 模糊匹配(更多)
  to="/find"  =>  地址栏 /find   /find/one   /find/two  ...

  router-link-exact-active 精确匹配
  to="/find"  =>  地址栏 /find  
*/
/* .footer_wrap a.router-link-active {
  background-color: purple;
} */

.footer_wrap a.active {
  background-color: purple;
}
.footer_wrap a:hover {
  background-color: #555;
}
</style>

5.5.2 声明式导航----跳转传参

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router/index'

Vue.config.productionTip = false

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

router/index.js代码

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', component: Home },
    { path: '/search', component: Search }
  ]
})

export default router

App.vue代码

<template>
  <div id="app">
    <div class="link">
      <router-link to="/home">首页</router-link>
      <router-link to="/search">搜索页</router-link>
    </div>

    <router-view></router-view>
  </div>
</template>

<script>
export default {};
</script>

<style scoped>
.link {
  height: 50px;
  line-height: 50px;
  background-color: #495150;
  display: flex;
  margin: -8px -8px 0 -8px;
  margin-bottom: 50px;
}
.link a {
  display: block;
  text-decoration: none;
  background-color: #ad2a26;
  width: 100px;
  text-align: center;
  margin-right: 5px;
  color: #fff;
  border-radius: 5px;
}
</style>

views/Home.vue代码

<template>
  <div class="home">
    <div class="logo-box"></div>
    <div class="search-box">
      <input type="text">
      <button>搜索一下</button>
    </div>
    <div class="hot-link">
      热门搜索:
      <!--to="/search?key=黑马程序员" 中的 ?key=黑马程序员 是路由的查询参数 key是键名 黑马程序员是键值-->
      <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>

<style>
.logo-box {
  height: 150px;
  background: url('@/assets/logo.jpeg') no-repeat center;
}
.search-box {
  display: flex;
  justify-content: center;
}
.search-box input {
  width: 400px;
  height: 30px;
  line-height: 30px;
  border: 2px solid #c4c7ce;
  border-radius: 4px 0 0 4px;
  outline: none;
}
.search-box input:focus {
  border: 2px solid #ad2a26;
}
.search-box button {
  width: 100px;
  height: 36px;
  border: none;
  background-color: #ad2a26;
  color: #fff;
  position: relative;
  left: -2px;
  border-radius: 0 4px 4px 0;
}
.hot-link {
  width: 508px;
  height: 60px;
  line-height: 60px;
  margin: 0 auto;
}
.hot-link a {
  margin: 0 5px;
}
</style>

views/Search.vue代码

<template>
  <div class="search">
    <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.参数名 获取
    // this.$route.query.key中的key是键名
    console.log(this.$route.query.key);
  }
}
</script>

<style>
.search {
  width: 400px;
  height: 240px;
  padding: 0 20px;
  margin: 0 auto;
  border: 2px solid #c4c7ce;
  border-radius: 5px;
}
</style>

5.5.3 声明式导航----动态路由传参

router/index.js代码

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', component: Home },
    // 添加一个路由参数 /search/:words 中的/search是路径,:words是参数,words是自定义名参数
    // 获取路由参数: this.$route.params.参数名 获取动态路由参数
    { path: '/search/:words', component: Search }
  ]
})

export default router

main.js代码

import Vue from 'vue'
import App from './App.vue'
import router from './router/index'

Vue.config.productionTip = false

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

App.vue

<template>
  <div id="app">
    <div class="link">
      <router-link to="/home">首页</router-link>
      <router-link to="/search">搜索页</router-link>
    </div>
    <router-view></router-view>
  </div>
</template>

<script>
export default {};
</script>

<style scoped>
.link {
  height: 50px;
  line-height: 50px;
  background-color: #495150;
  display: flex;
  margin: -8px -8px 0 -8px;
  margin-bottom: 50px;
}
.link a {
  display: block;
  text-decoration: none;
  background-color: #ad2a26;
  width: 100px;
  text-align: center;
  margin-right: 5px;
  color: #fff;
  border-radius: 5px;
}
</style>

views/Home.vue

<template>
  <div class="home">
    <div class="logo-box"></div>
    <div class="search-box">
      <input type="text">
      <button>搜索一下</button>
    </div>
    <div class="hot-link">
      热门搜索:
      <!--  path: '/search/:words',  : words是路由的参数,如黑马程序员-->
      <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>

<style>
.logo-box {
  height: 150px;
  background: url('@/assets/logo.jpeg') no-repeat center;
}
.search-box {
  display: flex;
  justify-content: center;
}
.search-box input {
  width: 400px;
  height: 30px;
  line-height: 30px;
  border: 2px solid #c4c7ce;
  border-radius: 4px 0 0 4px;
  outline: none;
}
.search-box input:focus {
  border: 2px solid #ad2a26;
}
.search-box button {
  width: 100px;
  height: 36px;
  border: none;
  background-color: #ad2a26;
  color: #fff;
  position: relative;
  left: -2px;
  border-radius: 0 4px 4px 0;
}
.hot-link {
  width: 508px;
  height: 60px;
  line-height: 60px;
  margin: 0 auto;
}
.hot-link a {
  margin: 0 5px;
}
</style>

views/Search.vue

<template>
  <div class="search">
    <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.参数名 获取查询参数
    // this.$route.params.参数名 获取动态路由参数
    console.log(this.$route.params.words);
  }
}
</script>

<style>
.search {
  width: 400px;
  height: 240px;
  padding: 0 20px;
  margin: 0 auto;
  border: 2px solid #c4c7ce;
  border-radius: 5px;
}
</style>


  • :参数名     表示,必须要传递参数,如果没有传递参数,则显示空白页面
  • :参数名? 表示 ,可以不传递参数,也能正常渲染页面

5.5.4  路由重定向

router/index.js代码

import Home from '@/views/Home'
import Search from '@/views/Search'
import NotFound from '@/views/NotFound'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化

// 创建了一个路由对象
const router = new VueRouter({
  // 注意:一旦采用了 history 模式,地址栏就没有 #,需要后台配置访问规则
  mode: 'history',
  routes: [
  // 重定向
    { path: '/', redirect: '/home' },
    { path: '/home', component: Home },
    { name: 'search', path: '/search/:words?', component: Search },
    { path: '*', component: NotFound }
  ]
})

export default router

5.5.5 路由的基本跳转

router/index.js代码

import Home from '@/views/Home'
import Search from '@/views/Search'
import NotFound from '@/views/NotFound'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化

// 创建了一个路由对象
const router = new VueRouter({
  // 注意:一旦采用了 history 模式,地址栏就没有 #,需要后台配置访问规则
  mode: 'history',
  routes: [
    { path: '/', redirect: '/home' },
    { path: '/home', component: Home },
    { name: 'search', path: '/search/:words?', component: Search },
    { path: '*', component: NotFound }
  ]
})

export default router

views/Home.vue代码

<template>
  <div class="home">
    <div class="logo-box"></div>
    <div class="search-box">
      <input type="text">
      <button @click="goSearch">搜索一下</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',
  methods: {
    goSearch () {
      // 1. 通过路径的方式跳转
      // (1) this.$router.push('路由路径') [简写]
      // this.$router.push('/search')

      // (2) this.$router.push({     [完整写法]
      //         path: '路由路径' 
      //     })
      // this.$router.push({
      //   path: '/search'
      // })

      // 2. 通过命名路由的方式跳转 (需要给路由起名字) 适合长路径
      //    this.$router.push({
      //        name: '路由名'
      //    })
      this.$router.push({
        name: 'search'
      })
    }
  }
}
</script>

<style>
.logo-box {
  height: 150px;
  background: url('@/assets/logo.jpeg') no-repeat center;
}
.search-box {
  display: flex;
  justify-content: center;
}
.search-box input {
  width: 400px;
  height: 30px;
  line-height: 30px;
  border: 2px solid #c4c7ce;
  border-radius: 4px 0 0 4px;
  outline: none;
}
.search-box input:focus {
  border: 2px solid #ad2a26;
}
.search-box button {
  width: 100px;
  height: 36px;
  border: none;
  background-color: #ad2a26;
  color: #fff;
  position: relative;
  left: -2px;
  border-radius: 0 4px 4px 0;
}
.hot-link {
  width: 508px;
  height: 60px;
  line-height: 60px;
  margin: 0 auto;
}
.hot-link a {
  margin: 0 5px;
}
</style>

5.5.6 编程式导航----路由跳转传参

router/index.js代码

import Home from '@/views/Home'
import Search from '@/views/Search'
import NotFound from '@/views/NotFound'
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化

// 创建了一个路由对象
const router = new VueRouter({
  // 注意:一旦采用了 history 模式,地址栏就没有 #,需要后台配置访问规则
  mode: 'history',
  routes: [
    { path: '/', redirect: '/home' },
    { path: '/home', component: Home },
    { name: 'search', path: '/search/:words?', component: Search },
    { path: '*', component: NotFound }
  ]
})

export default router

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router/index'

Vue.config.productionTip = false

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

App.vue

<template>
  <div id="app">
    <div class="link">
      <router-link to="/home">首页</router-link>
      <router-link to="/search">搜索页</router-link>
    </div>
    <router-view></router-view>
  </div>
</template>

<script>
export default {};
</script>

<style scoped>
.link {
  height: 50px;
  line-height: 50px;
  background-color: #495150;
  display: flex;
  margin: -8px -8px 0 -8px;
  margin-bottom: 50px;
}
.link a {
  display: block;
  text-decoration: none;
  background-color: #ad2a26;
  width: 100px;
  text-align: center;
  margin-right: 5px;
  color: #fff;
  border-radius: 5px;
}
</style>

views/Home.vue

<template>
  <div class="home">
    <div class="logo-box"></div>
    <div class="search-box">
      <input v-model="inpValue" type="text">
      <button @click="goSearch">搜索一下</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',
  data () {
    return {
      inpValue: ''
    }
  },
  methods: {
    goSearch () {
      // 1. 通过路径的方式跳转
      // (1) this.$router.push('路由路径') [简写]
      //     this.$router.push('路由路径?参数名=参数值')
      // this.$router.push('/search')
      // this.$router.push(`/search?key=${this.inpValue}`)
      // this.$router.push(`/search/${this.inpValue}`)

      // (2) this.$router.push({     [完整写法] 更适合传参
      //         path: '路由路径'
      //         query: {
      //            参数名: 参数值,
      //            参数名: 参数值
      //         }
      //     })
      // this.$router.push({
      //   path: '/search',
      //   query: {
      //     key: this.inpValue
      //   }
      // })
      // this.$router.push({
      //   path: `/search/${this.inpValue}`
      // })



      // 2. 通过命名路由的方式跳转 (需要给路由起名字) 适合长路径
      //    this.$router.push({
      //        name: '路由名'
      //        query: { 参数名: 参数值 },
      //        params: { 参数名: 参数值 }
      //    })
      this.$router.push({
        name: 'search',
        // query: {
        //   key: this.inpValue
        // }
        params: {
          words: this.inpValue
        }
      })
    }
  }
}
</script>

<style>
.logo-box {
  height: 150px;
  background: url('@/assets/logo.jpeg') no-repeat center;
}
.search-box {
  display: flex;
  justify-content: center;
}
.search-box input {
  width: 400px;
  height: 30px;
  line-height: 30px;
  border: 2px solid #c4c7ce;
  border-radius: 4px 0 0 4px;
  outline: none;
}
.search-box input:focus {
  border: 2px solid #ad2a26;
}
.search-box button {
  width: 100px;
  height: 36px;
  border: none;
  background-color: #ad2a26;
  color: #fff;
  position: relative;
  left: -2px;
  border-radius: 0 4px 4px 0;
}
.hot-link {
  width: 508px;
  height: 60px;
  line-height: 60px;
  margin: 0 auto;
}
.hot-link a {
  margin: 0 5px;
}
</style>

views/Search.vue

<template>
  <div class="search">
    <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.参数名 获取查询参数
    // this.$route.params.参数名 获取动态路由参数
    // console.log(this.$route.params.words);
  }
}

</script>

<style>
.search {
  width: 400px;
  height: 240px;
  padding: 0 20px;
  margin: 0 auto;
  border: 2px solid #c4c7ce;
  border-radius: 5px;
}
</style>

views/NotFound.vue

<template>
  <div>
    <h1>404 Not Found</h1>
  </div>
</template>

<script>
export default {

}
</script>

<style>

</style>

5.5.7 组件缓存

router/index.js代码

import Vue from 'vue'
import VueRouter from "vue-router";
import Layout from '@/views/Layout'
import Article from '@/views/Article'
import Collect from '@/views/Collect'
import Like from '@/views/Like'
import User from '@/views/User'
import ArticleDetail from '@/views/ArticleDetail'
Vue.use(VueRouter)

const router = new VueRouter({
  // /article 路径 → Article组件
  routes: [
    { 
      path: '/',
      component: Layout,
      redirect: '/article',
      // 通过 children 配置项,可以配置嵌套子路由
      // 1. 在children配置项中,配规则
      // 2. 准备二级路由出口
      children: [
        {
          path: '/article',
          component: Article
        },
        {
          path: '/collect',
          component: Collect
        },
        {
          path: '/like',
          component: Like,
        },
        {
          path: '/user',
          component: User
        }
      ]
    },
    {
      path: '/detail/:id',
      component: ArticleDetail
    }
  ]
})

export default router

main.js代码

import Vue from 'vue'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false

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

App.vue

<template>
  <div class="h5-wrapper">
    <!-- 包裹了keep-alive 一级路由匹配的组件都会被缓存
         LayoutPage组件(被缓存) - 多两个生命周期钩子
          - actived 激活时,组件被看到时触发
          - deactived 失活时,离开页面组件看不见触发
         ArticleDetailPage组件(未被缓存)

         需求:只希望Layout被缓存,include配置
         :include="组件名数组"
    -->
    <keep-alive :include="keepArr">
      <router-view></router-view>
    </keep-alive>
  </div>
</template>

<script>
export default {
  name: 'h5-wrapper',
  data () {
    return {
      // 缓存组件名的数组
      keepArr: ['LayoutPage']
    }
  }
}
</script>

<style>
body{
  margin: 0;
  padding: 0;
}
</style>
<style lang="less" scoped>
.h5-wrapper {
  .content {
    margin-bottom: 51px;
  }
  .tabbar {
    position: fixed;
    left: 0;
    bottom: 0;
    width: 100%;
    height: 50px;
    line-height: 50px;
    text-align: center;
    display: flex;
    background: #fff;
    border-top: 1px solid #e4e4e4;
    a {
      flex: 1;
      text-decoration: none;
      font-size: 14px;
      color: #333;
      -webkit-tap-highlight-color: transparent;
      &.router-link-active {
        color: #fa0;
      }
    }
  }
}
</style>

Article.vue

<template>
  <div class="article-page">
    <div
      v-for="item in articles"
      :key="item.id"
      @click="$router.push(`/detail/${item.id}`)"
      class="article-item">
      <div class="head">
        <img :src="item.creatorAvatar" alt="" />
        <div class="con">
          <p class="title">{{ item.stem }}</p>
          <p class="other">{{ item.creatorName }} | {{ item.createdAt }}</p>
        </div>
      </div>
      <div class="body">
        {{ item.content }}
      </div>
      <div class="foot">点赞 {{ item.likeCount }} | 浏览 {{ item.views }}</div>
    </div>
  </div>
</template>

<script>
// 首页请求渲染
// 1. 安装 axios   yarn add axios
// 2. 看接口文档,确认请求方式,请求地址,请求参数
//    请求地址: https://mock.boxuegu/mock/3083/articles
//    请求方式: get
// 3. created中发请求,获取数据,存起来
// 4. 页面动态渲染

// 跳转详情页传参 → 渲染
// 1. 查询参数传参 (更适合多个参数)
//    ?参数=参数值    =>  this.$route.query.参数名
// 2. 动态路由传参 (单个参数更优雅方便)
//    改造路由 => /路径/参数  =>  this.$route.params.参数名
//    细节优化:
//    (1) 访问 / 重定向到 /article   (redirect)
//    (2) 返回上一页 $router.back()



import axios from 'axios'
export default {
  name: 'ArticlePage',
  data () {
    return {
      articles: []
    }
  },
  async created () {
    const res = await axios.get('https://mock.boxuegu/mock/3083/articles')
    this.articles = res.data.result.rows
    // console.log(this.articles);
  }
}
</script>

<style lang="less" scoped>
.article-page {
  background: #f5f5f5;
}
.article-item {
  margin-bottom: 10px;
  background: #fff;
  padding: 10px 15px;
  .head {
    display: flex;
    img {
      width: 40px;
      height: 40px;
      border-radius: 50%;
      overflow: hidden;
    }
    .con {
      flex: 1;
      overflow: hidden;
      padding-left: 15px;
      p {
        margin: 0;
        line-height: 1.5;
        &.title {
          text-overflow: ellipsis;
          overflow: hidden;
          width: 100%;
          white-space: nowrap;
        }
        &.other {
          font-size: 10px;
          color: #999;
        }
      }
    }
  }
  .body {
    font-size: 14px;
    color: #666;
    line-height: 1.6;
    margin-top: 10px;
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
  }
  .foot {
    font-size: 12px;
    color: #999;
    margin-top: 10px;
  }
}
</style>

ArticleDetail.vue

<template>
  <div class="article-detail-page" v-if="article.id">
    <nav class="nav"> <span @click="$router.back()" class="back">&lt;</span> 面经详情</nav>
    <header class="header">
      <h1>{{ article.stem }}</h1>
      <p>{{ article.createdAt }} | {{ article.views }} 浏览量 | {{ article.likeCount }} 点赞数</p>
      <p>
        <img :src="article.creatorAvatar" alt=""> 
        <span>{{ article.creatorName }}</span> 
      </p>
    </header>
    <main class="body">  
      {{ article.content }}
    </main>
  </div>
</template>

<script>
// 请求地址: https://mock.boxuegu/mock/3083/articles/:id
// 请求方式: get
import axios from 'axios'
export default {
  name: 'ArticleDetailPage',
  data () {
    return {
      article: {}
    }
  },
  async created () {
    const id = this.$route.params.id
    const { data } = await axios.get(`https://mock.boxuegu/mock/3083/articles/${id}`)
    this.article = data.result
    // console.log(this.article);
  }
};
</script>

<style lang="less" scoped>
.article-detail-page {
  .nav {
    height: 44px;
    border-bottom: 1px solid #e4e4e4;
    line-height: 44px;
    text-align: center;
    .back {
      font-size: 18px;
      color: #666;
      position: absolute;
      left: 10px;
      top: 0;
      transform: scale(1, 1.5);
    }
  }
  .header {
     padding: 0 15px;
     p {
       color: #999;
       font-size: 12px;
       display: flex;
       align-items: center;
     }
     img {
       width: 40px;
       height: 40px;
       border-radius: 50%;
       overflow: hidden;
     }
  }
  .body {
     padding: 0 15px;
  }
}
</style>

Layout.vue

<template>
  <div class="h5-wrapper">
    <div class="content">
      <!-- 二级路由出口, -->
      <router-view></router-view>
    </div>
    <nav class="tabbar">
      <!-- 导航高亮
           1. 将a标签,替换成router-link (to)
           2. 结合高亮类名实现高亮效果 (router-link-active 模糊匹配)
      -->
      <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>

<script>
export default {
  // 组件名(如果没有配置 name,才会找文件名作为组件名)
  name: 'LayoutPage',
  // 组件缓存了,就不会执行组件的created,mounted,destroyed等钩子
  // 所以提供了 actived 和 deactived
  created () {
    console.log('created 组件被加载了');
  },
  mounted () {
    console.log('mounted dom渲染完了');
  },
  destroyed () {
    console.log('destroyed 组件被销毁了');
  },

  activated () {
    alert('你好,欢迎回到首页')
    console.log('activated 组件被激活了,看到页面了');
  },
  deactivated () {
    console.log('deactivated 组件失活,离开页面了');
  }
}
</script>

<style>
body{
  margin: 0;
  padding: 0;
}
</style>
<style lang="less" scoped>
.h5-wrapper {
  .content {
    margin-bottom: 51px;
  }
  .tabbar {
    position: fixed;
    left: 0;
    bottom: 0;
    width: 100%;
    height: 50px;
    line-height: 50px;
    text-align: center;
    display: flex;
    background: #fff;
    border-top: 1px solid #e4e4e4;
    a {
      flex: 1;
      text-decoration: none;
      font-size: 14px;
      color: #333;
      -webkit-tap-highlight-color: transparent;
    }
    a.router-link-active {
      color: orange;
    }
  }
}
</style>

5.6 自定义创建项目

1. 创建新的vue项目

vue create 项目名

2. 选择 Manually  select  features  :  手动选择功能

3. 选择功能

  •  Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection, and <enter> to proceed)
  • 检查项目所需的功能:(按<space>选择,按<a>切换全部,按<i>反转选择,按<enter>继续)

正常项目选择特性如下

4.选择Vue的版本

  •  Choose a version of Vue.js that you want to start the project with
  • 选择要启动项目的Vue.js版本

5.路由器配置

  • Use history mode for router? (Requires proper server setup for index fallback in production)
  • 路由器是否使用历史模式?(需要正确的服务器设置,以便在生产环境中进行索引回退)

6.选择CSS预处理器

  •  Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): (Use arrow keys)
  • 选择一个CSS预处理器(默认情况下支持PostCSS、Autoprefixer和CSS模块):(使用箭头键)

7.选择vue代码规范

  •  Pick a linter / formatter config: (Use arrow keys)
  • 选择一个linter/formatter配置:(使用箭头键)

8.校验时机

  •  Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection, and <enter> to proceed)
  • 选择其他棉绒特征:(按<space>选择,按<a>切换全部,按<i>反转选择,按<enter>继续)

9.选择配置文件是单独还是聚合

  • Where do you prefer placing config for Babel, ESLint, etc.
  • 您更喜欢在哪里放置Babel、ESLint等的配置。

10.是否保存未来项目的预设模板

  • Save this as a preset for future projects? (y/N)
  • 是否将此保存为未来项目的预设?(是/否)

5.7 ESlint 代码规范

  • JavaScript Standard Style 规范说明   https://standardjs/rules-zhcn.html
  • ESlint规则参考:规则参考 - ESLint - 插件化的 JavaScript 代码检查工具
  • CTRL + F 搜索

自动修正配置

在设置

6. Vuex

本文标签: 学习笔记vue