vue高级

52 阅读13分钟

10-父传子与todolist案例

hs 6/3/2022

# 1, 组件的通信

在一个Vue项目中,组件之间的通信是非常重要的环节

# 1.1, 父子传递数据给子组件

父子组件之间通信

  • 父组件传递给子组件:通过props属性
  • 子组件传递给父组件:通过$emit触发事件

父组件传递给子组件

  • 父组件有一些数据,需要子组件来进行展示,可以通过props来完成组件之间的通信
  • Props是你可以在组件上注册一些自定义的attribute
  • 父组件给这些attribute赋值,子组件通过attribute的名称获取到对应的值

Props有两种常见的用法

  • 方式一:字符串数组,数组中的字符串就是attribute的名称
  • 方式二:对象类型,对象类型我们可以在指定attribute名称的同时,指定它需要传递的类型、是否是必须的、默认值等等
// ======== main.js

// 引入vue核心库
import Vue from 'vue'
// 引入App组件
import App from './App.vue'

Vue.config.productionTip = false

// 初始化项目唯一的vm实例
// 把App组件渲染到容器中
new Vue({
  render: h => h(App),
}).$mount('#app')
// ======== App.vue
<template>
  <div id="app">
    <h1>父组件 --- 王健林</h1>
    <hr>
    <!-- car="二八大杠" 叫自定义属性,父传子就是靠自定义属性 -->
    <!-- :money="money" 也是自定义属性,值不是字符串,值是number -->
    <!-- :changeAppMoney="changeAppMoney" 也是自定义属性  值是一个方法  -->
    <MyComponent1 
      :car="car" 
      :money="money" 
      :changeAppMoney="changeAppMoney"
      :changeAppCar="changeAppCar"
    ></MyComponent1>
    <hr>
    <MyComponent2 :msg="msg"></MyComponent2>
  </div>
</template>

<script>
import MyComponent1 from "./components/MyComponent1.vue"
import MyComponent2 from "./components/MyComponent2.vue"
export default {
  name: 'App',
  components: {
    MyComponent1,
    MyComponent2
  },
  data(){
    return{
      money:10000000,
      car:"二八大杠",
      msg:"hello vue"
    }
  },
  methods:{
    // 定义修改状态的方法
    changeAppMoney(){
      this.money = "1个小目标"
    },
    changeAppCar(){
      this.car = "鬼火"
    }
  }
}
</script>

<style lang="less">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
// ======== MyComponent1.vue
<template>
    <div>
        <h1>王思聪组件</h1>
        <p>收到父的money ---- {{money}}</p>
        <p>收到父的car ---- {{car}}</p>
        <!-- <button @click="changeAppMoney">修改钱</button> -->
        <button @click="updateMoney">修改钱</button>
        
        <button @click="changeAppCar">修改车</button>
    </div>
</template>

<script>

export default{
    name:"MyComponent1",
    // props代表子组件接收父组件的数据
    // props有多种写法,第一种写法,props后面写一个数组
    // props中写自定义属性的名字
    props:["car","money","changeAppMoney","changeAppCar"],
    mounted(){
        // console.log(typeof this.car);
        // console.log(typeof this.money);

        // console.log(this.changeAppMoney);
    },
    data(){ return {

    } },
    methods:{
        updateMoney(){
            // 调用父传递过来的方法
            this.changeAppMoney();
        }
    }
}

</script>

<style lang="less" scoped>

</style>

type的类型都可以是哪些

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol
// ======== MyComponent2.vue

<template>
  <!-- 在vue2中,需要有一个唯一的根标签 -->
  <div>
    <h1>我是子组件MyComponent2</h1>
    <p>{{ msg }}</p>
  </div>
</template>

<script>
export default {
  name: "MyComponent2",
  // props:["msg"],
  // props的第二种写法:对象的写法
  props: {
    // msg后面也可以再配置一个对象
    //   msg:{
    //       type:String, // 期望父传递的是字符串类型,如果不是,会发出警告
    //       default:"haha", // 如果父没有传递msg,默认值是haha
    //   }

    // String  Number  Boolean  Array  Object  Date  Function  Symbol
    msg: {
      type: String, 
      required:true, // msg数据必须要传,如果不传就发出警告
    },
  },
  data() {
    return {};
  },
  methods: {},
};
</script>

<style lang="less" scoped>
</style>

Prop 的大小写命名

  • HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符
  • 这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名;

非Prop的Attribute

  • 当我们传递给一个组件某个属性,但是该属性并没有定义对应的props或者emits时,就称之为 非Prop的Attribute
  • 常见的包括class、style、id属性等
  • 当组件有单个根节点时,非Prop的Attribute将自动添加到根节点的Attribute中

禁用Attribute继承

  • 不希望组件的根元素继承attribute,可以在组件中设置 inheritAttrs: false
  • 禁用attribute继承的常见情况是需要将attribute应用于根元素之外的其他元素
  • 可以通过 $attrs来访问所有的 非props的attribute

# 2, TodoList

/*index.css*/
body {
    background: #fff;
}

.btn {
    display: inline-block;
    padding: 4px 12px;
    margin-bottom: 0;
    font-size: 14px;
    line-height: 20px;
    text-align: center;
    vertical-align: middle;
    cursor: pointer;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
    border-radius: 4px;
}

.btn-danger {
    color: #fff;
    background-color: #da4f49;
    border: 1px solid #bd362f;
}

.btn-danger:hover {
    color: #fff;
    background-color: #bd362f;
}

.btn:focus {
    outline: none;
}

.todo-container {
    width: 600px;
    margin: 0 auto;
}

.todo-container .todo-wrap {
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 5px;
}

/*header*/
.todo-header input {
    width: 560px;
    height: 28px;
    font-size: 14px;
    border: 1px solid #ccc;
    border-radius: 4px;
    padding: 4px 7px;
}

.todo-header input:focus {
    outline: none;
    border-color: rgba(82, 168, 236, 0.8);
    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}

/*main*/
.todo-main {
    margin-left: 0px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding: 0px;
}

.todo-empty {
    height: 40px;
    line-height: 40px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding-left: 5px;
    margin-top: 10px;
}

/*item*/
li {
    list-style: none;
    height: 36px;
    line-height: 36px;
    padding: 0 5px;
    border-bottom: 1px solid #ddd;
}

li label {
    float: left;
    cursor: pointer;
}

li label li input {
    vertical-align: middle;
    margin-right: 6px;
    position: relative;
    top: -1px;
}

li button {
    float: right;
    display: none;
    margin-top: 3px;
}

li:before {
    content: initial;
}

li:last-child {
    border-bottom: none;
}

/*footer*/
.todo-footer {
    height: 40px;
    line-height: 40px;
    padding-left: 6px;
    margin-top: 5px;
}

.todo-footer label {
    display: inline-block;
    margin-right: 20px;
    cursor: pointer;
}

.todo-footer label input {
    position: relative;
    top: -1px;
    vertical-align: middle;
    margin-right: 5px;
}

.todo-footer button {
    float: right;
    margin-top: 5px;
}
<!-- todo.html -->
<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <title>React App</title>

    <link rel="stylesheet" href="index.css">
</head>

<body>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
                <div class="todo-header">
                    <input type="text" placeholder="请输入你的任务名称,按回车键确认" />
                </div>
                <ul class="todo-main">
                    <li>
                        <label>
                            <input type="checkbox" />
                            <span>xxxxx</span>
                        </label>
                        <button class="btn btn-danger" style="display:none">删除</button>
                    </li>
                    <li>
                        <label>
                            <input type="checkbox" />
                            <span>yyyy</span>
                        </label>
                        <button class="btn btn-danger" style="display:none">删除</button>
                    </li>
                </ul>
                <div class="todo-footer">
                    <label>
                        <input type="checkbox" />
                    </label>
                    <span>
                        <span>已完成0</span> / 全部2
                    </span>
                    <button class="btn btn-danger">清除已完成任务</button>
                </div>
            </div>
        </div>
    </div>
</body>
</html>
// App.vue
<template>
  <div class="todo-container">
    <h4>{{todos}}</h4>
    <h1>{{isAllDone}}</h1>
    <div class="todo-wrap">
      <TodoHeader :addTodo="addTodo"></TodoHeader>
      <TodoMain :todos="todos" :updateDone="updateDone" :deleteTodo="deleteTodo"></TodoMain>
      <TodoFooter 
        :todos="todos" 
        :todoIsDone="todoIsDone" 
        :isAllDone="isAllDone"
        :updateAllDone="updateAllDone"
        :deleteDoneTodo="deleteDoneTodo"
      ></TodoFooter>
    </div>
  </div>
</template>

