vue基础知识

VCcode常用快捷键

	生成浏览器文件.html的快捷键:!+回车键
	代码格式化:Shift+Alt+F
	向上或者向下移动一行:Alt+UP或Alt+down
	清除:cls
	终止在终端中正在运行的前台命令使用:Ctrl+C
	
	打开终端的方法:
		打开文件夹位置,输入cmd这个指令,可以打开终端
		windows电脑打开终端  win+R 输入cmd 打开终端

vscode编辑器的配置

插件下载

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

//标签自动补全(tab键)
    "emmet.triggerExpansionOnTab": true,
    "emmet.includeLanguages": {
        "javascript": "javascriptreact",
        "wxml": "html"
    }

第一章 vue基本命令

官网:vuejs.org
开发环境:VCcode

创建一个项目的命令:(该方法可以创建Vue2和Vue3项目)

  			vue create 项目名
运行项目:
	第一步:进入项目根目录
  				cd 项目名
	第二步:运行项目
  				npm run serve

使用Vite创建Vue3的项目:(该命令只能创建Vue3的项目)

npm init vue //执行完该命令后,会弹出提示,按照提示需求即可完成项目的创建
npm i
npm run dev

Vue实例

每一个Vue的程序,都应该从一个Vue实例开始
例如我们项目中src/main.js文件中的以下代码,就是在创建一个Vue实例:

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

Vue.config.productionTip = false   // 关闭生产提示


new Vue({                     // 创建一个 Vue 实例
  render: h => h(App),        // 编译解析 App 组件
}).$mount('#app')             // 将 App 组件挂载(添加)到 id=app 的 div 中

第二章 模板语法

文本插值

双大括号:{{}}

数据绑定最常见的形式就是使用:{{}} 双大括号语法的文本插值
  <span>{{message}}</span>
一般配合js中的data()来设置数据
	<template>
	  <div class="hello">
	    <h3>2022.11.29</h3>
	    <h1>{{message}}</h1>
	  </div>
	</template>
	
	export default{
		data(){
			return{
				message:"填写所要显示的文本"		//注:此处的变量名message必须和上面双大括号里的一致
			}
		}
	}

v-once

当数据改变时,插值处的内容不会改变。

v-html

原始HTML:双大括号会将文本数据解释为普通文本,而非HTML代码。为了输出真正的HTML,需要使用:v-html命令
	<template>
	  <div class="hello">
	    <div>{{rawhtml}}</div>			//只能以文本的形式显示出来
	    <div v-html="rawhtml"></div>	//以网络连接的形式显示出来
	  </div>
	</template>
	
	<script>
	export default {
	  name: 'HelloWorld',
	  data(){
	    return{
	      rawhtml:"<a href='http://www.itbaizhan.com'>百战</a>"
	    }
	  }
	  
	}
	</script>

注:在网站上动态渲染任意 HTML 是非常危险的,因为这非常容易造成 XSS 漏洞。
请仅在内容安全可信时再使用 v-html,并且永远不要使用用户提供的 HTML 内容(script也属于HTML内容)。

v-bind

属性:双大括号语法不能在HTML属性中使用,然而,可以使用:v-bind指令(动态绑定属性内容)
v-bind可以简写成冒号(v-bind:id = :id)
<template>
  <div class="hello">
    <div v-bind:id="shuxingming"></div>
	<div v-pre>你们遇到这种情况:<span>{{shuxingming}}</span>情况,我们叫做插值语法</div>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data(){
    return{
      shuxingming:1001
    }
  }
}
</script>
<!-- 静态绑定,直接传入字符串 -->
    <img src="https://www.baidu.com/img/PCfb_5bf082d29588c07f842ccde3f97243ea.png" >
	
    <!-- 动态绑定,签名添加一个冒号,值一定是表达式 -->
    <img :src="url" >
	
    <!-- 全称v-bind -->
    <img v-bind:src="url" >

在网站上动态渲染任意 HTML 是非常危险的,因为这非常容易造成 XSS 漏洞。
请仅在内容安全可信时再使用 v-html,并且永远不要使用用户提供的 HTML 内容(script也属于HTML内容)。

为了确保Web应用程序的安全性,我们需要采用多层次的防御策略,
包括但不限于:对用户输入进行严格的校验和转义、使用HTTPS协议加密传输、
限制访问权限和操作权限、采用最新的Web安全协议和技术等。
只有这样,才能有效保护用户隐私和数据安全。

v-pre

	v-pre的用处就是告诉vue不需要解析当前行的内容

条件渲染

v-if

	v-if:指令用于条件性的渲染一块内容,这块内容只会在指令的表达式返回true值的时候被渲染,返回false值不被渲染

v-else

	v-else:可以使用v-else指令来表示v-if的else块
<div>
        <h2>条件渲染</h2>
        <h3 v-if="isRender">1. 我是通过 v-if 来渲染的</h3>
        <h3 v-else-if="true">2. 我是通过 v-else-if 来渲染的</h3>
        <h3 v-else>3. 我是通过 v-else 来渲染</h3>
    </div>

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

v-show

	v-show:另一个用于条件性展示的指令
注:v-if是真正的条件渲染,因为它会确保在切换过程中,条件块内的事件监听器和子组件适当地被销毁和重建,如果条件为假时,则什么也不会做,只有条件为真时才开始渲染条件块。
	v-show就简单的多,不管初始条件是什么,元素总会被渲染,并且只是简答地基于css进行切换(display:none)
	v-if有更高的切换开销(性能消耗),v-show有更高的初始化渲染开销。
	因此,如果需要非常频繁的切换,则使用v-show较好;
		  如果在运行时条件很少改变(初次渲染时),则使用v-if较好。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

<template>
  <div class="hello">
    <p v-if="flag">今天开心</p>
    <p v-else>今天不开心</p>
    <p v-show="flag">今天是真的开心</p>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data() {
    return {
      flag: true
    }
  }
}
</script>

列表渲染

v-for

v-for:用于把一个数组映射为一组元素
v-for指令基于一个数组来渲染一个列表,v-for指令需要使用item in items形式的特殊语法,
其中items是源数据数组名,而item则是被迭代的数组元素的别名。

<template>
  <div class="hello">
    <ul>
      <li v-for="item in newsList">
        {{ item.title }}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data() {
    return {
      newsList:[
        {
        id:1001,
        title:"今日新闻"
        },
        {
          id:1002,
          title:"今日新闻"
        },
        {
          id:1003,
          title:"今日新闻"
        }
      ]
    }
  }
}
</script>

:key

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
当更新使用v-for渲染的元素列表时,它不会再把所有元素重新渲染一遍,而是直接渲染所被更新的元素,因此为了确保索引位置正确,需要为每一项提供一个唯一的key

<div v-for="item in items" :key="item.id">
<!-- 内容 -->
</div>
或者:
<div v-for="(item.index) in items" :key="item.index">
<!-- 内容 -->
</div>

注意事项:

在vue2中,v-for的优先级更高

在vue3中,v-if的优先级更高
v-forv-if一起使用时,v-if的优先等级高于v-for,所有能放在同一标签一起使用。

监听事件

v-on

v-on:(通常使用缩写:@)用来监听DOM事件,并在触发事件时执行一些Javascript,用法为:v-on:click="methodName"或者简写:@click="methodName"

<template>
  <div class="hello">
    <button v-on:click="counter += 1">点击:counter={{counter}}</button>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data() {
    return {
      counter:1
    }
  }
}
</script>

v-on还可以接收一个需要调用的方法名称

<template>
  <div class="hello">
    <button v-on:click="counter += 1">点击:counter={{counter}}</button>
    <button @click="clickHandle">按钮</button>
    <p>{{message}}</p>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data() {
    return {
      counter:1,
      message:"消息通知"
    }
  },
  methods:{
    clickHandle(){
      this.message="消息被撤回了"
      //在事件中,读取data中的属性,是需要通过this.属性
    }
    /*clickHandle(event){
      this.message="消息被撤回了"
      event 是原生DOM event
      console.log(event);
      event.target.innerHTNL = "点击之后"
    }*/
  }
}
</script>

内联处理器中的方法,也叫事件传递参数

<template>
  <div class="hello">
    <button @click="say('hai')">say hi</button>
    <button @click="say('what')">say what</button>
    <ul>
      //@click="clickItemHandle(item)"实现内联传递
      <li @click="clickItemHandle(item)" v-for="(item,index) in names">{{item}}</li>
    </ul>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data() {
    return {
      names:["hai","hello","world"]
    }
  },
  methods:{
    say(data){
      console.log(data);  //通过data函数来传递参数:hai和what
    },
    clickItemHandle(item){
      console.log(item);
    }
  }
}
</script>

template标签

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

与v-if搭配使用

<template v-if="true">
            <h1>hello</h1>
            <h1>你好</h1>
        </template>

注意:v-show不能用在template身上(它是一个空标签,本身就无法渲染)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

事件修饰符(.stop、.prevent)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

.stop:是阻止事件流的传播,封装了event.stopPropagation()
.prevent:阻止标签默认行为,封装了event.preventDefault()
.capture 使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理
.self 只当在 event.target 是当前元素自身时触发处理函数
.once 事件将只会触发一次
.passive 告诉浏览器你不想阻止事件的默认行为

<div id="app">
    <div class="outer" @click="outclick">
      我是外层
	  
      <!-- 阻止冒泡事件 -->
      <div class="inner" @click.stop="inclick">
        我是内层div
      </div>
    </div>
	
    <hr>
    <!-- 阻止默认事件用prevent -->
    <a v-on:click.prevent="baidu" href="http://wwww.baidu.com">百度</a>
  </div>

表单输入绑定

	v-model:可在表单<input>、<textarea>、<select>元素上创建双向数据绑定
<template>
  <div class="hello">
    <input type="text" v-model="username">  //v-model="username"实现双向绑定
    <input type="text" v-model.lazy="password">
    <p>{{username}},{{password}}</p>
    <button @click="clickGetUserName">获取用户名</button>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data(){
    return{
      username:"",
      password:""
    }
  },
  methods:{
    clickGetUserName(){
      console.log(this.username);
    }
  }
}
</script>


	.lazy:在默认情况下,v-model在每次input事件触发后将输入的值与数据进行同步,可添加lazy修饰符,在输入完成后点击回车键才同步数据
	.trim:自动过滤用户输入的首尾空白字符
	.number:将用户输入的内容,通过 parseFloat() 方法转换为数字,如果无法转换,则保留原来数据
  	语法:v-model.lazy="message"
  		v-model.trim="message"

第三章 计算属性(computed)

当我们在 HTML中,要通过比较复杂的操作(计算)来得到一条数据,这种情况下,Vue官方建议将这操作(计算)过程放到JS的计算属性中
computed不支持异步,当computed内有异步操作时是无法监听数据的变化。
计算属性擅长处理的场景是:多个数据影响一个数据 ----购物车商品结算。

基础语法

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

<script>
    // 创建应用
    const app = Vue.createApp({
      // 初始化数据函数
      data() { // 返回一个对象,对象中写初始化数据
        return {
          num: 0, // 初始化一个的数据
          num1: 1,
        };
      },
	  
      // computed  计算属性,配置项是一个对象
      computed: {
        numx2() { // 是一个函数,返回值就是新生成的属性,只读属性
          // 如果它依赖的属性每一个都没有变化,就不会重新计算
          console.log('=================numx2');
          return this.num * 2 + this.num1;
        }
      },
	  
      methods: {
        multi2() {
          console.log('=================multi2');
          return this.num * 2;
        },
        changeNumx2() {
          this.numx2 = 123;
        }
      },
    });
    // 挂载应用
    app.mount('#app');
  </script>

计算属性的缓存

计算属性的缓存,指的是当同一个计算属性被使用多次时,只会执行第一次。因为,当第一次计算出结果后,浏览器会将计算结果保存到内存中
后续再使用时,就可以直接读取内存中之前计算的结果。
但是!当计算属性中依赖的任意一个原数据发生改变时, 计算属性都会重新执行重新计算。

计算属性的修改

默认情况下,计算属性是不能修改的,一但修改,就会出现以下报错:
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

计算属性的set方法

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

<script>
    // 创建应用
    const app = Vue.createApp({
      // 初始化数据函数
      data() { // 返回一个对象,对象中写初始化数据
        return {
          firstName: '曾', // 初始化一个的数据
          lastName: '阿牛',
        };
      },
	  
      computed: { // 计算属性配置项
        // 返回函数的时候,计算属性是只读的,如果要使它可写,一定要用对象格式
        // fullName() {
        //   return this.firstName + this.lastName;
        // }
		
		
        fullName: { // 有两个配置项,一个是get,读(getter),一个是set,写(setter)
          get() {
            return this.firstName + this.lastName;
          },
          set(fullName) { // 有一个参数,就是fullName的值
            // console.log('=================fullName', fullName);
            this.firstName = fullName[0]; // 取第一个字为姓
            this.lastName = fullName.slice(1);
          }
        },
      },
      methods: { // 函数配置项
        changeName() {
          this.firstName = '张';
          this.lastName = '无忌';
        },
        changeNameByFullName() {
          this.fullName = '张无忌';
        }
      }
    });
    // 挂载应用
    app.mount('#app');
  </script>

computed 和 methods的区别

computed 和 methods 能实现一样的效果:通过旧的数据得到新数据
但是,computed 和 methods 是有区别的:

  • computed 有缓存,多次使用同一个计算属性,只会执行一次(属性)
  • methods 没有缓存,多次调用同一个方法,会执行多次(方法)

因此,computed 和 methods 的应用场景,分别是:

  • computed:当我们希望通过一些旧数据进行计算得到一条新数据,同时并不会修改旧数据
  • methods:当我们需要对旧数据进行修改时

第四章 侦听器(watch)

侦听器,用来侦听属性数据的变化。当侦听的数据发生变化时,会执行对应的侦听函数
侦听器可以监听data里面的属性,也可以监听computed的属性

基础语法:

每一个组件对象都有一个watch属性,用来设置组件的侦听器:

<script>
export default {
    data() {
        return {
            num: 1
        };
    },
    watch: {	// 配置项是一个对象
		//需要侦听谁,就写谁
		// 函数的形式, 有两个参数,newValue是变化后的值,oldValue是上一次的值
        num(newValue, oldValue) {
            console.log("num 发生变化了");
        }
    },
};
</script>

停止侦听器

如果需要停止侦听器,不能使用选项配置,需要使用实例方法$watch函数。
$watch函数有两个参数,第一个参数是被监听的属性名称,第二个参数是一个函数,和选项配置里面的是一样的。
返回一个停止监听的函数,我们可以调用这个函数停止监听。

this.stopWatch = this.$watch('time', (cur, pre) => {
  if (cur >= 45) {
    console.log('下课了');
  }
});
...
this.stopWatch(); // 停止监听

侦听引用类型数据

默认情况下,引用类型的数据如果没有改变引用地址,watch是无法侦听到数据内部的变化的
但是,我们可以通过以下两种方式来解决:

  • 侦听单个属性变化
  • 对引用类型数据进行深度侦听
export default {
    data() {
        return {
            person: {
                name: "张三",
                age: 20,
            },
        };
    },
    watch: {
		
		//侦听单个属性变化
        "person.age"() {
            console.log("person.age 发生变化了");
        }
    },
};
export default {
    data() {
        return {
            person: {
                name: "张三",
                age: 20,
            },
        };
    },
    watch: {
        //s如果侦听器需要额外的配置,需要写成对象类型。
		//对引用类型数据进行深度侦听
        person: {
            handler(newValue, oldValue) {
                console.log("person 发生变化了");
            },
            deep: true, // 深度侦听(主要靠它)
			
            immediate: true, // 立即侦听
        },
    },
};

立即侦听

默认情况下,侦听器在首页进入时不会执行,如果我们希望侦听器能够在首页一加载就执行一次,那么可以设置“立即侦听”

语法:immediate: true 在所需要执行立即侦听的函数里添加即可

watch和computed的区别

watch监听的函数不用返回值,computed有返回值,并依赖函数里面提到的属性,并且生成了一个新属性
computed更像是一种依赖,别人变化,自己就跟着变
watch更主动,主动取监听某一个属性的变化
computed支持缓存,相依赖的数据发生改变才会重新计算;watch不支持缓存,只要监听的数据变化就会触发相应操作
computed不支持异步,当computed内有异步操作时是无法监听数据变化的;watch支持异步操作

第五章 组件

单文件组件:又名.vue文件,是一种特殊的文件格式,它允许将vue组件的模板、逻辑与样式封装在单个文件中
在components文件下创建.vue文件

<template>
	<h3>单文件组件</h3>
</template>

<script>
export default{
	name:"hello"
}
</script>
<!-- scoped:如果在style中添加此属性,就代表着,当前样式只能在当前组件中生效 -->
<style scoped>
h3{
	color:red;
}
</style>

