// ts是识别不了以 .vue结尾的文件 比如:import xxx from 'xxx.vue'
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}
// 使用jsx 补充一些类型声明
import Vue, { VNode } from 'vue'
declare global {
namespace JSX {
// tslint:disable no-empty-interface
interface Element extends VNode {}
// tslint:disable no-empty-interface
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: any;
}
}
}
https://cn.vuejs.org/v2/guide/typescript.html#ad
OptionsAPI
如果您在声明组件时更喜欢基于类的 API,则可以使用官方维护的 vue-class-component 装饰器:
import Vue from 'vue'
const Component = Vue.extend({
// 类型推断已启用
data() {
a: 1
},
methods: {
test() {}
}
})
const Component = {
// 这里不会有类型推断,
// 因为 TypeScript 不能确认这是 Vue 组件的选项
}
ClassAPIs
import Vue from 'vue'
import Component from 'vue-class-component'
// @Component 修饰符注明了此类为一个 Vue 组件
@Component({
// 所有的组件选项都可以放在这里
template: ''
})
export default class MyComponent extends Vue {
// 初始数据可以直接声明为实例的 property
message: string = 'Hello!'
// 组件方法也可以直接声明为实例的方法
onClick (): void {
window.alert(this.message)
}
}
项目中需要用到样式变量,为了避免每个组件都去引入,我们可以把样式组件注入到全局。
https://cli.vuejs.org/zh/guide/css.html#css-modules
vue.config.js
// vue.config.js
module.exports = {
css: {
loaderOptions: {
// 默认情况下 `sass` 选项会同时对 `sass` 和 `scss` 语法同时生效
// 因为 `scss` 语法在内部也是由 sass-loader 处理的
// 但是在配置 `prependData` 选项的时候
// `scss` 语法会要求语句结尾必须有分号,`sass` 则要求必须没有分号
// 在这种情况下,我们可以使用 `scss` 选项,对 `scss` 语法进行单独配置
scss: {
prependData: `@import "~@/styles/variables.scss";`
}
}
},
}
try { // 捕获异常
} catch(err) {
console.log(err)
}
https://router.vuejs.org/zh/guide/advanced/meta.html
router.beforeEach((to, from, next) => {
// to.matched 是一个数组(匹配到是路由记录)
if (to.matched.some(record => record.meta.requiresAuth)) { // vue文档
if (!store.state.user) {
// 跳转到登录页面
next({
name: 'login',
query: { // 通过 url 传递查询字符串参数
redirect: to.fullPath // 把登录成功需要返回的页面告诉登录页面
}
})
} else {
next() // 允许通过
}
} else {
next() // 允许通过
}
// // 路由守卫中一定要调用 next,否则页码无法展示
// next()
// if (to.path !== '/login') {
// // 校验登录状态
// }
})
当token失效的时候,会返回登录页,并且路径会拼接redirect参数 值是之前页面路由做了编码
有得项目为了安全,token过期时间设置得比较短,可能几分钟,那为了避免频繁登录,我们该如何处理?
import axios from 'axios'
import store from '@/store'
import { Message } from 'element-ui'
import router from '@/router'
import qs from 'qs'
const request = axios.create({
// 配置选项
// timeout
})
function redirectLogin () {
router.push({
name: 'login',
query: {
redirect: router.currentRoute.fullPath // 返回到原页面
}
})
}
function refreshToken () {
return axios.create()({ // 再次创建一个axios
method: 'POST',
url: '/front/user/refresh_token',
data: qs.stringify({
// refresh_token 只能使用1次
refreshtoken: store.state.user.refresh_token
})
})
}
// 请求拦截器
request.interceptors.request.use(function (config) {
// 我们就在这里通过改写 config 配置信息来实现业务功能的统一处理
const { user } = store.state
if (user && user.access_token) {
config.headers.Authorization = user.access_token
}
// 注意:这里一定要返回 config,否则请求就发不出去了
return config
}, function (error) {
// Do something with request error
return Promise.reject(error)
})
// 响应拦截器
let isRfreshing = false // 控制刷新 token 的状态
let requests: any[] = [] // 存储刷新 token 期间过来的 401 请求
request.interceptors.response.use(function (response) { // 状态码为 2xx 都会进入这里
// console.log('请求响应成功了 => ', response)
// 如果是自定义错误状态码,错误处理就写到这里
return response
}, async function (error) { // 超出 2xx 状态码都都执行这里
// console.log('请求响应失败了 => ', error)
// 如果是使用的 HTTP 状态码,错误处理就写到这里
// console.dir(error)
if (error.response) { // 请求发出去收到响应了,但是状态码超出了 2xx 范围
const { status } = error.response
if (status === 400) {
Message.error('请求参数错误')
} else if (status === 401) {
// token 无效(没有提供 token、token 是无效的、token 过期了)
// 如果有 refresh_token 则尝试使用 refresh_token 获取新的 access_token
if (!store.state.user) {
redirectLogin()
return Promise.reject(error)
}
// 刷新 token
if (!isRfreshing) {
isRfreshing = true // 开启刷新状态
// 尝试刷新获取新的 token
return refreshToken().then(res => {
if (!res.data.success) {
throw new Error('刷新 Token 失败')
}
// 刷新 token 成功了
store.commit('setUser', res.data.content)
// 把 requests 队列中的请求重新发出去
requests.forEach(cb => cb())
// 重置 requests 数组
requests = []
return request(error.config) // 这里很重要,拿到重新获得的token之后,要把失败的请求重新发送出去
}).catch(err => {
console.log(err)
Message.warning('登录已过期,请重新登录')
store.commit('setUser', null)
redirectLogin()
return Promise.reject(error)
}).finally(() => {
isRfreshing = false // 重置刷新状态
})
}
// 刷新状态下,把请求挂起放到 requests 数组中
return new Promise(resolve => {
requests.push(() => {
resolve(request(error.config))
})
})
} else if (status === 403) {
Message.error('没有权限,请联系管理员')
} else if (status === 404) {
Message.error('请求资源不存在')
} else if (status >= 500) {
Message.error('服务端错误,请联系管理员')
}
} else if (error.request) { // 请求发出去没有收到响应
Message.error('请求超时,请刷新重试')
} else { // 在设置请求时发生了一些事情,触发了一个错误
Message.error(`请求失败:${error.message}`)
}
// 把请求失败的错误对象继续抛出,扔给上一个调用者
return Promise.reject(error)
})
export default request
return request(error.config)
error.config,本次失败的请求
如果在token过期的情况下发起多次请求
获取token只会成功一次,第二次会失败,那如何避免调用第二次?
这里 使用到了开关,用了一个flag去判断
怎样把漏掉的请求发出去
数据加载期间 接口禁用
const express = require('express')
const app = express()
const path = require('path')
const { createProxyMiddleware } = require('http-proxy-middleware')
// 托管了 dist 目录,当访问 / 的时候,默认会返回托管目录中的 index.html 文件
app.use(express.static(path.join(__dirname, '../dist')))
app.use('/boss', createProxyMiddleware({
target: 'http://eduboss.lagou.com',
changeOrigin: true
}))
app.use('/front', createProxyMiddleware({
target: 'http://edufront.lagou.com',
changeOrigin: true
}))
app.listen(3000, () => {
console.log('running...')
})