<script>
import TodoHeader from "./components/TodoHeader.vue";
import TodoMain from "./components/TodoMain.vue";
import TodoFooter from "./components/TodoFooter.vue";
export default {
  name: "App",
  components: {
    TodoHeader,
    TodoMain,
    TodoFooter,
  },
  data() {
    return {
      // todos:[
      //   {id:"01",title:"学习vue",done:true},
      //   {id:"02",title:"学习react",done:false},
      //   {id:"03",title:"学习小程序",done:false},
      // ]
      todos:JSON.parse(localStorage.getItem("TODO")) || []
    };
  },
  methods: {
    // 更新todo的Done
    updateDone(id,done){
      // 01 false
      // 03 true
      // console.log(id,done);
      // for循环遍历
      // for(let i=0; i<this.todos.length; i++){
      //   if(this.todos[i].id == id){
      //     this.todos[i].done = done
      //   }
      // }

      // forEach
      // this.todos.forEach(todo=>{
      //   if(todo.id == id){
      //     todo.done = done;
      //   }
      // })

      // 使用map
      // this.todos = this.todos.map(item=>{
      //   if(item.id == id){
      //     return { ...item, done}
      //   }
      //   return item;
      // })

      // 简化后的
      this.todos = this.todos.map(item=>item.id==id?{...item,done}:item)
    },
    // 删除单个todo
    deleteTodo(id){
      // this.todos = this.todos.filter(item=>{
      //   if(item.id==id){
      //     return false;
      //   }else{
      //     return true
      //   }
      // })

      this.todos = this.todos.filter(item=>item.id==id?false:true)
    },
    // 添加todo
    addTodo(todo){

      // find:如果找到了,返回对应的todo 如果没有找到,返回und
      let repeat = this.todos.find(item=>item.title === todo.title);
      console.log(repeat);

      // if(!repeat){
      //   this.todos.push(todo)
      // }else{
      //   alert(`【${todo.title}】任务已存在`)
      // }

      // !repeat && this.todos.push(todo);

      !repeat ? this.todos.push(todo) : alert(`【${todo.title}】任务已存在`)
    },
    // 全选和反选
    updateAllDone(done){
      this.todos = this.todos.map(item=>({...item,done}))
    },
    // 清除已完成
    deleteDoneTodo(){
      this.todos = this.todos.filter(item=>!item.done)
    }
  },
  computed:{
    // 统计已完成的数据
    todoIsDone(){
      // 过滤出done为true的todo
      return this.todos.filter(item=>item.done)
    },
    // 统计是否全部已完成
    isAllDone(){
      return this.todos.every(item=>item.done)
    }
  },
  watch:{
    todos(){
      // 侦听todos,todos数据一旦变化,就会被侦听到
      // 需要把数据持久化存储  localStorage
      localStorage.setItem("TODO",JSON.stringify(this.todos))
    }
  }
};
</script>

<style lang="less">
</style>
// TodoHeader.vue
<template>
  <div class="todo-header">
    <input 
      type="text" 
      placeholder="请输入你的任务名称,按回车键确认" 
      @keyup.enter="enterHandler"
    />
  </div>
</template>

<script>
export default {
  name: "TodoHeader",
  props: ["addTodo"],
  data() {
    return {};
  },
  methods: {
    enterHandler(e){
      if(e.target.value.trim() === ""){
        alert("输入内容不能为空~")
        return;
      }

      // 拼装一个todo
      let newTodo = {id:Date.now(), title:e.target.value, done:false};
      // console.log(newTodo);
      this.addTodo(newTodo)

      e.target.value = ""; // 清空输入框
    }
  },
};
</script>

<style lang="less" scoped>
</style>
// TodoMain.vue
<template>
  <ul class="todo-main">
    <li 
      v-for="(todo,index) in todos" 
      :key="todo.id"
      @mouseenter="enterHander(index)"
      :class="{active:currentIndex === index}"
    >
      <label>
        <input type="checkbox" :checked="todo.done" @change="handler(todo,$event)" />
        <span>{{todo.title}}</span>
      </label>
      <button class="btn btn-danger" v-show="todo.done" @click="clickHandler(todo.id)">删除</button>
    </li>
  </ul>
</template>

<script>
export default {
  name: "TodoMain",
  props: ["todos","updateDone","deleteTodo"],
  data() {
    return {
      currentIndex:-1
    };
  },
  methods: {
    // 鼠标移入li
    enterHander(index){
      // console.log(index);
      this.currentIndex = index;
    },
    // 点击单选框
    handler({id},e){
      // console.log(id);
      // console.log(e.target.checked);
      this.updateDone(id,e.target.checked)
    },
    // 点击删除
    clickHandler(id){
      // 子调用父传递的方法
      this.deleteTodo(id)
    }
  },
};
</script>

<style lang="less" scoped>
.active{
  color: yellowgreen;
  background-color: #eee;
}
</style>
// TodoFooter.vue
<template>
  <div class="todo-footer">
    <label>
      <!-- <input type="checkbox" :checked="isAllDone" @change="changeHandler" /> -->
      <input type="checkbox" :checked="todoIsDone.length === todos.length && todos.length>0" @change="changeHandler" />
    </label>
    <span> <span>已完成{{todoIsDone.length}}</span> / 全部{{todos.length}} </span>
    <button class="btn btn-danger" @click="clickHandler">清除已完成任务</button>
  </div>
</template>

<script>
export default {
  name: "TodoFooter",
  props: ["todos","todoIsDone","isAllDone","updateAllDone","deleteDoneTodo"],
  data() {
    return {};
  },
  methods: {
    changeHandler(e){
      this.updateAllDone(e.target.checked)
    },
    // 点击清除已完成
    clickHandler(){
      this.deleteDoneTodo();
    }
  },
};
</script>

<style lang="less" scoped>
</style>

11-自定义指令

hs 6/3/2022

# 1, 自定义指令

# 1.1, 自定义指令

在Vue的模板语法中我们学习过各种各样的指令:v-show、v-for、v-model等等,除了使用这些指令之外,Vue也允许我们来自定义自己的指令

  • 在Vue中,代码的复用和抽象主要还是通过组件
  • 通常在某些情况下,你需要对DOM元素进行底层操作,这个时候就会用到自定义指令

自定义指令分为两种

  • 自定义局部指令:组件中通过 directives 选项,只能在当前组件中使用
  • 自定义全局指令:app的 directive 方法,可以在任意组件中被使用

自定义局部指令

// MyComponent1.vue
<template>
  <!-- 
        前面学习过各种各样的指令:
            v-html  v-text  v-model  v-if  v-elseif  v-else  v-show  v-for  v-bind   v-on   v-cloak   v-pre  v-once

        组件:
            代码复用

        指令:
            对DOM元素进行底层操作时,可以使用自定义指令

        自定义指令分两类:
            1)自定义局部指令:只能在当前组件中使用
            2)自定义全局指令:在任意组件中使用

        自定义局部指令:
            也是一个配置项,叫directives

     -->
  <div>
      <h1>{{msg}}</h1>
      <hr>
      <h1 v-upper="msg"></h1>
      <hr>
      <h2 v-upper="msg2"></h2>
  </div>
</template>

<script>
export default {
  name: "MyComponent1",
  props: [],
  data() {
    return {
        msg:"hello vue",
        msg2:"abcdef"
    };
  },
  methods: {},
  // 自定义指令是配置在directives选项中的
  directives: {
    // 自定义指令,也是写成函数的形式
    // 使用时,需要以v-打头  v-upper
    // 当使用v-upper时,下面的函数就会执行一次
    // 把字符串中的小写字母变大写
    // upper(element,options) {
    //     // element 表示当前绑定指令的真实DOM节点
    //     // console.log("upper....");
    //     // console.log(element);
    //     // options表示当前指令一些配置项参数(对象)
    //     // console.log(options);

    //     element.innerHTML = options.value.toUpperCase();
    // },
  },
};
</script>

<style lang="less" scoped>
</style>
// MyComponent2.vue
<template>
  <div>
      <input v-focus type="text">
  </div>
</template>

<script>
export default {
  name: "MyComponent2",
  props: [],
  data() {
    return {};
  },
  methods: {},
  directives: {
    // 自定义局部指令
    // focus() {},
  },
};
</script>

<style lang="less" scoped>
</style>
// App.vue
<template>
  <div id="app">
    App
    <!-- 在MyCommont1组件中定义的自定义指令,在其它组件中是不能使用的 -->
    <p v-upper="msg3"></p>
    <hr>
    <MyComponent2></MyComponent2>
    <hr>
    <MyComponent1></MyComponent1>
  </div>
</template>

<script>
import MyComponent1 from "./components/MyComonent1.vue"
import MyComponent2 from "./components/MyComponent2.vue"
export default {
  name: 'App',
  data(){
    return{
      msg3:"abc"
    }
  },
  components: {
    MyComponent1,
    MyComponent2
  }
}
</script>

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

自定义全局指令

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

Vue.config.productionTip = false

// 自定义全局指令
// 第一个参数,表示自定义指令的名字,定义时,不要加v-
// 第二个参数,是一个回调函数
Vue.directive("upper",(element,options)=>{
  element.innerHTML = options.value.toUpperCase();
})

Vue.directive("focus",{
    // 配置钩函数  会在合适的时机,自动调用
    // 当被绑定的元素插入到 DOM 中时……
    inserted(element){
      // element是对应的DOM元素
      console.log("element:",element);
      console.log("inserted........");
      element.focus(); // 自动获取焦点
    }
})

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

12-过滤器

hs 6/3/2022

# 1, 过滤器

# 1.1, 过滤器的使用

Vue.js 允许你自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)

  • 过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示

局部过滤器

// MyComponent1.vue
<template>
  <div>
    <!-- 使用过滤器,需要对time时间进行格式化,进行过滤 -->
    <h1>{{ time | timeFormat }}</h1>   
  </div>
</template>

<script>
// 引入moment  moment是专门用来处理时间
import moment from "moment";
export default {
  name: "MyComponent1",
  props: [],
  data() {
    return {
      time: Date.now(),
    };
  },
  methods: {},
  // 在filters选项中,可以配置局部过滤器
  // 在这里配置的局部过滤器,只能在当前组件中使用
  //   filters: {
  //       // 定义了一个过滤器,叫timeFormat
  //       // 在模板中,就可以使用过滤器了
  //       timeFormat(params){
  //         //   console.log(params);
  //         // 利用moment对时间进行格式化
  //         // return 666;

  //         // 需要return一个格式化后的时间
  //         return moment(params).format("YYYY-MM-DD")
  //       }
  //   },
};
</script>

<style lang="less" scoped>
</style>
// MyComponent2.vue
<template>
  <div>
      <h1>{{ time | timeFormat }}</h1>
  </div>
</template>

<script>
export default {
  name: "MyComponent1",
  props: [],
  data() {
    return {
        time:Date.now(),
    };
  },
  methods: {},
};
</script>

<style lang="less" scoped>
</style>
// App.vue
<template>
  <div id="app">
    App
    <hr>
    <MyComponent1></MyComponent1>
    <hr>
    <MyComponent2></MyComponent2>
  </div>
</template>

