Vue学习笔记

Vue(发音为 /vjuː/,类似 view) 是一款用于构建用户界面的 JavaScript 框架,vue两大特点:

  • 声明式渲染,基于标准HTML拓展一套模板语法,声明式描述最终HTML和JS的关系
  • 响应式,自动跟踪JS状态变化并响应式更新DOM

此时不学 更待何时,不想毕业就失业的我 (っ °Д °;) っ

学习路线

  1. 跟着黑马程序员b站课程完整学了一遍,边学边在typora做笔记,课程里知识点不敲代码,只做笔记,对于课程里的DEMO全部手敲,现已部署至 demo.ychch.top
  2. 跟着vue官方文档从头到尾整理一遍知识点,在这个过程中对笔记做查漏补缺
  3. 在github上面找项目完成

Vue 基础

初识 Vue

概念

  1. 构建用户界面
    • 用 vue 往 html 页面中填充数据
  2. 框架
    • 框架是一套现成的解决方案,遵循框架的规范去写代码
    • vue 的指令、组件(对 UI 结构的复用)、路由、Vuex

vue 的特性

  1. 数据驱动视图

    • 单向的数据绑定
    • vue监听数据变化,自动渲染页面结构
  2. 双向数据绑定

    在页面中,form表单负责采集数据,Ajax 负责提交数据

    • js 数据的变化,会被自动渲染到页面上
    • 页面上表单采集的数据发生变化的时候,会被 vue 自动获取到,并更新到 js 数据中

数据驱动视图 和 双向数据绑定 的底层原理是MVVM(Model 数据源、View 视图、ViewModel 就是vue的实例)

一个vue应用

应用实例和根组件

每个 Vue 应用都是通过 createApp 函数创建一个新的 应用实例

传入 createApp 的对象实际上是一个根组件,其他组件将作为其子组件。

1
2
3
4
5
import { createApp } from 'vue'
// 从一个单文件组件中导入根组件
import App from './App.vue'

const app = createApp(App)

大多数真实的应用都是由一棵嵌套的、可重用的组件树组成的。例如,一个待办事项 (Todos) 应用的组件树可能是这样的

1
2
3
4
5
6
7
8
App (root component)
├─ TodoList
│ └─ TodoItem
│ ├─ TodoDeleteButton
│ └─ TodoEditButton
└─ TodoFooter
├─ TodoClearButton
└─ TodoStatistics

挂载应用

应用实例必须在调用了 .mount() 方法后才会渲染出来,.mount() 方法应该始终在整个应用配置和资源注册完成后被调用。

1
app.mount('#app')

应用配置

应用实例会暴露一个 .config 对象允许我们配置一些应用级的选项,例如定义一个应用级的错误处理器,它将捕获所有由子组件上抛而未被处理的错误:

1
2
3
app.config.errorHandler = (err) => {
/* 处理错误 */
}

多个应用实例

reateApp API 允许你在同一个页面中创建多个共存的 Vue 应用,而且每个应用都拥有自己的用于配置和全局资源的作用域。

如果使用 Vue 来增强服务端渲染 HTML,并且要 Vue 去控制一个大型页面中特殊的一小部分,应避免将一个单独的 Vue 应用实例挂载到整个页面上,而是应该创建多个小的应用实例,将它们分别挂载到所需的元素上去。

模板语法

指令是模板语法,用于辅助开发者渲染页面的基本结构。

v- 作为前缀,表明它们是一些由 Vue 提供的特殊 attribute,将为渲染的 DOM 应用特殊的响应式行为。

Vue 在组件实例上暴露的内置 API 使用 $ 作为前缀,它也为内部属性保留 _ 前缀。

JS表达式的适用情况

  • 在文本插值中
  • 在任何 Vue 指令 (以 v- 开头的特殊 attribute) attribute 的值中
1
2
3
4
5
{{ number + 1}}

{{ ok ? 'yes' : 'no' }}

<div :id=" 'list-' + id"> </div>

内容渲染指令

  • {{ }},“Mustache”语法 ,插值表达式,内容占位符
  • v-html,可以将包含标签的字符串,渲染成真正的HTML内容

在网站上动态渲染任意 HTML 是非常危险的,这非常容易造成 XSS 漏洞。仅在内容安全可信时再使用 v-html