加载组件:

在App.vue文件下加载
	第一步:引入组件:import 组件名 from '组件目录地址'
	第二步:挂载组件:components:{组件名}
	第三步:显示组件:<组件名/>
<template>
	<MyComponent/>	<!--第三步:显示组件 -->
</template>

<script>
import MyComponent from './components/MyComponent.vue';		//第一步:引入组件
export default {
  name: 'App',
  components:{			//第二步:挂载组件
    MyComponent
  }
}
</script>

<style>

</style>

组件样式

静态样式

全局样式和局部样式

在Vue组件中,静态样式分为全局样式和局部样式

  • 全局样式:在任何组件中定义的样式,都会作用于所有组件
  • 局部样式:在当前组件中定义的样式,只能作用与当前组件
<style>
h1 {
    color: red;
}
</style>

scoped:如果在style中添加此属性,就代表着,当前样式只能在当前组件中生效

<style scoped>
h1 {
    color: red;
}
</style>
外部样式和内部样式

我们可以将组件的样式写在组件内的style标签中,也可以在外部创建一个.css文件,来编写外部样式
外部样式通过引入方式的不同,也可以分为全局样式和局部样式

引入外部的全局样式:

<style>
	@import './index.css'
</style>

引入外部的局部样式:

<style scoped src ='./index.css'>

</style>

动态样式

动态class

Vue中提供了对象的形式来设置动态class,基础语法如下:

<h2 :class="{ active: false }">动态样式</h2>			//active:为样式。false:为判断,判断它不生效