<script>
import MyComponent1 from "./components/MyComponent1.vue"
import MyComponent2 from "./components/MyComponent2.vue"
export default {
  name: 'App',
  data(){
    return{
      msg3:"abc"
    }
  },
  components: {
    MyComponent1,
    MyComponent2
  }
}
</script>

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

全局过滤器

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

import moment from "moment"

Vue.config.productionTip = false

// 定义全局过滤器
// 第一个参数是过滤器的名字
// 第二个参数是回调函数  
Vue.filter("timeFormat",(val)=>{
  return moment(val).format("YYYY-MM-DD")
})


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

13-自定义插件

hs 6/3/2022

# 1, 自定义插件

# 1.1, 自定义插件

插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种:

  • 添加全局方法或者 property。如:vue-custom-element
  • 添加全局资源:指令/过滤器/过渡等。如 vue-touch
  • 通过全局混入来添加一些组件选项。如 vue-router
  • 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
  • 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router
// plugins/myplugins.js

// 开发自定义插件,目的是给项目提供一些全局的功能 
import moment from "moment"
// plugins对象,叫插件对象
let plugins = {
    // vue规定,一个插件对象身上,必须要有install方法
    // 当Vue.use时,内部会自动执行indtall方法
    install(Vue, options) {
        // 第一个参数是Vue构造函数  
        // 第二个参数是Vue.use时,传递的第二个参数
        // console.log(Vue);
        // console.log(options);

        // 把自定义指令封装到一个插件中
        Vue.directive("upper", (element, options) => {
            element.innerHTML = options.value.toUpperCase();
        })

        // 把过滤器封装到插件中
        Vue.filter("timeFormat", (val) => {
            return moment(val).format("MM-DD")
        })

        // 还可以在Vue的原型对象上,添加公共的方法
        Vue.prototype.$wc = function () {
            alert("这是一只小狗,叫wc")
        }

        // 注册全局组件
        Vue.component("Count", {
            data() {
                return {
                    count: 0
                }
            },
            methods: {
                add() { this.count++ },
                minus() { this.count-- }
            },
            // render函数后面说
            render: function (createElement) {
                return createElement(
                  'h1',   
                  {},
                  "我是Count组件"
                )
              },
        })

        // 后面会讲两个非常重要的插件,vue-router   vuex
    }
}

// 对外暴露插件  为了让别的模块去使用插件
export default plugins;
// MMyComponent1.vue
<template>
  <div>
      <h1 v-upper="msg"></h1>
      <Count></Count>
  </div>
</template>

<script>
export default {
  name: "MyComponent1",
  props:[],
  data() {
    return {
        msg:"hello vue"
    };
  },
  methods: {},
};
</script>

<style lang="less" scoped>
</style>
// MMyComponent2.vue
<template>
  <div>
    <h1>{{ time | timeFormat }}</h1>
    <Count></Count>
  </div>
</template>

<script>
export default {
  name: "MyComponent2",
  props:[],
  data() {
    return {
      time:Date.now()
    };
  },
  methods: {},
};
</script>

<style lang="less" scoped>
</style>
// MMyComponent3.vue
<template>
  <div>
    <button @click="fn">点我</button>
    <Count></Count>
  </div>
</template>

<script>
export default {
  name: "MyComponent3",
  props: [],
  data() {
    return {};
  },
  methods: {
    fn() {
      // this  表示VC
      this.$wc();
    },
  },
};
</script>

<style lang="less" scoped>
</style>
// App.vue
<template>
  <div id="app">
    App
    <hr>
    <MyComponent1></MyComponent1>
    <hr>
    <MyComponent2></MyComponent2>
    <hr>
    <MyComponent3></MyComponent3>
  </div>
</template>

<script>
import MyComponent1 from "./components/MyComponent1.vue"
import MyComponent2 from "./components/MyComponent2.vue"
import MyComponent3 from "./components/MyComponent3.vue"
export default {
  name: 'App',
  data(){
    return{
      msg3:"abc"
    }
  },
  components: {
    MyComponent1,MyComponent2,MyComponent3
  }
}
</script>

<style lang="less">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
// main.js
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

// 使用插件
import myplugins from "./plugins/myplugins"
Vue.use(myplugins,{name:"upper"})

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

14-自定义事件与todolist案例

hs 6/3/2022

# 1, 自定义事件

# 1.1, 子组件传递给父组件

什么情况下子组件需要传递内容到父组件呢

  • 当子组件有一些事件发生的时候,比如在组件中发生了点击,父组件需要切换内容
  • 子组件有一些内容想要传递给父组件的时候

自定义事件的操作步骤

  • 首先,我们需要在子组件中定义好在某些情况下触发的事件名称
  • 其次,在父组件中以v-on的方式传入要监听的事件名称,并且绑定到对应的方法中
  • 最后,在子组件中发生某个事件的时候,根据事件名称触发对应的事件
// App.vue
<template>
  <div class="app">
    <h1>我是父组件 王健林</h1>
    <hr>
    <!-- 
      在使用子组件的位置绑定自定义事件

      事件源:MySon
      事件类型:wc
      监听器:handler

      如何触发MySon事件?
      答:在事件源(组件)内部通过$emit手动写代码触发!!!
          $emit("wc",500)
     -->
    <MySon @wc="handler" ref="cur"></MySon>
  </div>
</template>

<script>
import MySon from "./components/MySon.vue"
export default {
  name: 'App',
  components: {
    MySon
  },
  // 当组件挂载完毕执行一次mounted
  mounted(){  
    // 当代码走到这里,说明,组件都挂载,渲染完毕
    // 问:能不能获取组件实例? 答:可以

    // console.log(this.$refs.cur); // 得到VC实例

    // 可以通过$on绑定自定义事件
    // 当xq事件发生了,处罚监听器
    this.$refs.cur.$on("xq",(val)=>{
      // 谁触发了xq事件,就可以传递数据
      console.log("val:",val);
    })
  },  
  methods:{
    handler(val){
      // val是子传递给父的数据
      console.log("val:",val);
    }
  }
}
</script>

<style lang="less">
*{
  margin: 0;
  padding: 0;
}
html,body{
  width: 100%;
  height: 100%;
}
.app{
  width: 100%;
  height: 100%;
  background-color: pink;
}
</style>
// MySon.vue
<template>
  <div class="son">
      <h1>我是子组件 王思聪</h1>
      <!-- @click="clickHandler" 不叫自定义事件 -->
      <button @click="clickHandler">触发自定义事件</button>
  </div>
</template>

<script>
export default {
  name: "MySon",
  props:[],
  data() {
    return {
        money:500,
        car:"凤凰牌自行车"
    };
  },
  methods: {
      clickHandler(){
        //   console.log("clickHandler...");
        // 在这里,触发自定义事件

        // 在vc身上没有是$emit,沿着原型链可以去Vue的原型对象上找到$emit
        // console.log(this);

        // this.$emit("wc",this.money)

        this.$emit("xq",this.car)
      }
  },
};
</script>

<style lang="less" scoped>
div.son{
    width: 300px;
    height: 200px;
    background-color: gold;
    margin: 50px;
}
</style>

# 2, 自定义事件应用于TodoList

// App.vue
<TodoHeader @addTodo="addTodo"></TodoHeader>
// TodoHeader.vue
<template>
  <div class="todo-header">
    <input 
      type="text" 
      placeholder="请输入你的任务名称,按回车键确认" 
      @keyup.enter="enterHandler"
    />
  </div>
</template>

<script>
export default {
  name: "TodoHeader",
  data() {
    return {};
  },
  methods: {
    enterHandler(e){
      if(e.target.value.trim() === ""){
        alert("输入内容不能为空~")
        return;
      }

      // 拼装一个todo
      let newTodo = {id:Date.now(), title:e.target.value, done:false};

      // 触发自定义事件
      this.$emit("addTodo",newTodo)

      e.target.value = ""; // 清空输入框
    }
  },
};
</script>

<style lang="less" scoped>
</style>

15-事件总线

hs 6/3/2022

# 1, 事件总线

# 1.1, 事件总线

在开发中,我们构建了组件树之后,除了父子组件之间的通信之外,还会有非父子组件之间的通信

  • 全局事件总线
  • Provide/Inject
// ErZi1.vue
<template>
  <div class="erzi1">
      <h1>我是儿子1组件</h1>
      <button @click="$bus.$emit('maotai','一车茅台')">给老二一车茅台</button>
  </div>
</template>

<script>
export default {
  name: "ErZi1",
  props:[],
  data() {
    return {

    };
  },
  methods: {},
};
</script>

<style lang="less" scoped>
.erzi1{
    width: 400px;
    height: 200px;
    background-color: gold;
    margin: 50px 0px;
}
</style>
// ErZi2.vue
<template>
  <div class="erzi2">
    <h1>我是儿子2组件</h1>
    <SunZi></SunZi>
  </div>
</template>

<script>
import SunZi from "./SunZi.vue"
export default {
  name: "ErZi2",
  props: [],
  data() {
    return {};
  },
  mounted(){
    this.$bus.$on("maotai",val=>{
      console.log("val:",val);
    })
  },
  methods: {},
  components:{
    SunZi
  }
};
</script>

<style lang="less" scoped>
.erzi2 {
  width: 400px;
  height: 200px;
  background-color: pink;
  margin: 50px 0px;
}
</style>
// SunZi.vue
<template>
  <div class="sunzi">
      <span>我是孙子组件</span>
      <button @click="clickHandler">点击给爷爷100万</button>
  </div>
</template>

<script>
export default {
  name: "SunZi",
  props:[],
  data() {
    return {
      money:"100万"
    };
  },
  methods: {
    clickHandler(){
      // 发布
      this.$bus.$emit("money",this.money)
    }
  },
};
</script>

<style lang="less" scoped>
.sunzi{
    width: 300px;
    height: 100px;
    background-color: skyblue;
    margin: 20px 0px;
}
</style>
// App.vue
<template>
  <div id="app">
    <h1>我是App组件----{{money}}</h1>
    <ErZi1></ErZi1>
    <ErZi2></ErZi2>
  </div>
</template>

