admin 管理员组

文章数量: 887017

一、脚手架

1.1、安装脚手架:@vue/cli

第一步:执行安装命令
npm install -g @vue/cli

安装完毕后查看:
vue -V
@vue/cli 4.5.15
安装缓慢时,修改为淘宝镜像:npm config set registry https://registry.npm.taobao


第二步:切换到你要创建项目的目录,创建项目
vue create 项目名   (选择vue2版本)

项目创建成功提示:

 第三步:启动项目
npm run serve  (不是server)

输入地址,进入到默认的hello world页面

1.2、脚手架结构分析(项目名称vue_test_two)

 src/main.js文件分析

// 这个文件是项目的入口文件

// 引入Vue
import Vue from 'vue'
// 引入App组件,它是所有组件的父组件
import App from './App.vue'

// 关闭cue的生成提示
Vue.config.productionTip = false

// 创建Vue实例对象
// new Vue({
//   render: h => h(App),
// }).$mount('#app')

// 创建Vue实例对象(和上面的写法的区别,多了el)
new Vue({
  el:'#app',
  render: h => h(App),
})

public/index.html文件分析

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <!--   针对IE浏览器的特殊配置,含义:让IE浏览器以最高的渲染级别渲染页面   -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <!--   开启移动端的理想视口   -->
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <!--   <%= BASE_URL %> 就是public目录,这里就是配置页签图标   -->
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <!--   配置网页标题   -->
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>

  <body>
    <noscript>
      // 浏览器不支持JS时,"noscript标签"内的就会展示。如果支持的话,"noscript标签"就不展示。
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>

    <!--  容器  -->
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

1.3、render函数(分析main.js时,里面的render函数是做什么用的)

// 这个文件是项目的入口文件

// 引入Vue
import Vue from 'vue'
// 引入App组件,它是所有组件的父组件
import App from './App.vue'

// 关闭cue的生成提示
Vue.config.productionTip = false

// 创建Vue实例对象(创建脚手架后的原生配置)
// new Vue({
//   render: h => h(App),
// }).$mount('#app')

// 创建Vue实例对象(自定义配置,用到了el)
new Vue({
  // el:'#app' 等于:$mount('#app')
  el:'#app',
  // 最终版(脚手架默认版)
  render: h => h(App),

  // 第三版(箭头函数简写)这里传入2个参数是因为'h1'是HTML里面的内置元素,元素内需要传入具体的内容'你好啊'。
  // render:createElement=> createElement('h1','你好啊')

  // 第二版(箭头函数方式)
  // render要写成函数类型,加上返回值
  // render:(createElement)=>{
    // createElement是一个function类型(可以渲染模版内容),可以传值'h1'就是<h1>,'你好啊'就是内容,完整版就是"<h1>你好啊</h1>"
    // return createElement('h1','你好啊')
  // }

  // 第一版(普通函数方式)
  // render要写成函数类型,加上返回值
  // render(createElement){
    // createElement是一个function类型(可以渲染模版内容),可以传值'h1'就是<h1>,'你好啊'就是内容,完整版就是"<h1>你好啊</h1>"
    // return createElement('h1','你好啊')
  // }
})

// 单文件组件是这么实现的
// const vm = new Vue({
//   el:"#root",
//   template:`<App></App>`,
//   components:{
//     App:App,
//   }
// })

1.4、总结不同版本的vue.js文件的区别

  1.vue.js与vue.runtime.xxx.js的区别
      1.vue.js是完整版的Vue,包含:核心功能 + 模块解析器
      2.vue.runtime.xxx.js是运行版的Vue,只包含:核心功能,没有模块解析器

  2.因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,
       需要使用render函数接收到的createElement函数去指定具体内容。

1.5、vue.config.js 文件

官方文档:配置参考 | Vue CLI

vue.config.js 配置文件对可以对脚手架进程个性定制(这里仅做参考)

module.exports = {
    pages:{
        index:{
            // 配置主文件的入口是main.js文件
            entry: "src/main.js"
        },
    },
    // 关闭语法检查
    lintOnSave:false
}

1.6、组件的ref 属性

组件代码:

<template>
    <div>
        <!--  在标签内添加一个ref="xxx",然后在this.$refs.xxx就能拿到该标签的"dom元素"  -->
        <h1 v-text="msg" ref="title"></h1>
        <button ref="btn" @click="showDom">点我展示Dom</button>

        <!--  在自定义的组件内加一个ref="sch",然后在this.$refs拿到的就是该组件的实例对象VueComponent(简称vc)  -->
        <School ref="sch"></School>
    </div>
</template>

<script>
    import School from './components/School'
    import Student from './components/Student.vue'

    export default {
        name: "App",
        components:{
            School,
            Student,
        },
        data(){
            return {
                msg: '欢迎'
            }
        },
        methods:{
            showDom(){
                console.log(this.$refs.title)  // 拿到的是真实的dom元素
                console.log(this.$refs.btn) // 拿到的是真实的dom元素
                console.log(this.$refs) // 拿到的是该组件的实例对象VueComponent(简称vc)
            }
        }
    }
</script>

展示效果:

总结ref属性:
    1.被用来给元素或者"子组件"注册引用信息(id的替代者
    2.应用在html标签上获取的是真是DOM元素,应用在组件标签上是组件的实例对象(VC)
    3.使用方式:
      打标识:<h1 ref="title"></h1>,<School ref="sch"/>
      获取:this.$refs.xxx

1.7、组件的props属性(功能:让组件接收外部传过来的数据

注意点:传参的变量不能写已经被内置使用的名称

1.7.1、父组件传递参数给子组件

父组件传参:<Student name="sudada" sex="男" :age="18"/>

<template>
    <div>
        <!--  给组件Student传递参数:name="sudada" sex="男" :age="18"  -->
        <Student name="sudada" sex="男" :age="18"/>
    </div>
</template>

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

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

子组件接收参数:props:{},有三种方式,详见例子

<template>
<div>
    <h1>{{msg}}</h1>
    <h2>学生名字:{{name}}</h2>
    <h2>学生年龄:{{age}}</h2>
    <h2>学生性别:{{sex}}</h2>
</div>
</template>

<script>
    export default {
        name: "Student",
        data(){
            return {
                msg: '我是一个学生',
            }
        },
        // 组件接收参数:方式1,数组写法(简单接收,不能多写接收的参数)
        // props:['name','age','sex']

        // 组件接收参数:方式2,对象写法(规定了接收参数的类型)
        // props:{
        //     name:String,
        //     age:Number,
        //     sex:String,
        // }

        // 组件接收参数:方式3,对象写法(规定了接收参数的类型,和参数的默认值,以及参数是否必须要传)
        props:{
            name:{
                type:String, // name的类型是字符串
                required:true, // name是必须要传的参数(required表示这个属性是否是必须的)
            },
            age:{
                type:Number, // name的类型是整数
                default:99,  // 默认参数的值是99
            },
            sex:{
                type:String, // name的类型是字符串
                required:true, // sex是必须要传的参数(required表示这个属性是否是必须的)
            },
        }
    }
</script>

1.7.1、子组件接收的参数尽量不要修改(可以改,但是不要改),如果非要改的话,可以新建一个变量做替换,如下:

父组件传参

<template>
    <div>
        <!--  给组件Student传递参数:name="sudada" sex="男" :age="18"  -->
        <Student name="sudada" sex="男" :age="18"/>
    </div>
</template>

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

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

子组件接收参数

<template>
<div>
    <h1>{{msg}}</h1>
    <h2>学生名字:{{name}}</h2>
    <h2>学生年龄:{{new_age}}</h2>
    <h2>学生性别:{{sex}}</h2>
    <button @click="updateAge">点我修改年龄</button>
</div>
</template>

<script>
    export default {
        name: "Student",
        data(){
            return {
                msg: '我是一个学生',
                new_age: this.age
            }
        },
        methods:{
            updateAge(){
                this.new_age++
            }
        },
        // 组件接收参数:方式3,对象写法(规定了接收参数的类型,和参数的默认值,以及参数是否必须要传)
        props:{
            name:{
                type:String, // name的类型是字符串
                required:true, // name是必须要传的参数(required表示这个属性是否是必须的)
            },
            age:{
                type:Number, // name的类型是整数
                default:99,  // 默认参数的值是99
            },
            sex:{
                type:String, // name的类型是字符串
                required:true, // sex是必须要传的参数(required表示这个属性是否是必须的)
            },
        }
    }
</script>

1.8、混合(混入) mixins功能:

        两个组件内写了一个重复的配置项,那么就可以把这个重复的配置项提取出来单独定义成一个公共对象(js),然后导入这个对象(js)并使用mixins:[xxx]即可使用。

注意点:

        混合(js)内配置的对象的key:value,如果组件里面的对象不存在这个key:value,那么组件里面的对象就会多一个key:value,如果组件里面的对象已经存在这个key:value了,那么以组件里面对象已存在的key:value为主。

1.8.1例子(局部混合的使用):

 定义一个mixin.js文件(定义混合)

    const mixin = {
    methods:{
        showName(){
            alert(this.name)
        }
    }
}
export default mixin

子组件Student(局部使用混合)

<template>
<div>
    <h1>{{msg}}</h1>
    <h2>学生名字:{{name}}</h2>
    <h2>学生年龄:{{age}}</h2>
    <button @click="showName">点我显示学生名</button>
</div>
</template>

<script>
    // 引入一个混合(自定义的js文件)
    import mixin from "@/components/mixin";

    export default {
        name: "Student",
        data(){
            return {
                msg: '学生信息',
                name:'张三',
                age: 19
            }
        },
        // 使用混合(自定义的js文件)
        mixins:[mixin]
    }
</script>

子组件School(局部使用混合)

<template>
    <div class="demo">
        <h2>学校名称:{{name}}</h2>
        <h2>学校地址:{{address}}</h2>
        <button @click="showName">点我显示学校名称</button>
    </div>
</template>

<script>
    // 引入一个混合(自定义的js文件)
    import mixin from "@/components/mixin";

    export default {
        name:'School',
        data(){
            return {
                name:'上海大学',
                address:'shanghai'
            }
        },
        // 使用混合(自定义的js文件)
        mixins:[mixin]
    }
</script>

<style>
    .demo{
        background-color: orange;
    }
</style>

1.8.2例子(全局混合的使用)

在main.js内使用Vue.mixin(mixin),然后在组件内即可调用mixin内定义好的方法或对象。

import mixin from "@/mixin";
Vue.mixin(mixin)

1.9、插件

功能:用于增强vue

本质:包含install方法的一个对象,install的第一个参数是vue,第二个以后的参数是使用插件传递过来的值。

定义插件:
        // 1.添加全局过滤器
        Vue.filter(....)
        // 2.配置全局混合
        Vue.mixin(....)
        // 3.添加实例方法
        Vue.prototype.hello = ()=>{alert('你好啊')}

使用插件:Vue.use(plugins,12,23),其中12,23是给插件的传参

1.9.1、例子

定义一个plugins.js 文件

export default {
    install(Vue,a,b,c){
        // 全局过滤器,这里的mySlice可以参考下面的局部过滤器写法对比即可。
        Vue.filter('mySlice',function (value){
            return value.slice(0,4)
        })

        // 接收插件的参数
        console.log(a,b,c)

        // 定义混入(全局混合)
        Vue.mixin({
            methods:{
                showName(){
                    alert(this.name)
                }
            }
        })

        // Vue原型上的方法,vm和vc都可以使用
        Vue.prototype.hello = () => {
            alert('你好啊')
        }
    }
}

main.js文件内引入插件

// 引入插件
import plugins from "@/plugins";
//使用插件(插件还可以传多个值)
Vue.use(plugins,1,2,3)

组件内使用插件

<template>
<div>
    <h1>{{msg}}</h1>
    <h2>学生名字:{{name | mySlice}}</h2>
    <h2>学生年龄:{{age}}</h2>
    <button @click="showName">点我</button>
</div>
</template>

<script>
    export default {
        name: "Student",
        data(){
            return {
                msg: '学生信息',
                name:'张三asdasdsd',
                age: 19
            }
        },
    }
</script>

1.10、scoped样式

作用:让样式在局部生效,防止冲突(组件内的样式(style)最终都是会汇总到一起,如果定义了多个组件,然后这些组件内有些样式(style)的名称重复了,就会格式错乱)

App.vue组件内一般不使用scoped

1.10.1、例子  <style scoped> .... </style>

<style scoped>
    .demo{
        background-color: skyblue;
    }
</style>

二、todolist 案例和总结(组件化的编码流程讲解

1、组件编码流程:
      1.拆分静态组件:组件要按照功能点拆分,命名不能和html元素冲突
      2.实现动态组件:考虑好数据存放位置,数据是一个组件在用,还是多个组件在使用。
          一个组件在用: 组件自身即可。
          多个组件在使用: 在组件的公共父组件上。(状态提升)
      3.实现交互:从绑定事件开始。
2、props适用于:
      1.父组件===>子组件之间的通信。
      2.子组件===>父组件之间的通信(要求父组件给子组件一个函数)。
3、使用v-model时切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的。
4、props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这么做

2.1、子组件给父组件传值(子组件给父组件间的通信)

实现方式:父组件先自定义一个方法receive(xxx),然后把这个方法传递给子组件。子组件通过props:['receive']接收,然后通过this.receive(xxx)的方式把值传递给父组件。例子如下:

父组件
<template>
    <div>
        <MyHeader :receive="receive"/>
    </div>
</template>

<script>
    import MyHeader from './components/MyHeader'
    export default {
        name: "App",
        // 组件注册
        components:{
            MyHeader,
        },
        methods:{
            // 这个函数是传递给MyHeader子组件的,然后MyHeader子组件调用这个函数并传入"参数",这个传递的"参数"就是子组件给父组件传值的方法。
            receive(x){
                console.log('接收到了todoobj',x)
            }
        }
    }
</script>

子组件
<template>
    <div class="todo-header">
        <!--  @keyup.enter="add" 键盘回车事件,也就是敲键盘的enter键就会触发 -->
        <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/>
    </div>
</template>

<script>
    import {nanoid} from "nanoid"
    export default {
        name: "MyHeader",
        props:['receive'],
        data(){
            return{
                title:''
            }
        },
        methods:{
            add(){
                // 将用户输入的值,包装成一个todo对象
                const todoObj = {id:nanoid(),title:this.title,done:false}
                // console.log(todoObj)
                // 调用父组件传递过来的函数,把值传递给这个函数,实现了子组件给父组件传值
                this.receive(todoObj)
            }
        }
    }
</script>

2.2、浏览器的本地存储
  2.2.1、localStorage:特点,浏览器关闭存的值并不会立即删除。可手动删除/清空。
    对应的API如下:
      添加:localStorage.setItem('name','sudada')  内容都要写字符串格式,如果不写默认转为字符串格式
      查看:localStorage.getItem('name')  内容都要写字符串格式,如果不写默认转为字符串格式
      删除:localStorage.removeItem('name')  内容都要写字符串格式,如果不写默认转为字符串格式
      清空:localStorage.clear()  内容都要写字符串格式,如果不写默认转为字符串格式

  2.2.2、sessionStorage:特点,浏览器关闭后,存的值就没了。
    对应的API如下:
      添加:sessionStorage.setItem('name','sudada')  内容都要写字符串格式,如果不写默认转为字符串格式
      查看:sessionStorage.getItem('name')  内容都要写字符串格式,如果不写默认转为字符串格式
      删除:sessionStorage.removeItem('name')  内容都要写字符串格式,如果不写默认转为字符串格式
      清空:sessionStorage.clear()  内容都要写字符串格式,如果不写默认转为字符串格式

2.3、组件的"自定义事件"(子组件给父组件间的通信)

1.一种组件间通信的方式,适用于:子组件-->父组件

2.使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中定义"事件的回调方法"。然后A组件把这个方法通过v-on绑定给B组件(也就是下面的方式一)。然后B组件内调用这个"事件回调方法"。

3.绑定自定义事件
    1.方式一:

     在父组件中给子组件绑定一个"自定义事件":    <Student @atguigu="getStudentName"/>   getStudentName是父组件里面一个自定义的方法。

     在子组件中:调用 this.$emit("atguigu",参数),就会触发父组件里面的getStudentName方法

    2.方式二:

      在父组件中给子组件打一个标识:<Student ref="student"/>

      在父组件中定义:    mounted(){ this.$refs.student.$on('atguigu',this.getStudentName) }

      在子组件中:调用 this.$emit("atguigu",参数),就会触发父组件里面的getStudentName方法

3.若是想让"自定义事件只能触发一次",可以使用 .once 修饰符,或者使用 $once 方法。

        在父组件(使用 $once方法):mounted(){this.$refs.student.$once('atguigu',this.getStudentName)}

        在父组件(使用 .once修饰符):<Student @atguigu.once="getStudentName"/>

5.解绑自定义事件:

        在子组件(使用 $off方法):this.$off("atguigu")      # 解绑一个自定义事件"atguigu"

        在子组件(使用 $off方法):this.$off(["atguigu","demo"],参数)      # 解绑多个自定义事件

        在子组件(使用 $off方法):this.$off([],参数)      # 解绑所有的自定义事件

6.组件上也可以绑定原生DOM事件,需要使用native修饰符。

7.注意:通过this.$ref.xxx.$on('事件名',回调方法)绑定自定义事件时,自定义事件对应的方法 "要么配置在methods中,要么用箭头函数" 否则this指向会出问题(this不是App而是Student)

    // 调用自定义事件(方法1),必须写methods方法
    mounted() {
        this.$refs.addTodo.$on('addTodo',this.addTodo)
    },

    // 自定义事件的回调方法
    methods:{
        addTodo(todoObj){
            this.todos.unshift(todoObj)
        },
    }

    // 调用自定义事件(方法2),不需要额外再写methods:{todoObj(){}}方法了。
    mounted() {
        this.$refs.addTodo.$on('addTodo',(todoObj)=>{
            this.todos.unshift(todoObj)
        })
    },

8、在组件内写原生DOM事件"@click"时,click事件就会被当做自定义事件,需要Student组件内通过this.$emit('clicl')触发,否则不能直接使用。

        <Student @atguigu="getStudentName" @demo="m1" @click="show"/>

如何才能把@click当做原生DOM事件呢? 使用@click.native修饰符即可。

        <Student @atguigu="getStudentName" @demo="m1" @click.native="show"/>

2.4、全局事件总线任意组件之间的通信)★★★★★

1.安装全局事件总线:在Main.js里面
   beforeCreate() {
       Vue.prototype.$bus=this  // 这里的this就是当前的vm。"$bus"就是创建的"全局事件总线"(当前项目所有的VC和VM都能借助这个$bus触发一些自定义事件)
   }

// 创建vm
new Vue({
    el: '#app',
    render: h => h(App),
    // 安装全局事件总线
    beforeCreate() {
        Vue.prototype.$bus=this
    }
})

2.使用全局事件总线:以下例子(B组件作为发送者/子组件,A组件作为接收者/父组件。)
   2.1.接收数据:A组件想接收B组件发送过来数据,则在A组件中给$bus绑定自定义事件,事件的"回调留在A组件自身"。以下2个都是放在A组件的。
       methods(){
           demo(接收的参数){....}
       }
       .......
       mounted(){
           this.$bus.$on('xxxx',this.demo)       xxxx就是自定义事件名
       }
   2.2.发送数据:在B组件中通过this.$bus.$emit('xxxx',回调数据)送数据给A组件(触发自定义事件)。

3.在A组件中,最好在beforeDestroy钩子中,用this.$bus.$off('xxxx')去解绑"当前组件所用到的"自定义事件xxxx

2.4.1、全局事件总线,例子

main.js文件"安装全局事件总线"
new Vue({
    el: '#app',
    render: h => h(App),
    // 安装全局事件总线
    beforeCreate() {
        Vue.prototype.$bus=this
    }
})

App.vue组件内(父组件)
        mounted() {
            // 使用全局事件总线的方式,实现父组件给子组件传递数据。
            this.$bus.$on('addTodo',this.addTodo)
            this.$bus.$on('checkTodo',this.checkTodo)
            this.$bus.$on('deleteTodo',this.deleteTodo)
        },
        methods:{
            // 回调方法函数
            addTodo(todoObj){
                this.todos.unshift(todoObj)
            },
            checkTodo(id){
                this.todos.forEach((todo)=>{
                    if(todo.id === id) todo.done = !todo.done
                })
            },
            deleteTodo(id){
                this.todos = this.todos.filter((todo)=>{
                    return todo.id !== id
                })
            },
        },
        // 解绑全局事件总线
        beforeDestroy() {
            this.$bus.$off('addTodo')
            this.$bus.$off('checkTodo')
            this.$bus.$off('deleteTodo')
        }

Myitem.vue组件内(子组件)
    使用this.$bus.$emit('xxxx',回调数据)发送数据给父组件(触发自定义事件)。
    this.$bus.$emit('deleteTodo',todo_id)

2.5、消息订阅与发布任意组件之间的通信)★★★★★

订阅消息:订阅消息的名称
发布消息:发布消息的内容
1.引入pubsub-js,在A和B组件内:import pubsub from 'pubsub-js'  (安装:npm i pubsub-js)

2.订阅消息:A组件订阅了B组件的消息,那么订阅的回调函数写在A组件自身。
    写法1:常规写法
        methods(){
            // msgName这个是消息名称,data是回调的数据
            demo(msgName,data){....}
            }
        .......
        mounted(){
            // this.pubId:订阅消息的ID
            this.pubId = pubsub.subscribe('消息名', this.demo)
        }
    写法2:(方法写成箭头函数,不写methods)
        mounted(){
            // this.pubId:订阅消息的ID
            this.pubId = pubsub.subscribe('消息名',(msgName,data)=>{
            // msgName这个是消息名称,data是回调的数据
            console.log('消息的回调执行了',msgName,data)
        }

3.发布消息:B组件中:pubsub.publish('消息名',数据)

4.最好在A组件中,通过beforeDestroy钩子,用"pubsub.unsubscribe(this.pubId)"取消消息订阅

2.5.1、消息订阅与发布,例子

App.vue组件内(父组件)

import pubsub from 'pubsub-js'
mounted() {
    // 使用"消息订阅与发布"的方式(订阅消息)
    this.pubId = pubsub.subscribe('deleteTodo',this.deleteTodo)
},

methods:{
    deleteTodo(_,id){
        this.todos = this.todos.filter((todo)=>{
            return todo.id !== id
        })
    },
}

// 解绑全局事件总线
beforeDestroy() {
    pubsub.unsubscribe(this.pubId)
}

Myitem.vue组件内(子组件)
import pubsub from 'pubsub-js'

// 使用消息订阅与发布(发布消息)
pubsub.publish('deleteTodo',todo_id)

2.6、this.$nextTick

语法:this.$nextTick(回调函数)

作用:在下一次DOM更新结束后执行其指定的回调。

什么时候使用:当改变数据后,要基于更新后DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
 

三、请求API拿到数据并做展示

App.vue组件内容:
<template>
  <div class="container">
    <Search/>
    <List/>
  </div>
</template>


<script>
// 引入组件
import Search from "@/components/Search";
import List from "@/components/List";

export default {
  // App组件名称
  name: "App",

  // 注册组件
  components:{
    Search,
    List,
  },
}
</script>

Search.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="searchUsers">Search</button>
    </div>
  </section>
</template>


<script>
  // 请求接口的方法
  import axios from 'axios'
  export default {
    name: "Search",
      data(){
        return{
          keyWord:""
        }
      },
    methods:{
      searchUsers(){
        // 请求前更新List的数据
        this.$bus.$emit("updateListdata",{isFirst:false,isLoading:true,errMsg:'',users:[]})

        // ${this.keyWord}拿到的值就是this.keyWord,只不过这里用到了模板字符串解析,需要用反引号。
        // .then() 就是拿到请求的数据
        axios.get(`https://api.github/search/users?q=${this.keyWord}`).then(
          // 请求成功的返回值:response(response.data具体的返回值信息)
          response => {
            console.log("请求成功了",response.data)
            // 请求成功后,更新List组件信息
            this.$bus.$emit("updateListData",{isLoading:true,errMsg:'',users:response.data.items})
          },

          // 请求成功的返回值:error(error.message具体的返回值信息)
          error => {
            console.log("请求失败了",error.message)
            // 请求失败后,更新List组件信息
            this.$bus.$emit("updateListData",{isLoading:false,errMsg:error.message,users:[]})
          },
        )
      }
    },
  }
</script>

List.vue组件内容:
<template>
  <div class="row">
    <!--  展示用户列表:循环this.users列表里面的值,并取值做展示  -->
    <div v-show="info.users.length" class="card" v-for="user in info.users" :key="user.login">
      <a :href="user.html_url" target="_blank">
        <img :src="user.avatar_url" style='width: 100px'/>
      </a>
      <p class="card-text">{{user.login}}</p>
    </div>

    <!--  展示欢迎词  -->
    <h1 v-show="info.isFirst">欢迎使用!</h1>
    <!--  展示加载中  -->
    <h1 v-show="info.isLoading">加载中..</h1>
    <!--  展示错误信息  -->
    <h1 v-show="info.errMsg">{{ info.errMsg }}</h1>
  </div>
</template>


<script>
export default {
    name: "List",
    data () {
    return {
      info:{
        // 是否初次展示(页面初始的欢迎词)
        isFirst:true,
        // 是否处于加载中
        isLoading:false,
        // 存储错误信息
        errMsg:"",
        users:[]
      }
    }
    },
    methods:{
        saveObj(dateObj){
            console.log("我是List组件,收到了数据",dateObj)
            // 方法1:这里dateObj接收的是一个对象,刚好赋值给this.info(前提是要2边格式能对应上)
            // this.info = dateObj

            // 方法2:通过字面量的方式去合并一个对象(举例this.info有4个属性,dateObj有3个,那就只替换相同的3个(替换的值以dateObj为主,因为dateObj在"后面"),剩下的1个不替换)
            this.info = {...this.info,...dateObj}
        }
    },
    mounted() {
        this.$bus.$on("updateListData",this.saveObj)
    }
}
</script>


<style scoped>
  .album {
    min-height: 50rem; /* Can be removed; just added for demo purposes */
    padding-top: 3rem;
    padding-bottom: 3rem;
    background-color: #f7f7f7;
  }
  .card {
    float: left;
    width: 33.333%;
    padding: .75rem;
    margin-bottom: 2rem;
    border: 1px solid #efefef;
    text-align: center;
  }
  .card > img {
    margin-bottom: .75rem;
    border-radius: 100px;
  }
  .card-text {
    font-size: 85%;
  }
</style>

四、插槽

4.1、默认插槽,例子(数据存放在App组件,也就是插槽的使用者,插槽传递数据)

父组件(App):在"组件标签"里面的"标签体",写一个img标签:<Category><img><Category/>

子组件(Category):写一个"slot"标签接收父组件的<img>标签:<slot>默认展示内容</slot>

App.vue组件内:父组件
<template>
  <div class="container">
    <Category title="美食">
      <!--   在"组件标签"里面的"标签体"写,在"Category"组件内部要使用slot(插槽)接收   -->
      <img src="https://ecmb.bdimg/kmarketingadslogo/327c77f2512faf01c9430d70a4eeabae_259_194.png" alt="">
    </Category>

    <Category title="游戏">
      <!--  这里的ul标签先拿到数据,然后填充到Category组件内  -->
      <ul>
        <li v-for="(game,index) in games" :key="index">{{game}}</li>
      </ul>
    </Category>
  </div>
</template>
<script>
    // 引入组件
    import Category from "@/components/Category";

    export default {
        // App组件名称
        name: "App",
        // 注册组件
        components:{
            Category,
        },
        data () {
            return {
                foods:['foods1','foods2','foods3'],
                games:['games1','games2','games3'],
                films:['films1','films2','films3'],
            }
        },
    }
</script>

Category.vue组件内:子组件
<template>
  <div class="category">
    <h3>{{ title }}分类</h3>
    <!--  slot是一个特殊的标签(插槽),等待"组件的使用者"进行填充  -->
    <slot>当使用者没有传递值时,会默认展示</slot>
  </div>
</template>

4.2、命名插槽,例子(数据存放在App组件,也就是插槽的使用者,插槽传递数据)

父组件(App):在"组件标签"里面的"标签体",写一个img标签并指定名称:<Category><img slot="center"><Category/>

子组件(Category):写一个"slot"标签并"设置name属性"接收父组件的<img>标签:<slot name="center">默认展示内容</slot>

App.vue组件内:父组件
<template>
    <div class="container">
        <Category title="美食">
            <!--   在组件标签体内写,在组件内部要使用slot(插槽)接收,要明确插槽名称:slot="xxxx"   -->
            <img slot="center" src="https://ecmb.bdimg/kmarketingadslogo/327c77f2512faf01c9430d70a4eeabae_259_194.png" alt="">
            <div class="foot" slot="footer">
                <a href="http://www.baidu">更多美食1</a>
                <a href="http://www.baidu">更多美食2</a>
            </div>
        </Category>

        <Category title="游戏">
            <ul slot="center">
                <li v-for="(game,index) in games" :key="index">{{game}}</li>
            </ul>
            <div class="foot" slot="footer">
                <a href="http://www.baidu">更多游戏1</a>
                <a href="http://www.baidu">更多游戏2</a>
            </div>
        </Category>
    </div>
</template>
<script>
    // 引入组件
    import Category from "@/components/Category";
    export default {
        // App组件名称
        name: "App",
        // 注册组件
        components:{
            Category,
        },
        data () {
            return {
                foods:['foods1','foods2','foods3'],
                games:['games1','games2','games3'],
                films:['films1','films2','films3'],
            }
        },
    }
</script>

Category.vue组件内:子组件
<template>
  <div class="category">
    <h3>{{ title }}分类</h3>
    <!--  定义一个插槽(并给插槽命名),等待"组件的使用者"进行填充  -->
    <slot name="center">当使用者没有传递值时,会默认展示1</slot>
    <slot name="footer">当使用者没有传递值时,会默认展示2</slot>
  </div>
</template>

4.3、作用于插槽,例子(数据存放在Category组件,也就是插槽的使用者,接收插槽的数据

父组件(App):在"组件标签"里面的"标签体",写一个template标签:<Category><templater scop="自定义名称"></template><Category/>  # 这个'自定义名称'是一个"对象"(里面的值就是插槽对应组件传递过来的)

子组件(Category):写一个"slot"标签并绑定一个属性<slot :games="games">默认展示内容</slot>。父组件需要通过template来触发(<template scope="自定义名称">)

App.vue组件内:父组件
<template>
  <div class="container">

    <Category title="游戏分类">
        <!-- scope="atguigu",这个"atguigu"是自定义的名称 -->
      <!-- 新写法: <template slot-scope="atguigu"> -->
      <template scope="atguigu">
        <!--  {{atguigu}} 拿到的值是一个"对象"(里面的值就是插槽对应组件传递过来的): { "games": [ "环境", "稀有", "马力" ] }  -->
        <ul>
          <li v-for="(game,index) in atguigu.games" :key="index">{{game}}</li>
        </ul>
      </template>
    </Category>

    <Category title="游戏分类">
      <template scope="atguigu">
        <!--  {{atguigu}} 拿到的值是一个对象(里面的值就是插槽对应组件传递过来的): { "games": [ "环境", "稀有", "马力" ] }  -->
        <ol>
          <li style="color: red" v-for="(game,index) in atguigu.games" :key="index">{{game}}</li>
        </ol>
      </template>
    </Category>

  </div>
</template>

Category.vue组件内:子组件
<template>
  <div class="category">
    <h3>{{ title }}</h3>
      <!--  作用域插槽::games="games"。其他组件在使用这个插槽时,需要通过template来触发(<template scope="自定义名称">)  -->
      <slot :games="games">我是默认的一些内容</slot>
  </div>
</template>

<script>
  export default {
    name: "Category",
    props:['title'],
    data () {
      return {
        games:['环境','稀有','马力'],
      }
    },
  }
</script>

五、vuex

1)、概念:
        专门在Vue中实现集中式状态(数据)管理的一个Vue插件
        对vue应用中多个组件的共享状态进行集中式的管理(读写),也是一种组件间通信的方式,且适用于任意组件间通信

2)、什么时候使用Vuex:
        1.多个组件依赖于同一状态(多个组件数据共享)。
        2.来自不同组件的行为需要变更同一状态

    

3)、vue中使用vuex的版本选择
        1.vue2中用vuex3版本
        2.vue3中用vuex4版本

5.1、简单分析vuex的工作原理

action,mutations,state都是对象数据类型,都经过store管理。

5.2、搭建vuex环境

安装:npm i vuex@3

5.2.1、创建文件src/store/index.js

// 该文件用于创建Vuex最核心的store

// 准备actions:用于响应组件中的动作
const actions = {}
// 准备mutations:用于操作数据(state)
const mutations = {}
// 准备state:用于存储数据
const state = {}

// 引入Vue
import Vue from 'vue'
// 引入Vuex
import Vuex from "vuex"
// 应用Vuex插件
Vue.use(Vuex)

// 创建store,并暴露(导出) store
export default new Vuex.Store({
    actions:actions,
    mutations:mutations,
    state:state,
})

5.2.2、在main.js中创建vm时传入store配置项

// 引入store !!!
import store from './store/index'
// 创建vm
new Vue({
el:"#app",
render: h => h(App),
    // 使用store !!!。原生写法:store:store,这里用的是简写方式
    store,
})

5.2.3、验证是否正常(完成5.2.1和5.2.2)

在组建内通过 "console.log(this)",查看vue实例对象里面是否包含$store

5.3、vuex写一个例子(基本用法了解)

5.3.1、在vuex的"action"里面,函数 " jia(context,value){} ",传入的参数"context和value" 对应的值如下图:

// 准备actions:用于响应组件中的动作
const actions = {
    // "jia:function(){}" 可以简写为 "jia(){}"
    jia(context,value){
        console.log(context,value)
        contextmit('JIA',value)
    }
}

5.3.2、在vuex的"mutations"里面,函数 " JIA(state,value){} ",传入的参数"state和value" 对应的值如下图:

// 准备mutations:用于操作数据(state)
const mutations = {
    JIA(state,value){
        console.log(state,value)
    }
}

5.3.3、在vuex的"state"里面,存储的就是求和的值"sum",在组件内通过" this.$store.state.sum "取出。

// 准备state:用于存储数据
const state = {
    sum:0
}

在组件内取store里面的值:this.$store.state.xxx
<h1>当前值为:{{this.$store.state.sum}}</h1>

5.3.4、在vuex的"actions"里面,如果想要拿到求和的值"sum",可以通过" context.state.sum "取出.

// 准备actions:用于响应组件中的动作
const actions = {
    jishu(context,value){
        if (context.state.sum % 2){
            contextmit('JIA',value)
        }
        // 奇数在做加法
    },
}

5.3.5、在vuex的"actions"里面,函数 " jia(context,value){} " 如果需要继续调用"actions"里面的函数,可以通过:context.dispatch('新函数名',value)

// 1、准备actions:用于响应组件中的动作
const actions = {
    jian(context,value){
        context.dispatch("sudada",value)
    },
    sudada(context,value){
        contextmit("SUDADA",value)
    },
}

5.4、在vuex的"getters"的使用

1.概念:当state中的数据需要经过加工后在使用时使用getters
2.创建:在src/store/index.js里面新增getters配置
3.在组件中使用:this.$store.getters.xxxx

index.js文件内定义getters:

// 3、准备state:用于存储数据
const state = {
    sum:0,
}

// 4、用于:将state中的数据进行加工
const getters = {
    bigSum(state){  // 这里的"参数state"就是上面的"state"
        return state.sum*10   // 这里的值要写"return形式"的返回值
    }
}

Count.vue组件内使用"getters":
<h1>当前值为10倍:{{this.$store.getters.bigSum}}</h1>

5.5、vuex的"mapState"与"mapGetters"方法(计算属性computed里面代码的优化

前言:插值语法直接获取vuex里面的值时,使用的方法是:{{ this.$store.state.school }} ,但是这种方式不简洁。简洁写法:{{ school }},但是这样是拿不到$store.state的值

如何使用简洁的插值语法,直接获取到$store.state的值呢?

方法1:使用计算属性computed的原生写法

模板语法:
<h1>当前值为:{{ sum }}</h1>
<h1>当前值为10倍:{{ bigSum }}</h1>
<h3>我在{{ school }} 学习{{ project }}</h3>

js:
computed: {
    // 方法1:直接写计算属性
    sum() {
        return this.$store.state.sum
    },
    bigSum() {
        return this.$store.getters.bigSum
    },
    school() {
        return this.$store.state.school
    },
    project() {
        return this.$store.state.project
    },
}

vuex(store/index.js):
// 该文件用于创建Vuex最核心的store

// 1、准备actions:用于响应组件中的动作
const actions = {}

// 2、准备mutations:用于操作数据(state)
const mutations = {}

// 3、准备state:用于存储数据
const state = {
    sum:0,
    school:"上海大学",
    project:'数学',
}

// 4、用于:将state中的数据进行加工
const getters = {
    bigSum(state){
        return state.sum*10
    }
}

// 引入Vue
import Vue from 'vue'
// 引入Vuex
import Vuex from "vuex"
// 应用Vuex插件
Vue.use(Vuex)

// 创建store,并暴露(导出) store
export default new Vuex.Store({
    actions:actions,
    mutations:mutations,
    state:state,
    getters:getters,
})

方法2:借助mapState生成计算属性,从(vuex)$store.state中读取数据(对象写法用这种

模板语法:
<h1>当前值为:{{ sum }}</h1>
<h1>当前值为10倍:{{ bigSum }}</h1>
<h3>我在{{ school }} 学习{{ project }}</h3>

js:
import {mapState} from 'vuex'
import {mapGetters} from 'vuex'
computed: {
    ...mapState({sum:'sum',school:'school',project:'project'})

    // 同理:借助mapGetters生成计算属性,从(vuex)$store.getters中读取数据(数组写法),函数名和方法名默认一致。
    // ...mapGetters({sum:'sum',school:'school',project:'project'})},

vuex(store/index.js):
// 3、准备state:用于存储数据
const state = {
    sum:0,
    school:"上海大学",
    project:'数学',
}

// 4、用于:将state中的数据进行加工
const getters = {
    bigSum(state){
        return state.sum*10
    }
}

方法3:借助mapState生成计算属性,从(vuex)$store.state中读取数据(数组写法),函数名和方法名默认一致。

模板语法:
<h1>当前值为:{{ sum }}</h1>
<h1>当前值为10倍:{{ bigSum }}</h1>
<h3>我在{{ school }} 学习{{ project }}</h3>

js:
import {mapState} from 'vuex'
import {mapGetters} from 'vuex'
computed: {
    ...mapState(['sum','school','project']),

    // 同理:借助mapGetters生成计算属性,从(vuex)$store.getters中读取数据(数组写法),函数名和方法名默认一致。
    // ...mapGetters(['bigSum'])
},

vuex(store/index.js):
// 3、准备state:用于存储数据
const state = {
    sum:0,
    school:"上海大学",
    project:'数学',
}

// 4、用于:将state中的数据进行加工
const getters = {
    bigSum(state){
        return state.sum*10
    }
}

5.6、vuex的"mapMutations"方法(methods里面代码的优化

方法1:使用methods的原生写法

模板语法:
<button @click="jia">加</button>
<button @click="jian">加</button>

js:
data() {
    return {
        user_select: 1,
    }
},
methods: {
    jia() {
        this.$storemit("JIA", this.user_select)
    },
    jian() {
        this.$storemit("JIAN", this.user_select)
    },
}

vuex(store/index.js):
// 1、准备actions:用于响应组件中的动作
const actions = {}

// 2、准备mutations:用于操作数据(state)
const mutations = {
    JIA(state,value){
        state.sum += value
    },
    JIAN(state,value){
        state.sum -= value
    }
}

// 3、准备state:用于存储数据
const state = {}

// 4、用于:将state中的数据进行加工
const getters = {}

// 引入Vue
import Vue from 'vue'
// 引入Vuex
import Vuex from "vuex"
// 应用Vuex插件
Vue.use(Vuex)

// 创建store,并暴露(导出) store
export default new Vuex.Store({
    actions:actions,
    mutations:mutations,
    state:state,
    getters:getters,
})

方法2:借助mapMutations生成对应的方法,方法中会调用"this.$storemit"去联系(vuex中的)mutations(对象写法)用这种

模板语法:
<button @click="jia(user_select)">加</button>
<button @click="jian(user_select)">减</button>

js:
import {mapMutations} from 'vuex'
data() {
    return {
        user_select: 1,
    }
},
methods: {
    // 借助mapMutations生成对应的方法,方法中会调用"this.$storemit"去联系(vuex中的)mutations(对象写法)
    ...mapMutations({jia:"JIA",jian:"JIAN"}),
},

vuex(store/index.js):
// 2、准备mutations:用于操作数据(state)
const mutations = {
    JIA(state,value){
        state.sum += value
    },
    JIAN(state,value){
        state.sum -= value
    }
}
// 3、准备state:用于存储数据
const state = {
    sum:0,
    school:"上海大学",
    project:'数学',
}

方法3:借助mapMutations生成对应的方法,方法中会调用"this.$storemit"去联系(vuex中的)mutations(数组写法)

模板语法:
<button @click="JIA(user_select)">加</button>
<button @click="JIAN(user_select)">减</button>

js:
import {mapMutations} from 'vuex'
data() {
    return {
        user_select: 1,
    }
},
methods: {
    // 借助mapMutations生成对应的方法,方法中会调用"this.$storemit"去联系(vuex中的)mutations(数组写法),函数名和方法名默认一致。
    ...mapMutations(['JIA','JIAN']),
},

vuex(store/index.js):
// 2、准备mutations:用于操作数据(state)
const mutations = {
    JIA(state,value){
        state.sum += value
    },
    JIAN(state,value){
        state.sum -= value
    }
}
// 3、准备state:用于存储数据
const state = {
    sum:0,
    school:"上海大学",
    project:'数学',
}

5.7、vuex的"mapActions"方法(methods里面代码的优化

方法1:使用methods的原生写法

模板语法:
<button @click="jishu">当前求和为奇数在加</button>
<button @click="waitadd">等一等</button>

js:
data() {
    return {
        user_select: 1,
    }
},
methods: {
    jishu() {
        this.$store.dispatch("jishu", this.user_select)
    },
    waitadd() {
        this.$store.dispatch("waitadd", this.user_select)
    },
},

vuex(store/index.js):
// 1、准备actions:用于响应组件中的动作
const actions = {
    jishu(context,value){
        if(context.state.sum % 2){
            contextmit("JIA",value)
        }
    },
    waitadd(context,value){
        setTimeout(()=>{
            contextmit("JIA",value)
        },500)
    },
}

// 2、准备mutations:用于操作数据(state)
const mutations = {
    JIA(state,value){
        state.sum += value
    },
    JIAN(state,value){
        state.sum -= value
    }
}

// 3、准备state:用于存储数据
const state = {
    sum:0,
    school:"上海大学",
    project:'数学',
}

// 4、用于:将state中的数据进行加工
const getters = {
    bigSum(state){
        return state.sum*10
    }
}

// 引入Vue
import Vue from 'vue'
// 引入Vuex
import Vuex from "vuex"
// 应用Vuex插件
Vue.use(Vuex)

// 创建store,并暴露(导出) store
export default new Vuex.Store({
    actions:actions,
    mutations:mutations,
    state:state,
    getters:getters,
})

方法2:借助mapActions生成对应的方法,方法中会调用"this.$storemit"去联系(vuex中的)actions(对象写法)用这种

模板语法:
<button @click="jishu(user_select)">当前求和为奇数在加</button>
<button @click="waitadd(user_select)">等一等</button>

js:
import {mapActions} from 'vuex'
data() {
    return {
        user_select: 1,
    }
},
methods: {
    // 借助mapActions生成对应的方法,方法中会调用"this.$storemit"去联系(vuex中的)actions(对象写法)
    ...mapActions({jishu:"jishu",waitadd:"waitadd"})
},

vuex(store/index.js):
// 1、准备actions:用于响应组件中的动作
const actions = {
    jishu(context,value){
        if(context.state.sum % 2){
            contextmit("JIA",value)
        }
    },
    waitadd(context,value){
        setTimeout(()=>{
            contextmit("JIA",value)
        },500)
    },
}

// 2、准备mutations:用于操作数据(state)
const mutations = {
    JIA(state,value){
        state.sum += value
    },
    JIAN(state,value){
        state.sum -= value
    }
}

// 3、准备state:用于存储数据
const state = {
    sum:0,
    school:"上海大学",
    project:'数学',
}

// 4、用于:将state中的数据进行加工
const getters = {
    bigSum(state){
        return state.sum*10
    }
}

方法3:借助mapActions生成对应的方法,方法中会调用"this.$storemit"去联系(vuex中的)actions(数组写法),函数名和方法名默认一致。

模板语法:
<button @click="jishu(user_select)">当前求和为奇数在加</button>
<button @click="waitadd(user_select)">等一等</button>

js:
import {mapActions} from 'vuex'
data() {
    return {
        user_select: 1,
    }
},
methods: {
    // 借助mapActions生成对应的方法,方法中会调用"this.$storemit"去联系(vuex中的)actions(对象写法)
    ...mapActions(['jishu','waitadd'])
},

vuex(store/index.js):
// 1、准备actions:用于响应组件中的动作
const actions = {
    jishu(context,value){
        if(context.state.sum % 2){
            contextmit("JIA",value)
        }
    },
    waitadd(context,value){
        setTimeout(()=>{
            contextmit("JIA",value)
        },500)
    },
}

// 2、准备mutations:用于操作数据(state)
const mutations = {
    JIA(state,value){
        state.sum += value
    },
    JIAN(state,value){
        state.sum -= value
    }
}

// 3、准备state:用于存储数据
const state = {
    sum:0,
    school:"上海大学",
    project:'数学',
}

// 4、用于:将state中的数据进行加工
const getters = {
    bigSum(state){
        return state.sum*10
    }
}

5.8、vuex的模块化编码(开启命名空间)

5.8.1、总结

1.目的:让代码更好维护,让多种数据分类更加明确。
2.代码详见src/store/index.js
3.开启命名空间后,组件中读取state的数据:
    方式1:this.$store.state.person.list
    方式2:...mapState('person',['sum','school','subject'])
4.开启命名空间后,组件中读取getters的数据:
    方式1,自己直接读:
    this.$store.getters["personAbout/firstPersonName"]
    方式2,借助mapGetters读取:
    ...mapGetters('count',['bigSum'])
5.开启命名空间后,组件中调用dispatch
    方式1,自己直接dispatch
    this.$store.dispatch('person/addPersonWang',personObj)
    方式2,借助mapActions
    ...mapActions("count",["jishu","waitadd"]),
6.开启命名空间后,组件中调用commit
    方式1,自己直接commit
    this.$storemit('person/ADDPERSON',personObj)
    方式2,借助mapMutations
    ...mapMutations("count",{"jia":"JIA","jian":"JIAN"}),

5.8.2、例子如下

store/index.js文件

// 该文件用于创建Vuex最核心的store

// 引入Vue
import Vue from 'vue'
// 引入Vuex
import Vuex from "vuex"
// 应用Vuex插件
Vue.use(Vuex)

// 求和相关配置项
const countOptions = {
    // 开启命名空间
    namespaced: true,
    actions: {
        jishu(context, value) {
            if (context.state.sum % 2) {
                contextmit("JIA", value)
            }
        },
        waitadd(context, value) {
            setTimeout(() => {
                contextmit("JIA", value)
            }, 500)
        },
    },
    mutations: {
        JIA(state, value) {
            state.sum += value
            // console.log("mutations",state,value)
        },
        JIAN(state, value) {
            state.sum -= value
        },
    },
    state: {
        sum: 0,
        school: "上海大学",
        project: '数学',
    },
    getters: {
        bigSum(state) {
            return state.sum * 10
        }
    },
}

// 人员相关配置项
const personOptions = {
    // 开启命名空间
    namespaced: true,
    actions: {
        addPersonWang(context, value){
            if (value.name.indexOf('王') ===0){
                contextmit('ADD_PERSON',value)
            }else {
                alert('必须姓王')
            }
        },
    },
    mutations: {
        ADD_PERSON(state, value) {
            state.personList.unshift(value)
        },
    },
    state: {
        personList: [
            {id: '001', name: 'sudada'}
        ],
    },
    getters: {
        firstPersonName(state){
            return state.personList[0].name
        },
    },
}

// 创建store,并暴露(导出) store。
export default new Vuex.Store({
    // 命名空间写法
    modules: {
        countOptions: countOptions,
        personOptions: personOptions
    }
})

vue组件文件(Count.vue),vuex使用命名空间时的"简写方式"(通过mapState,mapGetters,mapActions,mapMutations这些方法)

<template>
    <div>
        <h1>当前值为:{{ sum }}</h1>
        <h2>当前值为10倍:{{ bigSum }}</h2>
        <h3>我在{{ school }} 学习{{ project }}</h3>
        <h3 style="color: red">Person组件的总人数是:{{personList.length}}</h3>

        <select v-model.number="user_select">
            <option value="1">1</option>
            <option value="2">2</option>
            <option value="3">3</option>
        </select>
        <button @click="jia(user_select)">加</button>
        <button @click="jian(user_select)">减</button>
        <button @click="jishu(user_select)">当前求和为奇数在加</button>
        <button @click="waitadd(user_select)">等一等</button>
    </div>
</template>

<script>
    import {mapState} from 'vuex'
    import {mapGetters} from 'vuex'
    import {mapActions} from 'vuex'
    import {mapMutations} from 'vuex'

    export default {
        name: "Count",
        data() {
            return {
                user_select: 1,
            }
        },
        computed: {
            // 借助mapState生成计算属性,从countOptions里面的$store.state中读取数据(对象写法)
            ...mapState('countOptions',{sum:'sum',school:'school',project:'project'}),
            // 借助mapState生成计算属性,从personOptions里面的$store.state中读取数据(对象写法)
            ...mapState('personOptions',{personList:'personList'}),
            // 借助mapGetters生成计算属性,从countOptions里面的$store.getters中读取数据(对象写法)
            ...mapGetters('countOptions',{bigSum:'bigSum'})
        },
        methods: {
            // 借助mapMutations生成对应的方法,方法中会调用"this.$storemit"去联系(vuex中的)countOptions里面的mutations(对象写法)
            ...mapMutations('countOptions',{jia:"JIA",jian:"JIAN"}),
            // 借助mapActions生成对应的方法,方法中会调用"this.$storemit"去联系(vuex中的)countOptions里面的actions(对象写法)
            ...mapActions('countOptions',{jishu:"jishu",waitadd:"waitadd"}),
        },
    }
</script>

<style scoped>
    button {
        margin-right: 5px;
    }
</style>

vue组件文件(Person.vue),vuex使用命名空间时的"原生写法"(通过this.$store.xxx.xxx调用)

<template>
    <div>
        <h1>人员列表</h1>
        <h3 style="color: red">Count组件求和值为:{{sum}}</h3>
        <h3>列表中第一个人的名字:{{firstPersonName}}</h3>
        <input type="text" placeholder="请输入名字" v-model="name">
        <button @click="add">添加</button>
        <button @click="addWang">添加一个姓王的</button>
        <ul>
            <li v-for="p in personList" :key="p.id">{{ p.name }}</li>
        </ul>
    </div>
</template>

<script>
    import {nanoid} from 'nanoid'
    export default {
        name: "Person",
        data(){
            return {
                name:''
            }
        },
        computed:{
            personList(){
                return this.$store.state.personOptions.personList
            },
            sum(){
                return this.$store.state.countOptions.sum
            },
            firstPersonName(){
                return this.$store.getters['personOptions/firstPersonName']
            },
        },
        methods:{
            add(){
                const personObj = {id:nanoid(),name:this.name}
                this.$storemit('personOptions/ADD_PERSON',personObj)
                this.name = ''
            },
            addWang(){
                const personObj = {id:nanoid(),name:this.name}
                this.$store.dispatch('personOptions/addPersonWang',personObj)
                this.name = ''
            },
        }
    }
</script>

<style scoped>

</style>

六、路由器

6.1、初识路由器

1.vue-router的理解:
    vue的一个专门插件库,专门用来实现SPA应用

2.对于SPA的理解:
    1.单网页应用
    2.整个应用只有一个完整版的页面(index.html)
    3.点击页面的导航链接不会刷新页面,只会做页面的局部刷新。
    4.数据通过ajax请求获取

3.路由:就是"一组key-value的对应关系"。

4.多个路由:需要经过"路由器"的管理。

5.安装vue-router:npm install vue-router@3  vue2版本安装vue-router@3,vue3版本安装vue-router@4

6.编写router配置项:router/index,js

// 该文件专门用于创建应用的路由器

// 引入VueRouter插件
import VueRouter from 'vue-router'

// 引入组件
import About from "@/pages/About";
import Home from "@/pages/Home";

// "创建"并"暴露"这个路由器
export default new VueRouter({
     routes:[
         // 一个个路由对象:
        {
            path:'/about',   // (key)请求路径
            component:About, // (value)请求路径对应的"组件名称"
        },
        {
            path:'/home',     // (key)请求路径
            component:Home,   // (value)请求路径对应的"组件名称"
        },
    ]
})

7.引用和注册路由:main.js

// 引入vue
import Vue from 'vue'
// 引入App
import App from './App'
// 关闭生产提示
Vue.config.productionTip = false


// 引入VueRouter
import VueRouter from 'vue-router'
// 应用VueRouter
Vue.use(VueRouter)

// 引入路由器
import router from "./router/index"

// 创建vm
new Vue({
    el:"#app",
    render: h => h(App),

    // vm注册router
    router:router,
})

8.使用路由器:实现路由切换,主要是 "router-link" 和 to="/about"

<div>
  // 使用router-link实现页面的切换,to="/about"就是跳转到哪个路径
  // active-class=""  元素被激活时候,显示的样式
  <router-link  class="list-group-item" active-class="active" to="/about">About</router-link>
  <hr>
  <router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
</div>

9.使用路由器:指定位置展示(组件内容)

<div class="panel-body">
  <!--  指定组件的呈现位置 -->
  <router-view></router-view>
</div>

10.使用路由器时的几个注意点:
        1."路由器组件"通常存放在"pages"文件夹"其他组件"放在components文件夹
        2.每次组件之间的切换,没有被展示页面的组件,都会被销毁需要用到的时候再去挂载
        3.每个组件都有自己的$route(路由)属性,里面存储自己的路由信息

        
        4.整个应用只有一个router(路由器),可以通过组件的$router属性获取到。

         

6.2、多级路由(嵌套路由)

1.路由规则配置,在一级路由内使用 "children" 关键字,详见:src/router/index.js

// 该文件专门用于创建应用的路由器

// 引入VueRouter插件
import VueRouter from 'vue-router'

// 引入组件
import About from "@/pages/About";
import Home from "@/pages/Home";
import News from "@/pages/News";
import Message from "@/pages/Message";

// 暴露这个路由器
export default new VueRouter({
     routes:[
         {
             // 一级路由
             path:'/about',   // 请求路径
             component:About, // 请求路径对应的"组件名称"
         },
        {
            // 一级路由
            path:'/home',     // 请求路径
            component:Home,   // 请求路径对应的"组件名称"
            children:[        // children关键字定义子路由
                {
                    // 子路由(二级路由)
                    path:'news',  // 二级路由请求path,不加"/"
                    component:News,
                },
                {
                    // 子路由(二级路由)
                    path:'message',  // 二级路由请求path,不加"/"
                    component:Message,
                },
            ]
        },
    ]
})

2.多级路由(嵌套路由)的跳转路径,要写绝对路径:

<div>
  <ul class="nav nav-tabs">

    //使用router-link实现页面的切换,to="/home/news"就是跳转到哪个路径(这里要写绝对路径)
    <li>
      <router-link class="list-group-item" active-class="active" to="/home/news">News</router-link>
    </li>
    <li>
      <router-link class="list-group-item" active-class="active" to="/home/message">Message</router-link>
    </li>
  </ul>

  <!--  指定组件的呈现位置 -->
  <router-view></router-view>
</div>

6.3、路由传参,query方式(方法一)

1.在组件内写一个路由传参代码【to="/home/message/detail?id=666&title=Hello!"】,传递的参数是:{id:"666",title:"Hello!"}

在其他组件内可以通过:this.$route.query 拿到,如下图:

2.路由传参的query方法使用:

方式1(传参时,to的字符串写法):

        【:to="`/home/message/detail?id=${m.id}&title=${m.title}`"】注释: ":to"后面的东西会当做js语法解析。加了``(模板语法):可理解为'',${message.id} 就是在模板语法(``)里面取变量的值(js里面取变量的方式)

<router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link>

方式2(传参时,to的对象写法):

<router-link :to="{
  path:'/home/message/detail',   // 请求的路径,关键字path
  query:{                        // 请求的参数(封装成一个对象),关键字query
    id:m.id,
    title:m.title,
  }
}">
  {{m.title}}
</router-link>

3.组件内接收路由传递过来的参数:this.$route.query 即可拿到。

<template>
  <div>
    <ul>
      <li>消息编号: {{ $route.query.id }}</li>
      <li>消息标题: {{ $route.query.title }}</li>
    </ul>
  </div>
</template>

6.4、路由命名

作用:可以简化路由的跳转

1.给路由命名
 routes:[
    {
        name:"guanyu", // 路由命名,关键字name
        path:'/about',
        component:About,
    },
    ]
2.使用命名的路由:(前提条件,只能在对象里面使用,也就是:to={})
    <router-link :to="{
      name:'xiangqing',  // 通过给路由命名的方式(前提条件,只能在对象里面使用,也就是:to={}),这里就不写path,写name即可。
      query:{                       
        id:message.id,
        title:message.title,
      }
    }">
      {{message.title}}
    </router-link>&nbsp;&nbsp;

6.5、路由传参,params方式(方法二)

1.在组件内写一个路由传参代码【:to="`/home/message/detail/666/Hello!`"】,传递的参数是:{id:"666",title:"Hello!"}

在其他组件内可以通过:this.$route.query 拿到,如下图:

对应路由器index.js里面:(由path:'detail'改为path:'detail/:id/:title')

{
    path:'detail/:id/:title',  // "detail/" 是路由层级,":id/:title"是参数(占位符,也就是参数传递过来后是这样子的{id:666,title:你好啊} )
    component:Detail,
},

2.路由传参的params方法使用:

方式1(传参时,to的字符串写法):

【:to="`/home/message/detail/${m.id}/${m.title}!`"】

<router-link :to="`/home/message/detail/${m.id}/${m.title}!`">{{m.title}}</router-link>

对应路由器index.js里面:(由path:'detail'改为path:'detail/:id/:title')

{
    path:'detail/:id/:title',  // "detail/" 是路由层级,":id/:title"是参数(占位符,也就是参数传递过来后是这样子的{id:666,title:你好啊} )
    component:Detail,
},

方式2(传参时,to的对象写法):

<router-link :to="{
  name:'xiangqing',   // 这里必须使用name,也就是路由的名称。不能写path
  // path:'/home/message/detail',  // 这里不能写path,会报错
  params:{
    id:m.id,
    title:m.title,
  }
}">
  {{m.title}}
</router-link>

对应路由器index.js里面:(由path:'detail'改为path:'detail/:id/:title'),同时还必须加上name(路由命名)

{
    name:'xiangqing',
    path:'detail/:id/:title',  // "detail/" 是路由层级,":id/:title"是参数(占位符,也就是参数传递过来后是这样子的{id:666,title:你好啊} )
    component:Detail,
},

3.组件内接收路由传递过来的参数:this.$params.query 即可拿到。

<template>
  <div>
    <ul>
      <li>消息编号: {{ $route.params.id }}</li>
      <li>消息标题: {{ $route.params.title }}</li>
    </ul>
  </div>
</template>

6.6、路由传参,props方式(方法三)

作用:让路由组件更方便的收到参数

方式1:props传递一个对象(该对象中的所有key,value都会以props的形式传给Detail组件,Detail组件通过props接收。)

router/index.js

{
    path:'message',  // 子路由请求path,不加"/"
    component:Message,
    children:[
        {
            name:'xiangqing',
            path:'detail',
            component:Detail,
            // props的第一种写法:值为对象,该对象中的所有key,value都会以props的形式传给Detail组件,Detail组件通过props接收。
            props:{a:1, b:2,}
        },
    ]
},

在组件内接收props传递的值:props:["a","b"]

<template>
  <div>
    <ul>
      <li>a:{{a}}</li>
      <li>b:{{b}}</li>
    </ul>
  </div>
</template>

<script>
export default {
  name: "Detail",
  props:["a","b"],
}
</script>

方式2:props传递一个布尔值(props为true时,就把该路由组件收到的所有params参数,都会以props的形式传给Detail组件,Detail组件通过props接收。)

router/index.js

{
    path:'message',  // 子路由请求path,不加"/"
    component:Message,
    children:[
        {
            name:'xiangqing',
            path:'detail/:id/:title',  // "detail/" 是路由层级,":id/:title"是参数(占位符,也就是参数传递过来后是这样子的{id:666,title:你好啊} )
            component:Detail,
            // props的第二种写法:值为布尔值,为true就把该路由组件收到的所有params参数,都会以props的形式传给Detail组件,Detail组件通过props接收。
            props:true
        },
    ]
},

在组件内接收props传递的值:props:["id","title"]

<template>
  <div>
    <ul>
      <li>消息编号: {{ id }}</li>
      <li>消息标题: {{ title }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  name: "Detail",
  props:["id",'title']
}
</script>

方式3:props传递一个值为函数,通过接收参数'$route'的方式取出值,以props的形式返回给Detail组件,Detail组件通过props接收。

router/index.js

{
    path:'message',  // 子路由请求path,不加"/"
    component:Message,
    children:[
        {
            name:'xiangqing',
            path:'detail/:id/:title',  // "detail/" 是路由层级,":id/:title"是参数(占位符,也就是参数传递过来后是这样子的{id:666,title:你好啊} )
            component:Detail,
            // props的第三种写法(推荐):值为函数,通过接收参数'$route'的方式取出值,以props的形式返回给Detail组件,Detail组件通过props接收。
                // 通过query传参就使用$route.query查到值,
                // 通过params传参就使用$route.params查到值
            props($route){
                console.log($route)
                return {id: $route.params.id, title: $route.params.title}
            },
        },
    ]
},

在组件内接收props传递的值:props:["id","title"]

<template>
  <div>
    <ul>
      <li>消息编号: {{ id }}</li>
      <li>消息标题: {{ title }}</li>
    </ul>
  </div>
</template>

<script>
export default {
  name: "Detail",
  props:["id",'title']
}
</script>

6.7、router-link 的 push 和 replace属性

作用:控制路由跳转时,操作浏览器历史记录的模式

1.router-link默认是"push"模式,使用push模式(追加历史记录),浏览器可以正常前进后退

2.:replace="true" 简写为 replace :开启router-link的"replace"模式 (替换当前记录,开启之后浏览器不能前进后退了)

3.开启:

6.8、编程式路由导航

作用:不借助 router-link 的路由导航,让路由跳转更加灵活

router-link写法的路由导航:

<template>
  <div>
    <ul>
      <li v-for="m in messageList" :key="m.id">
        <router-link :to="{
          path:'/home/message/detail',  // 这里不能写path,会报错
          query:{
            id:m.id,
            title:m.title,
          }
        }">
          {{ m.title }}
        </router-link>
      </li>
    </ul>
    <hr>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: "Message",
  data() {
    return {
      messageList: [
        {id: "001", title: "消息001"},
        {id: "002", title: "消息002"},
        {id: "003", title: "消息003"},
      ]
    }
  },
}
</script>

编程时路由导航写法(不需要在写router-link了):使用了this.$router.push({xxx}) 和 this.$router.replace({xxx})

<template>
  <div>
    <ul>
      <li v-for="m in messageList" :key="m.id">
        <button @click="pushShow(m)">push查看</button>
        <button @click="replaceShow(m)">replace查看</button>
      </li>
    </ul>
    <hr>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: "Message",
  data() {
    return {
      messageList: [
        {id: "001", title: "消息001"},
        {id: "002", title: "消息002"},
        {id: "003", title: "消息003"},
      ]
    }
  },
  methods: {
    pushShow(m) {
      // push 属性
      this.$router.push({
        path:'/home/message/detail',
        query:{
          id:m.id,
          title:m.title,
        }
      })
    },
    replaceShow(m){
      // replace 属性
      this.$router.replace({
        path:'/home/message/detail',
        query:{
          id:m.id,
          title:m.title,
        }
      })
    },
  }
}
</script>