属性绑定指令

v-bind: 指令,为元素的属性动态绑定值,可以简写为 :

在指令参数上也可以使用一个 JavaScript 表达式,需要包含在方括号内

1
2
3
//举例来说,如果你的组件实例有一个数据属性 attributeName,其值为 "href"
// 那么这个绑定就等价于 v-bind:href
<a :[attributeName]="url"> ... </a>

事件绑定指令

v-on: 可以被简写为 @ 使用方法: @事件名称 = “事件处理函数 (参数)”

事件处理函数 在Vue实例下的 methods 中定义,this指的就是 创建的Vue实例对象

vue 内置变量 $event,它就是原生DOM 事件对象e,如果默认事件对象e被覆盖了,则手动传递$event

  • Vue 自动为 methods 绑定了永远指向组件实例的 this
  • 不应该在定义 methods 时使用箭头函数,因为箭头函数没有自己的 this 上下文。
1
2
3
4
5
6
7
8
<button @click="add(2, $event)">+1</button>
...
methods: {
add(step, e) {
e.target.style.backgroundColor = 'red'
this.count += step
}
}

事件修饰符

在事件处理函数中调用 event.preventDafault() 或 event.stopPropagation()是常见的需求,因此,vue提供了事件修饰符来辅助对事件触发进行控制,常用命令有:

  • .prevent ,阻止默认行为(如网页跳转、表单提交)
  • .stop,阻止事件冒泡
  • .capture,捕获模式触发当前的事件处理函数
  • .once,只触发一次
  • .self,只有在event.target 是当前元素自身时触发事件处理函数

按键修饰符

监听键盘,.enter .tab .delete .esc .space .up .down .ctrl .alt….很多

1
2
3
4
5
6
7
<input @keyup.enter='submit'> 
<input @keyup.esc='clearinput'>

methods{
submit(){},
clearinput(){}
}

v-model交互绑定指令

只有表单数据能与用户产生交互,故表单数据使用v-model才有意义,适用于input、textarea、select

为了方便对用户输入的内容进行处理,v-model提供了3个修饰符,分别是

修饰符 作用 示例
.number 将用户输入转为数值类型 <input v-model.number="age" />
.trim 删除输入的首尾空白字符 <input v-model.trim="msg">
.lazy 当失去焦点时,才更新数据,类似防抖 <input v-model.lazy="msg">

条件渲染指令

按需控制DOM的显示与隐藏,两者的显示效果一样,但仍有区别

可以使用 v-elsev-if 添加一个“else 区块”

实现原理不同:

  • v-if 通过创建或删除 DOM 元素来控制元素的显示与隐藏
  • v-show 通过添加或删除元素的 style="display: none" 样式来控制元素的显示与隐藏

性能消耗不同:

  • v-if 切换开销更高,如果运行时条件很少改变,使用 v-if 更好
  • v-show 初始渲染开销更高,如果切换频繁,使用 v-show 更好

列表渲染指令

v-for 基于一个数组来循环渲染相似的UI结构

v-for 的使用需要 item in items 的特殊语法,可选的第二个参数表示属性名或者索引,即(item , index/index ) in items,可选的第三个参数表示位置索引(value,key, index ) in items

使用key维护列表状态

  • 当列表数据变化时,vue复用已存在的DOM元素,从而提升渲染的性能,但这种默认性能优化策略,导致有状态的列表无法被正确更新
  • 为了让vue跟踪每个节点的身份,保证有状态的列表被正确更新的前提下,提升渲染性能,需要为每项提供一个唯一的key属性

key 的注意事项:

  • key 的值只能是字符串数字类型
  • key 的值必须具有唯一性(即:key 的值不能重复)
  • 建议把数据项 id 属性的值作为 key 的值(因为 id 属性的值具有唯一性)
  • 使用 index 的值当作 key 的值没有意义(因为 index 的值不具有唯一性)
  • 建议使用 v-for 指令时一定要指定 key 的值(既提升性能、又防止列表状态紊乱)

数组变化侦测:

Vue 能够侦听响应式数组的变更方法,对原数组进行变更,包括push()pop()shift()unshift()splice()sort()reverse()