<script>
import ErZi1 from "./components/ErZi1.vue"
import ErZi2 from "./components/ErZi2.vue"
export default {
  name: 'App',
  data(){
    return{
      money:0
    }
  },
  mounted(){
    // 在mounted中进行订阅
    // 绑定一个自定义事件
    this.$bus.$on("money",(val)=>{
      console.log("爷爷接收money:",val);
      this.money = val
    })
  },
  components: {
    ErZi1,
    ErZi2
  }
}
</script>

<style lang="less">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
// main.js
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

// let $bus = new Vue(); // $buts就相当于是vm实例  通过$bus可以得到$on  $emit

// Vue.prototype.$bus = new Vue(); 

new Vue({
  beforeCreate(){
    // this表示vm
    // 配置全局事件总线,说白了,就是在Vue原型对象上添加$bus属性,值是vm
    // this是vm,不是vc
    // 在$bus身上有,$on和$emit  $on可以用来接收数据   $emit可以用来发送数据
    Vue.prototype.$bus = this;
  },
  render: h => h(App),
}).$mount('#app')

// 目前为止,vue中数据通信的方案?
//   1)props  自定义属性   一般是父传子  如果传递的是方法,也可以实现子传父
//   2)emit  自定义事件   子传子    子可以把数据传递给父
//   3)事件总线   $bus  值是一个vm   $on订阅   $emit发布   

16-github案例(数据请求)

hs 6/3/2022

# 1, 数据请求实战案例

// MyHeader.vue
<template>
   <section class="jumbotron">
      <h3 class="jumbotron-heading">Search Github Users</h3>
      <div>
        <input 
          type="text" 
          placeholder="enter the name you search"
          v-model="keyword"
        />&nbsp;<button @click="clickHandler">Search</button>
      </div>
    </section>
</template>

<script>
export default {
  name: "MyHeader",
  props:[],
  data() {
    return {
      keyword:""
    };
  },
  methods: {
    clickHandler(){
      if(this.keyword.trim() === ""){
        alert("用户名不能为空!")
        return;
      }
      // 目的:发送数据级兄弟
      this.$bus.$emit("sendKeyword",this.keyword)
      this.keyword = ""; // 清除输入框中的内容
    }
  },
};
</script>

<style lang="less" scoped>
</style>
// MyMain.vue
<template>
  <div>

    <!-- 欢迎界面 -->
    <div v-show="show==0">欢迎....</div>

    <!-- 正在加载中 -->
    <div v-show="show==1">正在加载中....</div>

    <!-- 正常显示用户信息 -->
    <div class="row" v-show="show==2">
      <div class="card" v-for="(card) in items" :key="card.id">
        <a href="https://github.com/xxxxxx" target="_blank">
          <img :src="card.avatar_url" style="width: 100px" />
        </a>
        <p class="card-text">{{ card.login }}</p>
      </div>
    </div>

    <!-- 失败 -->
    <div v-show="show==3">失败....</div>
  </div>
</template>

<script>
// import axios from "axios";
import request from "../api/request"
export default {
  name: "MyMain",
  props: [],
  // mounted() {
  //   this.$bus.$on("sendKeyword", async (val) => {

  //     // 发送ajax请求
  //     this.show = 1;

  //     // console.log("val:",val);
  //     try {
  //       // let result = await axios.get(
  //       //   `https://api.github.com/search/users?q=${val}`
  //       // );

  //       // 现在的api接口是 /search/users?q=${val}
  //       // 前面没有写域名,没有写端口
  //       // 默认就向当前服务器发请求
  //       // 当前服务器是谁?
  //       // 答:http://localhost:8081/
  //       let result = await axios.get(`/api/search/users?q=${val}`);
  //       // console.log(result);
  //       this.items = result.data.items;

  //       this.show = 2;
  //     } catch (error) {
  //       console.log("error:", error);

  //       this.show = 3;
  //     }
  //   });
  // },

   mounted() {
    this.$bus.$on("sendKeyword", async (val) => {
      this.show = 1;
      try {
        // request是我们二次封装的axios
        // 后面写项目,一般都是使用封装后的axios
        let result = await request.get(`/api/search/users?q=${val}`);
        this.items = result.items;

        this.show = 2;
      } catch (error) {
        console.log("error:", error);
        this.show = 3;
      }
    });
  },
  data() {
    return {
      items: [],
      show: 0, // 0表示欢迎界面   1表示正在加载   2表示用户信息   3失败
    };
  },
  methods: {},
};
</script>

<style lang="less" scoped>
</style>
// App.vue
<template>
  <div id="app">
    <div class="container">
      <MyHeader></MyHeader>
      <MyMain></MyMain>
    </div>
  </div>
</template>

<script>
import MyHeader from "./components/MyHeader.vue";
import MyMain from "./components/MyMain.vue";
export default {
  name: "App",
  data() {
    return {
      money: 0,
    };
  },
  mounted() {},
  components: {
    MyHeader,
    MyMain,
  },
};
</script>

<style lang="less">
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
// main.js
import Vue from 'vue'
import App from './App.vue'


Vue.config.productionTip = false


new Vue({
  beforeCreate(){
    Vue.prototype.$bus = this;
  },
  render: h => h(App),
}).$mount('#app')
// api/request.js
// 对axios的二次封装

import axios from "axios";
// 引入进度条
import nprogress from "nprogress";
// 引入对应的样式
import "nprogress/nprogress.css"

// 配置请求拦截器
axios.interceptors.request.use(config=>{
    // 在请求拦截器中,还可以做很多的事情

    // 开启进度条
    nprogress.start();
    return config
})

// 配置响应拦截器
axios.interceptors.response.use(res=>{
    nprogress.done();
    return res.data;
},()=>{
    // 响应失败
    nprogress.done();
    // 终止Promise链
    return new Promise();
})

// 导出我们封装后的axios
export default axios;

17-vuex状态管理

hs 6/3/2022

# 1, vuex状态管理

# 1.1, 为什么要使用Vuex

Vue应用程序的状态(state)管理器。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

  • 把“状态”换成“数据”。
  • 如果你的项目中,需要用到在各个子组件中共享数据,则你就需要用到vuex。

# 1.2,示例

设置如下组件:

  • 一个vue实例,充当根组件
  • 子组件AddNumber
  • 子组件SubNumber

希望在两个子组件AddNumber和subNumer之间共同维护和使用数据项:counter

  • 在vue实例中:显示 counter的值
  • 在子组件AddNumber中:显示 counter的值;添加counter的值
  • 在子组件subNumber中:显示 counter的值;减少counter的值
  • 由于上面的需要,我们需要在三个地方共同使用同一数据项counter,所以我们需要用到 vuex

第一步:先引入vue.js,再引入vuex.js

 <!-- 引入Vue.js -->
 <script src="./libs/vue.js"></script>
 <!-- 引入Vuex.js -->
 <script src="./libs/vuex.js"></script>

第二步:实例化vuex中的store对象

  • 实例化一个对象,就是通过new的方式去创建一个对象
// 创建一个仓库  需要传入一个配置对象
let store = new Vuex.Store({
    // state是状态  是集中管理的状态
    // 状态是响应式的
    // 组件中的data中的状态也是状态式
    state: {
        counter: 0
    },
    // 修改状态的唯一途径
    mutations: {
        // 每一个mutations就是一个函数
        // 第1个参数是仓库中的state
        // payload 是commit mutation时,传递的参数
        add(state, payload) {
            // state.counter++;
            state.counter += payload
        },
        sub(state) {
            state.counter--;
        }
    },
    // 放异步代码,如果有异步操作,代码需要写在actions中
    actions: {

    },
    // 类似于组件中的计算属性
    // 根据已有状态计算出一个新的状态
    getters: {

    }
});

第三步:注入到Vue实例中

 let vm = new Vue({
     el: "#app",
     store, // vuex也是一个插件,需要挂载到根组件上,就意味着,它的子子孙孙,都可以使用这个仓库了
     components: {
         AddCounter,
         SubCounter
     },
     data() {
         return {

         }
     }
 });

第四步:定义两个组件并在App中使用之

 let AddCounter = Vue.extend({
     template: `
        <div>
            <p>我是addcounter组件</p>
            <p>在addcounter组件中使用仓库中的数据:{{$store.state.counter}}</p>
            <button @click="add">加1</button>
        </div>
    `,
     methods: {
         add() {
             // 这样是粗暴地修改状态 极力不推荐
             // 如果其它组件也这样修改状态,状态不好追踪了
             // this.$store.state.counter++;

             // 需要commit一个mutation,通过mutation去修改状态
             this.$store.commit("add", 100)
         }
     }
 })
 let SubCounter = Vue.extend({
     template: `
        <div>
            <p>我是subcounter组件</p>
            <p>在subcounter组件中使用仓库中的数据:{{$store.state.counter}}</p>
            <button @click="sub">减1</button>
        </div>
    `,
     methods: {
         sub() {
             // this.$store.state.counter--;

             this.$store.commit("sub")
         }
     }
 })

第五步:使用store中的数据

  • 一旦你在vue的实例中注入了store,则在所有的子组件及 vue的实例中,你都可以通过:this.$store.state. 数据名 去获取数据
  • 类似于我们把router注入到vue实例中,我们就可以通过this.routerthis.router 和 this.route操作路由。
 <p>在addcounter组件中使用仓库中的数据:{{$store.state.counter}}</p>
 <p>在subcounter组件中使用仓库中的数据:{{$store.state.counter}}</p>
  • 可以通过:this.$store.state. 数据名 = 值 去修改数据,但是,vuex反对这么做

你可以有两种方法去使用数据

  • 获取:this.$store.state. 数据名
  • 修改:在组件内部通过this.$commit方法触发事件,执行mutations当中的对应的方法

# 2, 在vue脚手架中使用vuex

设置如下组件:

  • 一个vue实例,充当根组件
  • 子组件AddNumber。
  • 子组件subNumber。