模拟浏览器的前进后退模式:

methods:{
  back(){
    // 相当于浏览器的回退功能
    this.$router.back()
  },
  forward(){
    // 相当于浏览器的前进功能
    this.$router.forward()
  },
  testgo(){
    // 相当于浏览器的"前进|后退"功能,正数就是前进N步,负数就是后退N步
    this.$router.go(-1)
},

6.9、缓存路由组件

作用:让不展示的组件保持挂载,不被销毁

缓存单个组件时的写法:代码里面的"News" 指的是"组件名"

<keep-alive include="News">
  <router-view></router-view>
</keep-alive>

缓存多个组件时的写法:代码里面的 'News'和'Message' 指的是"组件名"

<keep-alive :include="['News','Message']">
  <router-view></router-view>
</keep-alive>

缓存所有组件时的写法:

<keep-alive>
  <router-view></router-view>
</keep-alive>

6.10、路由的生命周期钩子

作用:路由组件独有的2个钩子,用户捕获路由组件的激活状态

activated:"组件"激活(被挂载)的时候,触发这个API。

deactivated:"组件"失活(从"被挂载"变为"取消挂载")的时候,触发这个API。

<script>
export default {
  name: "News",
  activated() {
    console.log("News组件activated(激活)了")
  },
  deactivated() {
    console.log("News组件deactivated(失活)了")
  }
}
</script>

6.11、路由守卫

作用:对路由进行权限控制
分类:全局守卫,独享守卫,组件内守卫

6.11.1、全局前置路由守卫(router/index.js):router.beforeEach

// 该文件专门用于创建应用的路由器

// 引入VueRouter插件
import VueRouter from 'vue-router'

// 引入组件
import About from "@/pages/About";
import Home from "@/pages/Home";
import News from "@/pages/News";
import Message from "@/pages/Message";
import Detail from "@/pages/Detail";

// 创建一个路由器
const router = new VueRouter({
     routes:[
         {
             // 一级路由
             name:'guanyu',
             path:'/about',   // 请求路径
             component:About, // 请求路径对应的"组件名称"
             meta: {isAuth:true},  // 添加自定义的"权限校验"字段
         },
    ]
})

// 全局"前置"路由守卫
router.beforeEach(function (to,from,next){
    console.log('在每一次路由切换(路由路径改变)之前,都会调用下面的"函数"')

    // to: 要去往哪个"路由"地址
    console.log('to',to)
    // from:从哪个"路由"地址过来的
    console.log("from",from)

    // next:放行(不写"next()"的话,每次请求都会被路由守卫拦住,无法前进到目标路由)
    // 方式一:通过to.path判断请求路径的方式
    if(to.path === '/home/news' || to.path === '/home/message'){
        if(localStorage.getItem('school') === 'shanghai'){
            next()
        }else {
            alert("当前学校无权限查看")
        }
    }else {
        next()
    }

    // 方式二:通过"to.meta"(专门用来存放自定义数据的对象,需要提前在路由里面定义meta:{}),判断是否走路由守卫的校验。
    if(to.meta.isAuth){
        if(localStorage.getItem("school") === 'shanghai'){
            next()
        } else {
            alert('当前学校无权限查看')
        }
    } else {
        next()
    }
})

// 暴露这个路由器
export default router

6.11.2、全局后置路由守卫(router/index.js):router.afterEach

// 该文件专门用于创建应用的路由器

// 引入VueRouter插件
import VueRouter from 'vue-router'

// 引入组件
import About from "@/pages/About";
import Home from "@/pages/Home";
import News from "@/pages/News";
import Message from "@/pages/Message";
import Detail from "@/pages/Detail";

// 创建一个路由器
const router = new VueRouter({
     routes:[
         {
             // 一级路由
             name:'guanyu',
             path:'/about',   // 请求路径
             component:About, // 请求路径对应的"组件名称"
             meta: {title:'guanyu'},  // 添加自定义字段
         },
    ]
})

// 全局"后置"路由守卫
router.afterEach((to,from)=>{
    console.log('"后置"路由守卫,在每一次路由切换(路由路径改变)之后,都会调用下面的"函数"')

    // to: 要去往哪个"路由"地址
    console.log('to',to)
    // from:从哪个"路由"地址过来的
    console.log("from",from)

    // 修改页签标头(在页面切换之后)
    document.title = to.meta.title
})

// 暴露这个路由器
export default router

6.11.3、独享前置路由守卫(无后置)(router/index.js):beforeEnter

某一个路由所独享的:

// 该文件专门用于创建应用的路由器

// 引入VueRouter插件
import VueRouter from 'vue-router'

// 引入组件
import About from "@/pages/About";
import Home from "@/pages/Home";
import News from "@/pages/News";
import Message from "@/pages/Message";
import Detail from "@/pages/Detail";

// 创建一个路由器
const router = new VueRouter({
     routes:[
         {
             // 一级路由
             name:'guanyu',
             path:'/about',   // 请求路径
             component:About, // 请求路径对应的"组件名称"
             meta: {title:'zhuye',isAuth:true},  // 添加自定义字段

             // 组件独享的路由守卫:只有在进入到"News"组件时被调用,同时调用下面的"函数"(普通函数和箭头函数都可以)。
             beforeEnter: function (to,from,next){

                 console.log('组件独享的路由守卫')
                 // to: 要去往哪个"路由"地址
                 console.log('to',to)
                 // from:从哪个"路由"地址过来的
                 console.log("from",from)

                 if(to.meta.isAuth){
                     if(localStorage.getItem("school") === 'shanghai'){
                         next()
                     } else {
                         alert('当前学校无权限查看')
                     }
                 } else {
                     next()
                 }
             }
         },
    ]
})

// 暴露这个路由器
export default router

6.11.4、组件内路由守卫

定义路由:router/index.js

// 该文件专门用于创建应用的路由器

// 引入VueRouter插件
import VueRouter from 'vue-router'

// 引入组件
import About from "@/pages/About";

// 创建一个路由器
const router = new VueRouter({
     routes:[
         {
             // 一级路由
             name:'guanyu',
             path:'/about',   // 请求路径
             component:About, // 请求路径对应的"组件名称"
             meta: {title:'guanyu',isAuth:true},  // 添加自定义字段
         },
    ]
})

// 暴露这个路由器
export default router

组件内路由守卫写法:About.vue组件

    通过路由规则,"进入"到当前组件之前,会被调用:beforeRouteEnter
    通过路由规则,在"离开"当前组件之前,会被调用:beforeRouteLeave

<template>
  <h2>我是About的内容</h2>
</template>

<script>
export default {
  name: "About",
  // 通过路由规则,"进入"到当前组件之前,会被调用
  beforeRouteEnter( to,from,next ){
    console.log("beforeRouteEnter")
    if(to.meta.isAuth){
        if(localStorage.getItem("school") === 'shanghai'){
            next()
        } else {
            alert('当前About组件无权限查看')
        }
    } else {
        next()
    }
  },

  // 通过路由规则,在"离开"当前组件之前,会被调用
  beforeRouteLeave(to,from,next){
    console.log("beforeRouteLeave")
    next()
  },
}
</script>

6.12、路由器的两种工作模式

对于一个url来说,什么是hash值? #号及后面的内容就是hash值。

1.hash模式:
  1.地址中一直带着#号,不美观。
  2.若以后将地址通过第三方手机APP分享,若app校验严格,则会将地址标记为不合法。
  3.兼容性较好

2.history模式:
  1.地址干净,美观。
  2.兼容性和hash模式相比略差。
  3.应用部署上线后,需要后端人员支持,解决刷新页面404的问题。

// 该文件专门用于创建应用的路由器

// 引入VueRouter插件
import VueRouter from 'vue-router'

// 引入组件
import About from "@/pages/About";
import Home from "@/pages/Home";
import News from "@/pages/News";
import Message from "@/pages/Message";
import Detail from "@/pages/Detail";

// 创建一个路由器
const router = new VueRouter({
     // 设置路由器的工作模式,默认是hash模式
     mode:'history',
     routes:[
         {
             name:'guanyu',
             path:'/about',   // 请求路径
             component:About, // 请求路径对应的"组件名称"
             meta: {title:'guanyu',isAuth:true},  // 添加自定义字段
         },
    ]
})

// 暴露这个路由器
export default router

七、Vue UI 组件库 (PC端)

1.Element UI    https://element.eleme/

 安装:npm i element-ui -S

2.完整引入Element库(组件依赖过多,文件很大)

// 引入vue
import Vue from 'vue'
// 引入App
import App from './App'
// 关闭生产提示
Vue.config.productionTip = false
// 引入ElementUI组件库
import ElementUI from 'element-ui';
// 引入ElementUI全部样式
import 'element-ui/lib/theme-chalk/index.css';

// 应用ElementUI
Vue.use(ElementUI);

// 创建vm
new Vue({
    el:"#app",
    render: h => h(App),
})

组件内使用:App.vue

<template>
  <div>
    <el-button>
      <el-button>默认按钮</el-button>
      <el-button type="primary">主要按钮</el-button>
    </el-button>
    <hr>
    <el-date-picker
        type="date"
        placeholder="选择日期">
    </el-date-picker>
  </div>
</template>

<script>
  export default {
    // App组件名称
    name: "App",
  }
</script>

3.局部引用:main.js 

先安装:npm install babel-plugin-component -D

// 引入vue
import Vue from 'vue'
// 引入App
import App from './App'
// 关闭生产提示
Vue.config.productionTip = false

// 按需引入(借助 babel-plugin-component,我们可以只引入需要的组件,以达到减小项目体积的目的。)
import { Button,Row,DatePicker } from 'element-ui';
// 应用ElementUI,原生的方式
Vueponent(Button.name,Button)
Vueponent(Row.name,Row)
Vueponent(DatePicker.name,DatePicker)

// 创建vm
new Vue({
    el:"#app",
    render: h => h(App),
})

然后修改:babel.config.js

// 按需引入配置写法
module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset',
    ["@babel/preset-env", { "modules": false }],
  ],
  plugins: [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}

组件内使用:App.vue

<template>
  <div>
    <el-button>
      <el-button>默认按钮</el-button>
      <el-button type="primary">主要按钮</el-button>
    </el-button>
    <hr>
    <el-date-picker
        type="date"
        placeholder="选择日期">
    </el-date-picker>
  </div>
</template>

<script>
  export default {
    // App组件名称
    name: "App",
  }
</script>

<!--
checked:直接使用就是默认勾选。
:checked="true/false",动态决定标签内一个属性是否存在,true存在checked,false不存在checked。
@click="handleCheck(todoObj.id):绑定点击事件逻辑,并传入id值。
@change="handleCheck(todoObj.id)":捕捉改变的事件,这是二选一即可。
-->

本文标签: 框架 Vue