filter()concat()slice()这些方法为不可变方法,即不更改原数组,而是返回一个新数组

过滤器 (3版本已废弃)

过滤器本质就是JavaScript函数

  • filters常用于文本的格式化,被添加在JavaScript表达式的尾部,由“管道符 | ”进行调用,用于插值表达式和v-bind属性绑定上。
  • 过滤器只在 vue 2.xvue 1.x 中支持,vue 3.x 废弃了过滤器,官方建议使用计算属性或方法代替过滤器。

过滤器分为私有过滤器和全局过滤器,私有在实例vue对象里通过filters来定义,全局在main.js中定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 定义私有过滤器
const vm = new Vue({
el:'#app',
data: {}
filters: {
capitalize(str) {
return str.charAt(0).toUpperCase()
}
})

// 定义全局过滤器
Vue.filter('capitalize', (str) => {
return str.charAt(0).toUpperCase() + str.slice(1)
})

如果私有过滤器和全局过滤器冲突,按照就近原则调用私有过滤器

连续调用多个过滤器

过滤器从左往右调用,前一个过滤器的结果交给下一个过滤器继续处理。

1
<p>{{ text | capitalize | maxLength }}</p>

组件基础

单页面应用程序

single page application SPA,所有的功能与交互都在这唯一的页面中进行

特点

  • 仅在web页面初始化时加载相应的资源
  • SPA 进行页面重加载或跳转,而利用JavaScript动态变化HTML内容

优缺点

优点 缺点
1. 良好交互体验
2. 良好的前后端工作分离模式
3. 减轻服务器压力
1. 首屏加载慢
2. 不利于SEO

首屏加载慢处理办法

  • 路由懒加载
  • 代码压缩
  • CDN加速
  • 网络传输压缩

不利于SEO处理办法

  • SSR 服务器端渲染

vite目录结构及运行流程

通过main.js 把 App.vue 渲染到 index.html 的指定区域中

  • App.vue,待渲染的模板结构
  • index.html,需要预留el区域
  • main.js,把 App.vue 渲染至预留区

vue 组件

Vue 组件即单独的一个 .vue 文件,简称为 SFC

  • template,组件模板结构,必选

  • script节点,组件的JavaScript行为

    • name节点,说明该组建的名称
    • data节点,渲染期间需要用到的数据,不能直接定义,需要作为一个函数return出去
  • style节点,组件样式 (scoped防止样式冲突,lang指定css语法)

    • <style>标签的lang属性默认是css,表示支持普通的css语法,可选值还有less、scss等

组件的注册使用

  • 组件引用原则:先注册后使用
  • 组件注册 分为 全局注册 和 局部注册
    • 全局注册,在mainjs中 使用app.component() 方法注册,直接以标签的形式进行使用
    • 局部注册,在import导入,再使用component节点的键值对形式,后以标签的形式进行使用
  • 定义组件注册名称的方式只有两种
    • 短横线命名,my-search
    • 大驼峰命名,MySearch

组件的生命周期

在实际开发中,created是最常用的生命周期函数!

mounted 和 updated

mounted 只在元素第一次插入DOM时被调用,当DOM更新时mounted 不会被触发;updated函数会在每次DOM更新完成后被调用

vue2,mounted被称为bind,updated被称为 update

如果mounted 和updated函数任务逻辑完全相同,可以简写为

1
2
3
app.directive('focus',(el)=>{
el.focus()
})

scoped解决组件间样式冲突

默认情况下,vue组件下的样式会全局生效,容易导致多个组件间的样式冲突问题,其原因是:

  • 单页面程序中,所有组件的DOM结构,都是基于唯一的html页面呈现的
  • 每个组件的样式,都会影响唯一页面的DOM元素

style节点的 **scoped **属性,为每个组件分配唯一的自定义属性,从而防止组件之间的样式冲突问题。

但如果想让某些样式对子组件生效,可以使用 深度选择器 :deep()

class与style绑定

数据绑定的一个常见需求场景是操纵元素的 CSS class 列表和内联样式。

classstyle 都是 attribute,我们可以和其他 attribute 一样使用 v-bind 将它们和动态的字符串绑定。

1
2
3
4
5
<h3 :class="isItalic ? 'italic' : ''"></h3>

<h3 :class="[isItalic ? 'italic':'','isDelete' ? 'delete':'']"</h3>

<h3 :style="{color:color,fontSize:fsize}"></h3>

props属性

props是组件的自定义属性,使用者可以通过props把数据传递到子组件内部供其使用

props的使用

  1. 在子组件中定义声明
1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div>
<h3>title:{{title}}</h3>
<h3>author:{{author}}</h3>
</div>
</template>

<script>
export default {
name:'MyArticle'
props:['title','author']
}
</script>
  1. 在父组件中导入引用
1
2
3
4
5
6
7
8
9
<MyArticle title="个人博客" author="cyc"></article> 

<script>
import MyArticle from './Article.vue'
export default {
name:'MyApp'
component:{ MyArticle }
}
</script>

props作用:父组件通过props向子组件传递要展示的数据,提供组件的复用性。

props校验规则

使用对象类型的props节点,可以对外界传递的props数据进行合法性校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script>
export default {
name:'MyArticle'
props:{
//8种类型:String、Number、Boolean、Array、Object、Date、Function、Symbol
type: Number,
//多种数据类型 用列表
title: [String,Number],
//必填项校验
propA:{
type:String,
required:true
}
//指定默认值
propA:{
type:String,
default:100
}
}
}
</script>

计算属性

计算属性本质就是function,监听数据,返回新值,以function形式声明到组件的computed选项中

计算属性值会基于其响应式依赖被缓存,仅会在其响应式依赖更新时才重新计算。

即计算属性会缓存计算的结果,性能会更好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<p>{{ count }}乘以2的值为{{ plus }}</p>

<script>
export default {
name:'MyArticle',
data(){
return { count : 1}
},
computed:{
plus(){
return this.count*2
}
}
}
</script>

自定义事件(子传父)

在封装组件时,先声明再触发;在使用组件时,监听自定义事件

  1. 声明自定义,在emits节点中声明

  2. 触发自定义,通过this.$emit( ‘自定义事件的名称’, 第二个参数向外传参 ) 方法进行触发

  3. 监听自定义,通过 v-on 监听自定义事件

1
2
3
4
5
6
7
8
<script>
export default {
emits:['change'],
methods:{
this.$emits('change', this.count)
}
}
</script>

组件上的v-model

需要维护内外组件数据同步时,可以使用v-model指令

v-model不适用于双向绑定至props值,props值是只读的,需要的话,可以把props值转存到data中

**父传子 **v-bind + props

  • 父组件通过-v-bind: 属性绑定的形式,把数据传递给子组件

  • 子组件中,通过 props 接受父组件传递过来的数据

子传父

  • v-bind: 指令前添加v-model指令
  • 在子组件中声明 emits 自定义事件,格式为 update:xxx
  • 调用 $emit( update:xxx ) 触发自定义事件,更新父组件中的数据

watch侦听器

监听数据变化,针对数据变化做特定操作,在watch节点下定义,将变量名直接当成方法来调用

  • 深层侦听:当watch侦听的是一个对象,如果对象中的属性值发生了变化,此时需要加上deep选项才可被监听

  • 监听单个属性:只想监听对象中单个属性变化,则可以 'info.username':{ }

  • 创建即调用:watch默认懒执行,不侦听初始数据,可以通过handler 方法和 immediate: true 选项 强制回调函数立即执行

  • this.$watch() ,可以命令式创建侦听器

计算属性vs侦听器,侧重的应用场景不同:

  • 计算属性:监听多个值变化,计算后返回新值
  • 侦听器:监听单个值变化,执行特定业务,不需要有返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script>
export default {
data(){
return {
username:''
info{ name: ''}
}
},
watch:{
username(newVal,oldVal){},
info:{
asyc handler(newVal,oldVal){},
deep:true
},
},
}
</script>

组件之间的数据共享

父子共享

父传子,父通过v-bind向子共享,子需要props接受数据

子传父,子通过自定义事件向父共享

父子之间的双向同步,使用 v-model 指令维护组件内外数据的双向同步

兄弟共享

EventBus,借助第三方包mitt来创建eventBus对象

后代关系组件间的数据共享

后代关系,指的是 父组件向其子孙组件共享数据,使用 provide 和 inject 实现。

  • 父组件通过 provide节点 向子孙共享数据
  • 子孙组件使用 inject 数组 接受数据

如果要传递响应式的数据,即 父变 子孙变

  • 父节点要结合computed函数共享
  • 子孙组件使用时 要加 .value

vuex

有了 vuex,以上组件共享 都不用学了……

image-20220831213547125

axios数据请求

axios,用于数据请求,几乎每个组件都会用到,一般可以在全局配置好。在main.js 入口文件中,通过app.config.globalProperties全局挂在 axios

如下图,1,2是配置 ,3 是如何使用

ref 访问DOM元素

ref 可以按需直接访问底层 DOM 元素

1
2
3
4
5
6
7
<h3 ref='input'></h2>

methods:{
getRef(){
console.log(this.$refs.input)
}
}

$nextTick方法

$nextTick(cb),会把cb回调推迟到下一个DOM更新周期之后执行。

即等到组件的DOM异步重新完成渲染后,再执行cb回调函数,从而保证回调函数可以操作到最新的DOM元素。

动态组件 与 keep alive

动态组件:动态切换组件的显示与隐藏

keep alive:保持组件不被销毁

1
2
3
<keep-alive>
<component is='要渲染的组件名称'> </component>
</keep-alive>

插槽

插槽slot,组件封装期间,为用户预留的内容占位符

使用场景:在封装组件的时候,如果不确定组件的DOM渲染成什么样子,同时需要把数据交给用户,可以通过作用域插槽传给用户,从而提高组件复用率!

后备内容

这是后备内容,如果没有为插槽提供任何内容,则后备内容会生效

具名插槽

如果要预留多个插槽,则为每个指定具体的name名称

没有指定name名称的插槽,默认名称为 “default”

引用时需要外包裹 <template v-slot:“插槽名”>

v-slot: 可以简写为 #

作用域插槽

可以为slot插槽绑定 props数据,即作用域插槽,搭配 解构赋值使用

1
2
3
4
5
6
7
8
9
10
11
12
//定义 组件
<template>
<slot name = "header" :info=‘’>这是后备内容</slot>
<slot name = "footer">这是后备内容</slot>
</template>

//引用组件
<zujian>
<template v-slot:header='{info}'>
<p>1111</p>
</template>
</zujian>

自定义指令

除了v-for、v-model、v-if等内置指令,还可以自定义指令,分为私有和全局两种

声明 不需要加 v-,使用 需要加 v-

在绑定指令时,可以通过 “等号” 形式为指令绑定具体参数值

引用时,(el,binding) el指该组件,binding.value为指令绑定值

  • 私有自定义指令,在directives节点下声明
  • 全局自定义指令,需要在 SPA 实例对象 main.js 里声明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 私有
directives:{
focus:{
// mounted:当被绑定元素被渲染时,自动触发mounted函数
mounted(el){
//focus 自动获取焦点
el.focus()
}
}
}
// 全局
const app = createApp(App)
app.directive('focus',{
......
})

路由

路由的本质就是 对应关系

  • 前端路由:Hash地址与组件之间的对应关系
  • 后端路由:请求方式、请求地址与function处理函数之间的对应关系

SPA项目中,不同组件内的切换通过 前端路由 来实现!

前端路由的工作方式

  1. 用户点击页面上的路由链接
  2. URL地址栏中的Hash值发生变化
  3. 前端路由监听Hash地址的变化
  4. 前端路由把当前 Hash 地址对应的组件渲染到浏览器中

vue-router

  • vue-router 3.x 只能结合 vue2 进行使用
  • vue-router 4.x 只能结合 vue3 进行使用
  • 使用步骤
    • 安装并定义路由组件 npm i vue-router@next -S
    • 声明路由链接 <router-link to='home'> 和路由占位符 <router-view>
    • 创建路由模块,导入并挂载
      • 从 vue-router 中按需导入两个方法
      • 导入所需路由控制的组件
      • 创建路由示例对象并向外共享
      • 在main.js中导入并挂载 app.use()

路由重定向

用户访问地址A时,强制用户跳转地址C,通过redirect属性来指定

被激活的路由链接

被激活的路由链接,默认应用一个 router-link-active 类名,可以用此类明选择器,来设置不同的样式

也可以基于 linkActiveClass属性,自定义被激活路由链接所应用的类名

嵌套路由

组件的嵌套展示

  1. 声明 子路由链接和子路由占位符
  2. 在父路由规则中,通过children属性嵌套声明子路由规则
  3. 子路由的hash地址不要以 / 开头
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import {createRouter,createWebHashHistory} from 'vue-router'

const router = createRouter({
history: createWebHashHistory(),
// 默认的router-link-active类名会被覆盖
linkActiveClass:'router-active'
routes:[
{ path:'/',redirect:'/home'},
{ path:'/home',component: Home},
{ path:'/movie',component: Movie},
{ path:'/about',component: About,
//通过 children 属性嵌套子路由规则
children:[
{ path:'tab1',component:Tab1 },
{ path:'tab2',component:Tab2 },
]
},
]
})

export default router

动态路由

把 Hash 地址中可变的部分通过 定义为 参数项,从而提高规则复用性

1
{ path:'/movie/:id', component: Movie }
$route.params 参数对象

通过动态路由匹配渲染出来的组件中,使用$route.params 来访问动态匹配的参数值

1
2
3
4
5
{ path:'/movie/:id', component: Movie}

<template>
<h3> {{ $route.params.id }} </h3>
</template>
使用props接受路由参数

vue-router允许在路由规则中开启props传参

1
2
3
4
5
6
7
8
9
10
11
{ path:'/movie/:id', component: Movie, props:true}

<template>
<h3> {{ id }} </h3>
</template>

<script>
export default{
props:['id']
}
</script>

编程式导航

  • 声明式导航:调用API实现导航的方式 eg: location.href
  • 编程式导航:点击链接实现导航的方式 <a> <router-link>

两个常用的API:

  1. this.$router.push(‘hash地址’),跳转到指定hash地址,展示对应组件
  2. this.$router.go(数值N),实现浏览历史的前进和后退
1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<button @click='gotoMovie(3)'> go to movie</h3>
</template>

<script>
export default{
methods:{
gotoMovie(){
this.$router.push('/movie/${id}')
}
}
}
</script>

命名路由

通过name属性为路由规则定义名 称的方式,叫做命名路由

1
{ path:'/movie/:id',name:'mov', component: Movie }
使用命名路由实现声明式导航

name属性指定跳转路由规则,params属性指定携带参数

1
<router-link :to="{name:'mov',params:{id:3}}"></router-link>
使用命名路由实现编程式导航

调用push函数指定配置对象

导航守卫

控制路由的访问权限

  • 全局导航守卫,接受三个形参(to, from, next)
    • to,往哪去
    • from,从哪来
    • next,放行方法。如果不声明next形参,默认允许访问每一个路由;如果声明,必须调用next(),否则不允许访问任何一个路由。
    • 直接放行:next() ; 强制停留当前页面:next(false) ; 强制跳转登录页面:next(‘/login’)
1
2
3
4
5
6
7
8
9
10
const router = createRouter({......})
// fn是一个函数,每次拦截到路由请求前,都会调用fn函数
router.beforeEach((to,from,next)=>{
const token = localStorage.getItem('token')
if(to.path === '/main' && !token){
next('/login')
} else{
next()
}
})

vue组件库

前端开发者将自己封装的vue组件整理打包并发布为npm包,被称为vue组件库

vue组件库与bootstrap区别

  • bootstrap:提供原材料(css、html结构、js特效)
  • vue组件库:遵循vue语法、高度定制的现成组件

常用组件库

  1. pc端
  2. 移动端

项目引入

  • 完整引入
  • 按需引入,在官方文档中自己看,不再赘述
1
2
3
4
5
6
// main.js文件中
import Vue from 'vue'
import ElementUI from 'element-Ui'
import 'element-ui/lib/theme-chalk/index.css'

Vue.use(ElementUI)

proxy跨域代理

如果项目运行地址和后端接口地址 存在着跨域问题,则不能正常访问到数据

两种解决办法:

  • 后端接口 开启CORS跨域资源共享
  • 前端通过代理解决接口的跨域问题

参考资料