希望在两个子组件AddNumber和subNumer之间共同维护和使用数据项:counter

  • 在vue实例中:显示 counter的值
  • 在子组件AddNumber中:显示 counter的值;添加counter的值
  • 在子组件subNumber中:显示 counter的值;减少counter的值

# 3, vuex的四个概念

  • state: 放数据。在组件之间共享
  • mutations: 修改数据 修改数据的唯一途径
  • getters: 从state中的数据中,取出一部分来,依据数据项产生新的结果。类似于vue实例中的computed(计算属性)。
  • actions: 在对数据实现异步操作时,要用的

# 3.1, store

  • 每一个 Vuex 应用的核心就是 store(仓库)
  • store是一个容器,包含着vue应用的状态(state)

两大特点

  • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新
  • 你不能直接更改 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交(commit) mutations。这样使得我们可以方便地跟踪每一个状态的变化

在组件中应该如何去获取保存在vuex. 中的数据

  • 方法一:this.$store.state.xxx
  • 方法二:变通一下,在组件内部用一个计算属性来获取数据
  • 方法三:mapState

mapState

  • this.$store.state.count写起来比较麻烦。在vuex中提供一个便捷的写法:mapState。
  • 它的作用:是把写在vuex.store.state中的数据直接映射到组件中计算属性中。

# 3.2, Getters

有时希望在state中的数据基础上,派生出一些其它的数据,此时可以使用getters

在组件中使用getters

  • 方法一:this.$store.getters.failedNumber
  • 方法二 :mapGetters
  • 与mapState类似,它的作用也是用来帮助我们去简化代码。我们如果直接定义一个计算属性也是可以的。

# 3.3, Mutations

作用是:它是唯一的用来修改数据的工具

在组件中使用的方式

  • 方法一:this.$store.commit
  • 方法二:mapMutations

# 3.4, Actions

在mutations中,如果操作是异步的,在控制台中并不能追踪到

  • 可以把异步操作写在actions中
  • 虽然把异步操作写在actions中,但是改变状态的唯一方式是不变,还是通过mutations

action与mutations的区别

  • actions 提交的是 mutations,而不是直接变更状态。
  • actions 可以包含任意异步操作。
  • action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,可以通过context.commit提交,也可以通过context.state获取state。

在组件中使用actions

  • 方法一: this.$store.dispatch(“actions名”)
  • 方法二:mapActions

# 4, Vuex版本的TodoMVC

目标

# 4.1, 分析

数据项

  • 准备两个数据项。放在vuex中,供组件去使用
  • todos:[{text:"学习vuex, So easy~",done:false},{text:"学习React, So easy~",done:false}]
  • visibilty:,筛选的条件 :all completed active

功能

  • 显示todos数据
  • 添加一条todos数据
  • 删除todos数据
  • 修改某一项的状态:完成,没有完成之间切换
  • 批量修改状态:全部完成,全部未完成
  • 统计没有完成的数量
  • 批量删除已经完成的
  • 三种状态的筛选
  • 编辑todos的内容
  • 本地存储

组件

  • 在脚手架工具的基础上,额外设置两个组件

# 4.2, 处理静态资源

  • 把app.vue中的template的内容用todo.html的内容来代替
  • 在main.js中引入对应的css

# 4.3, 创建并引入vuex

在main.js中去引入

import Vue from 'vue'
import App from './App.vue'
import store from "./store/index"

Vue.config.productionTip = false

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

# 4.4, 拆分组件(显示todo数据)

listTodo基本的使用

在app.vue组件中,使用listtodo组件

在app.vue组件中给listTodo传递数据,显示todos

现在对list-todo进行循环

效果如下:

通app.vue这个父组件给list-todo这个子组件进行数据传递

  • 因为我们需要把一个一个todos具体内容传递list-todo,让它来显示具体的信息

在使用子组件时,设置自定义属性

在子组件中,设置props

# 4.5, 添加一条todos数据(AddTodo组件)

在app.vue中使用

下面就要具体去实现添加一条todo的功能

  • 本质上需去vuex.store.state.todos中加一条记录,这里就是涉及修改vuex中的数据,必须要用mutations
  • 所以,我们应该先去vuex中去创建好一个mutations。

把这个方法直接映射到addTodo组件的methods中

# 4.6, 删除一条todos数据

删除操作也是要去修改数据,则对应的也要建立一个mutations。

这个删除操作是在listTodo组件中完成的。在 listTodo组件中给删除的按钮绑定事件

# 4.7, 修改某一项的状态:完成,没有完成之间切换

这个操作,本质就是要去修改某一个todo项的done属性。所以也需要在mutations中去写好对应的方法。

这个工作是在listTodo组件完成的,我们去给按钮加点击事件。

在app.vue组件,我们根据todo是否已经完成,设置一个特殊的类:

# 4.8, 批量修改状态:全部完成,全部未完成

  • 我们通过input的check属性来设置css样式。如果它是处于check,则说明全部的任务已经完成,则它的样式应该是“高亮”的。
  • 首先,我们要去判断当前情况下,是否所有的任务已经全部完成。如果全部完成,则它应该要处于check状态。
  • 现在,我们需要在原始数据的基础,加工得到一个新数据:是否全部完成。
  • 所以,应该要在vuex.store中的加一个getters

在app.vue中使用:

现在只是实现了:根据是否全部完成来设置input的checked。下面,我们还要给它加点击事件:

  1. 如果当前是处于checked ,则点击事件发生,要全部改成done:false
  2. 如果当前是没有选中,则点击事件发生,要全部改成done:true

上面的操作,也是要修改数据,则对应地要去建立mutations

在app.vue中:

使用toggleAllTodo

# 4.9, 统计没有完成的数量

不需要对数据进行修改,只是在原始数据的基础上,加工得到新数据。

  • 去vuex.store中设置一个 Getters

把它map到app.vue组件中,成为一个计算属性。

使用:

# 4.10, 批量删除已经完成的

这个按钮,只有在“完成了数量>0”的情况下才可见。

  • todos.length是所有任务的数量
  • unDoneNumber:还没有完成的数量
  • 当Todos.length >unDoneNumber 说明,有一部分是已经完成了的。此时应该要显示这个按钮

下面,接给它添加点击事件,点击之后,要把已经完成的任务从整个的todos当中删除掉。由于这里涉及修改数据 ,我们需要去建立 一个mutations。

下面,把这个功能映射到app.vue组件的methods 中。

给按钮加点击事件:

# 4.11, 三种状态的筛选

这个操作不会更改数据,只是在原数据的基础上进行筛选,得到新数据。

  • 所以,我们去创建getters

每次点击不同的按钮,就相当于是要去修改visibility,所以,我们要去设置一个mutations

把上面的changeVisibility映射到app.vue组件的方法中去。

使用:

最重要的一点:现在的数据的来源不是整个的todos,而是根据当前条件过滤的数据:

  • 把原来的todos改成了filterTodo。
  • 由于对于数据的来源产生的变化,所以我们重新去写一个getter来处理是否显示“清除已完成”

# 4.12, 编辑todos的内容

当我们在todos的内容上双击时,则会处于编辑状态:

开启编辑状态:加一个editing这个类.

  • 去listtodo中,设置双击加一个类的效果
  • 给listTodo这个组件加一个isEdit数据项,用它来表示当前是否处于编辑状态

对文件框和整个的li,它们在编辑状态会有不同的样式:

对文本框来说:

  • 初始值就是当前的todo的text.

  • 它失去焦点,或者是回车时,都应该结束编辑

上面的finishEdit是一个方法,如下:

但,结束编辑时,应该去修改数据。我们应该定义 一个mutations去处理这种情况。

下面,我们把这个mutations映射到listTodo组件的methods中去。

修改finishEdit方法:

# 4.13, 本地存储

新建一个模块:

在store/index.js中引入这个模块

18-过渡动画

hs 6/3/2022

# 1, Vue中的动画

# 1.1, Vue中的动画介绍

Vue中为我们提供一些内置组件和对应的API来完成动画,利用它们我们可以方便的实现过渡动画效果

  • 没有动画的情况下,整个内容的显示和隐藏会非常的生硬
  • 如果我们希望给单元素或者组件实现过渡动画,可以使用 transition 内置组件来完成动画

# 1.2, Vue的transition动画

Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡

  • 条件渲染 (使用 v-if)条件展示 (使用 v-show)
  • 动态组件
  • 组件根节点

当插入或删除包含在 transition 组件中的元素时,Vue 将会做以下处理

  • 自动嗅探目标元素是否应用了CSS过渡或者动画,如果有,那么在恰当的时机添加/删除 CSS类名
  • 如果 transition 组件提供了JavaScript钩子函数,这些钩子函数将在恰当的时机被调用
  • 如果没有找到JavaScript钩子并且也没有检测到CSS过渡/动画,DOM插入、删除操作将会立即执行

过渡动画class(Vue就是帮助我们在这些class之间来回切换完成的动画)

  • v-enter-from:定义进入过渡的开始状态

    • 在元素被插入之前生效,在元素被插入之后的下一帧移除。
  • v-enter-active:定义进入过渡生效时的状态,

    • 在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线 函数
  • v-enter-to:定义进入过渡的结束状态

    • 在元素被插入之后下一帧生效 (与此同时 v-enter-from 被移除),在过渡/动画完成之后移除
  • v-leave-from:定义离开过渡的开始状态

    • 在离开过渡被触发时立刻生效,下一帧被移除
  • v-leave-active:定义离开过渡生效时的状态

    • 在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟 和曲线函数
  • v-leave-to:离开过渡的结束状态

    • 在离开过渡被触发之后下一帧生效 (与此同时 v-leave-from 被删除),在过渡/动画完成之后移除

class的name命名规则如下

  • 如果我们使用的是一个没有name的transition,那么所有的class是以 v- 作为默认前缀
  • 如果我们添加了一个name属性,比如 ,那么所有的class会以 wc- 开头