语法说明:

  • class需要通过v-bind指令进行动态绑定
  • 对象的键,是我们想要使用的class选择器的名称(例如上例中的active
  • 对象的值,是一个布尔值,通过true或false来控制动态的class是否生效
动态style

Vue中提供了对象的形式来设置动态style,基础语法如下:

<h2 :style="{ color: fontColor}">动态的 style</h2>

语法说明:

  • style需要通过v-bind指令进行动态绑定
  • 对象的键,是我们需要设置的CSS样式的名称
  • 对象的值,是我们需要设置的CSS样式的值,是一个动态的数据(例如上例的fontColor)设置多样式的变化

组件交互

props

使用props可以实现从App.vue文件中传递参数到自己所创建的组件中(把父文件参数传送到子文件中)

注意:由于Vue中有“单向数据流”的概念,因此在Vue子组件中,所有的props的值都不能修改

在App.vue文件如下操作:

<template>
  <MyComponent :title="title" :names="names"/>	//使用子组件:进行静态显示::title="title" :names="names"传递过去的内容
</template>

<script>
import MyComponent from './components/MyComponent.vue';		//引入子组件
export default {
  name: 'App',
  //填写数据
  data(){
    return{
      title:"我是一个标题",
      names:["hai","hello","word"]
    }
  },
  components:{    	//挂载子组件
    MyComponent
  }
}
</script>

<style>

</style>

在自己创建的组件MyComponent.vue中进行如下操作:

<template>
<h3>我是单文件</h3>
<p>{{title}}</p>
<ul>
    <li v-for="(item,index) in names" :key="index">{{item}}</li>
</ul>
</template>

<script>
export default{
    name:"MyComponent",
	//props: ['names', 'title'], //当不需要验证时,通过数组字符串接收,可以使用this.name进行访问
	
	// 通过对象进行接收并配置更多选项0
    props:{					//使用props来接收父组件传过来的数据
        title:{
            type:String,    //指定数据类型
            default:"",      //指定默认值,当父组件没有传递该数据,则子组件可以使用默认值
			validator(value) { // 是一个函数, 参数value就是当前的值, 返回true,验证通过,返回false,验证不通过
			            if (value.length < 3) {
			              return false;
			            }
			            return true;
			          },
        },
        names:{
            type:Array,
            //数组和对象必须使用函数进行返回
            default:function(){
                return[]
            }
        }
    }
}
</script>

<style>
h3{
    color: aqua;
}
</style>

$emit

自定义事件可以在组件中反向传递数据,也就是说可以把子组件中的数据传送到父组件中(也可以进行子组件修改父组件的数据)
在子组件中填入数据:

<template>
    <h1>子组件的数据传递到父组件</h1>
    <!-- $emit不能直接进行数据传递,需要一个事件来触发它进行数据传递 -->
    <button @click="sendClickHandle">点击传递</button>
</template>

<script>
export default{
    name:'hello',
    data(){
        return{
            message:"我是MyComponent的数据"
        }
    },
	emits: ['onEvent'],// 通过数组字符串接收,可以使用this.$emit进行发送
    methods:{                   //声明一个事件
        sendClickHandle(){
            //$emit来进行数据传递,其内有两个参数
            //参数1:字符串:理论上是随便的,但是必须要有意义
            //参数2:传递的数据
            this.$emit("onEvent",this.message)
        }
    }
}
</script>

<style scoped>
h1{
    color: rebeccapurple;
}
</style>

把数据传递到父组件App.vue中

<template>
  <!-- @onEvent是我们自定义的一个事件,onEvent是子组件中$emit函数的第一个参数 -->
  <MyComponent @onEvent="getDataHandle" />
  <p>传递的数据为:{{message}}</p>
</template>

<script>
import MyComponent from './components/MyComponent.vue';
export default {
  name: 'App',
  components: {
    MyComponent
  },
  methods: {
    getDataHandle(data) {
      console.log(data)         //在控制台输出数据
      this.message = data       //输出到桌面

    }
  },
  data(){
    return{
      message:""
    }
  }
}
</script>

<style>

</style>

注:在实际开发过程中,我们通常把自定义事件名和方法名命名成一个相同的名称
<MyComponent @getDataHandle="getDataHandle" />

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.staticfile.org/vue/3.0.5/vue.global.js"></script>
    <style>
        
    </style>
</head>
<body>
    <div id="app" class="box">
        <h1>我是父组件</h1>
        <h2>我的财富是{{money}}</h2>
        <hr>

        <!-- 3、使用子组件 -->
        <child :name="name" @requiremoney="requiremoney" :requiremoney="requiremoney"></child>
    </div>
    <script>
        // 1、定义子组件
        const Child ={
            data() {
                return {
                    money:0,
                }
            },
            props:["name",'requiremoney'],      //父组件传到子组件的元素
            template:`<h3>我是子组件</h3>
                        <h3>我叫{{name}}</h3>
                        <h3>我有{{money}}元</h3>
                        <button @click="yaoqian">要钱</button>
                        <button @click="yaoqianFun">函数要钱</button>
            
                        `,
            emits:["requiremoney"],         //子传父
            methods: {
                yaoqian(){
                    this.$emit("requiremoney",1000)
                    this.money += 1000
                },
                yaoqianFun(){
                    this.requiremoney(1500)
                    this.money += 1500
                }
            },
        }
      
        // 定义模块
        const app = Vue.createApp({
           data() {
            return {
                money:10000,
                name:"千峰",
            }
            },
            
            components:{
                Child,      //2、挂载子组件
            },
            methods:{
                requiremoney(data){
                    // console.log(data)
                    this.money -= data
                }
            }
         
        }).mount('#app')
    </script>
</body>
</html>

组件间的其它通信方式

组件通信,也就是组件之间的传值

通信方式

  • 父组件给子组件传值:props
  • 子组件给父组件传值:自定义事件(@emit)
  • 非父子组件之间传值:事件总线(中央消息总线传值)、Vuex(状态机)
  • ref:用于注册元素或子组件的引用
  • 依赖注入:要为组件后代提供数据,需要使用到 provide 选项;子组件使用 inject 选项来注入上层组件提供的数据

ref 通过引用的方式进行传值

获取子组件的引用
ref 用于注册元素或子组件的引用。
使用选项式 API,引用将被注册在组件的 this.$refs 对象里
放在DOM元素上,获取DOM节点,放到组件上,获取子组件的实例,可以直接使用子组件的属性和方法。

<Child ref="child"></Child>
...
<script>
  ...
  this.$refs.child.getName(); // 最好是调用子组件的方法查看或者修改子组件的数据
  this.$refs.child.setName('六耳猕猴');
  // this.$refs.child.name = '六耳猕猴'; // 这种不推荐,但也得知道,你代码规范,不见得同事也规范。
  ...
</script>

依赖注入

如果我们有一个数据,需要各级的组件都能访问到,我们可以通过 props 属性自上而下(由父及子)进行传递的,但此种用法对于某些类型的属性而言是极其繁琐的。
依赖注入提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
相当于提供了一个全局变量的样子,在所有的子组件都可以访问到。实际例子相当于的家谱图,老祖宗留了一本家谱图,想知道姓什么的时候去翻一下就知道了。

要为组件后代提供数据,需要使用到 provide 选项:

{
  data(){ return { surname: '朱' } },
  // 实际情况二取一
  provide: {
    surname: '朱',
    getSurname: this.getSurname, // 也可以传递方法
  },
  provide() {  // 也可以使用函数的形式
    return {
      surname: this.surname,
      getSurname: this.getSurname, // 也可以传递方法
    }
  }
}

子组件使用 inject 选项来注入上层组件提供的数据

{
  // 实际情况二取一
  inject: ['surname', 'getSurname'], // 通过数组的形式获取
  inject: {
    surname: {
      from: 'surname', // 当与原注入名同名时,这个属性是可选的
      default: '朱', // 可以设置默认值
    },
  },
  created() {
    console.log(this.surname) // injected value
  }
}

中央消息总线传值

只有在Vue2中才有中央消息总线的概念,Vue3需要使用event来模拟,我们可以使用mitt

Vue2

中央消息总线相当于我们的电话系统,我们需要鉴定消息的地方放一台电话,给这个电话一个号码,其他地方需要的时候拨打这个号码,我们监听的电话就能收到消息了。
Vue2中Vue自身就是一个消息总线,使用new Vue()生成一个消息总线实例,实例有两个方法on和eimit,使用on进行监听,使用eimit来发送消息。

$on 这个函数有两个参数,第一个参数式消息名称(相当于电话号码),第二个参数式监听到消息后执行的回调函数,参数是发送的数据,发送了多少个就用多少个接收。

$emit 这个函数至少有一个参数,第一个参数式消息名称(相当于电话号码),后面的是参数,参数可以有0个或多个。

Vue.prototype.$bus = new Vue(); // 生成一个消息总线并设置为全域变量,用this.$bus可以访问到
// 其实因为我们已经创建了一个Vue实例,可以直接使用
beforeCreate() {
  Vue.prototype.$bus = this; // 用已经创建的Vue实例
}
// 在父组件的created函数里面监听
created() {
  this.$bus.$on('requestMoneyByBus', count=>{
    ...
  });
}
// 子组件发送消息
this.$bus.$emit('requestMoneyByBus', 100);

Vue3中中央消息总线(模拟)

Vue3 本事是不支持消息总线,这里我们使用eventemitter3来模拟消息总线。我们还使用了依赖注入i提供了一个全局的$bus;

<script src="https://unpkg.com/mitt/dist/mitt.umd.js"></script>

需要在根组件提供一个全局的$bus
<script>
provide: { // 注入一个全局的$bus
  $bus: mitt(),
},
</script>

在父组件中注入$bus,并在created中监听
<script>
inject: ['$bus'],
...
created() {
  this.$bus.on('requestMoneyByBus', count=>{ // 注意这里是on,不是$on
    ...
  });
},
</script>

在子组件中注入$bus,并在需要的时候发送请求
<script>
inject: ['$bus'],
...
this.$bus.emit('requestMoneyByBus', 100); // 注意这里是emit,不是$emit
</script>

插槽

插槽就是子组件预留给父组件放入其他组件的一个位置。

插槽类型有:
单个(匿名)插槽
具名插槽:具名插槽(v-slot属性,#简写)
作用域插槽:作用域插槽是带数据的插槽,子组件提供给父组件的参数,父组件根据子组件传过来的插槽数据来进行不同的展现和填充内容。在标签template中通过v-slot指令来接受数据。

组件的生命周期函数(Vue3)

组件的生命周期函数有八个,八个函数可分为四类:
	创建时:beforeCreate、created
	渲染时:beforeMount、mounted
	更新时:beforeUpdate、updated
	卸载时:beforeUnmount、unmounted
<template>
    <p>组件生命周期</p>
</template>

<script>
    export default{
        data(){
            return{
                message:""
            }
        },
        beforeCreate(){		// 创建前 数据还没有初始化
		// 注意点:在此时不能获取data中的数据,也就是说 this.info 得到的是null
            console.log("beforCreate:组件创建之前")
        },
        created(){		// 已创建 数据已经初始化了,可以做一些网络请求,定时器,延时器的初始化
		// 这里就可以获取data中的数据
            console.log("created:组件创建完成")
        },
        beforeMount(){		// 挂载前 获取不到dom
            console.log("beforMount:组件渲染之前")
        },
        mounted(){			// 已挂载 获取到dom,对dom进行操作。
            console.log("mounted:组件渲染完成")
        },
        beforeUpdate(){
            console.log("beforupdata:组件更新之前");
        },
        updated(){
            console.log("updated:组件更新完成")
        },
        beforeUnmount(){		// 清理
            console.log("beforeUnmount:组件卸载之前")
        },
        unmounted(){
            console.log("unmounted:组件卸载之后")
        }
    }
</script>

<style>

</style>

其中:

  • 我们可以在 created 或 mounted 中来向后端发送网络请求,获取页面的初始数据
  • 从created 开始后,才能访问data中的数据
  • 从mounted 开始后,才能获取页面中的结点

Vue2.x的区别
unmount -> destroy
beforeUnmount -> beforeDestroy // 销毁前
unmounted -> destroyed // 已销毁

生命周期的执行顺序

加载渲染过程:
1.父组件 beforeCreate
2.父组件 created
3.父组件 beforeMount
4.子组件 beforeCreate
5.子组件 created
6.子组件 beforeMount
7.子组件 mounted
8.父组件 mounted

更新过程:
父组件 beforeUpdate
2.子组件 beforeUpdate
3.子组件 updated
4.父组件 updated

销毁过程:
父组件 beforeUnmount
2.子组件 beforeUnmount
3.子组件 unmounted
4.父组件 unmounted

动态组件

基础语法

Vue中提供了 来实现动态组件

<template>
    <div>
        <button @click="componentId = 'ChildA'">组件A</button>
        <button @click="componentId = 'ChildB'">组件B</button>
        <button @click="componentId = 'ChildC'">组件C</button>
        <div>
            <!-- 动态组件 -->
            <component :is="componentId"></component>
        </div>
    </div>
</template>

<script>
import ChildA from "./ChildA.vue";
import ChildB from "./ChildB.vue";
import ChildC from "./ChildC.vue";
export default {
    components: {
        ChildA,
        ChildB,
        ChildC,
    },
    data() {
        return {
            componentId: "ChildA",
        };
    },
};
</script>

<style>
</style>

动态组件的生命周期

默认情况下,动态组件在切换时,切换走的组件,会被销毁;显示的组件,会被创建,组件之间都是不断的销毁和重建。
因此,在组件切换走时,会触发两个生命周期函数:

  • beforeUnmount
  • unmounted

在显示组件时,会触发四个生命周期函数:

  • beforeCreate
  • created
  • beforMount
  • mounted

$nextTick()

绑定在实例上的 nextTick() 函数。

和全局版本的 nextTick() 的唯一区别就是组件传递给 this.$nextTick() 的回调函数会带上 this 上下文,其绑定了当前组件实例。

<div ref="num">{{ count }}</div>
<script>
...
this.count++;
console.log(this.count) // 2
console.log(this.$refs.num.innerHTML) // 1 
this.$nextTick(() => { // 可以获取真实的DOM节点的值,瀑布流布局
  console.log(this.$refs.num.innerHTML) // 2
})
...
</script>

第六章、数据变更检查

在Vue中,当页面中使用的数据发生变化时,页面会重新自动渲染
这是因为,当一个Vue组件被创建时,Vue会将data中的所有数据都加入到“响应式系统”中。后继,当data中的数据发生变化时,“响应式系统”就会通知页面进行更新
但是,有一些数据的变化,是响应式系统检测不到的,一旦数据变化后,响应式系统没有检测到,那么页面就不会更新

无法检测到的数据变化

Vue的响应式系统无法检测到数组和对象的以下四种变化

  • 无法检测到对象属性的新增
  • 无法检测到对象属性的删除
  • 无法检测到对数组长度的修改
  • 无法检测到通过下标操作数组

解决方法

对象属性的新增

//新增一个age属性时:

//ES6方法:
this.person = {
	...this.person,
	age: 20
}

//官方方法:($set:只有Vue2支持,Vue3已经移除)
this.$set(this.person, 'age', 20)		//this.$set(要修改的对象, '新增的属性名', 属性值)

对象属性的删除

//删除name属性

//官方方法:
this.$delete(this.person, 'name')		//this.$delete(要修改的对象, '要删除的属性名')

修改数组的长度

//想要将数组长度设为:0
this.students = []		//直接赋值一个空数组

//修改数组长度
this.students.splice(1)		//使用splice()方法

通过下标操作数组

//把下标为0的元素修改为'王五'

this.students.splice(0, 1, '王五')		//使用splice()方法

//官方方法
thsi.$set(this.students, 0, '王五')

第七章、Vue全家桶(新版)

Vue全家桶创建项目(使用VueCLI来创建项目)

VueCLI:
官网地址
Vue2 时期官方的一款脚手架工具。不再推荐。
虽然不推荐了,但是还是可以使用它来创建 Vue2、Vue3 的项目。)
内部使用的构建工具是  Webpack

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

接着下载第三方插件:

下载axios插件

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

下载element插件

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

样式穿透

当我们对Element UI框架的组件样式,进行局部样式(scoped)的设置时,可能会出现的问题是:
局部样式无法作用到UI框架内部的标签身上。
这种情况下,我们可以使用样式穿透来解决: >>>

<style scoped>
.avatar-uploader >>> .el-upload {                 /*   >>> 这是加上了样式穿透效果 */
    border: 1px dashed #d9d9d9;
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
}
.avatar-uploader >>> .el-upload:hover {
    border-color: #409eff;
}
</style>

安装时的常见报错:
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
解决方法:

  • 先卸载:npm uninstall node-sass sass sass-loader
  • 再重新安装:npm install node-sass sass sass-loader -D

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
解决方法:

  • 以管理员方式打开windows powershell:输入下列代码
  • set-ExecutionPolicy RemoteSigned (接下来选择全是:A)

1、路由与SPA

SPA的概念

SPA指的是“单页应用”,指的是在整个项目中,只有一个.html页面,例如:Vue项目中的public/index.html
在SPA中,是通过切换组件,来达到类似于多页应用中页面跳转的效果

路由(Vue Router)

因为SPA中只有一个页面,那么在组件进行切换时,我们需要一个工具来进行辅助管理,帮助我们管理组件与浏览器之间的关系,所有Vue Router
路由插件,就为我们提供了这样的功能:

  • 可以通过路由来管理路径和组件之间的对应关系
  • 当路径发送改变时,路由会切换页面中对应的组件

2、路由的配置(router)

基础配置

src/router/index.js文件中,有一个数组routes,专门来配置项目中所有页面路由:

//引入对应组件的路径
import LoginView from '../views/login/LoginView.vue'
import HomeView from '../views/home/HomeView.vue'
import RegisterView from '../views/register/RegisterView.vue'

Vue.use(VueRouter)


// 用于配置项目中所有需要页面(组件和路径)
const routes = [
	{
	    
	    path: '/',
	    redirect: '/home'		//配置路由重定向:
	  },
	  {
	    path: '/home',
	    name: 'home',
	    component: HomeView
	},
	
	//这种写法是直接在配置中引入路径
	//{
	//    path: '/home',
	//    component: () => import('../views/home/HomeView.vue')
	// },
	  
	  
	{
		path: '/login',
		name: 'Login',
		component: LoginView
	},
	
]

数组中每一个对象,就对应着项目中一个页面,其中,每一个路由对象都有两个必须属性:

  • path:路由路径,指的是浏览器中每一个页面的路径(名字是自定义:路径建议首字母不需要大小)
  • component:组件路径,指的是浏览器中需要渲染的组件(首字母必须大小)
  • name :(可选属性),路由自定义名称(建议首字母大写)

redirect(路由重定向):指的是我们可以将一个路由重定向到另外一个路由。这样,每当用户访问原路由时,都会直接跳转到重定向的新路由

路由出口

当组件加载完成后,我们需要通过路由出口<router-view>来告诉浏览器组件渲染在什么位置
通常,我们会将最外层的路由出口配置在App.vue中:

<template>
    <router-view></router-view>         <!-- 设置路由显示出口 -->
</template>

<script>

</script>

<style>

/* 引入外部单独创建的styles文件下的scss文件 */
@import './styles/global.scss';

</style>

有了路由的基本配置和路由出口,我们就可以在浏览器中手动切换路径,来实现页面组件的切换

嵌套路由(设置子路由)

src/router/index.js文件中

//引入对应组件的路径
import LoginView from '../views/login/LoginView.vue'
import HomeView from '../views/home/HomeView.vue'
import RegisterView from '../views/register/RegisterView.vue'

//引入子路由:
import StudentsList from '../views/home/students/StudentsList.vue'
import StudentsAdd from '../views/home/students/StudentsAdd.vue'

import NotFound from '../views/404/NotFound.vue'

Vue.use(VueRouter)


// 用于配置项目中所有需要页面(组件和路径)
const routes = [
	{
		path: '/login',
		name: 'Login',
		component: LoginView
	},
	{
		path: '/register',
		name: 'Register',
		component: RegisterView
	},
	{
		path: '/home',
		//name:'Home',					//删除掉此属性
		component: HomeView,
		
		//挂载子路由:
		children: [
					// 默认子路由
					{
						path: '',				//path 文空字符串
						name: 'Home',			//把父级路由的 name 属性,设置在默认子路由身上,删除掉父级name
						component: Charts,
					},
					{
						// 子路由的 path 不能以 / 开头
						path: 'studentsList',
						name: 'StudentsList',
						component: StudentsList,
						
					},
					{
						path: 'studentsAdd',
						name: 'StudentsAdd',
						component: StudentsAdd,
						
					},
	},
	
// * 表示可以匹配任何路径,放在所有的路由配置最后,当前面的路由都没能匹配上后,就会匹配它,
//在它对应的组件内编写路由加载信息:提示报错信息:404
	{
		path: '*',				
		name: 'NotFound',
		component: NotFound
	}
]

以上两个子路由配置成功后,我们就可以在浏览器中通过/home/studentslist/home/studentsAdd来访问对应的路由

最后,在对应的父组件中显示配置的子路由,也需要配置一个路由出口:<router-view>

 <router-view></router-view>         <!-- 设置路由显示出口 -->

路由的跳转及传参

Vue Router 中提供了两种方式来实现路由的跳转

1、组件跳转(标签跳转)
2、API跳转(方法跳转)push()、replace()

组件跳转

Vue Router中提供了一个<router-link>组件来实现路由之间的跳转:(相当于a标签)只能在html内使用

 <router-link to="/所要跳转到的页面的路径:path">没有账号?去注册</router-link>

如果要跳转到某一个二级路由,路径必须配置完整的路径:

 <router-link to="/home/studentsList">没有账号?去注册</router-link> 
API 跳转:push()、replace()
<template>
    <div>
		//方法一:直接引入
		//<button @click="$router.push("/login")">注册</button>
		
		//方法二:调用方法:register()
        <button @click="register">注册</button>
    </div>
</template>

<script>
export default {
    methods: {
        register() {
			
            // 注册成功后跳转到登录页
            this.$router.push("/login");        //this.$router.push("/所要跳转到的页面的路径:path");
        },
    },
};
</script>

push()跳转完成后,用户可以通过浏览器的返回按钮,返回到上一页。

methods: {
        register() {
			
            // 注册成功后跳转到登录页
            this.$router.replace("/login");       //this.$router.replace("/所要跳转到的页面的路径:path");
        },
    },

replace() 跳转后,会将上一个页面的访问记录删除掉,因此用户无法再返回到上一个页面

路由的返回
返回上一级
this.$router.back()
重新回到下一级
this.$router.forward()
横跨历史

this.$router.go(n)

// 向前移动一条记录,与 this.$router.forward() 相同
this.$router.go(1)

// 返回一条记录,与 this.$router.back() 相同
this.$router.go(-1)

// 前进 3 条记录
this.$router.go(3)

// 如果没有那么多记录,静默失败
this.$router.go(-100)
this.$router.go(100)
路由的传参(主要有两种方式:query、params)

如果要携带参数时:

<!--字符串形式-->
<router-link to="/cart?day=30">显示最近30天添加的物品</cart-link>

<!-- 对象形式-->
<router-link :to="{path: '/cart', query: {day:30}}">显示最近30天添加的物品</cart-link>
query传参:

形成的路径:/cart?day=30
路由配置文件中(route.js)的格式:{path: ‘/cart’, component: Cart}
传递的方式:

//对象的形式并且带参数
this.$router.push('/cart?day=30');
// 等价于 this.$router.push({path:'/cart?day=30'});
this.$router.push({path: '/cart', query: {day: 30}});

参数的接收:在 Vue 实例中,可以通过 this.$route 获取路由的参数。

console.log(this.$route.query.day); // 30
params传参:

形成的路径:/cart/30
路由配置文件中(route.js)的格式: { path: “/cart/:day”,component: Cart, name: “cart”}
传递的方式:

<!--字符串形式,在 path 后面跟上对应的值 格式: 路由地址/参数1/参数2 -->
<router-link to="/cart/30">显示最近30天添加的物品</cart-link>

<!--对象形式-->
<router-link :to="{name:'cart',params:{day:30}}">显示最近30天添加的物品</cart-link>
<!--在传递params参数并目使用对象的写法时,不再是path属性了,而是name配置项,而且必须是name,如果是path配置项,会报错-->

使用API跳转时,解决重复点击触发同一路由跳转报错问题(Vue2)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 方法一:vue-router 降级处理(不推荐)
npm i vue-router@3.0.7
  • 方法二:直接在push方法最后添加异常捕获 .catch(err=>{} ,例如:
<li
	@click="$router.push('/home').catch(err=>{})">首页
</li>
  • 方法三:直接修改原型方法push
//直接把这段代码复制粘贴到 router/index.js中的 vue.use(VueRouter)之前
const originalPush = VueRouter.prototype.push;
VueRouter.prototype.push = function(location){
	return originalPush.call(this, location).catch(err => {})
};

3、动态路由

大部分时候,我们项目中的路由都普通路由,用户在浏览器中访问一个页面时,浏览器的路径必须和路由配置的路径一致,
才能匹配成功,才能进入到对应的页面,

但是,我们在实际研发中,也需要使用“动态路由”。动态路由,指的就是路由的路径中,有一部分内容是可以动态发生变化的。

1.动态路由跳转

export default{
//跳转到学生修改页面,同时将要修改的学生的 _id 通过路由进行传递
        handleEdit(_id) {
            // this.$router.push("/home/studentsUpdate/" + _id);        //有两种写法
            this.$router.push(`/home/studentsUpdate/${_id}`);
        },
}

2.配置动态路由

const routes = [
	path: '/home',
	component: HomeView,
	children: [
		//动态路由的配置
			{
				path: 'studentsUpdate/:_id',			//path: '路径名 /: 任意变量名'
				name: 'StudentsUpdate',
				component: StudentsUpdate,
			},
]

说明:动态路由中,:_id 用来匹配路径中动态的内容,其中 _id可以是任意变量名
有了第一二步,我们已经可以完成两个页面之间的跳转了

3.获取动态路由的参数

通常,我们会用动态路由中动态的那一部分,来实现路由之间的传参。因此,我们需要在组件中来接收路由路径中的参数

console.log(this.$route.params)				//this.$route.属性名

$router 和 $route 的区别

  • $router 是当前整个项目的路由实例对象,主要用于提供一些路由方法,例如路由跳转
  • $route 是当前页面的路由信息对象,主要用于提供当前路由的相关信息,例如路径、参数等

4、路由的其它配置

路由重定向

指的是我们可以将一个路由重定向到另外一个路由。这样,每当用户访问原路由时,都会直接跳转到重定向的新路由

404路由

当用户访问的路由,和我们项目中的任何一个路由都不匹配时,我们希望能给用户显示一个404的提示
我们可以在所有路由配置的最后,设置一个 * 路由。当用户访问的路径和前面所有的路由都匹配不上时,就会匹配到 * 路由

路由懒加载

指的是当用户访问当前路由时,才开始加载对应的组件。
通常,我们会将一些用户访问率比较低,且页面结构比较简单的组件,用来做路由懒加载。例如:注册页面、404页面

import Vue from 'vue'
import VueRouter from 'vue-router'
import { MessageBox } from 'element-ui';
import api from '@/api';
import store from '@/store';

//引入对应组件的路径
import LoginView from '../views/login/LoginView.vue'
import HomeView from '../views/home/HomeView.vue'
import StudentsList from '../views/home/students/StudentsList.vue'
import StudentsAdd from '../views/home/students/StudentsAdd.vue'
import StudentsUpdate from '../views/home/students/StudentsUpdate.vue'
import MajorsList from '../views/home/majors/MajorsList.vue'
import Charts from '../views/home/Charts.vue'

// import RegisterView from '../views/register/RegisterView.vue'
// import NotFound from '../views/404/NotFound.vue'
//路由懒加载的写法
const RegisterView = () => import('../views/register/RegisterView.vue');
const NotFound = () => import('../views/404/NotFound.vue');

Vue.use(VueRouter)


// 用于配置项目中所有需要页面(组件和路径)
const routes = [
	{	
		//配置路由重定向
		path: '/',						
		redirect: '/home'
	},
	{
		path: '/login',
		name: 'Login',
		component: LoginView
		meta:{
		      noAuth:true   //用来判断是否需要登录后才显示,true:不需要登录就能显示
		    }
	},
	{
		path: '/register',
		name: 'Register',
		component: RegisterView
		meta:{
		      noAuth:true   //用来判断是否需要登录后才显示,true:不需要登录就能显示
		    }
	},
	{
		path: '/home',
		component: HomeView,
		meta:{
		      noAuth:false, //需要登录才能显示
		    },
		children: [
			// 设置默认子路由
			{
				path: '',
				name: 'Home',
				component: Charts,		
			},
			{
				// 子路由的 path 不能以 / 开头
				path: 'studentsList',
				name: 'StudentsList',
				component: StudentsList,
				meta: {
					title: ['首页', '学生管理', '学生列表'],
				}
			},
			{
				path: 'studentsAdd',
				name: 'StudentsAdd',
				component: StudentsAdd,
				meta: {
					title: ['首页', '学生管理', '新增学生'],
					isKeepAlive: true
				}
			},
			// 动态路由的配置
			{
				path: 'studentsUpdate/:_id',			//path: '路径名 /: 任意变量名'
				name: 'StudentsUpdate',
				component: StudentsUpdate,
				meta: {
					title: ['首页', '学生管理', '修改学生']
				}
			},
			{
				path: 'majorsList',
				name: 'MajorsList',
				component: MajorsList,
				meta: {
					title: ['首页', '专业管理', '专业列表']
				}
			}
		]
	},


	{
		path: '*',				// * 表示可以匹配任何路径,当前面的路由都没能匹配上后,就会匹配它,提示报错信息
		name: 'NotFound',
		component: NotFound
	}
]

//路由模式:history模式
const router = new VueRouter({
	mode: 'history',
	base: process.env.BASE_URL,
	routes
})


export default router

路由模式

Vue Router 中提供了两种路由模式:

  • hash:浏览器路径中会自动添加 #
  • history:浏览器路径中没有 #
//路由模式:history模式
const router = new VueRouter({
	mode: 'history',
	base: process.env.BASE_URL,
	routes
})

如果需要切换到 hash 模式,直接将 mode 和 base 去掉即可

5、路由元信息

配置路由对象时,除了常规的 path、component 等属性外,还有一个重要的属性:路由元信息———— meta

1.设置路由元信息

meta 属性的值,是一个对象,对象中可以定义任意属性。当我们需要给不同的路由对象添加一些不同的数据时,就可以使用路由元信息

{
	path: 'studentsList',
	name: 'StudentsList',
	component: StudentsList,
	
	meta: {
		//任意数据
			}
}

2.获取路由元信息

在组件中,可以通过 this.$route 获取当前页面的路由对象,因此,我们可以通过以下方式来获取路由对象身上的元信息:

this.$route.meta

3.应用场景

  • 面包屑
  • 路由组件的缓存

Vue中提供了一个 组件,用来包裹其他组件,所有被 包裹的组件,在切换时都会缓存组件自己的状态(不会被销毁)
例如,我们可以用 组件将所有子路由包裹起来:

//Vue(2)中的使用方式
<keep-alive>
    <router-view v-if="isKeepAlive"></router-view>		//添加一个判断,是否需要缓存
</keep-alive>

通过上述代码处理后,项目中所有的子路由组件都会被缓存下来,但在实际开发过程中,我们可能只需要一部分子组件为缓存状态

在Vue3中,现在必须通过 v-slot API 在 RouterView 内部使用

//Vue(3)中的使用方式
<router-view v-slot="{Component}">
      <keep-alive>
        <component :is="Component"></component>
      </keep-alive>
    </router-view>
{
	path: 'studentsAdd',
	name: 'StudentsAdd',
	component: StudentsAdd,
	meta: {
		title: ['首页', '学生管理', '新增学生'],
		//用来设置在 keep-alive 是否需要进行组件缓存
		isKeepAlive: true
		
	}	//然后在通过 this.$route.meta 来获取它,最后做一个 if 判断,如上面代码
}		

keep-alive

组件,用来包裹其他组件,所有被 包裹的组件,在切换时都会缓存组件自己的状态(不会被销毁)
例如:我们可以使用 搭配动态组件 ,或者搭配路由出口 来使用

生命周期:
因为被包裹的组件,不会被销毁,那么也就意味着重新进入时,也不会重新创建,
也就意味着,组件销毁阶段和创建、挂载阶段的生命周期函数都不会执行。

但是,被包裹的组件,默认会新增两个生命周期函数:

  • activated:组件激活时,即进入组件时
  • deactivated:组件失效时,即离开组件时

属性:
标签身上,提供了一些属性,来对缓存数组进行筛选:

  • include:设置需要缓存的组件名称
  • exclude:设置不需要缓存的组件名称
<keep-alive include="ChildA">
	<component :is= "Tab"> <component>
</keep-alive>

6、导航守卫

指的是用来控制路由跳转时,能否正常通过,完成跳转。

导航守卫的分类

我们可以将Vue Router 路由中导航守卫大致分为三类:

  • 全局守卫:作用于所有路由(beforeEach(全局前置守卫)、afterEach(全局后置守卫))
  • 路由独享守卫:作用于指定的路由(beforeEnter:只有前置)
  • 组件内的守卫:作用于组件所在的路由(分别有前置守卫,后置守卫,路由改变守卫)
1.路由独享守卫
const routes = [
	path:'/home',
	component:HomeView,
	
	//添加导航守卫
	beforeEnter:(to,from,next) => {
		
	},
	
	children:[...]
]

语法说明:
beforeEnter:是路由独享守卫中的前置守卫
每一个守卫函数都有三个参数,分别是:to(进入的路由对象),from(离开的路由对象),next(操作路由跳转的方法)

2.全局守卫
//路由模式:history模式
const router = new VueRouter({
	......
})


// 添加全局守卫,判断页面是否需要登录后才能显示
router.beforeEach((to,from,next)=>{
  if (to.meta.noAuth) {       //如果noAuth为true,直接进入
    next()
  }else if(!localStorage.userId){       //如果未登录,跳转到登录页面
    router.push('/login')
  }else{      //把个人信息存储到全局状态vuex中
    axios.post('/api/detail/member',{id:localStorage.userId}).then(res=>{
      const data = res.data
      if (!data.success) {
        router.push('/login')
      }else {
        store.commit('setPersonal',data.result)		//把个人信息存储到全局状态vuex中
        next()
      }
    })
    
  }

网络请求:axios

网络请求,指前端向后端服务器发送请求,来对数据库的数据进行操作

在现在的前端技术中,可以用来发送网络请求的方式有很多种:

  • AJAX(ES5)
  • axios(第三方插件)
  • Fetch(ES6)

下载

axios是一个第三方的插件,因此我们在任何项目中,如果需要使用axios,都得先进行下载

npm i axios

使用

1、引入axios

在所需要使用的页面进行引入:

import axios from "axios";
2、基本用法

//第二步:再确定什么时候调用该方法来获取数据

// 生命周期函数:组件创建完成,通过调用 getStudents() 来获取学生数据
    created() {
        this.getStudents();
    },
	
methods:{
//第一步:先写获取数据的方法
async getStudents(){                    //也就是说:await 和 async 必须同时搭配使用
            const res = await axios({           //await:等待获取的结果(await等待的方法必须是异步操作,因此方法名前必须得加:async)
                url:"http//:nocat.life:3008/students",       //请求地址
                method:"GET",                                //请求类型
				//需要传参时:设置如下
				params: {
				        phone:this.phone,					//参数名:参数值
				        password:this.password
				        }, 
            });
            console.log(res);               //输出后端返回的结果
        }
}

created() {
	
	//axios.获取参数的方法('网络请求的地址',{需要传递的参数}).then(res => {})
	
    axios.get('http://kumanxuan1.f3322.net:8881/cms/products/recommend').then(res => {
      console.log(res)
    })

  },
3、请求参数

axios的属性中,除了url 和 method 外,还有一个属性,用来向后端发送参数:

  • data:当 method 是除了GET 以外的其他类型时,用data来发送参数给后端
  • params:当 method 是GET 类型时,用params来发送参数给后端
const studentsApi = {
    // 获取学生数据
    get() {
        return axios({
            url: '/students',
            method: 'GET',
            params:{
				//参数
			}
        })
    },
    // 删除学生数据
    delete() {                      //当需要传递参数时,把形参设置成data
        return axios({
            url: '/students',
            method: 'DELETE',
            //data: data                  //当对象的键和值相同时,可以简写为:data
            data:{
				//参数
			}
        })
    },

跨域

由于浏览器中同源策略的限制,在浏览器中默认是不允许跨域访问的,一旦跨域访问,浏览器中就会抛出报错

域,可以理解为域名,每一个域至少都有三部分组成:
1.协议
2.IP地址
3.端口号

例如:http//:nocat.life:3008/students域中,http就是协议,nocat.life就是ID地址,3008就是端口号

跨域的概念

当两个域之间,协议、IP、端口三者中有任意一个不一致,则为“不同源”的域

跨域的解决方案

网络请求跨域的解决方案,常用的有以下几种方法:

1.JSONP:只能解决GET请求的跨域
2.CORS:纯后端处理
3.proxy(代理服务器):项目开发过程中最常用的方式
4.Nginx 发向代理:项目上线后最常用的方式

proxy方法:
在Vue项目的根目录中,都会有一个vue.config.js文件,我们在该文件中添加如下配置代码,来解决跨域问题:

// 当前文件中任何配置发生改变,项目都需要重新启动

module.exports = defineConfig({
	
	publicPath: '/mobile/', // 顶级路由(用于打包时使用)
	
	// 开发服务器配置
	devServer: {
		proxy: {
			// 匹配项目中所有路径以 /api 开头的请求
			'/api': {
				target: 'http://nocat.life:3008', // 目标服务器地址
				changeOrigin: true,   //设置允许跨域 解决跨域

				// 将所有请求中的 /api 替换成空字符串
				pathRewrite: {
					'/api': ''
				}
			}
		}
	}
})

以上配置表示,会匹配到项目中所有以 /api 开头的请求URL,然后将这些请求路径中的 /api 换成空字符串,然后转发到target目标服务器

附加说明:如果采用以上配置,项目中所有的网络请求地址,都要以/api开头:

methods: {
        // 获取学生数据
        
         async getStudents(){                    //也就是说:await 和 async 必须同时搭配使用
             const res = await axios({           //await:等待获取的结果(await等待的方法必须是异步操作,因此方法名前必须得加:async)
                 //url:"http//:nocat.life:3008/students",       //请求地址
				 
				 url:'/api/students'						//处理了跨域后的请求地址
                 method:"GET"                                //请求类型
             });
             console.log(res);               //输出后端返回的结果
			 
		     //在控制台查看返回数据后,来一个判断,然后取出自己所需要的数据
		                 // if(res.data.code){
		                 //     // console.log(res.data.data.rows)
		                 //     this.tableData = res.data.data.rows             //把取出的数据放到自定义的 tableData[] 数组中
		                 // }
		         // },
       },
}

网络请求封装

通常,在一个前端项目中,我们会对网络请求进行两个步骤的封装:

1、封装axios,对axios的一些公共配置进行处理
2、封装请求API,对项目中所有的axios请求API同一管理

1、封装axios
  • 1、创建封装文件
    在项目src目录中,创建一个 utils 目录,用来存放项目中的工具文件。
    utils 目录中,创建一个 request.js 文件,用来作为axios的封装文件

  • 2、配置axios
    request.js 文件中进入如下配置

import axios from 'axios';

// 基础路径:用来配置所有 axios 请求 url 前面相同的一部分
axios.defaults.baseURL = "/api";

// 请求超时时长:当请求超过 5s 都还没有结束,会自动断开连接并抛出报错(看个人是否需要该配置)
axios.defaults.timeout = 5000;


// 响应拦截器:当后端将请求结果返回到前端组件之前,会被“响应拦截器”拦截下来
axios.interceptors.response.use((res) => {
    // 将 return 的数据返回给前端组件
    return res.data;
});
  • 3、引入axios配置文件

得在main.js文件中进行引入,让 axios 的配置文件运行生效

import './utils/request.js';
  • 4、使用axios

当完成了以上的axios封装后,我们在组件中,在去使用axios发送请求,以及接收请求结果时,会变成如下代码:

 methods: {
         // 获取学生数据
         
          async getStudents(){                    //也就是说:await 和 async 必须同时搭配使用
              const res = await axios({           //await:等待获取的结果(await等待的方法必须是异步操作,因此方法名前必须得加:async)
			  
                  //url:"http//:nocat.life:3008/students",       //请求地址(未做任何处理时的请求地址)
 				 //url:'/api/students'						//处理了跨域后的请求地址
				 
				 url:'/students'							//封装后的请求地址
                  method:"GET"                                //请求类型
              });
              console.log(res);               //输出后端返回的结果
 			 
 		     //在控制台查看返回数据后,来一个判断,然后取出自己所需要的数据
 		                 // if(res.code){
 		                 //     // console.log(res.data.rows)
 		                 //     this.tableData = res.data.rows             //把取出的数据放到自定义的 tableData[] 数组中
 		                 // }
 		         // },
        },
 }
2、封装API

实际开发中,我们不会将每一个请求过程全都分散在各个组件中。更常用的方式,是根据请求数据来进行分类,对不同数据类型的请求进行统一的管理

  • 1、创建封装文件
	src
	|----api							//存放项目中所有的axios请求
	|----|----modules					//根据请求的数据对所有请求进行模块划分
	|----|------|-------studentsApi.js
	|----|------|-------majorsApi.js
	|----|------|-------...
	|----|----index.js					//将所有模块的 api 进行汇总
  • 2、封装api
    以学生模块的请求为例:studentsApi.js
import axios from "axios";                  //引入axios

const studentsApi = {
    // 获取学生数据
    get() {
        return axios({
            url: '/students',
            method: 'GET',
        })
    },
    // 删除学生数据
    delete(data) {                      //当需要传递参数时,把形参设置成data
        return axios({
            url: '/students',
            method: 'DELETE',
				
            //data: data                  //当对象的键和值相同时,可以简写为:data
            data
        })
    },
}

export default studentsApi;             //最后把studentsApi暴露出去
  • 3、合并api
    我们针对每一个数据模块都创建了一个单独的 .js 文件来处理相关请求
    但是,为了后继合并使用,我们还需要将每个请求模块在index.js中做一个汇总
//汇总所有的网络请求

//首先引入
import students from './modules/studentsApi.js';
import majors from './modules/majorsApi';

//汇总:
const api = {
    students,
    majors
}

export default api;         //暴露出去
  • 4、全局挂载 api 对象(vue2中的方法)全局挂载后,不需要引入,就可以直接使用
    经过前面的处理,所有的请求都已经汇总在了api对象中,组件内如果需要调用方法来发送请求,就必须先引入api对象,为了避免每个组件
    使用时都要单独引入一次api对象,所有我们直接将api对象挂载到全局。

main.js中添加如下配置

// 引入封装好的 api 对象
import api from './api/index.js';

// 全局挂载 api 对象
Vue.prototype.$api = api;
  • 5、使用api(vue2中的方法)
    在组件中,可以通过this.$api获取到全局挂载的api对象:
export default {
    components: {
        Breadcrumb,         //挂载面包屑
    },
    data() {
        return {
            tableData: [],      //定义一个空数组,用来存放后端取到的数据
    },

    // 生命周期函数:组件创建完成,通过调用 getStudents() 来获取学生数据
    created() {
        this.getStudents();
    },
    
    methods: {
        // 获取学生数据
        async getStudents() {
			
            const res = await this.$api.students.get();		//通过 this.$api 来获取所需要的数据
			
            if (res.code) {
                this.tableData = res.data.rows;
            }
        },
		
		// 删除学生数据
        async handleDelete(_id) {
            const res = await this.$api.students.delete({ _id });           //当需要传递参数时,这里也是进行了简写:_id: _id
            if (res.code) {
                // 删除成功后,需要重新获取最新的数据库的学生数据
                this.getStudents();
            }
        },
};
  • 4 全局挂载 api 对象(vue3中的方法) 全局挂载后,不需要引入,就可以直接使用
    经过前面的处理,所有的请求都已经汇总在了api对象中,组件内如果需要调用方法来发送请求,就必须先引入api对象,为了避免每个组件
    使用时都要单独引入一次api对象,所有我们直接将api对象挂载到全局。

vue.config.js中添加如下配置

//全局挂载api(第一步:添加以下两行代码)
const webpack = require("webpack")
const path = require("path")

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
	transpileDependencies: true,
	lintOnSave: false,

	// 全局挂载 api 对象(第二步:配置如下文件)
	configureWebpack: {
    plugins: [
      new webpack.ProvidePlugin({ // 注册为全局变量
        'api': [path.resolve('src/api/index.js'), 'default'],		//'全局变量名':[path.resolve('api文件路径'),'default']
      }),
    ]
  },


	// 解决跨域
	// 开发服务器配置
	devServer: {
		proxy: {
			// 匹配项目中所有路径以 /api 开头的请求
			'/api': {
				target: 'http://localhost:5000', // 目标服务器地址
				changeOrigin: true,   //设置允许跨域 解决跨域

				// 将所有请求中的 /api 替换成空字符串
				pathRewrite: {
					'/api': ''
				},
			},
			'/uploadFile': { // 工程的接口以/api开头
				target: 'http://localhost:5000', // 被代理服务
				changeOrigin: true, // 改变源
			},
		}
	}
})

  • 5、使用api(vue3中的方法)
    在组件中,可以通过api.获取到全局挂载的api对象:
methods: {
        // 获取学生数据
        async getStudents() {
			
            const res = await api.students.get();		//通过 api. 来获取所需要的数据
			
            if (res.code) {
                this.tableData = res.data.rows;
            }
        },

网络请求的封装模板:方法一:(直接复制粘贴使用)

utils/request.js文件下对axios进行封装

// axios的封装网络请求

import axios from "axios";          //引入axios

const instance = axios.create({
    //baseURL:"http://kumanxuan1.f3322.net:8881/cms",             //网络请求公共地址
	baseURL:process.env.NODE_ENV === 'production' ? "/" : "/api",             //(用于需要打包时)判断是否是生产模式?如果是生产模式,不用要代理,不然就代表它是开发模式,需要使用代理
    timeout:5000                                                //网络请求超时
})

//主要有两个东西:1、请求拦截器: 在每个请求发出去之前执行该代码
instance.interceptors.request.use( config => {

    //config是一个对象,记录了本次请求的相关信息(可以用来做一些请求前的准备工作。例如:添加请求头)
    // console.log(config)

    return config
}),
//请求失败时
err => {
    return Promise.reject(err)
}

	
//2、响应拦截器:当后端将请求结果返回到前端组件之前,会被“响应拦截器”拦截下来
instance.interceptors.response.use( res => {

    //res是一个对象,对服务器响应回来的数据做统一的处理
    // console.log(res)

    

    return res
}),
//请求失败时
err => {
    
    return Promise.reject(err)
}

// 暴露出去
export default instance

api/index.js文件下对API进行封装

// 进行API封装

//data:当 method 是除了GET 以外的其他类型时,用data来发送参数给后端
//params:当 method 是GET 类型时,用params来发送参数给后端


import instance from "@/utils/request";

//首页精品推荐请求
//封装方法一:
// export const JinpinAPI = () => (instance.get("/products/recommend"))

//封装方法二:
export const JinpinAPI = () => {
	return instance({
		url:'/products/recommend',			//请求地址
		method:"GET"		//get方法可以省略不写
	})
	
// 发送短信验证码请求(要传参数时)
export const SendSMSAPI =(data) => (instance.post('/sendSMS',data))
}

在各组件中调用

<template>
  <div class="home">
  
  </div>
</template>

<script>
import axios from 'axios'
import instance from '../utils/request'
import {JinpinAPI} from '../api/index'

export default {
  data() {
    return {

    }
  },
  
   async created() {
    // this.getJinpin()
    // 最原始的(未封装):方法一
    axios.get('http://kumanxuan1.f3322.net:8881/cms/products/recommend').then(res => {
      // console.log(res)
    })
	
	
    // request封装后
    instance.get('/products/recommend').then(res => {
      // console.log(res)
    })
	
	
	
    // API封装(第一种读取方法)
    // JinpinAPI().then(res =>{
    //   console.log(res)
    // })

    // await 后面一般放 Promise对象           如果看见这种:await Xxxx()    就是对Promise进行了封装:await new Promise()
	//API封装后的异步读取方法
    let res = await JinpinAPI();
    // console.log(res)

    this.getGoods()

    

  },
  methods: {
    // 网络请求原始(未封装):方法二:
    async getJinpin() {
      const res = await axios({
        url: 'http://kumanxuan1.f3322.net:8881/cms/products/recommend',
        method: 'GET'
      })
      // console.log(res)
    },

    // API封装(第二种读取方法:异步读取)
    async getGoods(){
				const res = await JinpinAPI();
				console.log(res)
			}

  }

}
</script>

<style lang="less" scoped>

</style>

网络请求的封装方式二:

import axios from 'axios';//引入axios
import { showDialog } from 'vant';
import { showLoadingToast } from 'vant';//引入ui组件

//使用回调函数
function post(url,data={},wait){//定义我们的post接口

  if(process.env.NODE_ENV=='production'){
    //如果是生产模式,才去掉/api
    url=url.replace('/api','');
  }

  return new Promise(resolve=>{//使用Promise封装异步
    console.log(`${url}发送:`,data);
    const toast=wait && showLoadingToast({
      message:'数据获取中,请稍后...',
      loadingType:'spinner',
      forbidClick:true,
      duration:0,//让他一直显示
    });

    data.userId=localStorage.userId;//每一个接口要跟一个userId
    
    axios.post(url,data).then(res=>{
      const data=res.data;//接受返回值
      if(!data.success){//如果登录失败,显示失败的信息

        resolve();//返回不成功,返回控制回去
        wait && toast.close();//关闭等待窗口
        return showDialog({message:data.message});
      }
      console.log(`${url}接受:`,data.result);
      wait && toast.close();//关闭等待窗口
      resolve(data.result);//网络成功的处理
    }).catch(e=>{//异常处理
      console.log(`${url}错误:`,e);
      resolve();//服务器错误,返回空值回去
      wait && toast.close();//关闭等待窗口
      showDialog({message:'服务器错误'});
    });
  });
}
//暴露出去
export default post;

Vuex 状态机

Vuex,Vue官方推荐的“状态管理模式”,又称为:状态机
状态,实际上指的就是数据。状态机,就是针对项目中的公共数据,以及数据相关的公共方法,来进行统一管理

基础配置

在store文件下新建自己所需要的模块:JS文件

//进行模块化
export default{
    namespaced: true,       //表示真正的化为模块化
	
	//state:保存公共数据
    state: {
    },
	
    getters: {
    },
	
	//mutations:保存修改 state 的同步方法,也是唯一修改 state 的途径
    mutations: {
    },
	
    actions: {
    }
}

src/store/index.js 文件中,来进行状态机的相关配置

import Vue from 'vue'
import Vuex from 'vuex'

//import 模块名(文件名) from '模块名(文件名)路径'

Vue.use(Vuex)

export default new Vuex.Store({
	state: {
	},
	mutations: {
		
	},
	actions: {
	},
	
	//将 store 仓库中的各个模块挂载到这里
	modules: {
		//模块名(文件名)
	}
})

五大核心属性

Vuex中有五大核心属性,分别是:

  • state:保存公共数据
  • getters:保存公共的计算属性
  • mutations:保存修改 state 的同步方法,也是唯一修改 state 的途径
  • actions:保存公共的异步方法
  • modules:将 store 仓库划分为多个模块

状态机的使用

1.配置状态机

(1)配置数据的初始值
在状态机中,通过 state 来定义公共数据的初始值:

(2)配置异步请求方法
在状态机中,通过 actions 来定义公共的异步方法

(3)配置修改state的方法
因为 actions 中请求到的专业数据,无法直接修改到 state 中,所以,我们需要创建一个 mutations 的方法

(4)actions 中调用 mutations
在 actions 中的方法中,通过 commit 方法来调用 mutations ,同时进行传值
调用mutation里的方法用commit,dispatch

import api from '@/api/index';
export default new Vuex.Store({

    state: {
        // 专业数据的初始值(第一步)
        majors: [],
    },
    mutations: {
        SET_MAJORS(state, payload) {            //state:为默认参数,指state对象;payload:指用来接收外部传递的数据(第三步)
            state.majors = payload.rows;
            state.total = payload.total;
        }
    },
    actions: {
        // 发送请求获取后端的专业数据(第二步)
        async getMajorsAsync(context, payload) {
            const res = await api.majors.get(payload);
            if (res.code) {
                // 想要将 action 中请求到的数据,保存到 state 中,但是 action 不能直接修改 state,因此:
                // 1. action 方法内调用 mutations 的方法:context.commit('mutations的方法名')
                // 2. 调用时将数据传递给 mutations 的方法
                // 3. mutations 的方法接收到数据后,去修改 state
                context.commit('SET_MAJORS', res.data);				//(第四步)
            }
        }
    },
})
2.组件中使用状态机

(1)获取 state 数据
在组件中,我们通常会用 computed 来接收仓库中的数据:

export default {
	computed:{
		majors(){
			return this.$store.state.majors;
		}
	}
}

后继,在组件中我们可以直接使用计算属性 majors

(2)调用actions 的方法
在组件中,需要调用状态机内 actions的方法,来发送请求获取最新的数据库的数据

export default {
	 created() {
	        this.getMajors();
	    },
	methods: {
	        getMajors() {
	            // 调用主仓库的 action 方法
	            this.$store.dispatch("getMajorsAsync");
}

辅助函数

辅助函数是Vuex为了方便我们在组件中操作状态机,而提供的一组方法

  • mapState: 获取state数据
  • mapGetters: 获取getters数据
  • mapMutations: 获取mutation方法
  • mapActions:获取actions方法
使用辅助函数

1、获取辅助函数
组件中如果要通过辅助函数来获取状态机中的数据和方法,那么需要先获取辅助函数。
引入辅助函数时,也要分为两种情况:

  • 获取主仓库的数据和方法
  • 获取仓库模块的数据和方法
//获取 主仓库 的数据和方法
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'

//获取 仓库模块 的数据和方法
import {createNamespacedHelpers} from "vuex"
const {mapState,mapGetters,mapMutations,mapActions} = createNamespacedHelpers('majorsModule')

2、使用辅助函数
不管是操作主仓库,还是操作仓库模块,只要获取到了辅助函数,后继的使用没有任何区别

export default{
	computed:{
		//获取仓库中的 state
		...mapState(['majors'])				//调用辅助函数
	},
	created(){
		this.getMajors();
	},
	methods:{
		...mapActions(['getMajorsAsync']),
		getMajors(){
			this.getMajorsAsync();
		}
	}
}
注意事项

如果在一个组件中,需要同时获取主仓库和仓库模块的辅助函数,或者需要同时获取多个仓库模块的辅助函数时,
那么这种情况下,我们需要对辅助函数进行重命名。

例如:我们要同时获取主仓库和仓库模块函数

//获取 主仓库 的数据和方法
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'

//获取 仓库模块 的数据和方法
import {createNamespacedHelpers} from "vuex"
const {
	mapState: majorsState,			//函数名:重新命名
	mapGetters: majorsGetters,
	mapMutations: majorsMutations,
	mapActions: majorsActions
	} = createNamespacedHelpers('majorsModule')

这样处理后,我们在组件中使用专业模块的辅助函数时,就直接调用 majorsModule 等即可

export defualt{
	computed:{
		...majorsState(['majors', 'xxx']),
		...majorsGetters(['xxx'])
	},
	methods:{
		...majorsMutations(['xxx']),
		...majorsActions(['getMajorsAsync'])
	}
}

状态机的使用模板

第一步:新建模块

store文件下,创建每个模块所需要的文件夹,在文件里创建index.js文件

//进行模块化
export default{
    namespaced: true,       //表示真正的化为模块化

    //state:保存公共数据
    state: {
		
        // 用来表示登录模态窗口的显示true或者隐藏false
        isShowLoginModal:false
    },
	
	//保存公共的计算属性
    getters: {
    },

    // mutations:保存修改 state 的同步方法,也是唯一修改 state 的途径
    //state:为默认参数,指state对象;payload:指用来接收外部传递的数据
    mutations: {
        // 修改isShowLoginModal的值
        chanIsShowLoginModal(state,payload){
			
			//state.变量名 = payload
            state.isShowLoginModal = payload
        }
    },
	
	//保存公共的异步方法
    actions: {
    },
}
第二步:引入到index.js文件
import Vue from 'vue'
import Vuex from 'vuex'

// 引入各个模块
//import 模块文件名 from '模块文件名/index.js'
import showLoginModal from './showLoginModal/index.js'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
  },
  getters: {
  },
  mutations: {
  },
  actions: {
  },
  
  //将 store 仓库中的各个模块挂载到这里
  modules: {
	
	//模块文件名
    showLoginModal
  }
})

// 状态,实际上指的就是数据。状态机,就是针对项目中的公共数据,以及数据相关的公共方法,来进行统一管理

第三步:在各组件中调用状态机里的数据或者方法
<script>

//引入状态机的辅助函数
import {mapState,mapMutations} from 'vuex'

export default {
  data() {
    return {
      
    }
  },

  // 使用computed来接收状态机中的数据
  computed:{
	  
    // 获取状态机中:state的公共数据:isShowLoginModal 
    ...mapState({
		
      // 状态机中的 数据名:state => state.文件名.数据名
      isShowLoginModal:state => state.showLoginModal.isShowLoginModal
    })
  },

    methods: {
      // 使用 mapMutations 来获取状态机中的方法
  ...mapMutations({
	  
    // 状态机中的方法名:"文件名/方法名"
        chanIsShowLoginModal:'showLoginModal/chanIsShowLoginModal'
      })
    }
}
</script>

用户身份认证

用户身份认证,主要分为两部分:
1、判断用户的登录状态
2、判断登录用户的角色权限

判断用户的登录状态

当用户访问项目中的一些权限页面时,首先需要先判断当前用户是否登录,如果没有登录,我们会直接提示用户去登录,并强制跳转到登录页面

1、用户登录

当用户登录成功后,后端会返回一个包含用户信息的 token 给前端,前端需要将 token 保存在本地存储中

export default {
    data() {
        return {
            form: {
                username: "admin",
                password: "123",
            },
        };
    },
    methods: {
        async login() {
            const res = await this.$api.users.login(this.form);
            if (res.code) {
                // 将后端返回的 token 保存在本地存储中
                localStorage.user_token = res.token;
                this.$message({
                    message: "恭喜你,登录成功",
                    type: "success",
                });
                this.$router.replace("/home");
            } else {
                this.$message.error("账号或密码错误,请重新登录。");
            }
        },
    },
};

2、判断用户的登录状态

在权限页面中,我们并不知道用户是登录后才访问的权限页面,还是未登录就直接访问权限页面。
因此,我们会在用户接入权限页面之前,对用户的登录状态进行验证。

给home 路由配置一个“路由独享守卫”

import { MessageBox } from 'element-ui';

const routes = [
	path:'/home',
	component:HomeView,
	
	//添加导航守卫
	beforeEnter:(to,from,next) => {
		const token = localStorage.user_token;		//获取本地存储的 token
		if(token){
			next()									//如果获取成功,就进入页面
		}else{
			MessageBox.alert('你还未登录,去登录','警告',{
				confirmButtonText:'确定',
				callback:action => {
					next('/login')				//登录失败后,强制跳转到登录页面
				}
			})
		}
	},
	
	children:[...]
]

设置全局前置守卫(推荐)

//路由模式:history模式
const router = new VueRouter({
	......
})

//全局守卫
router.beforeEach((to, from, next) => {
	if(to.path.includes('/home')){
		//进入if,在表明用户访问的页面需要权限
		const token = localStorage.user_token;		//获取本地存储的 token
		if(token){
			next()									//如果获取成功,就进入页面
		}else{
			MessageBox.alert('你还未登录,去登录','警告',{
				confirmButtonText:'确定',
				callback:action => {
					next('/login')				//登录失败后,强制跳转到登录页面
				}
			})
	}else{
		//进入else,则表明用户访问的页面不需要权限
		next()
	}
})

判断用户角色权限

大部分时候,在一个项目中,登录的用户会分成多种角色,不同的角色拥有不同的权限

不同权限的用户,登录成功后,能够访问的菜单是不一样的,因此,在用户登录成功符,我们需要向后端发送请求,来获取当前用户能访问的菜单数据。
但是,后端需要拿到前端存储的 token ,才能根据token来判断用户的角色。因此,我们前端需要将token添加到前端请求头中,然后随着请求一起发送给后端

1.配置请求头的token

我们找到项目中的 src/utils/request.js 文件,在该文件中,
给 axios 配置请求拦截器,在请求拦截器中,统一给项目中所有请求的 header 中添加 token

//将token添加到前端请求头中(用于判断角色权限)
// 请求拦截器:当前端将请求发送给后端之前,会被请求拦截器拦截下来
axios.interceptors.request.use((config) => {
    // 将 token 添加到请求头中,其中 Authorization 是后端定义的
    config.headers.Authorization = localStorage.user_token;
    return config;
})

其中,Authorization 是由后端决定的,通常在接口文档中都会有说明

2.发送请求获取菜单数据

我们在全局的前置守卫中,当判断用户登录成功后,我们就可以开始发送请求来向后端获取菜单数据

router.beforeEach(async (to, from, next) => {
	if (to.path.includes('/home')) {
		// 判断用户的登录状态
		const token = localStorage.user_token;
		if (token) {
			// 登录成功后,在来获取菜单数据
			const res = await api.users.getMenus();
			if (res.code) {
				console.log(res.data);
				// 将菜单数据保存到状态机(将菜单数据传递到主仓库中)
				store.commit('SET_MENUS', res.data);
			}

			next();
		} else {
			MessageBox.alert('你还未登录,请先登录。', '警告', {
				confirmButtonText: '确定',
				callback: action => {
					next('/login');
				}
			});
		}
	} else {
		next();
	}
})

3.保存菜单数据到状态机

用户相关的数据,通常会直接导出在主仓库中:

export default new Vuex.Store({
	state: {
		menus: []		//菜单数据初始值
	},
	mutations: {
		SET_MENUS(state, payload) {
			state.menus = payload;
		}
	},
	modules: {
		.....
	}
})

然后在请求成功后,调用mutations 的方法,将菜单数据传递到主仓库中:

4.菜单组件渲染数据

在菜单组件中,通过辅助函数,获取到主仓库中的菜单数据,并渲染:

import { mapState } from "vuex";
export default {
    computed: {
        ...mapState(["menus"]),
    },
};

身份认证过期处理

通常,后端在生成 token 时,都会设置一个token的有效期,每一次请求的时候,前端都会将token 携带在请求头中发送给后端,后端会对token的有效期进行判断

src/utils/request.js 文件中,在axios的响应拦截器中来处理401的报错

// 响应拦截器:当后端将请求结果返回到前端组件之前,会被“响应拦截器”拦截下来
axios.interceptors.response.use((res) => {
    // 将 return 的数据返回给前端组件
    return res.data;
},
//报错处理
 (err) => {
    if (err.response?.status == 401) {
        MessageBox.alert('登录已过期,请重新登录', '警告', {
            confirmButtonText: '确定',
            callback: action => {
                router.replace('/login');
            }
        });
    }
    return Promise.reject(err.message);
});

Vue全家桶

Swiper滑动插件

	Swiper:是开源、免费、强大的触摸滑动插件
		   是纯JavaScript打造的滑动特效插件,面向手机、平板电脑等移动终端
		   是能实现触屏焦点图、触屏Tab切换、触屏轮番图切换等常用效果
	vue中的官方文档:https://swiperjs.com/vue
	安装到指定的package.json文件中的命令:npm install --save swiper
	安装指定版本命令:npm install --save swiper@版本数
<template>
  <div class="hello">
    <!-- 第三步:显示 -->
    <swiper>
      <SwiperSlide>
        <img src="../assets/logo.png">
      </SwiperSlide>
      <SwiperSlide>
        <img src="../assets/logo.png">
      </SwiperSlide>
    </swiper>
  </div>
</template>

<script>
//第一步:引入swiper
import { Swiper, SwiperSlide } from 'swiper/vue';
import 'swiper/css';

export default {
  name: 'HelloWorld',
  //第二步:挂载
  components:{
    Swiper,
    SwiperSlide
  }
}
</script>

<style scoped>
img{
  width:100%
}

</style>

添加指示器:

<template>
  <div class="hello">
    <!-- 第三步:显示 -->
    <swiper :modules="modules" :pagination="{clickable:true}">
      <SwiperSlide>
        <img src="../assets/logo.png">
      </SwiperSlide>
      <SwiperSlide>
        <img src="../assets/logo.png">
      </SwiperSlide>
    </swiper>
  </div>
</template>

<script>
//第一步:引入swiper
import { Swiper, SwiperSlide } from 'swiper/vue';
import 'swiper/css';

import {Pagination} from 'swiper';    //添加指示器
import 'swiper/css/pagination';

export default {
  name: 'HelloWorld',
  //第二步:挂载
  components:{
    Swiper,
    SwiperSlide
  },
  //添加指示器是所需的固定语法
  data(){
    return{
      modules:[Pagination]
    }
  }
}
</script>

<style scoped>
img{
  width:100%
}

</style>

Axios网络请求库

Axios是一个基于promise的网络请求库
安装指令:npm install --save axios

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

引入

	组件中引入:import axios from "axios"
	全局引入:
		1.第一步在main.js文件中引入且进行挂载:
import axios from "axios"		//引入
//将axios挂载到全局
const app = createApp(App)
app.config.globalProperties.$axios = axios
app.mount('#app')
			
		2.在组件中调用
			this.$axios + 内容

网络请求基本示例

get请求
<template>
  <div class="hello">
  </div>
</template>

<script>
import axios from "axios";      //组件中导入axios
export default {
  name:"helloworld",

  //输出到桌面
  data(){
    return{
      chengpin:{}
    }
  },
  mounted(){
    //get请求方式
    axios({
      method:"get",
      url:"http://iwenwiki.com/api/blueberrypai/getChengpinDetails.php"
    }).then(res =>{
      //console.log(res.data);
      this.chengpin = res.data.chengpinDetails[0]   //输出到桌面
    })
  }
}
</script>
--------------------------------------------------
get快捷请求方式:
    axios.get("http://iwenwiki.com/api/blueberrypai/getChengpinDetails.php").then(res =>{
      console.log(res.data)
     }),
post请求
	温馨提示:post请求参数需要额外处理
		1.安装依赖:npm install --save querystring
		2.转换参数格式:ps.stringify({})
<template>
  <div class="hello">
    
  </div>
</template>

<script>
import axios from "axios";      //组件中导入axios
import QueryString from "querystring";		//导入querystring

export default {
  name:"helloworld",
  
    //post请求方式
    axios({
      method:"post",
      url:"http://iwenwiki.com/api/blueberrypai/login.php",
      data:QueryString.stringify({			//转换参数格式
        user_id:"iwen@qq.com",
        password:"iwen123",
        verification_code:"crfvw"
      })
    }).then(res =>{
      console.log(res.data);
    })
  }
}
</script>
---------------------------------------------------------
//post快捷请求方式
    axios.post("http://iwenwiki.com/api/blueberrypai/login.php",QueryString.stringify({
      user_id:"iwen@qq.com",
      password:"iwen123",
      verification_code:"crfvw"
    })).then(res =>{
      console.log(res.data);
    })

Axios网络请求封装

在src目录下创建文件夹utils文件,并创建文件http.js或者request.js,用来存储网络请求对象axios的请求方法
然后安装axios:npm install --save axios
在安装querystring:npm install --save querystring
//导入
import axios from "axios"
import { Promise } from "core-js/shim"
import querystring from "querystring"

//参考文档:搜索:axios看云
const errorHandle = (status, info) => {
    switch (status) {
        case 400:
            console.log("语义错误");
            break;
        case 401:
            console.log("服务器认证失败");
            break;
        case 403:
            console.log("服务器拒绝访问");
            break;
        case 404:
            console.log("地址错误");
            break;
        case 500:
            console.log("服务器遇到意外");
            break;
        case 502:
            console.log("服务器无响应");
            break;
        default:
            console.log(info);
    }
}


const instance = axios.create({
    //存放网络请求的公共配置
    timeout: 5000

})

//最常用的是拦截器
//1.发送数据之前
instance.interceptors.request.use(
    //成功时
    config => {
        if (config.methods === "post") {
            config.data = querystring.stringify(config.data)        //post请求时需要进行格式转换
        }
        //config:包含网络请求的所有信息
        return config;
    },
    //失败时
    error => {
        return Promise.reject(error)
    }
)

//2.获取数据之前
instance.interceptors.response.use(
    //成功时
    response => {
        return response.status === 200 ? Promise.resolve(response) : Promise.reject(response)
    },
    error => {
        const { response } = error;
        //错误才是我们需要关注的重点
        errorHandle(response.status, response.info)
    }
)

export default instance;
再在src目录下创建文件夹api文件(存放网络请求),并创建两个文件path.js和index.js,

path.js文件下:

const base = {
    //存放所有的公共地址
    baseUrl:"http://iwenwiki.com",
    chengpin:"/api/blueberrypai/getChengponDetails.php"
}

export default base;

index.js文件下

//存放所有的网络请求
import axios from "../utils/request";
import path from "./path"

const api = {
    //成品详细地址
    getChengpin(){
        return axios.get(path.baseUrl + path.chengpin)
    }
}

export default api

网络请求跨越解决方案

JS采取的是同源策略
	同源策略是浏览器的一项安全策略,浏览器只允许js代码请求和当前所在服务器域名,端口,协议相同的数据接口上的数据,这就是同源策略
	也就是说,当协议,域名,端口任意一个不相同时,都会产生跨越问题
目前主流的跨越解决方案有两种:
	1.后台解决:cors
	2.前台解决:proxy
//前台解决方案:在vue.config.js文件中添加如下代码
devServer:{
	proxy:{
		'api':{
			target:'所要跨越的地址'
			changeOrigin:true
		}
	}
}

//配置完成后,需要重启服务器

解决跨域的详尽办法

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

vue引入路由配置

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

页面加载的配置

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Vue Router(未导入自己进行配置)

在Vue中,我们可以通过vue-router路由器管理路径与页面之间的关系
Vue Router是Vue.js的官方路由。它与Vue.js核心深度集成,让用Vue.js构建单页应用变得轻易而举

第一步:安装路由
npm install --save vue-router

第二步:配置独立的路由文件(在src目录下新建一个文件router,在router文件里新建index.js文件)

import { createRouter,createWebHashHistory } from "vue-router";

//引入所需要的显示页面
import HomeView from "../views/HomeView"
import AboutView from "../views/AboutView"

//在路由配置信息中需要页面的相关配置
const ruotes = [
    {
        name:'',
        path:"/",                       //path:指定访问时的路径
        component:HomeView              //component:页面所对应的组件名
    },
    {
        name:'',
        path:"/about",                       
        component:AboutView
    }
]

//创建路由
const router = createRouter({
    history:createWebHashHistory(),
    /**
     * createWebHashHistory
     *      home:http://localhost:8080/#/
     *      about:http://localhost:8080/#/about
     *  原理:a标签锚点连接
     */
    /**
     * creatWebHistory
     *      home:http://localhost:8080/
     *      about:http://localhost:8080/about
     * 此种方式,需要后台配合做重定向,否则会出现404问题
     * 原理:H5 pushState()
     * 
     */
    routes
})

//导出
export default router;

第三步:引入路由到项目

//在main.js文件下引入router文件
import router from './router'

createApp(App).use(router).mount('#app')            //通过添加.use(router)来明确安装路由功能

第四步:指定路由显示入口
第五步:指定路由跳转

//在App.vue组件进行编辑

<template>
  <!-- 指定路由跳转:to="跳转的路径" -->
  <router-link to="/">首页</router-link>|
  <router-link to="/about">关于</router-link>
  
  <!-- 路由的显示入口 -->
  <router-view></router-view>

</template>


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

<style>

</style>

路由传递参数

第一步:在路由配置中指定参数的key

{
    path: '/newsdetails/:name',			//此处指定参数的key值为:name
    name: 'newsdetails',
    component: () => import("../views/NewsDetailsView.vue")
  }

第二步:在跳转过程中携带参数

<template>
    <ul>
        <li><router-link to="/newsdetails/百度">百度新闻</router-link></li>		//参数为:百度
        <li><router-link to="/newsdetails/网易">头条新闻</router-link></li>		//参数为:网易
        <li><router-link to="/newsdetails/腾讯">腾讯新闻</router-link></li>		//参数为:腾讯
    </ul>
</template>

第三步:在详情页面读取路由携带的参数

<template>
    <p>详细信息来自:{{ $route.params.name }}</p>
</template>

嵌套路由配置

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

路由的跳转

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第一步:创建子路由要加载显示的页面
第二步:在路由配置文件中添加子路由配置

import { createRouter, createWebHashHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'

const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView
  },
  {
    path: '/about',
    name: 'about',
    component: () => import('../views/AboutView.vue'),
	
    //重定向(首页指向)
    redirect:'/about/us',
    
    //嵌套添加二级导航
    children:[
      {
        //二级导航的路径不要加 /
        path:'us',
        component:() => import('../views/AboutViews/AboutUs.vue')
      },
      {
        path:'info',
        component:() => import('../views/AboutViews/AboutInfo.vue')
      }

    ]
  }
]

const router = createRouter({
  history: createWebHashHistory(),
  routes
})

export default router

第三步:指定子路由显示位置
第四步:添加子路由跳转链接

第五步:重定向配置(指定首页显示内容)

Vue状态管理(Vuex)

Vuex是一个专为Vue.js应用程序开发的状态管理模式+库。
	简单来说,状态管理可以理解成为了更方便的管理组件之间的数据交互,提供了一个集中式的管理方案,任何组件都可以按照指定的
	方式读取和改变数据。

引入Vuex的步骤

第一步:安装Vuex
npm install --save vuex
第二步:配置Vuex文件(在src目录下新建store文件,在此文件里创建index.js文件,进行如下操作)

import { createStore } from "vuex";


//Vuex的核心作用就是帮我们管理组件之间的状态
const store = createStore({
    //所有的状态都放在这里(数据)
    state:{
        counter:0
    }

})

export default store;

第三步:在主文件中引入Vuex(main.js文件下添加)

import store from './store'
app.use(store)

第四步:在组件中读取状态(任何组件中都可以读取)

<template>
  <div>
    <p>第一种方式:在app.vue中读取counter = {{$store.state.counter}}</p>
    <p>第二种读取方式:在app.vue中读取counter ={{counter}}</p>
  </div>
</template>

<script>
//第二种方式:
import { mapState } from 'vuex';
export default {
//专门来读取Vuex的数据
  computed: {
    ...mapState(["counter"])
  }
}
</script>

<style>

</style>

Vuex状态管理的核心

最常用的核心概念包括:State、Getter、Mutation、Action
Getter:对Vuex中的数据进行过滤
//store文件下的index.js文件
import { createStore } from 'vuex'

export default createStore({
	
  //存放公共数据
  state: {
    counter:0
  },

  //对数据进行筛选(保存公共的计算属性)
  getters: {
    getCounter(state){
      return state.counter > 0 ? state.counter :"counter数据异常"
    }
  },
  
  //保存修改state的同步方法,也是唯一修改state的途径
  mutations: {
  },
  
  //保存公共的异步方法
  actions: {
  },
  
  //将store仓库划分为多个模块
  modules: {
  }
})

在任意组件中读取:

<template>
  <div class="home">
    <p>state读取数据count={{ $store.state.counter }}</p>
    <p>getter筛选数据={{ $store.getters.getCounter }}</p>
    <p>getter第二种读取方式{{getCounter}}</p>
  </div>

</template>

<script>
import { mapGetters } from 'vuex';

export default {
  name: 'HomeView',
  //getter第二种读取方式
  computed:{
    ...mapGetters(["getCounter"])
  }
}
</script>

Mutation
更改Vuex的store中的状态的唯一方法是提交mutation。Vuex中的mutation非常类似于事件:每个mutation都有一个字符串的
	事件类型(type)和一个回调函数(handler)。这个回调函数就是我们实际进行状态更改的地方,并且他会接受state作为第一个参数。

ACtoin
Action类似于mutation,不同在于:
	Action提交的是mutation,而不是直接变更状态
	Action可以包含任意异步操作

Element-plus

官网:https://element.eleme.cn
Element,一套为开发者准备的基于Vue2.0的桌面端组件库
Element Plus基于Vue3.0,面向开发者的组件库

安装Element-Plus:

	npm install element-plus --save

完整引用

在main.js文件下添加,如果对打包后的文件大小不是很在乎,那么使用完整导入会更方便
import Elementplus from 'element-plus'
import 'element-plus/dist/index.css'

createApp(App).mount('#app')
apply.use(Elementplus)
App.mount('#app')
//简写:
//createApp(App).use(Element).mount('#app')

按需导入

首先需要安装unplugin-vue-components和unplugin-auto-import则两款插件

安装: npm install -D unplugin-vue-components unplugin-auto-import
然后修改vue.config.js配置文件

const {defineConfig} = require('@vue/cil-service')
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const {ElementPlusResolver} = require('unplugin-vue-components/resolvers')

module.exports=defineConfig({
	transpileDependencies: true,
	configureWebpack:{
		plugins:[
			AutoImport({
				resolvers:[ElementPlusResolver()]
			}),
			Components({
				resolvers:[ElenentPlusResolver()]
			})
		]
	}
})

样式穿透(::v-deep)

<style scope lang="scss">
::v-deep 需要修改的element组件类名{
	color:red
}

</style>

Vuex 状态机

辅助函数

辅助函数是Vuex为了方便我们在组件中操作状态机,而提供的一组方法

  • mapState: 获取state数据
  • mapGetters: 获取getters数据
  • mapMutations: 获取mutation方法
  • mapActions:获取actions方法
使用辅助函数

1、获取辅助函数
组件中如果要通过辅助函数来获取状态机中的数据和方法,那么需要先获取辅助函数。
引入辅助函数时,也要分为两种情况:

  • 获取主仓库的数据和方法
  • 获取仓库模块的数据和方法
//获取 主仓库 的数据和方法
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'

//获取 仓库模块 的数据和方法
import {createNamespacedHelpers} from "vuex"
const {mapState,mapGetters,mapMutations,mapActions} = createNamespacedHelpers('majorsModule')

2、使用辅助函数
不管是操作主仓库,还是操作仓库模块,只要获取到了辅助函数,后继的使用没有任何区别

export default{
	computed:{
		//获取仓库中的 state
		...mapState(['majors'])
	},
	created(){
		this.getMajors();
	},
	methods:{
		...mapActions(['getMajorsAsync']),
		getMajors(){
			this.getMajorsAsync();
		}
	}
}
注意事项

如果在一个组件中,需要同时获取主仓库和仓库模块的辅助函数,或者需要同时获取多个仓库模块的辅助函数时,
那么这种情况下,我们需要对辅助函数进行重命名。

例如:我们要同时获取主仓库和仓库模块函数

//获取 主仓库 的数据和方法
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'

//获取 仓库模块 的数据和方法
import {createNamespacedHelpers} from "vuex"
const {
	mapState: majorsState,			//函数名:重新命名
	mapGetters: majorsGetters,
	mapMutations: majorsMutations,
	mapActions: majorsActions
	} = createNamespacedHelpers('majorsModule')

这样处理后,我们在组件中使用专业模块的辅助函数时,就直接调用 majorsModule 等即可

export defualt{
	computed:{
		...majorsState(['majors', 'xxx']),
		...majorsGetters(['xxx'])
	},
	methods:{
		...majorsMutations(['xxx']),
		...majorsActions(['getMajorsAsync'])
	}
}

第八章、Vue3(使用组合式API编程)

项目构建工具

我们要创建Vue3的项目,有两个项目构建工具可选

1、Vue CLI 脚手架工具(基于webpack)
2、Vite(推荐)

Vite 构建 Vue3 项目

1、创建项目

npm init vue || npm create vue

//执行完该命令后,会弹出提示,按照提示需求即可完成项目的创建

2、下载依赖

npm i

3、启动项目

npm run dev

注:使用Vite创建的Vue3项目也可以使用选项式编程

选项式API和组合式API的区别

选项式API

选项式 API,具有相同功能的放在一起,可以用包含多个选项的对象来描述组件的逻辑,例如 data、methods 和 mounted等等Vue的配置项。
选项所定义的属性都会暴露在函数内部的 this 上,它会指向当前的组件实例。

组合式API

一个功能逻辑的代码组织在一起(包括数据,函数等等)。

vue2和vue3响应式区别

Vue2响应式

vue2 的响应式原理是利es5 的个 API ,Object.defineProperty()对数据进劫持结合发布订阅模式的式来实现的。

优点:
兼容性好,支持 IE9。
缺点:
只能劫持对象的属性(key值.Object.key()),因此需要对每个对象的每个属性进行遍历。
无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。是通过重写数据的操作方法(push、unshift…)来对数组进行监听的。
不能对 es6 新产生的 Map,Set 这些数据结构做出监听。

Vue3响应式

vue3 中使了 es6 的 proxy API 对数据代理,通过 reactive() 函数给每个对象都包层 Proxy,通过 Proxy 监听属性的变化,从⽽ 实现对数据的监控。
简而言之,Proxy 可以劫持整个对象,并返回一个新的对象。

优点:
可以直接监听对象而非属性;
可以直接监听数组的变化;
有多种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等是Object.defineProperty 不具备的;
返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改。
Proxy的优势
1.defineProperty只能监听某个属性,不能对全对象监听
2.可以省去for in、闭包等内容来提升效率(直接绑定整个对象即可)
3.可以监听数组,不再去单独的对数组做特异性操作,通过Proxy可以直接拦截所有对象类型数据的操作,完美⽀持对数组的监听。 (vue3.x可以检测到数组内部数据的变化)

setup函数

setup 函数是一个钩子函数,是在组件中使用组合式 API 的入口

setup的参数

setup(props, context) / setup(props, {attrs, slots, emit, expose})
第一个参数是组件的 props,和标准的组件一致。
第二个参数是一个 Setup 上下文对象,它暴露了其他一些在 setup 中可能会用到的值:
attrs: 包含没有在props配置中声明的属性的对象, 相当于 this. a t t r s e m i t :  用来分发自定义事件的函数 ,  相当于  t h i s . attrs emit: 用来分发自定义事件的函数, 相当于 this. attrsemit:用来分发自定义事件的函数,相当于this.emit
slots: 包含所有传入的插槽内容的对象, 相当于 this.$slots
expose: 如果返回值是函数,需要expose暴露公共属性

响应式变量:reactive()、ref()

定义:当我改变这个变量的值后,所有引用了这个变量的地方的值都会一起变化
选项式API中,data里面初始化的数据都是深层次的响应式变量
组合式API中,只能通过reactive()或者ref()来生成响应式变量(使用它们需要引入:import { reactive, ref } from ‘vue’)

<script setup>
import { reactive, ref } from 'vue'; // 引入reactive,用来做响应式变量

// 如果是基础变量,不能使用reactive来生成响应式变量,必须使用ref
// ref会把基础变量生成一个RefImpl对象,这个对象包含一个value的属性,我们在setup必须对value进行操作
// 在外面和普通变量一致
let num = ref(0);
// 相当于 reactive({ value: 0 })
const add = () => {
  num.value++; // 只要在setup中,我们就一定要用value来操作
}

// reactive生成的响应式变量只能改变它的属性,不能替换它的值,如果需要替换,还得使用ref
// let person = reactive({ name: '孙悟空' }) // personal是一个对象
let person = ref({ name: '孙悟空' }) // personal是一个对象

const change72 = () => { // 72变
  person.value.name = '猪八戒';// 只要在setup中,我们就一定要用value来操作
};
const replaceSun = () => {
  person.value = { name: '六耳猕猴' }; // 只要在setup中,我们就一定要用value来操作
};
</script>

<template>
  <div>
    <div>{{num}}</div>
    <button v-on:click="add">增加</button>
    <!-- 如果在setup之外,和普通变量一致 -->
    <button v-on:click="num--">减少</button>
    <hr>
    <div>{{ person }}</div>
    <div>{{ person.name }}</div>
    <button v-on:click="change72">72变</button>
    <button v-on:click="replaceSun">六耳猕猴来了</button>
    <button v-on:click="person='假孙悟空'">六耳猕猴不需要通过如来佛祖</button>
  </div>
</template>

<style scoped>
</style>

组件之间的交互

父组件使用ref调用子组件

App.vue(父组件)

<script setup>
// 在组合式api中,直接引入子组件就可以使用了,不需要注册
import Child from './Child.vue';
import { ref } from 'vue';

const childRef = ref(null); // 生成一个ref对象,用来指向子组件
const modifyChildName = ()=>{
  // console.log('=================childRef', childRef);
  childRef.value.modifyNameByFather(); // 只有暴露出来的方法才能使用
  // childRef.value.name = '朱公子';
};
</script>

<template>
  <div>
    <div>我是父组件</div>
    <button @click="modifyChildName">修改孩子的名字</button>
    <hr>
    <Child ref="childRef"></Child>
  </div>
</template>

Child.vue(子组件)

<script setup>
  import {ref, defineExpose} from 'vue';
  
  let name = ref('朱小明');
  // 子组件的属性和方法都只能在该组件中使用,如果外部需要使用,需要暴露出去
  const modifyName = () => {
    name.value = '朱大肠';
  };
  
  // 使用defineExpose暴露出去,参数是一个对象,每一个key值就是暴露出去的名称,值就是方法或者属性
  defineExpose({ modifyNameByFather: modifyName, name });
</script>

<template>
  <div>
    <div>我是{{ name }}</div>
    <button @click="modifyName">自己修改</button>
  </div>
</template>

父传子

将原来的props项换成defineProps
使用defineProps接收属性

App.vue(父组件)

<script setup>
import Child from '@/Child.vue';
const show = () => {
  console.log('=================123', 123);
}
</script>

<template>
  <div>
    我是父组件
    <hr>
    <!-- 当子组件接收需要验证时:这里name传123会报错 -->
    <!-- <Child :name="123" :age="30" :show="show"></Child> -->
    <!-- 没有传name会报错 -->
    <Child :age="30" :show="show"></Child>
    <!-- 因为姓李,所以也不通过 -->
    <!-- <Child name="李大鸣" :age="30" :show="show"></Child> -->
	
    <Child name="朱大鸣" :age="30" :show="show"></Child>
    <Child name="朱的开" :age="20" :show="show"></Child>
    <Child name="朱小明" :age="10" :show="show"></Child>
  </div>
</template>

Child.vue(子组件)

<script setup>
import { defineProps } from 'vue';

// 将原来的props项换成defineProps
// 使用defineProps接收属性

// 不需要验证直接接收时:使用数组进行接收
// const props = defineProps(['name', 'age', 'show']);
// const { name, age, ...api } = defineProps(['name', 'age', 'show']); // 使用数组来接收

// 需要验证时:使用对象进行接收
const props = defineProps({
  age: Number, // 要求age必须是Number
  name: { // 更多的验证
    type: String, // 必须传String
    validator(v) {
      if (v[0] !== '朱') {
        return false; // 不通过,会报错
      }
      return true; // 通过
    },
    // required: true, // 必传
    default: '新生儿', // 默认值
  },
  show: null, // 不需要验证用null
});

// const log = (...args) => {
//   console.log(...args);
// }

const click = () =>{
  // 只要父组件传过来的所有属性和方法都可以使用,不需要defineExpose
  // props.show();
  props.show();
};
</script>

<template>
  <div>
    <!-- 我叫{{ props.name }},我今天{{ props.age }}岁 -->
    我叫{{ props.name }},我今天{{ props.age }}岁
    <br>
    <button @click="click">执行父组件的方法</button>
  </div>
</template>

子传父(自定义事件的使用)

App.vue(父组件)

<script setup>
import { ref } from 'vue';
import Child from './Child.vue'
let childName = ref('朱小明');
const changeChildName= (name) =>{
  childName.value = name;
}
</script>

<template>
  <div>
    我是父组件
    <hr>
    <Child :name="childName" @changeName="changeChildName"></Child>
  </div>
</template>

Child.vue(子组件)

<script setup>
import { defineProps, defineEmits, ref } from 'vue';

const props = defineProps(['name']); // 接收到的属性是只读属性,不能自己修改

// 如果要修改,通知父组件修改
// const emits = defineEmits(['changeName']); // 需要要验证时:一般用数组接收,返回的是一个函数

// 需要验证时:
const emits = defineEmits({
  changeName(v) {
    if (v[0] === '朱') { // 判断是否姓朱
      return true; // 返回true表示验证成功
    }
    return false;// 返回false表示验证失败
  }
});

const changeName = ()=>{
  // props.name = '朱公子'; // 是只读属性,不能修改,需要发送事件
  // this.$emit('changeName')
  // 不能直接进行数据传递,需要一个事件来触发它进行数据传递
  emits('changeName', '朱三公子'); // 第一个参数是事件名称
  // emits('changeName', '李三公子'); // 会验证失败
};
</script>

<template>
<div>
  <div>我是子组件,我叫{{ props.name }}</div>
  <button @click="changeName">改名字</button>
</div>
</template>

计算属性(computed函数)

computed函数
如果是只读属性,只需要传入一个返回值为响应式变量的getter函数。
如果是可写属性,需要传入一个对象,两个key值,分别是set和get,get是一个getter函数,set是一个setter函数。

<script setup>
import { computed, reactive } from 'vue';
import _ from 'lodash';

const list = reactive([ // 一定要用reactive生成响应式变量
  {
    name: '商品1',
    price: 10,
    count: 1,
    selected: false,
  },
  {
    name: '商品2',
    price: 9,
    count: 1,
    selected: false,
  },
  {
    name: '商品3',
    price: 8.8,
    count: 1,
    selected: false,
  },
]);

// computed是一个函数,如果只要只读属性,回调是一个函数
const totalFee = computed(()=>{
  let arrs = list.filter(o=>o.selected); // 筛选出选择的
  const sum = arrs.reduce((r, o)=>r+o.price*o.count, 0); // 计算总价
  return sum;
});

// 如果需要可写属性,需要设置一个对象,set和get属性
const allSelected = computed({
  get() {
    return _.every(list, o=>o.selected);
  },
  set(v) { // set属性一定有一个参数,就是当前的选择的值
    if (v) {
      list.forEach(o=>o.selected = true); // 如果全选,那么每一个都选择
    } else {
      list.forEach(o=>o.selected = false); // 否则,每一个都不选择
    }
  }
});


</script>

<template>
<div>
  <div v-for="item in list" :key="item.name" class="row">
    <input type="checkbox" v-model="item.selected" class="check">
    <Item :item="item"></Item>
  </div>
  <div class="row">
   <input type="checkbox" v-model="allSelected">
   <div>¥{{ totalFee }}</div> 
   <button>立即支付</button>
  </div>
</div>
</template>


<style scoped>
.row {
  display: flex;
  flex-direction: row;
  margin: 10px 0;
}
.check {
  margin-right: 20px;
}
</style>

侦听器(watch函数)

第一个参数是侦听器的源。这个来源可以是以下几种:
一个 ref 对象或者 Proxy对象
ref 对象或者 Proxy对象组成的数组
有返回值的函数(getter)

第二个参数是在发生变化时要调用的回调函数。这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用。
当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值。

第三个可选的参数是一个对象,支持以下这些选项:
immediate:在侦听器创建时立即触发回调。
deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。
watch的返回值是stop函数,用于停止监听。

<div>还有{{leftTime}}分钟下课</div>
<div>{{state}}</div>
...
<script>
  ...
  setup() {
    const leftTime = ref(45); // 距离下课时间
    const state = ref('上课中...'); // 状态
    const stopWatch = watch(leftTime, (cur, pre, onCleanup) => {
        if (cur === 0) {
          state.value = '下课了';
        }
    });
    
    return { // 返回值会暴露给模板,模板可以直接使用
      leftTime,
      state,
      stopWatch, // 可以直接调用这个函数
    };
  },
  ...
</script>

可以用数组的形式监听多个参数,回调中当前状态和上一次状态也是数组。

setup() {
  const count = ref(0);
  const count1 = ref(0);
  const stop = watch([count, count1], (curs, pres) => {
    console.log(curs, pres);
  })
}

watchEffect函数
只有一个参数,是一个监听的回调函数,它会在第一运行的时候去自动收集依赖源,依赖源更新时重新执行自身。因此watchEffect 的回调函数是惰性立即执行的。这个回调函数可以带一个用于注册副作用清理的回调函数。
watchEffect的返回值是stop函数,用于停止监听。

<div>还有{{leftTime}}分钟下课</div>
<div>{{state}}</div>
...
<script>
  ...
  setup() {
    const leftTime = ref(45); // 距离下课时间
    const state = ref('上课中...'); // 状态
    const stopWatch = watchEffect((onCleanup) => {
        if (leftTime.value == 0) {  // 自动收集依赖源
          state.value = '下课了';
        }
    });
    
    return { // 返回值会暴露给模板,模板可以直接使用
      leftTime,
      state,
      stopWatch, // 可以直接调用这个函数
    };
  },
  ...
</script>

生命周期钩子函数

组合式API开发中的生命周期钩子
只有3个阶段共6个函数,3阶段分别是 mount(挂载) update(更新) unmount(卸载),用onBeforeXX 和 onXXed 的形式得到6个函数。

不再有create这个阶段,原来为什么有create是因为有一个数据初始化的阶段,在组合式API中数据放在setup里面初始化了,所以就不需要这个阶段了。

生命周期钩子的参数是一个回调函数,我们可以在回调函数中添加我们的代码。
生命周期钩子函数必须放在setup函数中。

setup() {
  onBeforeMount(() => { // 挂载前
    console.log('onBeforeMount')
  });
  onMounted(() => { // 已挂载
    // DOM操作、数据请求、实例化、计时器、延时器、订阅数据
    console.log('onMounted')
  });
  onBeforeUpdate(() => { // 更新前
    console.log('onBeforeUpdate')
  });
  onUpdated(() => { // 已更新
      // DOM操作、实例化
    console.log('onUpdated')
  });
  onBeforeUnmount(() => { // 卸载前
    // 取消订阅,清除计时器、延时器等
    console.log('onBeforeUnmount')
  });
  onUnmounted(() => { // 已卸载
    console.log('onUnmounted')
  });
}

依赖注入

前面我们学过,依赖注入提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
相当于提供了一个全局变量的样子,在所有的子组件都可以访问到。实际例子相当于的家谱图,老祖宗留了一本家谱图,想知道姓什么的时候去翻一下就知道了。

选项式API开发中的依赖注入

...
// 使用 provide 选项为组件后代提供数据
provide: {
  surname: '朱',
},
...
//子组件使用 inject 选项来注入上层组件提供的数据
inject: ['surname'], // 通过数组的形式获取

组合式API中的依赖注入
提供一个值,可以被后代组件注入。
provide函数接受两个参数:第一个参数是要注入的 key,可以是一个字符串或者一个 symbol,第二个参数是要注入的值。

setup () {
  provide('surname'); // 提供值,可以被后代组件注入,可以是数据,也可以是函数
}

inject函数
子组件使用inject函数来注入上层组件提供的数据

第一个参数是注入的 key。Vue 会遍历父组件链,通过匹配 key 来确定所提供的值。
如果父组件链上多个组件对同一个 key 提供了值,那么离得更近的组件将会“覆盖”链上更远的组件所提供的值。
如果没有能通过 key 匹配到值,inject函数将返回 undefined,除非提供了一个默认值。

第二个参数是可选的,即在没有匹配到 key 时使用的默认值。
它也可以是一个工厂函数,用来返回某些创建起来比较复杂的值。
如果默认值本身就是一个函数,那么你必须将 false 作为第三个参数传入,表明这个函数就是默认值,而不是一个工厂函数。

setup () {
  const surname = inject('surname', '孙'); // 子组件使用inject函数来注入上层组件提供的数据
  return () => `我姓:${surname}`;
}

路由

1、安装:vue-router

我们在使用脚手架的时候选择是否创建router选择为Yes就可以了。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2、路由的配置(和选项式api一样)

src/router/index.js文件下进行路由的配置

import { createRouter, createWebHistory } from 'vue-router'

import { usePersonalStore } from '../stores/counter'		//引入全局状态管理
import axios from 'axios'			//引入网络请求axios


const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/login',  //跳转路径
      component: () => import('@/pages/login/Login.vue'),	//引入组件
      name: 'Login',
      meta:{
        noAuth:true				//路由元信息(用来判断是否需要验证才能进入该页面)
      }
    },
    {
      path: '/',  //主页面
      component: () => import('@/pages/Main.vue'),
      name: 'Main',
      redirect: '/home',			//路由重定向
      meta:{
        noAuth:false
      },
      children: [{
        path: '/home',  //首页
        component: () => import('@/pages/home/index.vue'),
        name: 'Home',
      },
       {
        path: '/member',  //员工管理
        component: () => import('@/pages/member/index.vue'),
        name: 'Member',
      }, {
        path: '/mine',  //个人页面
        component: () => import('@/pages/mine/index.vue'),
        name: 'Mine',
      }]
    }
  ]
})

// 添加全局守卫,判断页面是否需要登录后才能显示
let personalStore;  //定义一个全局变量

router.beforeEach((to,from,next)=>{
  if (!personalStore) {
    personalStore = usePersonalStore() //生成personalStores实例
  }
  if (to.meta.noAuth) {   //如果noAuth为true,直接进入
    next()
  }else if(!localStorage.userId){   //如果未登录,跳转到登录页面
    router.push('/login')
  }else{
    // console.log('存储到全局状态');
    axios.post('/api/detail/member',{id:localStorage.userId}).then(res=>{
      const data=res.data
      // console.log('全局守卫',data.result);
      
      if (!data.success) {  
        router.push('/login') 
      }else{ 
        personalStore.setPersonal(data.result)    //把个人信息存储到全局状态中(调用stores里面的setPersonal方法,把传递过来的参数存储到全局状态)
        next()
      }
    })
    
  }
})

export default router

3、路由出口:

RouterView 是一个功能性组件,用于渲染路径匹配到的视图组件。

RouterLink
标签支持用户在具有路由功能的应用中(点击)导航。

| 属性 | 类型 | 说明 |
| ---- | ------ | -------------- | -------------------------- |
| to | String/Object | 目标路由/目标位置的对象 |
| replace | Boolean | 不留下导航记录 |
| append | Boolean | 在当前路径后加路径 /a => /a/b |
| tag | String | 指定渲染成何种标签 |
| active-class | String | 激活时使用的Class |

路由的跳转及传参

在组合式api中,因为我们没有访问 this,所以不能再直接访问 this.$router。作为替代使用 useRouter 函数:

import { useRouter, useRoute } from 'vue-router'

const router = useRouter()		//路由的跳转
const route = useRoute()		//获取路由的参数及方法

// 字符串路径
router.push('/users/eduardo')
// 带有路径的对象
router.push({ path: '/users/eduardo' })
// 命名的路由,并加上参数,让路由建立 url
router.push({ name: 'user', params: { username: 'eduardo' } })
// 带查询参数,结果是 /register?plan=private
router.push({ path: '/register', query: { plan: 'private' } })
// 带 hash,结果是 /about#team
router.push({ path: '/about', hash: '#team' })


const username = 'eduardo'
// 我们可以手动建立 url,但我们必须自己处理编码
router.push(`/user/${username}`) // -> /user/eduardo
// 同样
router.push({ path: `/user/${username}` }) // -> /user/eduardo


// 如果可能的话,使用 `name` 和 `params` 从自动 URL 编码中获益
router.push({ name: 'user', params: { username } }) // -> /user/eduardo
// `params` 不能与 `path` 一起使用
router.push({ path: '/user', params: { username } }) // -> /user

获取传过来的参数:
因为我们在 setup 里面没有访问 this,所以不能再直接访问 this.$route。作为替代使用 useRoute 函数:

import { useRoute } from 'vue-router'		//引入

const route = useRoute()		//实例化


console.log(route.query); // 路由配置为 { path: "/cart?day=30" } 时
console.log(route.params); // 路由配置为 { path: "/cart/:day" } 时

网络请求:axios

跨域的处理

import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({

  base:'/admin/',   //(使用vite开发时)添加顶级路由,用于打包程序给测试人员用

  plugins: [
    vue(),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  server: {  // 对开发服务器进行设置
    proxy: { // 设置跨域
      '/api': {
        changeOrigin: true,  //开启代理:在本地会创建一个虚拟服务端,然后发送请求数据
        
        target: 'http://localhost:5000',		//这里是后端接口
        rewrite:(path)=> {
          return path.replace(/^\/api/,'') // 会将本地的 /api/list/goods 代理为:http://localhost:5000/list/goods
        }
      },
      '/uploadFile': { // 工程的接口以/api开头(图片处理)
        target: 'http://localhost:5000', // 被代理服务
        changeOrigin: true, // 改变源
      },
    }
  }
})

网络请求的封装

import axios from 'axios'; // 引入axios
import { ElMessageBox } from 'element-plus'
import { ElLoading } from 'element-plus'		//引入UI组件

// url 为我们的接口
// params 为参数
// wait 是否等待,默认不等待
function post(url, params = {}, wait) { // 定义我们的post接口
  if (process.env.NODE_ENV === 'production') { // 如果是生产模式,才去掉/api
    url = url.replace('/api', '');
  }
  return new Promise(resolve => { // 使用Promise封装异步
    console.log(`${url}发送:`, params);
    const toast = wait && ElLoading.service({
      lock: true,
      text: '数据获取中,请稍后...',
      background: 'rgba(0, 0, 0, 0.7)',
    });

    params.userId = localStorage.userId; // 每一个接口要跟一个userId
    axios.post(url, params).then(res => {
      const data = res.data; // 接受返回值
      if (!data.success) { // 如果登录失败,显示失败的信息
        resolve(); // 返回不成功,返回空值回去
        wait && toast.close(); // 关闭等待窗口
        return ElMessageBox.alert(data.message); // 使用好看的显示
      }
      console.log(`${url}接收:`, data.result);
      wait && toast.close(); // 关闭等待窗口
      resolve(data.result); // 网络成功的处理
    }).catch(e => { // 异常处理
      console.log(`${url}错误:`, e);
      resolve();  // 服务器错误,返回空值回去
      wait && toast.close(); // 关闭等待窗口
      ElMessageBox.alert('服务器错误')
    });
  });
}
// 暴漏出去
export default post;

全局状态管理:pinia

pinia 是vue官方推荐的全局状态管理工具,pinia只能在vue3中使用,不能再vue2中使用。

Pinia与Vuex的区别

pinia只有state、getters、actions,没有mutations,简化了状态管理的操作
pinia模块划分不需要modules,
pinia自动化代码拆分
pinia对ts支持很好以及vue3的composition API
pinia体积更小,性能更好

手动安装时的操作:(使用脚手架npm create vue创建的时候可以自动安装pinia)

npm i -S pinia

创建pinia实例

在根目录下创建一个名为 store 的文件夹,并在其中创建一个名为 counter.js 的文件

选项式api的格式:
import { defineStore } from 'pinia'; // 导入相应的方法

 // vuex是使用createStore来创建一整个store,pinia是使用defineStore来创建一个叫counter的store
export const useCounterStore = defineStore('counter', { // 名称counter必须唯一
  state: () => { // 相当于我们学习的组件中的data函数
    return {
      count: 0,
    }
  },
  getters: {  // 相当于我们学习的组件中的computed
    doubleCount: (state) => {
      return state.count * 2;
    }
  },
  actions: { // 相当于我们学习的组件中的methods,pinia同步异步均支持
    increment(n) {
      this.count += n;
    }
  }
})

组合式api的格式:
import { ref} from 'vue'
import { defineStore } from 'pinia'

export const usePersonalStore = defineStore('personal', () => {   //personal是唯一的key
  const personal = ref(null)    //用来存储个人信息
  function setPersonal(info){   //修改个人信息的函数
    personal.value = info
  }
  
  return { personal, setPersonal}   //把方法和属性暴露出去
})

使用

import { usePersonalStore } from '../stores/counter'		//引入
personalStore = usePersonalStore() //生成personalStores实例

personalStore.setPersonal(data.result)    //把个人信息存储到全局状态中(调用stores里面的setPersonal方法,把传递过来的参数存储到全局状态)
        

案例二:
创建:

import { defineStore } from 'pinia'; // 导入相应的方法
import { ref, computed } from 'vue';
 // vuex是使用createStore来创建一整个store,pinia是使用defineStore来创建一个叫counter的store
export const moneyStore = defineStore('account', ()=>{
  const count = ref(10000); // 初始化余额
  const doubleCount = computed(()=>{ // 使用computed计算金豆的数量
    return count.value * 100;
  });

  const increment = (n) => { // 增加的方法
    count.value += n;
  };
  return { count, doubleCount, increment }; // 暴露出数据和方法
});

使用:
用对象的方式使用:

<script setup>
import { useCounterStore } from './stores/counter.js'
// 里面的变量提取出来,直接使用counter.count调用,因为counter是响应式的,因此使用counter.count做操作也是响应式的
const counter = useCounterStore();
</script>

<template>
  <main>
    <p>数字:{{ counter.count }}</p> 
    <p>2倍数字:{{ counter.doubleCount }}</p>
    <button @click="counter.increment">增加</button>
  </main>
</template>

提取属性的方式使用:
为了从 Store 中提取属性,同时保持其响应性,您需要使用storeToRefs(),它将为每个响应性属性创建引用。

<script setup>
import { useCounterStore } from './stores/counter.js'
const counter = useCounterStore();
// const { count, doubleCount } = counter; // 直接提取属性,count和doubleCount不具备相应性
const { count, doubleCount } = storeToRefs(counter); // 现在count和doubleCount具备相应性
</script>

<template>
  <main>
    <p>数字:{{ count }}</p> 
    <p>2倍数字:{{ doubleCount }}</p>
    <button @click="increment">增加</button>
  </main>
</template>

实用工具

moment

设置时间日期的函数库
官网:moment官网

dayjs

设置时间日期的函数库
我们一般使用dayjs来代替moment,因为dayjs更加轻量
官网地址

lodash

数组方法库
官网地址

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="https://cdn.staticfile.org/lodash.js/4.17.11/lodash.min.js"></script>
</head>
<body>
  <script>
    // 1、从数组[1,2,3,4,5]得到新数组[2,4,6,8,10]
    let list=[1,2,3,4,5]
    let newList = _.map(list,item=>item*2)
    console.log("1、从数组[1,2,3,4,5]得到新数组[2,4,6,8,10]-----",newList);

    // 2、从数组[{a:1, b: 5}, {a: 2, b: 0}, {a: 3, b: 3}]得到新数组[{ a: 1 }, {a: 2}, {a: 3}]
    let list1 = [{a:1, b: 5}, {a: 2, b: 0}, {a: 3, b: 3}]
    console.log("2、从数组[{a:1, b: 5}, {a: 2, b: 0}, {a: 3, b: 3}]得到新数组[{ a: 1 }, {a: 2}, {a: 3}]-----",_.map(list1,item=>({a:item.a})));

    // 3、计算数组[{a:1, b: 5}, {a: 2, b: 0}, {a: 3, b: 3}]中所有a的和
    console.log("3、[{a:1, b: 5}, {a: 2, b: 0}, {a: 3, b: 3}]中所有a的和:-----",_.sum(_.map(list1,item=>item.a)));

    // 4、从数组[1,2,3,4,5,6]中提取所有的奇数[1,3,5]
    console.log("4、从数组[1,2,3,4,5,6]中提取所有的奇数[1,3,5]-----",_.filter(list,item=>item%2));

    // 5、从对象中{ a: 1, b: 2, c: 3, d: 4 }中提取所有的key,得到['a','b','c','d']
    let obj={ a: 1, b: 2, c: 3, d: 4 }
    console.log("5、从对象中{ a: 1, b: 2, c: 3, d: 4 }中提取所有的key,得到['a','b','c','d']-----",_.keys(obj));

    // 6、将对象{ a: 1, b: 2, c: 3, d: 4 }转化为 { fa: 1, fb: 2, fc: 3, fd: 4 }
    console.log("6、将对象{ a: 1, b: 2, c: 3, d: 4 }转化为 { fa: 1, fb: 2, fc: 3, fd: 4 }-----",_.mapKeys(obj,(v,k)=>"f"+k));

    // 7、将对象{ a: 1, b: 2, c: 3, d: 4 }转化为 { fa: 2, fb: 4, fc: 6, fd: 8 }
    console.log("7、将对象{ a: 1, b: 2, c: 3, d: 4 }转化为 { fa: 2, fb: 4, fc: 6, fd: 8 }-----",_.mapValues(_.mapKeys(obj,(v,k)=>"f"+k),(v,k)=>v*2));

    // 8、计算数组[ {a: 3}, {a: 1}, {a: 4}, {a: 8} ]中 a>2的个数
    let list2 =[ {a: 3}, {a: 1}, {a: 4}, {a: 8} ]
    console.log("8、计算数组[ {a: 3}, {a: 1}, {a: 4}, {a: 8} ]中 a>2的个数-----",_.size(_.pickBy(list2,v=>v.a>2)));
    
    // 9、找到数组[ {a: 3, b: 1}, {a: 1, b: 2}, {a: 4, b: 3}, {a: 8, b: 4} ]中第一个大于3的b的值。
    let list3 = [ {a: 3, b: 1}, {a: 1, b: 2}, {a: 4, b: 3}, {a: 8, b: 4} ]
    console.log("9、找到数组[ {a: 3, b: 1}, {a: 1, b: 2}, {a: 4, b: 3}, {a: 8, b: 4} ]中第一个大于3的b的值-----",_.find(list3,item=>item.b>3));
    
    // 10、找到数组[ {a: 3, b: 1}, {a: 1, b: 2}, {a: 4, b: 3}, {a: 8, b: 4} ]中最后一个大于3的b的值。
    console.log("10、找到数组[ {a: 3, b: 1}, {a: 1, b: 2}, {a: 4, b: 3}, {a: 8, b: 4} ]中最后一个大于3的b的值-----",_.findLast(list3,item=>item.b>3));

    // 11、将数组[ {a: 3}, {a: 1}, {a: 4}, {a: 8} ]按照a排序
    console.log("11、将数组[ {a: 3}, {a: 1}, {a: 4}, {a: 8} ]按照a排序-----",_.orderBy(list2,"a","desc"));

    // 12、判断数组[ {a: 3}, {a: 1}, {a: 4}, {a: 8} ]中是否所有的a都大于0
    console.log("12、判断数组[ {a: 3}, {a: 1}, {a: 4}, {a: 8} ]中是否所有的a都大于0-----",_.every(list2,item=>item.a>0));

    // 13、判断数组[ {a: 3}, {a: 1}, {a: 4}, {a: 8} ]中是否有a大于10
    console.log("13、判断数组[ {a: 3}, {a: 1}, {a: 4}, {a: 8} ]中是否有a大于10-----",_.some(list2,item=>item.a>10));

    // 14、判断数组[ 1, 4, 5, 1, 2, 4, 5 ]是否包含3
    let list4 = [ 1, 4, 5, 1, 2, 4, 5 ]
    console.log("14、判断数组[ 1, 4, 5, 1, 2, 4, 5 ]是否包含3-----",_.includes(list4,3));

    // 15、将数组[ 1, 4, 5, 1, 2, 4, 5 ]去重
    console.log("15、将数组[ 1, 4, 5, 1, 2, 4, 5 ]去重-----",_.uniq(list4));

    // 16、将数组[ {a: 1}, {a: 4}, {a: 1} ]按照a的值去重
    let list5=[ {a: 1}, {a: 4}, {a: 1} ]
    console.log("16、将数组[ {a: 1}, {a: 4}, {a: 1} ]按照a的值去重-----",_.uniqBy(list5,item=>item.a));

    // 17、生成一个数组 [1,2,3,4,5]
    console.log("17、生成一个数组 [1,2,3,4,5]-----",_.range(1,6));

    // 18、生成一个数组 [2,4,6,8,10]
    console.log("18、生成一个数组 [2,4,6,8,10]-----",_.range(2,11,2));

    // 19、提取对象{ a: 1, b: 2, c: 3, d: 4 }中a和c得到 { a: 1, c: 3}
    console.log("19、提取对象{ a: 1, b: 2, c: 3, d: 4 }中a和c得到 { a: 1, c: 3}-----",_.pick(obj,"a","c"));

    // 20、将数组[ {a: 1}, {b: 4}, {c: 2} ]得到数组[1,4,2]
    let list6=[{a: 1}, {b: 4}, {c: 2}]
    console.log("20、将数组[ {a: 1}, {b: 4}, {c: 2} ]得到数组[1,4,2]-----",_.map(list6,item=>_.values(item)[0]));

    // 21、求数组[1,3,5]和[2,3,4,1]的交集
    console.log("21、求数组[1,3,5]和[2,3,4,1]的交集-----",_.intersection([1,3,5],[2,3,4,1]));

    // 22、求数组[1,3,5]和[2,3,4,1]的并集
    console.log("22、求数组[1,3,5]和[2,3,4,1]的并集-----",_.union([1,3,5],[2,3,4,1]));

    // 23、求数组[1,3,5,6]中,在数组[2,3,4,1]不存在的元素组成的数组。
    console.log("23、求数组[1,3,5,6]中,在数组[2,3,4,1]不存在的元素组成的数组-----",_.difference([1,3,5,6],[2,3,4,1]));

    // 24、将对象{b:{a:{c:1}}}深拷贝
    let obj1 = {b:{a:{c:1}}}
    console.log("24、将对象{b:{a:{c:1}}}深拷贝-----",_.cloneDeep(obj1));

    // 25、写一个中奖概率为10%的算法
      // 生成0-9之间的随机整数
      let probability = _.random(0, 9);
      if (probability ===0 ) {
        console.log("我的中奖了概率为10%");
      } else {
        console.log("没中奖的概率为90%");
      }
    
  </script>
</body>
</html>
  • 19
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值