一、slot是什么
解释
编译作用域
分类
默认插槽
具名插槽(子组件定义 多个 对应插入内容)
作用域插槽
二、Observable 是什么
三、Key是什么
使用场景
四、Keep-alive 是什么
使用场景
缓存后如何获取数据
beforeRouteEnter:每次组件渲染的时候,都会执行beforeRouteEnter
actived:在keep-alive缓存的组件被激活的时候,执行actived钩子
五、Vue中的修饰符
表单修饰符
事件修饰符
鼠标按钮修饰符
键盘修饰符
v-bind修饰符
六、自定义指令
自定义指令的案例
七、过滤器filter
八、虚拟DOM
什么是虚拟DOM
为什么需要虚拟DOM
如何实现虚拟DOM
九、diff算法
十、Vue中的axios
Axios特性
如何使用Axios
如何封装Axios
如何实现Axios
十一、SSR
什么是SSR
SSR发展历程
SSR解决了什么
使用SSR同样存在以下缺点:
十二、vue项目的目录结构
单页面目录结构
多页面目录结构
十三、vue怎么做权限管理,控制到按钮级别的权限怎么做
如何权限控制
十四、如何解决跨域
CORS
Proxy
十五、vue项目本地开发完成后部署到服务器后报404
为什么history模式下有问题
为什么hash模式下没有问题
解决方案
十六、处理vue项目中的错误
后端接口错误
代码逻辑问题
生命周期钩子
slot (插槽) 是 Vue 的内容分发机制,组件内部的模板引擎使用 slot 元素作为承载分发内容的出口 (即Web组件内的一个占位符)。通俗来说,solt
在组件模板中占好位置,当使用该组件标签时,组件标签里的内容就会自动填坑(替换组件模板中slot
位置)。插槽 slot 是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示由父组件决定。
slot 能够在父组件中添加内容,并填充到到子组件的‘坑位’中。也可以添加父组件内任意的data值:
//父组件:(引用子组件 childbutton)
// 填充内容
{{parent}}
new Vue({
el:'.app',
data:{
parent:'父组件'
}
})
但是如果在父组件中使用子组件内的数据,是不成立的,因为父级模板里的所有内容是在父级作用域中编译,子模板里的所有内容是在子作用域中编译:
// 子组件 : (名为:childbutton)
new Vue({
el:'.button',
data:{
child:'子组件'
}
})
// 父组件:(引用子组件 childbutton)
{{child}}
slot
分为:默认插槽、具名插槽、作用域插槽
如果在父组件中的
//子组件
默认内容 // slot这个位置就是父组件添加内容的显示位置
//父组件
注:如果在父组件没有填充内容之前,就会显示在slot标签内设定的默认内容
在有多个
//子组件
1
2
3
父组件通过 'v-slot : name' 的方式添加内容:
//父组件
这是插入到one插槽的内容
这是插入到two插槽的内容
这是插入到three插槽的内容
针对编译作用域中的问题,父组件可以添加内容并显示在子组件的插槽中,但反之不成功,可以通过作用域插槽解决,即 v-slot:
//子组件
这就是默认值1 // 绑定child1的数据
这就是默认值2 // 绑定child2的数据,这里没有命名slot
new Vue({
el:'.button',
data:{
child1:'数据1',
child2:'数据2'
}
})
//父组件
// 通过v-slot,将插槽one的值赋值给svalue1
{{svalue1.value1}}
// 通过v-slot,将插槽2的值赋值给svalue2,由于子组件没有给slot命名,默认值为default
{{ svalue2.value2 }}
Vue.observable
让一个对象变成响应式数据。Vue
内部会用它来处理 data
函数返回的对象。返回的对象可以直接用于渲染函数和计算属性内,并且会在发生变更时触发相应的更新,也可以作为最小化的跨组件状态存储器
在 Vue 2.x
中,被传入的对象会直接被 Vue.observable
变更,它和被返回的对象是同一个对象
在 Vue 3.x
中,则会返回一个可响应的代理,而对源对象直接进行变更仍然是不可响应的
Vue实现了虚拟DOM,可以不直接操作DOM元素,只操作数据便可以重新渲染页面,其中涉及到Diff算法,其核心是基于两个简单的假设:
1.两个相同的组件产生类似的DOM结构,不同的组件产生不同的DOM结构
。
2.同一层级的一组节点,他们可以通过唯一的id
进行区分。
也就是说key的存在主要是为了高效的更新虚拟DOM
假设 nums 为 [1, 2, 3, 5, 9]
{{num}}
这种情况下应当是渲染了5个
原先内容为1的
在这种情况下,Vue会通过改变原来元素的内容和增加/减少元素来完成这个改变,因为没有key属性,Vue无法跟踪每个节点,只能通过这样的方法来完成变更。
1.在使用v-for
时,给单元加上 key: index
index 对应了数组中每个元素的索引,使得每个元素的key值都不同
此时如果nums从[1, 2, 3, 5, 9] 变成 [0, 1, 2, 3, 5, 9],渲染输出的更新步骤:
新增一个
添加 key 属性之后,Vue会记住数组内各个元素的顺序,并根据这个顺序在适当的位置插入/删除元素,来完成更新,这种方法就比地复用策略效率更高。
2.+new Date()
生成的时间戳作为key
用+new Date()
生成的时间戳作为key
,手动强制触发重新渲染。当拥有新值的rerender作为key时,拥有了新key的Comp出现了,那么旧key Comp会被移除,新key Comp触发渲染
keep-alive
是vue
中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM
keep-alive
可以设置以下props
属性:
include
- 字符串或正则表达式(只有名称匹配的组件会被缓存)
exclude
- 字符串或正则表达式(任何名称匹配的组件都不会被缓存)
max
- 数字(最多可以缓存多少组件实例)
匹配首先检查组件自身的 name
选项,如果 name
选项不可用,则匹配它的局部注册名称 (父组件 components
选项的键值),匿名组件不能被匹配
设置了 keep-alive 缓存的组件,会多出两个生命周期钩子(activated
与deactivated
):
首次进入组件时:
beforeRouteEnter
>beforeCreate
>created
>mounted
>activated
> ... ... >beforeRouteLeave
>deactivated
再次进入组件时:
beforeRouteEnter
>activated
> ... ... >beforeRouteLeave
>deactivated
当我们在某些场景下不需要让页面重新加载时,可以使用 keepalive:
首页
–>列表页
–>商详页
–>返回到列表页(需要缓存)
–>返回到首页(需要缓存)
–>再次进入列表页(不需要缓存)
这时可以按需来控制页面的 keep-alive
,在路由中设置 keepAlive
属性判断是否需要缓存
{
path: 'list',
name: 'itemList', // 列表页
component (resolve) {
require(['@/pages/item/list'], resolve)
},
meta: {
keepAlive: true,
title: '列表页'
}
}
// 使用 keep-alive
// 不需要缓存的视图组件
解决方案可以有以下两种:
beforeRouteEnter
actived
beforeRouteEnter
beforeRouteEnter(to, from, next){
next(vm=>{
console.log(vm)
// 每次进入路由执行
vm.getData() // 获取数据
})
},
keep-alive
缓存的组件被激活的时候,执行actived
钩子注:服务器端渲染期间actived
不被调用
activated(){
this.getData() // 获取数据
},
vue
中修饰符分为:表单修饰符、事件修饰符、鼠标按键修饰符、键值修饰符、v-bind修饰符
关于表单的修饰符有:
lazy:当信息填写完成之后,光标离开标签时才会将值赋予给value
{{value}}
trim:自动过滤用户输入的首空格字符,而中间的空格不会过滤
number:自动将用户的输入值转为数值类型,但如果这个值无法被parseFloat
解析,则会返回原来的值
stop:阻止了事件冒泡,相当于调用了event.stopPropagation
方法
//只输出1
prevent:阻止了事件的默认行为,相当于调用了event.preventDefault
方法
self:只当在 event.target
是当前元素自身时触发处理函数
...
使用修饰符时,顺序很重要:
v-on:click.prevent.self
会阻止所有的点击
v-on:click.self.prevent
只会阻止对元素自身的点击
once:绑定了事件只能触发一次
capture:使事件触发从包含这个元素的顶层开始往下触发
obj1
obj2
obj3
obj4
// 输出: 1 2 4 3
passive:在移动端,监听元素滚动事件时,会一直触发onscroll
事件,使用passive 相当于给onscroll
事件配置了.lazy
修饰符
...
不要把
.passive
和.prevent
一起使用因为
.prevent
将会被忽略,同时浏览器可能会展示一个警告
passive
会告诉浏览器你不想阻止事件的默认行为
native:让组件变成像html
内置标签那样监听根元素的原生事件,否则组件上使用 v-on
只会监听自定义事件
使用.native修饰符来操作普通HTML标签会令事件失效
left 左键点击 、right 右键点击 、middle 中键点击
键盘修饰符是用来修饰键盘事件(onkeyup
,onkeydown
)的,keyCode
存在很多,但vue
为我们提供了别名,分为以下两种:
// 只有按键为keyCode的时候才触发
还可以自定义一些全局的键盘码别名
Vue.config.keyCodes.f2 = 123
async:能对props
进行双向绑定
//父组件
//子组件
this.$emit('update:myMessage',params);
使用
async
需要注意:1子组件传递的事件名格式必须为
update:value
,其中value
必须与子组件中props
中声明的名称完全一致2.注意带有
.sync
修饰符的v-bind
不能和表达式一起使用3.将
v-bind.sync
用在一个字面量的对象上,例如v-bind.sync=”{ title: doc.title }”
,是无法正常
props:设置自定义标签属性,避免暴露数据,防止污染HTML结构
camel:将命名变为驼峰命名法,如将view-Box
属性名转换为 viewBox
自定义指令主要是为了重用涉及普通元素的底层 DOM 访问的逻辑。一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。钩子函数会接收到指令所绑定元素作为其参数。
例如:当一个 input 元素被 Vue 插入到 DOM 中后,它会被自动聚焦
const focus = {
mounted: (el) => el.focus()
}
export default {
directives: {
// 在模板中启用 v-focus
focus
}
}
表单防止重复提交(设置节流阀)、图片懒加载 、一键 Copy、拖拽指令、页面水印、权限校验
过滤器实质不改变原始数据,只是对数据进行加工处理后返回过滤后的数据再进行调用处理
Vue
允许自定义过滤器,可被用于一些常见的文本格式化
{{ message | capitalize }}
局部过滤器优先于全局过滤器被调用
一个表达式可以使用多个过滤器
过滤器之间需要用管道符 “|” 隔开,其执行顺序从左往右
是一层对真实DOM
的抽象,以JavaScript
对象 (VNode
节点) 作为基础的树
用对象的属性来描述节点,最终可以通过一系列操作,使这棵树映射到真实环境上
在Javascript
对象中,虚拟 DOM
表现为一个 Object
对象。并且最少包含标签名 (tag
)、属性 (attrs
) 和子元素对象 (children
) 三个属性,不同框架对这三个属性的名命可能会有差别
创建虚拟DOM
就是为了更好将虚拟的节点渲染到页面视图中,所以虚拟DOM
对象的节点与真实DOM
的属性一一照应
用传统的原生api
或jQuery
去操作DOM
时,浏览器会从构建DOM
树开始从头到尾执行一遍流程
例如当需要更新10个DOM
节点,浏览器收到第一个更新DOM
请求后,会马上执行流程,最终执行10次流程。而通过VNode
,同样更新10个DOM
节点,虚拟DOM
不会立即操作DOM
,而是将这10次更新的diff
内容保存到本地的一个js
对象中,最终将这个js
对象一次性attach
到DOM
树上,避免大量的无谓计算
虚拟 DOM 优势之一是 diff 算法,减少 JS 操作真实 DOM 带来的性能消耗。
最大的优势在于抽象了原本的渲染过程,实现了跨平台的能力。
虚拟dom是将dom结构表示成一个对象,当组件的状态发生更改时,新的虚拟dom会与dom进行比较,只将修改的部分替换掉,而不需要重新渲染整个dom。
步骤分为:
1.创建hyperscript function来实现dom渲染
2.创建一个简单的应用程序,使用hyperscript来渲染
3.使用虚拟dom实现动态渲染
4.实现diffing算法
diff
算法是一种通过同层的树节点进行比较的高效算法,整体策略为:深度优先,同层比较
其有两个特点:
1.比较只会在同层级进行, 不会跨层级比较
2.在diff比较的过程中,循环从两边向中间比较
diff
算法在 vue
中作用于虚拟 dom
渲染成真实 dom
的新旧 VNode
节点比较:
如果在比较中,发现了旧节点存在与新节点相同的节点,直接复用当前旧节点作为
diff
后的第一个真实节点,如果没有与当前新节点相同的节点,直接创建新的真实节点。
axios
是支持 Promise
的 HTTP库
。基于 XMLHttpRequest
服务来执行 HTTP
请求(get 请求/post 请求),支持浏览器端和 Node.js
端发送请求。
1.可以在浏览器中发送 XMLHttpRequests
2.可以在 node.js 发送 http 请求
3.支持 Promise API
4.拦截请求和响应
5.转换请求数据和响应数据
6.能够取消请求
7.自动转换 JSON 数据
8.客户端支持保护安全免受 XSRF 攻击
1.安装
// 项目中安装
npm install axios --S
或者引入
// cdn 引入
2.导入
import axios from 'axios'
3.发送请求
axios({
url:'http://www.baidu.com', // 设置请求的地址
method:"GET", // 设置请求方法
params:{ // get请求使用params进行参数凭借,如果是post请求用data
type: '',
page: 1
}
}).then(res => {
// res为后端返回的数据
console.log(res);
})
并发请求axios.all([])
function getUserAccount() {
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
axios.all([getUserAccount(), getUserPermissions()])
.then(axios.spread(function (res1, res2) {
// res1第一个请求的返回的内容,res2第二个请求返回的内容
// 两个请求都执行完成才会执行
}));
在项目中,一般会有类似于设置超时时间、设置请求头、根据项目环境判断使用哪个请求地址、错误处理等重复的操作,为避免冗余,可以在项目中二次封装一下
axios
再使用
设置接口请求前缀:根据开发、测试、生产环境的不同,前缀需要加以区分
利用
node
环境变量来作判断,用来区分开发、测试、生产环境,在本地调试的时候,还需要在vue.config.js
文件中配置devServer
实现代理转发,从而实现跨域
请求头 : 来实现一些具体的业务,必须携带一些参数才可以请求
大部分情况下,请求头都是固定的。当需要特殊请求头时,将特殊请求头作为参数传入,覆盖基础配置
状态码: 根据接口返回的不同status,
来执行不同的业务
请求方法:根据get
、post
等方法进行再次封装
先引入封装好的方法,在要调用的接口重新封装成一个方法暴露出去
// get 请求
export function httpGet({
url,
params = {}
}) {
return new Promise((resolve, reject) => {
axios.get(url, {
params
}).then((res) => {
resolve(res.data)
}).catch(err => {
reject(err)
})
})
}
// post请求
export function httpPost({
url,
data = {},
params = {}
}) {
return new Promise((resolve, reject) => {
axios({
url,
method: 'post',
transformRequest: [function (data) {
let ret = ''
for (let it in data) {
ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
}
return ret
}],
// 发送的数据
data,
// url参数
params
}).then(res => {
resolve(res.data)
})
})
}
把封装的方法放在一个api.js
文件中
import { httpGet, httpPost } from './http'
export const getorglist = (params = {}) => httpGet({ url: 'apps/api/org/list', params })
页面中直接调用
// .vue
import { getorglist } from '@/assets/js/api'
getorglist({ id: 200 }).then(res => {
console.log(res)
})
这样可以把
api
统一管理起来,以后维护修改只需要在api.js
文件操作即可
请求拦截器: 根据请求的请求头设定,来决定哪些请求可以访问
比如:必须密码正确才能登录成功,利用token字符串判断用户身份
响应拦截器: 根据 后端 返回的状态码判定执行不同业务
比如:购物车结算功能必须是处于登录状态时才可以使用,根据登录状态从而判断是否可以访问请求
这里参考一篇大佬的文章:http://t.csdn.cn/D7mit
以get请求为例,实现一个axios
实现ajax的get请求
var Ajax={
get: function(url, fn) {
// XMLHttpRequest对象用于在后台与服务器交换数据
var xhr = new XMLHttpRequest();
// 发起get请求
xhr.open('GET', url, true);
xhr.onreadystatechange = function() {
// readyState == 4说明请求已完成
if (xhr.readyState == 4 && xhr.status == 200) {
// 从服务器获得数据
fn.call(this, xhr.responseText);
}
};
// 发送请求
xhr.send();
}
}
封装Ajax,实现Axios进行回调
var Axios = {
get: function(url) {
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function() {
// readyState == 4说明请求已完成
if (xhr.readyState == 4 && xhr.status == 200) {
// 从服务器获得数据
resolve(xhr.responseText)
}
};
xhr.send();
})
},
}
SSR (Server-Side Rendering) 指由服务器完成页面的 HTML
结构拼接的页面处理技术,发送到浏览器,然后为其绑定状态与事件,成为完全可交互页面的过程
此处参考文献:服务端渲染SSR及实现原理 - 掘金
以下两种情况使用 SSR
需更好的支持 SEO 优势在于同步。搜索引擎爬虫是不会等待异步请求数据结束后再抓取信息的,如果 SEO 对应用程序至关重要,但页面是异步请求数据时
需更快的到达时间 优势在于慢网络和运行缓慢的设备场景。传统 SPA 需完整的 JS 下载完成才可执行,而SSR 服务器渲染标记在服务端渲染 html 后即可显示,用户会更快的看到首屏渲染页面。关注首屏渲染时间转化率时
传统web开发
网页内容在服务端渲染完成,⼀次性传输到浏览器,打开页面查看源码,浏览器拿到的是全部的dom
结构
单页应用SPA
单页应用优秀的用户体验,使其逐渐成为主流,页面内容由JS
渲染出来,这种方式称为客户端渲染,打开页面查看源码,浏览器拿到的仅有宿主元素#app
,并没有内容
服务端渲染SSR
后端渲染出完整的首屏dom
结构返回,前端拿到的内容包括首屏及完整spa
结构,应用激活后依然按照spa
方式运行
Vue SSR
是一个在SPA
上进行改良的服务端渲染通过
Vue SSR
渲染的页面,需要在客户端激活才能实现交互
Vue SSR
将包含服务端渲染的首屏和交互的SPA
seo:搜索引擎优先爬取页面
HTML
结构,使用SSR时,服务端已经生成了和业务相关联的HTML
,有利于seo
首屏呈现渲染:用户无需等待页面所有
js
加载完成就可以看到页面视图,需要权衡哪些用服务端渲染,哪些交给客户端
SSR
同样存在以下缺点:整个项目的复杂度、库的支持性,代码兼容、性能问题
1.每个请求都是
n
个实例的创建,消耗变得很大2.缓存
node serve
、nginx
判断当前用户有没有过期,如果没过期就缓存3.降级:监控
cpu
、内存占用过多,就spa
,返回单个的壳4.相对于前后端分离服务器,只需要提供静态资源,服务器负载更大
使用SSR
前,需要考虑
1.需要SEO
的页面是否数量较少,这些是否可以使用预渲染(Prerender SPA Plugin)实现
2.首屏的请求响应逻辑是否复杂,数据返回是否大量且缓慢
如何实现SSR:参考文献:服务端渲染SSR及实现原理 - 掘金
划分项目结构的时候,需要遵循一些基本的原则
1.文件夹和文件夹内部文件的语义一致性
2.单一入口/出口
3.就近原则,紧耦合的文件应该放到一起,且应以相对路径引用
4.公共的文件应该以绝对路径的方式从根目录引用
5./src
外的文件不应该被引入
project
│ .browserslistrc
│ .env.production
│ .eslintrc.js
│ .gitignore
│ babel.config.js
│ package-lock.json
│ package.json
│ README.md
│ vue.config.js
│ yarn-error.log
│ yarn.lock
│
├─public
│ favicon.ico
│ index.html
│
|-- src
|-- components
|-- input
|-- index.js
|-- index.module.scss
|-- pages
|-- seller
|-- components
|-- input
|-- index.js
|-- index.module.scss
|-- reducer.js
|-- saga.js
|-- index.js
|-- index.module.scss
|-- buyer
|-- index.js
|-- index.js
my-vue-test:.
│ .browserslistrc
│ .env.production
│ .eslintrc.js
│ .gitignore
│ babel.config.js
│ package-lock.json
│ package.json
│ README.md
│ vue.config.js
│ yarn-error.log
│ yarn.lock
│
├─public
│ favicon.ico
│ index.html
│
└─src
├─apis //接口文件根据页面或实例模块化
│ index.js
│ login.js
│
├─components //全局公共组件
│ └─header
│ index.less
│ index.vue
│
├─config //配置(环境变量配置不同passid等)
│ env.js
│ index.js
│
├─contant //常量
│ index.js
│
├─images //图片
│ logo.png
│
├─pages //多页面vue项目,不同的实例
│ ├─index //主实例
│ │ │ index.js
│ │ │ index.vue
│ │ │ main.js
│ │ │ router.js
│ │ │ store.js
│ │ │
│ │ ├─components //业务组件
│ │ └─pages //此实例中的各个路由
│ │ ├─amenu
│ │ │ index.vue
│ │ │
│ │ └─bmenu
│ │ index.vue
│ │
│ └─login //另一个实例
│ index.js
│ index.vue
│ main.js
│
├─scripts //包含各种常用配置,工具函数
│ │ map.js
│ │
│ └─utils
│ helper.js
│
├─store //vuex仓库
│ │ index.js
│ │
│ ├─index
│ │ actions.js
│ │ getters.js
│ │ index.js
│ │ mutation-types.js
│ │ mutations.js
│ │ state.js
│ │
│ └─user
│ actions.js
│ getters.js
│ index.js
│ mutation-types.js
│ mutations.js
│ state.js
│
└─styles //样式统一配置
│ components.less
│
├─animation
│ index.less
│ slide.less
│
├─base
│ index.less
│ style.less
│ var.less
│ widget.less
│
└─common
index.less
reset.less
style.less
transition.less
权限是对特定资源的访问许可,权限控制是确保用户只能访问到被分配的资源
所有的请求发起都触发自前端路由或视图,对触发权限的源头进行控制,最终实现:
路由方面,用户登录后只能看到自己有权访问的导航菜单,也只能访问自己有权访问的路由地址,否则将跳转
4xx
提示页视图方面,用户只能看到自己有权浏览的内容和有权操作的控件
最后再加上请求控制作为最后一道防线,路由可能配置失误,按钮可能忘了加权限,这种时候请求控制可以用来兜底,越权请求将在前端被拦截
1.接口权限控制
一般采用jwt
的形式来验证,没有通过的话一般返回401
,跳转到登录页面重新进行登录,登录成功后拿到token并
存起来,通过axios
请求拦截器进行拦截,每次请求的时候头部携带token
2.路由权限控制
方案一:初始化即挂载全部路由,并且在路由上标记相应的权限信息,每次路由跳转前做校验
这种方式存在以下四种缺点:
加载所有的路由,如果路由很多,而用户并不是所有的路由都有权限访问,对性能会有影响
全局路由守卫里,每次路由跳转都要做权限判断
菜单信息写死在前端,要改个显示文字或权限信息,需要重新编译
菜单跟路由耦合在一起,定义路由的时候还有添加菜单显示标题,图标之类的信息,而且路由不一定作为菜单显示,还要多加字段进行标识
方案二:初始化先挂载不需要权限控制的路由,比如登录页,404等。如果用户通过URL进行强制访问,则会直接进入404,相当于从源头上做了控制。登录后,获取用户的权限信息,然后筛选有权限访问的路由,在全局路由守卫里进行调用addRoutes
添加路由
按需挂载,在用户登录进来时要知道当前用户拥有哪些路由权限
这种方式也存在了以下的缺点:
- 全局路由守卫里,每次路由跳转都要做判断
- 菜单信息写死在前端,要改个显示文字或权限信息,需要重新编译
- 菜单跟路由耦合在一起,定义路由的时候还有添加菜单显示标题,图标之类的信息,而且路由不一定作为菜单显示,还要多加字段进行标识
3.菜单权限控制
菜单权限可以理解成将页面与路由进行解耦
方案一:菜单与路由分离,后端返回菜单,前端定义路由信息
name
字段都不为空,需要根据此字段与后端返回菜单做关联,后端返回的菜单信息中必须要有name
对应的字段,并且做唯一性校验,全局路由守卫做判断。每次路由跳转的时候都要判断权限,菜单的name
与路由的name
是一一对应的,而后端返回的菜单就已经是经过权限过滤的。如果根据路由name
找不到对应的菜单,就表示用户有没权限访问
如果路由很多,可以在应用初始化的时候,只挂载不需要权限控制的路由。取得后端返回的菜单后,根据菜单与路由的对应关系,筛选出可访问的路由,通过addRoutes
动态挂载。
这种方式的缺点:
- 菜单需要与路由做一一对应,前端添加了新功能,需要通过菜单管理功能添加新的菜单,如果菜单配置的不对会导致应用不能正常使用
- 全局路由守卫里,每次路由跳转都要做判断
方案二:菜单和路由都由后端返回,前端统一定义路由组件
在将后端返回路由通过addRoutes
动态挂载之间,需要将数据处理一下,将component
字段换为真正的组件。如果有嵌套路由,后端功能设计时,要注意添加相应的字段,前端拿到数据也要做相应的处理
这种方法也会存在缺点:
- 全局路由守卫里,每次路由跳转都要做判断
- 前后端的配合要求更高
4.按钮权限控制
方案一:按钮权限也可以用v-if
判断
但是如果页面过多,每个页面都要获取用户权限role
和路由表里的meta.btnPermissions
,然后再做判断
方案二:通过自定义指令进行按钮权限的判断
跨域本质是浏览器基于同源策略的一种安全手段。同源策略是一种约定,它是浏览器最核心也最基本的安全功能,所谓同源(即指在同一个域)具有以下三个相同点:
非同源请求是协议、端口、主机其中一项不相同的时候,这时候就会产生跨域
跨域资源共享 是一个系统,它由一系列传输的HTTP头组成,这些HTTP头决定浏览器是否阻止前端 JavaScript 代码获取跨域请求的响应
CORS
实现只需要增加一些 HTTP
头,让服务器能声明允许的访问来源
添加中间件,直接设置Access-Control-Allow-Origin
响应头
app.use(async (ctx, next)=> {
ctx.set('Access-Control-Allow-Origin', '*');
ctx.set('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
if (ctx.method == 'OPTIONS') {
ctx.body = 200;
} else {
await next();
}
})
代理 也称网络代理,是一种特殊的网络服务,允许一个(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接。一些网关、路由器等网络设备具备网络代理功能。一般认为代理服务有利于保障网络终端的隐私或安全,防止攻击
方案一:通过vue-cli
脚手架工具搭建项目,可以通过webpack
起一个本地服务器作为请求的代理对象。通过该服务器转发请求至目标服务器,得到结果再转发给前端,但是最终发布上线时如果web应用和接口服务器不在一起仍会跨域
方案二:通过服务端实现代理请求转发
方案三:通过配置nginx
实现代理
HTTP 404 错误意味着链接指向的资源不存在,vue
项目在本地时运行正常,但部署到服务器中,刷新页面,出现了404错误
Vue
是属于单页应用,而SPA
是一种网络应用程序或网站的模型,所有用户交互是通过动态重写当前页面,不管应用有多少页面,构建物都只会产出一个index.html
nginx
配置
server {
listen 80;
server_name www.xxx.com;
location / {
index /data/dist/index.html;
}
}
可以根据 nginx
配置得出,当在地址栏输入 www.xxx.com
时,这时会打开 dist
目录下的 index.html
文件,然后在跳转路由进入到 www.xxx.com/login
。当在 xxx.com/login
页执行刷新操作,nginx location
是没有相关配置的,所以就会出现 404 的情况
router hash
模式我们都知道是用符号#表示的,如 website.com/#/login
, hash
的值为 #/login
它的特点在于:
hash
虽然出现在URL
中,但不会被包括在HTTP
请求中,对服务端完全没有影响,因此改变hash
不会重新加载页面。hash
模式下,仅hash
符号之前的内容会被包含在请求中,如website.com/#/login
只有website.com
会被包含在请求中 ,因此对于服务端来说,即使没有配置location
,也不会返回404错误
问题的本质是路由是通过JS来执行视图切换的,当进入到子路由时刷新页面,web
容器没有相对应的页面此时会出现404。只需要配置将任意页面都重定向到 index.html
,把路由交由前端处理
对nginx
配置文件.conf
修改,添加try_files $uri $uri/ /index.html;
server {
listen 80;
server_name www.xxx.com;
location / {
index /data/dist/index.html;
try_files $uri $uri/ /index.html;
}
}
修改完配置文件后记得配置的更新
nginx -s reload
但所有路径都会返回 index.html
文件。在 Vue
应用里面覆盖所有的路由情况,然后在给出一个 404 页面
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '*', component: NotFoundComponent }
]
})
错误来源包括:后端接口错误、代码本身逻辑错误
通过axios
的interceptor
实现网络请求的response
先进行一层拦截
apiClient.interceptors.response.use(
response => {
return response;
},
error => {
if (error.response.status == 401) {
router.push({ name: "Login" });
} else {
message.error("出错了");
return Promise.reject(error);
}
}
);
设置全局错误处理函数
Vue.config.errorHandler = function (err, vm, info) {
// handle error
}
errorCaptured
是 2.5.0 新增的一个生命钩子函数,当捕获到一个来自子孙组件的错误时被调用
(err: Error, vm: Component, info: string) => ?boolean
此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。
此钩子可以返回 false
以阻止该错误继续向上传播
参考文献:面试官:SSR解决了什么问题?有做过SSR吗?你是怎么做的? | web前端面试 - 面试官系列