// App.vue
<template>
  <div>
    <button @click="show = !show">点击我切换显示与隐藏</button>
    <!-- transiton:组件,全局组件。可以在任意组件内直接使用-->
    <!-- name属性值:如果页面中有多个多度动画需求,区分样式类名。 如果没有书写name,默认类名v-xxx开头,
       如果有name属性值,类名就以name属性值开头使用。这样可以去多个过渡动画效果!!!
     -->
    <transition name="jch">
      <div class="box" v-show="show">
        <h3>我是H3标题</h3>
      </div>
    </transition>
  </div>
</template>

<script>
export default {
  name: "",
  data() {
    return {
      show: false,
    };
  },
};
</script>

<!-- 在style标签身上加上一个lang=less属性,代表style里面书写的less样式 -->
<!-- 进入阶段的过渡动画 -->
<style scoped lang="less">
@color: cyan;
@f: red;
.box {
  width: 400px;
  height: 200px;
  background: @color;
  h3 {
    color: @f;
  }
}
/* 进入阶段的过渡动画*/
/*进入开始阶段*/
.jch-enter {
  opacity: 0;
  transform: rotate(0deg);
}

/* 定义进入阶段过渡动画时间、速率类名地方*/
.jch-enter-active {
  /*定义过渡动画时间、速率等等*/
  transition: all 5s;
}

/*进入结束阶段*/
.jch-enter-to {
  opacity: 1;
  transform: rotate(360deg);
}

/*定义离开阶段的过渡动画*/
/*
  离开阶段的开始状态
*/
.jch-leave {
  opacity: 1;
  transform: scale(1);
}

/* 定义离开阶段过渡动画时间、速率类名地方*/
.jch-leave-active {
  transition: all 3s;
}
/*离开阶段的结束状态*/
.jch-leave-to {
  opacity: 0;
  transform: scale(0);
}
</style>

过渡动画遮罩层

// components/ElDialog.vue
<template>
  <div>
    <transition>
      <div class="mask" v-show="visible"></div>
    </transition>
  </div>
</template>

<script>
export default {
  name: "",
  props: ["visible"],
};
</script>

<style scoped>
.mask {
  position: fixed;
  left: 20%;
  top: 20%;
  right: 20%;
  bottom: 20%;
  background: rgba(0, 0, 0, 0.5);
  z-index: 1000;
}

.v-enter {
  transform: scale(0);
}

.v-enter-active {
  transition: all 2s;
}

.v-enter-to {
  transform: scale(1);
}

.v-leave{
 transform: scale(1);
 border-radius: 0px;
}
.v-leave-active{
  transition: all 2s;
}
.v-leave-to{
 transform: scale(0);
 border-radius:50%;
}
</style>
// App.vue
<template>
  <div>
       <button @click="visible=!visible">我是按钮需要遮罩层组件</button>
       <ElDialog :visible="visible"></ElDialog>
  </div>
</template>

<script>
import ElDialog from '@/components/ElDialog'
export default {
  name: '',
  data() {
    return {
      visible:false
    }
  },
  components:{
    ElDialog
  }
}
</script>

<style scoped>

</style>

19-路由

hs 6/3/2022

# 1, 认识前端路由

# 1.1, 前端路由介绍

路由其实是网络工程中的一个术语

  • 在架构一个网络时,非常重要的两个设备就是路由器和交换机
  • 目前在我们生活中路由器也是越来越被大家所熟知,因为我们生活中都会用到路由器
  • 路由器主要维护的是一个映射表,映射表会决定数据的流向

前端路由的实现方案(前端路由是如何做到URL和内容进行映射呢?监听URL的改变)

  • 前端路由的核心是什么呢?改变URL,但是页面不进行整体的刷新
  • URL的hash
  • HTML5的History

URL的hash

  • URL的hash也就是锚点(#), 本质上是改变window.location的href属性
  • 我们可以通过直接赋值location.hash来改变href, 但是页面不发生刷新
  • hash的优势就是兼容性更好,在老版IE中都可以运行,但是缺陷是有一个#,显得不像一个真实的路径
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <a href="#/home">home</a>
        <a href="#/about">about</a>
        <div class="router-view"></div>
    </div>

    <script>
        // 1,获取router-view
        const routerViewEl = document.querySelector(".router-view");
        // 2, 监听hashchange
        window.addEventListener("hashchange", () => {
            switch (location.hash) {
                case "#/home":
                    routerViewEl.innerHTML = "home";
                    break;
                case "#/about":
                    routerViewEl.innerHTML = "about";
                    break;
                default:
                    routerViewEl.innerHTML = "default";
            }
        })
    </script>
</body>

</html>

HTML5的History(history接口是HTML5新增的, 它有六种模式改变URL而不刷新页面)

  • replaceState:替换原来的路径
  • pushState:使用新的路径
  • popState:路径的回退
  • go:向前或向后改变路径
  • forward:向前改变路径
  • back:向后改变路径
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title></title>
</head>
<style>
    * {
        /* global font */
        font-family: Verdana;
        font-size: 18px;
    }

    #root {
        display: flex;
        flex-direction: row;
    }

    #content {
        display: flex;
        display: block;
        width: 800px;
        height: 250px;
        /* vertically centered text */
        line-height: 250px;
        border: 2px solid #555;
        margin: 32px;
        text-align: center;
    }

    .route {
        cursor: pointer;
        justify-content: center;
        width: 150px;
        height: 50px;
        /* vertically centered text */
        line-height: 50px;
        position: relative;
        border: 2px solid #555;
        background: white;
        text-align: center;
        margin: 16px;
    }

    .route.selected {
        background: yellow;
    }
</style>

<body>
    <section id="root">
        <section class="route" id="home">/home</section>
        <section class="route" id="about">/about</section>
        <section class="route" id="gallery">/gallery</section>
        <section class="route" id="contact">/contact</section>
        <section class="route" id="help">/help</section>
    </section>
    <main id="content">Content loading...</main>
</body>
<script type="text/javascript">
    window.onload = event => {
        //监听 路由的点击事件  执行push方法
        window["home"].addEventListener("click", event => push(event))
        window["about"].addEventListener("click", event => push(event))
        window["gallery"].addEventListener("click", event => push(event))
        window["contact"].addEventListener("click", event => push(event))
        window["help"].addEventListener("click", event => push(event))
    }

    function select_tab(id) {
        // 改变 路由按钮的样式
        document.querySelectorAll(".route").forEach(item => item.classList.remove('selected'));
        document.querySelectorAll("#" + id).forEach(item => item.classList.add('selected'));
    }

    function load_content(id) {
        //更新内容
        document.querySelector("#content").innerHTML = 'Content loading for /' + id + '...';
    }

    function push(event) {
        let id = event.target.id;
        select_tab(id);
        document.title = id;
        load_content(id);
        window.history.pushState({
            id
        }, `${id}`, `/page/${id}`);
    }
    window.addEventListener("popstate", event => {
        let stateId = event.state.id;
        select_tab(stateId);
        load_content(stateId);
    });
</script>

</html>

# 2, 认识vue-router

# 2.1, vue-router介绍

目前前端流行的三大框架, 都有自己的路由实现

  • Angular的ngRouter
  • React的ReactRouter
  • Vue的vue-router

Vue Router 是 Vue.js 的官方路由

  • 它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用(SPA)变得非常容易
  • 路由用于设定访问路径, 将路径和组件映射起来
  • 在vue-router的单页面应用中, 页面的路径的改变就是组件的切换
  • 安装Vue Router:npm install vue-router@3.5.3

路由的使用步骤

  • 第一步:创建路由需要映射的组件(打算显示的页面)

  • 第二步:通过new VueRouter创建路由对象,并且传入routes和history模式

    • 安装插件 Vue.use(VueRouter);
    • 配置路由映射: 组件和路径映射关系的routes数组
    • 创建基于hash或者history的模式
  • 第三步:通过 router 配置参数注入路由,从而让整个应用都有路由功能

  • 第四步:路由使用: 通过router-link和router-view

// router/index.js

//配置路由
//引入vue-router插件:经过打印查看,引入进来的VueRouter构造函数
//vue基础学习构造函数:Vue、VueComponent、Vuex.Store、VueRouter
import VueRouter from 'vue-router';
import Vue from 'vue';
//安装插件
Vue.use(VueRouter);
//引入路由组件
import Home from '../pages/Home';
import About from '../pages/About';

//配置项目的路由
//通过VueRouter【路由器】类,创建了一个VueRouter类的一个实例!!!
//对外暴露
export default new VueRouter({
    mode: 'history',
    routes: [{
    // path配置的是根路径: /
    path: "/",
    // redirect是重定向, 也就是我们将根路径重定向到/home的路径下, 这样就可以得到我们想要的结果了
    redirect: "/home"
}, {
    //path设置路由的K,path有的属性值务必都是小写的
    path: "/home",
    //component设置路由的V,一个K对应一个V
    component: Home,
}, {
    path: '/about',
    component: About
},
    ]
});
// main.js
import Vue from 'vue'
import App from './App.vue'
import router from "./routes/router"

// 把路由对象挂载到根实例上
// 那么它的子子孙孙都可以使用这个路由对象
new Vue({
    router,
    render: h => h(App),
}).$mount('#app')
// Home.vue
<template>
  <div>
    <h1>Home组件</h1>
  </div>
</template>

<script>
export default {
  name: "Home",
};
</script>

<style>
</style>
// About.vue
<template>
  <div>
      <h1>About组件</h1>
  </div>
</template>

<script>
export default {
  name:"About"
}
</script>

<style>

</style>
// App.vue
<template>
  <div id="app">
    <!-- router-link 当成a标签 -->
    <router-link to="/home" active-class="active">Home</router-link> &nbsp;&nbsp;
    <router-link to="/about" active-class="active">About</router-link>
    <hr>
    <!-- 路由的出口:路由匹配到的组件,需要放到路由出口 -->
    <router-view />
  </div>
</template>

<script>
export default {
  name: "App",
};
</script>

<style lang="less">
.active{
  color: red;
}
</style>

# 3, router-link

# 3.1, router-link属性

router-link属性

  • to属性: 是一个字符串,或者是一个对象
  • replace属性:设置 replace 属性的话,当点击时,会调用 router.replace(),而不是 router.push();
  • active-class属性:设置激活a元素后应用的class,默认是router-link-active
  • exact属性:精确匹配模式

# 4, 路由懒加载

# 4.1, 路由懒加载

当打包构建应用时,JavaScript 包会变得非常大,影响页面加载:

  • 如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效
  • 也可以提高首屏的渲染效率
  • Vue Router默认就支持动态来导入组件
  • 因为component可以传入一个组件,也可以接收一个函数,该函数需要放回一个Promise,而import函数就是返回一个Promise
  • 分包是没有一个很明确的名称的,其实webpack从3.x开始支持对分包进行命名(chunk name)
{
    path: "/",
    redirect: "/home"
}, {
    path: "/home",
    component: ()=>import(/* webpackChunkName: "home" */"../pages/Home.vue"),
}, {
    path: '/about',
    component: ()=>import(/* webpackChunkName: "about" */"../pages/About.vue"),
},

打包后的代码如下:

# 5, 路由其他属性

# 5.1, 路由其他属性

  • name属性:路由记录独一无二的名称;
  • meta属性:自定义的数据
{
    path: "/",
    redirect: "/home"
}, {
    path: "/home",
    component: ()=>import(/* webpackChunkName: "home" */"../pages/Home.vue"),
    name:"home"
}, {
    path: '/about',
    component: ()=>import(/* webpackChunkName: "about" */"../pages/About.vue"),
    name:"about",
    meta:{
        name:"wc",
        age:18
    }
},

# 6, 嵌套路由和404组件

# 6.1, 嵌套路由

什么是路由的嵌套呢?

  • 我们匹配的Home、About等都属于第一级路由,我们在它们之间可以来回进行切换

  • Home页面本身,也可能会在多个组件之间来回切换

    • 比如Home中包括Cart、Mine,它们可以在Home内部来回切换
    • 这个时候我们就需要使用嵌套路由,在Home中也使用 router-view 来占位之后需要渲染的组件

404组件

  • 对于哪些没有匹配到的路由,我们通常会匹配到固定的某个页面
  • 在路由规则最后面配置:{ path:"*", component:NotFount },
import Vue from "vue";
import VueRouter from "vue-router"

// 自定义的组件  直接引入  下面的组件,一上来,就全部引入
import Home from "../components/Home"
// import About from "../components/About"
import NotFount from "../components/NotFount"
import Cart from "../components/Cart"
// import Mine from "../components/Mine"

// 能不能点击某个连接时,用到了这个组件,再去引入呢?
// 答:可以,使用路由的懒加载


Vue.use(VueRouter);

// 配置路由规则
let routes = [
    { path:"/", redirect:"/home" },
    { 
        path:"/home", 
        component:Home,
        children:[
            { path:"/home", redirect:"/home/cart" },
            { path:"/home/cart", component:Cart },
            { path:"/home/mine", component:()=>import(/* webpackChunkName: "Mine" */"../components/Mine") },
        ]
    },
    // { path:"/about", component:About },
    { path:"/about", component:()=>import(/* webpackChunkName: "About" */"../components/About") },
    { path:"*", component:NotFount },
];

let router = new VueRouter({
    // hash路由有一个特点,就是#
    mode:"hash", // hash路由
    routes
})

export default router;
// Home.vue
<template>
  <div>
    <h1>Home组件</h1>
    <router-link to="/home/cart">购物车</router-link>  &nbsp;&nbsp;
    <router-link to="/home/mine">我的</router-link>
    <hr>
    <router-view />
  </div>
</template>

<script>
export default {
  name: "Home",
};
</script>

<style>
</style>
// About.vue
<template>
  <div>
      <h1>About组件</h1>
  </div>
</template>

<script>
export default {
  name:"About"
}
</script>

<style>

</style>
// Cart.vue
<template>
  <div>
      <h3>购物车</h3>
  </div>
</template>

<script>
export default {

}
</script>

<style>

</style>
// Mine.vue
<template>
  <div>
      <h3>我的</h3>
  </div>
</template>

<script>
export default {

}
</script>

<style>

</style>
// NotFount.vue
<template>
  <div>
      <h3>你的页面飞了</h3>
  </div>
</template>

<script>
export default {

}
</script>

<style>

</style>
// App.vue
<template>
  <div id="app">
    <!-- router-link 当成a标签 -->
    <router-link to="/home" active-class="active">Home</router-link> &nbsp;&nbsp;
    <router-link to="/about" active-class="active">About</router-link>
    <hr>
    <!-- 路由的出口:路由匹配到的组件,需要放到路由出口 -->
    <router-view />
  </div>
</template>

<script>
export default {
  name: "App",
  data(){
    return{
    }
  },
  methods:{
  
  },
  components: {
  },
};
</script>

<style lang="less">
.active{
  color: red;
}
</style>

# 7, 动态路由

# 7.1, 动态路由基本匹配

很多时候我们需要将给定匹配模式的路由映射到同一个组件

  • 例如,我们可能有一个 User 组件,它应该对所有用户进行渲染,但是用户的ID是不同的;
  • 在Vue Router中,我们可以在路径中使用一个动态字段来实现,我们称之为 路径参数;

获取动态路由的值

  • 在template中,直接通过 $route.params获取值
  • 在created中,通过 this.$route.params获取值
import VueRouter from "vue-router";
import Vue from "vue";

import Home from "../components/Home"
import About from "../components/About"
import Mine from "../components/Mine"
import NotFount from "../components/NotFount"

Vue.use(VueRouter);

let routes = [
    { path:"/", redirect:"/home" },
    { path:"/home", component:Home },
    { path:"/about", component:About },
    { path:"/mine/:name/:age/:address", component:Mine },
    { path:"*", component:NotFount },
];

let router = new VueRouter({
    // hash带#    
    // history不带#
    mode:"history",
    routes
})

export default router;
// Home.vue
<template>
  <div>
      <h2>Home</h2>
  </div>
</template>

<script>
export default {

}
</script>

<style>

</style>
// About.vue
<template>
  <div>
      <h2>About</h2>
  </div>
</template>

<script>
export default {

}
</script>

<style>

</style>
// Mine.vue
<template>
  <div>
      <h2>Mine -- {{ name }}</h2>
      <h2>Mine -- {{ age }}</h2>
      <h2>Mine -- {{ address }}</h2>
  </div>
</template>

<script>
export default {
  // 需要获取动态路由参数
  // 使用路由,需要知道4个知识点
  //     1)<router-view />  路由出口
  //     2)<router-link />  a标签   点击跳转
  //     3)$router  对象  有一堆的方法  挂载到当前组件实例上  this
  //     3)$route   对象  有一堆的属性  挂载到当前组件实例上  this

  data(){
    return{
      name:"",
      age:"",
      address:"",
    }
  },
  // 生命周期函数(钩子函数)
  created(){
    // 获取动态路由参数  
    console.log(this.$route);
    console.log(this.$route.params.name);

    this.name = this.$route.params.name
    this.age = this.$route.params.age
    this.address = this.$route.params.address
  }

}
</script>

<style>

</style>
// NotFount.vue
<template>
  <div>
      <h2>404</h2>
  </div>
</template>

<script>
export default {

}
</script>

<style>

</style>
// App.vue
<template>
  <div id="app">
    <router-view />
  </div>
</template>

<script>
export default {
  name: "App",
  data(){
    return{
    }
  },
  methods:{
  
  },
  components: {
  },
};
</script>

<style lang="less">
.active{
  color: red;
}
</style>

在router-link中进行如下跳转:

<router-link to="/main/wc/18/bj">我的</router-link>

# 8, 编程式路由(代码的页面跳转)

# 8.1, 代码的页面跳转

有时候我们希望通过代码来完成页面的跳转,比如点击的是一个按钮

  • 此时,我们可以使用编程式路由

  • this.$router.push("/about")

  • 当然,我们也可以传入一个对象

    • this.$router.push({path:"/about"})
    • this.$router.push({name:"about"})

query方式的参数

  • this.$router.push({path:"/about",query:{name:"wc",age:18}})

  • 在界面中通过 $route.query 来获取参数

    • query: -

params方式的参数

  • this.$router.push({path:"/about",params:{ address:"bj" }})
  • 在界面中通过 $route.params 来获取参数

页面的前进后退

  • router的go方法:

    • router.go(1) 向前移动一条记录,与router.forword()相同
    • router.go(-1) 向后移动一条记录,与router.back()相同
    • router.go(3) 前进3条记录
    • router.go(100) 如果没有那么多记录,静默失败
    • router.go(-100) 如果没有那么多记录,静默失败
  • router也有back:

    • 通过调用 history.back() 回溯历史。相当于 router.go(-1)
  • router也有forward:

    • 通过调用 history.forward() 在历史中前进。相当于 router.go(1)
import VueRouter from "vue-router";
import Vue from "vue";

import Home from "../components/Home"
import About from "../components/About"
import Mine from "../components/Mine"
import NotFount from "../components/NotFount"

Vue.use(VueRouter);

let routes = [
    { path:"/", redirect:"/home" },
    { path:"/home", name:"home", component:Home },
    { path:"/about", name:"about", component:About },
    { path:"/mine", name:"mine", component:Mine },
    { path:"*", component:NotFount },
];

let router = new VueRouter({
    // hash带#    
    // history不带#
    mode:"history",
    routes
})

export default router;
// Home.vue
<template>
  <div>
    <h2>Home</h2>
    <button @click="toAbout">去about</button>
    <button @click="forward">forward</button>
  </div>
</template>

<script>
export default {
  methods: {
    toAbout() {
      // 实现跳转到about
      // this上有route  还有router
      // route上有一堆的属性
      // router上有一堆方法
      // 跳转方式一:
      // this.$router.push("/about")

      // 跳转方式二:
      // this.$router.push({ path:"/about" })

      // 跳转方式三:
      // this.$router.push({ name:"about" })

      // 跳转传参方式一:
      // this.$router.push({ name:"about", params:{ address:"bj" } })

      // 跳转传参方式二:
      this.$router.push({ name:"about", query:{ score:"100分" } })

      // replace
      // this.$router.replace({ name:"about", query:{ score:"100分" } })
    },

    forward(){
      // this.$router.forward()
      this.$router.go(1)  // go(1) 等价于 forward
    }
  },
};
</script>

<style>
</style>
// About.vue
<template>
  <div>
      <h2>About</h2>
      <!-- <p>{{ $route.params.address }}</p> -->
      <p>{{ $route.query.score }}</p>
      <button @click="back">back</button>
  </div>
</template>

<script>
export default {
  // 4个东西:两个组件   两个对象route  rotuer
  created(){
    console.log(this.$route);
    // console.log(this.$route.params.address);

    console.log(this.$route.query.score);


  },
  methods:{
    back(){
      // this.$router.back();
      this.$router.go(-1); // go(-1) 等价于 back
    }
  }
}
</script>

<style>

</style>
// Mine.vue
<template>
  <div>
      <h2>Mine</h2>
  </div>
</template>

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

<style>

</style>
// NotFount.vue
<template>
  <div>
      <h2>404</h2>
  </div>
</template>

<script>
export default {

}
</script>

<style>

</style>
// App.vue
<template>
  <div id="app">
    <router-link to="/home" active-class="active">home</router-link> &nbsp;
    <router-link to="/about" active-class="active">about</router-link> &nbsp;
    <router-link to="/mine" active-class="active">mine</router-link> &nbsp;
    <hr>
    <router-view />
  </div>
</template>

<script>
export default {
  name: "App",
  data(){
    return{
    }
  },
  methods:{
  
  },
  components: {
  },
};
</script>

<style lang="less">
.active{
  color: red;
}
</style>

# 9, 路由导航守卫

# 9.1, 路由导航守卫

vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航

全局的前置守卫beforeEach是在导航触发时会被回调的:

  • to:即将进入的路由Route对象
  • from:即将离开的路由Route对象
  • next: 在Vue2中我们是通过next函数来决定如何进行跳转的,是在Vue3中我们是通过返回值来控制的,不再推荐使用next函数,这是因为开发中很容易调用多次next;
// /router/index.js
import VueRouter from "vue-router";
import Vue from "vue";

import Home from "../components/Home"
import About from "../components/About"
import Mine from "../components/Mine"
import Login from "../components/Login"
import NotFount from "../components/NotFount"

Vue.use(VueRouter);

let routes = [
    { path:"/", redirect:"/home" },
    { path:"/home", name:"home", component:Home },
    { path:"/about", name:"about", component:About },
    { path:"/mine", name:"mine", component:Mine },
    { path:"/login", name:"login", component:Login },
    { path:"*", component:NotFount },
];

let router = new VueRouter({
    // hash带#    
    // history不带#
    mode:"history",
    routes
})

// 配置全局路由守卫
// to表示去哪
// from表示从哪里来
// next表示是否放行  放行到哪里
router.beforeEach((to,from,next)=>{
    // console.log("from:",from);
    // console.log("to:",to);
    if(to.path !== "/login"){
        // 去的地方不是/login
        // 只有登录了,才能去/home /about /mine
        if(window.isLogin){
            // 表示已登录
            next();
        }else{
           return next("/login");
        }
    }
    next();
});

export default router;
// Home.vue
<template>
  <div>
    <h2>Home</h2>
  </div>
</template>

<script>
export default {
  methods: {
    toAbout() {
     
    },
  },
};
</script>

<style>
</style>
// About.vue
<template>
  <div>
      <h2>About</h2>
  </div>
</template>

<script>
export default {
  methods:{
  }
}
</script>

<style>

</style>
// Mine.vue
<template>
  <div>
      <h2>Mine</h2>
  </div>
</template>

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

<style>

</style>
// Login.vue
<template>
  <div>
      <button @click="login">登录</button>
  </div>
</template>

<script>
export default {
    methods:{
        login(){
            // 给GO上放一个isLogin是true
            window.isLogin = true;
        }
    }
}
</script>

<style>

</style>
// NotFount.vue
<template>
  <div>
      <h2>404</h2>
  </div>
</template>

<script>
export default {

}
</script>

<style>

</style>
// App.vue
<template>
  <div id="app">
    <router-link to="/home" active-class="active">home</router-link> &nbsp;
    <router-link to="/about" active-class="active">about</router-link> &nbsp;
    <router-link to="/mine" active-class="active">mine</router-link> &nbsp;
    <hr>
    <router-view />
  </div>
</template>

<script>
export default {
  name: "App",
  data(){
    return{
    }
  },
  methods:{
  
  },
  components: {
  },
};
</script>

<style lang="less">
.active{
  color: red;
}
</style>

其他导航守卫:

  • Vue还提供了很多的其他守卫函数,目的都是在某一个时刻给予我们回调,让我们可以更好的控制程序的流程或者功能
  • next.router.vuejs.org/zh/guide/ad…

完整的导航解析流程:

  • 导航被触发。
  • 在失活的组件里调用 beforeRouteLeave 守卫。
  • 调用全局的 beforeEach 守卫。
  • 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
  • 在路由配置里调用 beforeEnter。
  • 解析异步路由组件。
  • 在被激活的组件里调用 beforeRouteEnter。
  • 调用全局的 beforeResolve 守卫(2.5+)。
  • 导航被确认。
  • 调用全局的 afterEach 钩子。
  • 触发 DOM 更新。
  • 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

21-vant组件库

hs 8/17/2022

# 1. vant组件库

# 1.0 vant组件库-介绍

目标: vant是一个轻量、可靠的移动端 Vue 组件库, 开箱即用

vant官网

(opens new window)

特点:

  • 提供 60 多个高质量组件,覆盖移动端各类场景
  • 性能极佳,组件平均体积不到 1kb
  • 完善的中英文文档和示例
  • 支持 Vue 2 & Vue 3
  • 支持按需引入和主题定制

# 1.1 全部引入

目标: 看官网文档, 下载, 引入vant组件库

全部引入, 快速开始:vant-contrib.gitee.io/vant/#/zh-C…

  1. 全部引入, 快速开始: vant-contrib.gitee.io/vant/#/zh-C…

  1. (opens new window)
  2. 下载Vant组件库到当前项目中
  3. 在main.js中全局导入所有组件,
  4. 使用按钮组件 – 作为示范的例子

  1. 下载vant组件库到当前项目中
   yarn add vant -D
  1. 导入所有组件, 在main.js中
   import Vue from 'vue';
   import Vant from 'vant';
   import 'vant/lib/index.css';

   Vue.use(Vant);
  1. 使用按钮组件

    vant-contrib.gitee.io/vant/#/zh-C…

   <van-button type="primary">主要按钮</van-button>
   <van-button type="info">信息按钮</van-button>
   <van-button type="default">默认按钮</van-button>
   <van-button type="warning">警告按钮</van-button>
   <van-button type="danger">危险按钮</van-button>

# 1.2 手动按需引入

目标: 只引入使用的组件

  1. 手动单独引入, 快速开始: vant-contrib.gitee.io/vant/#/zh-C…

  1. (opens new window)
// 方式2: 手动 按需引入
// import Button from 'vant/lib/button'; // button组件
// import 'vant/lib/button/style'; // button样式
  1. 注册
// components: { // 手动注册组件名
//   // VanButton: Button
//   // 等价的
//   [Button.name]: Button
// }
  1. 使用
<van-button type="primary">主要按钮</van-button>
<van-button type="info">信息按钮</van-button>
<van-button type="default">默认按钮</van-button>
<van-button type="warning">警告按钮</van-button>
<van-button type="danger">危险按钮</van-button>

# 1.3 自动按需引入

目标: 按需加载组件

babel-plugin-import

(opens new window) 是一款 babel 插件,它会在编译过程中将 import 的写法自动转换为按需引入的方式。

  1. 安装插件
   yarn add babel-plugin-import -D
  1. 在babel配置文件里 (babel.config.js)
   module.exports = {
       plugins: [
           ['import', {
               libraryName: 'vant',
               libraryDirectory: 'es',
               style: true
           }, 'vant']
       ]
   };
  1. 全局注册 - 会自动按需引入
   // 方式1: 全局 - 自动按需引入vant组件
   // (1): 下载 babel-plugin-import
   // (2): babel.config.js - 添加官网说的配置 (一定要重启服务器)
   // (3): main.js 按需引入某个组件, Vue.use全局注册 - 某个.vue文件中直接使用vant组件
   import {
       Button
   } from 'vant';
   Vue.use(Button) // Button组件全局注册, 真正注册的组件名VanButton

# 1.4 弹出框使用

目标: 使用弹出框组件

vant-contrib.gitee.io/vant/#/zh-C…

<template>
  <div>
    <van-button type="primary" @click="btn">主要按钮</van-button>
    <van-button type="info">信息按钮</van-button>
    <van-button type="default">默认按钮</van-button>
    <van-button type="warning">警告按钮</van-button>
    <van-button type="danger">危险按钮</van-button>
  </div>
</template>

<script>
// 方式2: 手动 按需引入
// import Button from 'vant/lib/button'; // button组件
// import 'vant/lib/button/style'; // button样式

// 目标: 使用弹出框
// 1. 找到vant文档
// 2. 引入
// 3. 在恰当时机, 调用此函数 (还可以用组件的用法)
import { Dialog } from "vant";
export default {
  // components: { // 手动注册组件名
  //   // VanButton: Button
  //   // 等价的
  //   [Button.name]: Button
  // }
  methods: {
    btn() {
      Dialog({ message: "提示", showCancelButton: true }); // 调用执行时, 页面就会出弹出框
    },
  },
};
</script>

# 1.5 表单使用

目标: 使用vant组件里的表单组件

vant-contrib.gitee.io/vant/#/zh-C…

表单验证规则: