perf: refresh token && delete console

This commit is contained in:
xingyu 2022-08-03 00:41:59 +08:00
parent 2bf09cbfc2
commit 21cf922723
10 changed files with 100 additions and 63 deletions

View File

@ -24,6 +24,6 @@ export const updateUserPwdApi = (oldPassword: string, newPassword: string) => {
} }
// 用户头像上传 // 用户头像上传
export const uploadAvatarApi = (params) => { export const uploadAvatarApi = (data) => {
return request.upload({ url: '/system/user/profile/update-avatar', params }) return request.upload({ url: '/system/user/profile/update-avatar', data: data })
} }

View File

@ -102,7 +102,6 @@ export default {
* @description 刷新 * @description 刷新
* */ * */
const refresh = () => { const refresh = () => {
console.log(instance.value)
if (instance.value.refresh) { if (instance.value.refresh) {
instance.value.refresh() instance.value.refresh()
} }
@ -271,7 +270,6 @@ export default {
-moz-box-sizing: content-box; -moz-box-sizing: content-box;
box-sizing: content-box; box-sizing: content-box;
border: 1px solid #ddd; border: 1px solid #ddd;
-webkit-border-radius: 4px;
} }
.verify-bar-area .verify-move-block { .verify-bar-area .verify-move-block {
@ -284,7 +282,6 @@ export default {
-moz-box-sizing: content-box; -moz-box-sizing: content-box;
box-sizing: content-box; box-sizing: content-box;
box-shadow: 0 0 2px #888888; box-shadow: 0 0 2px #888888;
-webkit-border-radius: 1px;
} }
.verify-bar-area .verify-move-block:hover { .verify-bar-area .verify-move-block:hover {

View File

@ -242,7 +242,6 @@ export default {
// //
var x = e.touches[0].pageX var x = e.touches[0].pageX
} }
console.log(barArea)
startLeft.value = Math.floor(x - barArea.value.getBoundingClientRect().left) startLeft.value = Math.floor(x - barArea.value.getBoundingClientRect().left)
startMoveTime.value = +new Date() // startMoveTime.value = +new Date() //
if (isEnd.value == false) { if (isEnd.value == false) {

View File

@ -1,10 +1,11 @@
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios' import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
import { ElMessage, ElNotification } from 'element-plus' import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
import qs from 'qs' import qs from 'qs'
import { config } from '@/config/axios/config' import { config } from '@/config/axios/config'
import { getAccessToken, getTenantId, removeToken } from '@/utils/auth' import { getAccessToken, getRefreshToken, getTenantId, removeToken, setToken } from '@/utils/auth'
import errorCode from './errorCode' import errorCode from './errorCode'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { resetRouter } from '@/router'
const tenantEnable = import.meta.env.VITE_APP_TENANT_ENABLE const tenantEnable = import.meta.env.VITE_APP_TENANT_ENABLE
const BASE_URL = import.meta.env.VITE_BASE_URL const BASE_URL = import.meta.env.VITE_BASE_URL
@ -20,9 +21,9 @@ const ignoreMsgs = [
export const isRelogin = { show: false } export const isRelogin = { show: false }
// Axios 无感知刷新令牌,参考 https://www.dashingdog.cn/article/11 与 https://segmentfault.com/a/1190000020210980 实现 // Axios 无感知刷新令牌,参考 https://www.dashingdog.cn/article/11 与 https://segmentfault.com/a/1190000020210980 实现
// 请求队列 // 请求队列
// const requestList = [] let requestList: any[] = []
// 是否正在刷新中 // 是否正在刷新中
// const isRefreshToken = false let isRefreshToken = false
export const PATH_URL = base_url[import.meta.env.VITE_API_BASEPATH] export const PATH_URL = base_url[import.meta.env.VITE_API_BASEPATH]
@ -91,6 +92,7 @@ service.interceptors.request.use(
service.interceptors.response.use( service.interceptors.response.use(
async (response: AxiosResponse<Recordable>) => { async (response: AxiosResponse<Recordable>) => {
const { data } = response const { data } = response
console.info(data)
if (!data) { if (!data) {
// 返回“[HTTP]请求没有返回值”; // 返回“[HTTP]请求没有返回值”;
throw new Error() throw new Error()
@ -112,16 +114,38 @@ service.interceptors.response.use(
return Promise.reject(msg) return Promise.reject(msg)
} else if (code === 401) { } else if (code === 401) {
// 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了 // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了
return handleAuthorized() if (!isRefreshToken) {
// if (!isRefreshToken) { isRefreshToken = true
// isRefreshToken = true // 1. 如果获取不到刷新令牌,则只能执行登出操作
// // 1. 如果获取不到刷新令牌,则只能执行登出操作 if (!getRefreshToken()) {
// if (!getRefreshToken()) { return handleAuthorized()
// return handleAuthorized() }
// } // 2. 进行刷新访问令牌
// // 2. 进行刷新访问令牌 try {
// // TODO: 引入refreshToken会循环依赖报错 const refreshTokenRes = await refreshToken()
// } // 2.1 刷新成功,则回放队列的请求 + 当前请求
setToken(refreshTokenRes.data)
requestList.forEach((cb: any) => cb())
return service(response.config)
} catch (e) {
// 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。
// 2.2 刷新失败,只回放队列的请求
requestList.forEach((cb: any) => cb())
// 提示是否要登出。即不回放当前请求!不然会形成递归
return handleAuthorized()
} finally {
requestList = []
isRefreshToken = false
}
} else {
// 添加到队列,等待刷新获取到新的令牌
return new Promise((resolve) => {
requestList.push(() => {
;(config as Recordable).headers.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token 请根据实际情况自行修改
resolve(service(response.config))
})
})
}
} else if (code === 500) { } else if (code === 500) {
ElMessage.error(t('sys.api.errMsg500')) ElMessage.error(t('sys.api.errMsg500'))
return Promise.reject(new Error(msg)) return Promise.reject(new Error(msg))
@ -165,14 +189,32 @@ service.interceptors.response.use(
return Promise.reject(error) return Promise.reject(error)
} }
) )
const refreshToken = async () => {
return await service({
url: '/system/auth/refresh-token?refreshToken=' + getRefreshToken(),
method: 'post'
})
}
const handleAuthorized = () => { const handleAuthorized = () => {
const { t } = useI18n() const { t } = useI18n()
if (!isRelogin.show) { if (!isRelogin.show) {
removeToken()
isRelogin.show = true isRelogin.show = true
ElNotification.error(t('sys.api.timeoutMessage')) ElMessageBox.confirm(t('sys.api.timeoutMessage'), t('common.confirmTitle'), {
confirmButtonText: t('login.relogin'),
cancelButtonText: t('common.cancel'),
type: 'warning'
})
.then(() => {
removeToken()
resetRouter() // 重置静态路由表
isRelogin.show = false
location.href = '/'
})
.catch(() => {
isRelogin.show = false
})
} }
location.href = '/'
return Promise.reject(t('sys.api.timeoutMessage')) return Promise.reject(t('sys.api.timeoutMessage'))
} }
export { service } export { service }

View File

@ -10,6 +10,7 @@ import { createRouter, createWebHashHistory } from 'vue-router'
import { usePermissionStoreWithOut } from '@/store/modules/permission' import { usePermissionStoreWithOut } from '@/store/modules/permission'
import { useDictStoreWithOut } from '@/store/modules/dict' import { useDictStoreWithOut } from '@/store/modules/dict'
import { listSimpleDictDataApi } from '@/api/system/dict/dict.data' import { listSimpleDictDataApi } from '@/api/system/dict/dict.data'
import { isRelogin } from '@/config/axios'
const permissionStore = usePermissionStoreWithOut() const permissionStore = usePermissionStoreWithOut()
@ -48,14 +49,14 @@ router.beforeEach(async (to, from, next) => {
next({ path: '/' }) next({ path: '/' })
} else { } else {
if (!dictStore.getIsSetDict) { if (!dictStore.getIsSetDict) {
isRelogin.show = true
// 获取所有字典 // 获取所有字典
const res = await listSimpleDictDataApi() const res = await listSimpleDictDataApi()
if (res) { dictStore.setDictMap(res)
dictStore.setDictMap(res) dictStore.setIsSetDict(true)
dictStore.setIsSetDict(true)
}
} }
if (permissionStore.getIsAddRouters) { if (permissionStore.getIsAddRouters) {
isRelogin.show = false
next() next()
return return
} }
@ -77,7 +78,7 @@ router.beforeEach(async (to, from, next) => {
if (whiteList.indexOf(to.path) !== -1) { if (whiteList.indexOf(to.path) !== -1) {
next() next()
} else { } else {
next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页 next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
} }
} }
}) })

View File

@ -6,12 +6,12 @@ import { ElRow, ElCol, ElUpload, ElMessage, ElDialog } from 'element-plus'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { uploadAvatarApi } from '@/api/system/user/profile' import { uploadAvatarApi } from '@/api/system/user/profile'
const cropper = ref() const cropper = ref()
const dialogVisible = ref(false)
const cropperVisible = ref(false)
const props = defineProps({ const props = defineProps({
img: propTypes.string.def('') img: propTypes.string.def('')
}) })
const state = reactive({ const options = reactive({
dialogVisible: false,
cropperVisible: false,
dialogTitle: '编辑头像', dialogTitle: '编辑头像',
options: { options: {
img: props.img, // img: props.img, //
@ -27,8 +27,11 @@ const state = reactive({
}) })
/** 编辑头像 */ /** 编辑头像 */
const editCropper = () => { const editCropper = () => {
state.dialogVisible = true dialogVisible.value = true
state.cropperVisible = true }
//
const modalOpened = () => {
cropperVisible.value = true
} }
/** 向左旋转 */ /** 向左旋转 */
const rotateLeft = () => { const rotateLeft = () => {
@ -44,7 +47,7 @@ const changeScale = (num: number) => {
cropper.value.changeScale(num) cropper.value.changeScale(num)
} }
// //
const requestUpload = () => {} const requestUpload: any = () => {}
/** 上传预处理 */ /** 上传预处理 */
const beforeUpload = (file: Blob) => { const beforeUpload = (file: Blob) => {
if (file.type.indexOf('image/') == -1) { if (file.type.indexOf('image/') == -1) {
@ -54,64 +57,65 @@ const beforeUpload = (file: Blob) => {
reader.readAsDataURL(file) reader.readAsDataURL(file)
reader.onload = () => { reader.onload = () => {
if (reader.result) { if (reader.result) {
state.options.img = reader.result as string options.options.img = reader.result as string
} }
} }
} }
} }
/** 上传图片 */ /** 上传图片 */
const uploadImg = () => { const uploadImg = () => {
cropper.value.getCropBlob((data) => { cropper.value.getCropBlob((data: any) => {
let formData = new FormData() let formData = new FormData()
formData.append('avatarfile', data) formData.append('avatarfile', data)
uploadAvatarApi(formData) uploadAvatarApi(formData)
}) })
} }
/** 实时预览 */ /** 实时预览 */
const realTime = (data) => { const realTime = (data: any) => {
state.previews = data options.previews = data
} }
watch( watch(
() => props.img, () => props.img,
() => { () => {
if (props.img) { if (props.img) {
state.options.img = props.img options.options.img = props.img
state.previews.img = props.img options.previews.img = props.img
state.previews.url = props.img options.previews.url = props.img
} }
} }
) )
</script> </script>
<template> <template>
<div class="user-info-head" @click="editCropper()"> <div class="user-info-head" @click="editCropper()">
<img :src="state.options.img" title="点击上传头像" class="img-circle img-lg" alt="" /> <img :src="options.options.img" title="点击上传头像" class="img-circle img-lg" alt="" />
</div> </div>
<el-dialog <el-dialog
v-model="state.dialogVisible" v-model="dialogVisible"
:title="state.dialogTitle" :title="options.dialogTitle"
width="50%" width="800px"
:maxHeight="350" append-to-body
style="padding: 30px 20px" style="padding: 30px 20px"
@opened="modalOpened"
> >
<el-row> <el-row>
<el-col :xs="24" :md="12" style="height: 350px"> <el-col :xs="24" :md="12" style="height: 350px">
<VueCropper <VueCropper
ref="cropper" ref="cropper"
:img="state.options.img" :img="options.options.img"
:info="true" :info="true"
:autoCrop="state.options.autoCrop" :autoCrop="options.options.autoCrop"
:autoCropWidth="state.options.autoCropWidth" :autoCropWidth="options.options.autoCropWidth"
:autoCropHeight="state.options.autoCropHeight" :autoCropHeight="options.options.autoCropHeight"
:fixedBox="state.options.fixedBox" :fixedBox="options.options.fixedBox"
@real-time="realTime" @real-time="realTime"
v-if="state.cropperVisible" v-if="cropperVisible"
/> />
</el-col> </el-col>
<el-col :xs="24" :md="12" style="height: 350px"> <el-col :xs="24" :md="12" style="height: 350px">
<div class="avatar-upload-preview"> <div class="avatar-upload-preview">
<img <img
:src="state.previews.url" :src="options.previews.url"
:style="state.previews.img" :style="options.previews.img"
style="!max-width: 100%" style="!max-width: 100%"
alt="" alt=""
/> />

View File

@ -47,7 +47,6 @@ const getTree = async () => {
const res = await MenuApi.listSimpleMenusApi() const res = await MenuApi.listSimpleMenusApi()
const menu = { id: 0, name: '主类目', children: [] as any[] } const menu = { id: 0, name: '主类目', children: [] as any[] }
menu.children = handleTree(res) menu.children = handleTree(res)
console.info(menu)
menuOptions.value = menu menuOptions.value = menu
} }
// ========== ========== // ========== ==========

View File

@ -155,7 +155,7 @@ getList()
</Table> </Table>
</ContentWrap> </ContentWrap>
<Dialog v-model="dialogVisible" :title="dialogTitle"> <Dialog v-model="dialogVisible" :title="dialogTitle" width="60%" maxHeight="420px">
<!-- 对话框(添加 / 修改) --> <!-- 对话框(添加 / 修改) -->
<Form <Form
v-if="['create', 'update'].includes(actionType)" v-if="['create', 'update'].includes(actionType)"

View File

@ -110,7 +110,7 @@ const defaultProps = {
label: 'name', label: 'name',
value: 'id' value: 'id'
} }
const treeOptions = ref([]) // const treeOptions = ref<any[]>([]) //
const treeRef = ref<InstanceType<typeof ElTree>>() const treeRef = ref<InstanceType<typeof ElTree>>()
const dialogScopeVisible = ref(false) const dialogScopeVisible = ref(false)
const dialogScopeTitle = ref('数据权限') const dialogScopeTitle = ref('数据权限')
@ -133,7 +133,6 @@ const handleScope = async (type: string, row: RoleVO) => {
const menuRes = await listSimpleMenusApi() const menuRes = await listSimpleMenusApi()
treeOptions.value = handleTree(menuRes) treeOptions.value = handleTree(menuRes)
const role = await PermissionApi.listRoleMenusApi(row.id) const role = await PermissionApi.listRoleMenusApi(row.id)
console.info(role)
if (role) { if (role) {
// treeRef.value!.setCheckedKeys(role as unknown as Array<number>) // treeRef.value!.setCheckedKeys(role as unknown as Array<number>)
defaultCheckedKeys.value = role defaultCheckedKeys.value = role
@ -142,7 +141,6 @@ const handleScope = async (type: string, row: RoleVO) => {
const deptRes = await listSimpleDeptApi() const deptRes = await listSimpleDeptApi()
treeOptions.value = handleTree(deptRes) treeOptions.value = handleTree(deptRes)
const role = await RoleApi.getRoleApi(row.id) const role = await RoleApi.getRoleApi(row.id)
console.info(role)
dataScopeForm.dataScope = role.dataScope dataScopeForm.dataScope = role.dataScope
if (role.dataScopeDeptIds) { if (role.dataScopeDeptIds) {
// treeRef.value!.setCheckedKeys(role.dataScopeDeptIds as unknown as Array<number>, false) // treeRef.value!.setCheckedKeys(role.dataScopeDeptIds as unknown as Array<number>, false)
@ -155,7 +153,6 @@ const handleScope = async (type: string, row: RoleVO) => {
// //
const submitScope = async () => { const submitScope = async () => {
const keys = treeRef.value!.getCheckedKeys(false) as unknown as Array<number> const keys = treeRef.value!.getCheckedKeys(false) as unknown as Array<number>
console.info(keys)
if ('data' === actionScopeType.value) { if ('data' === actionScopeType.value) {
const data = ref<PermissionAssignRoleDataScopeReqVO>({ const data = ref<PermissionAssignRoleDataScopeReqVO>({
roleId: dataScopeForm.id, roleId: dataScopeForm.id,

View File

@ -19,7 +19,7 @@ const defaultProps = {
value: 'id' value: 'id'
} }
// ========== ========== // ========== ==========
const menuOptions = ref([]) // const menuOptions = ref<any[]>([]) //
const treeRef = ref<InstanceType<typeof ElTree>>() const treeRef = ref<InstanceType<typeof ElTree>>()
const treeNodeAll = ref(false) const treeNodeAll = ref(false)
// / // /
@ -84,11 +84,9 @@ const submitForm = async () => {
if (actionType.value === 'create') { if (actionType.value === 'create') {
await TenantPackageApi.createTenantPackageTypeApi(data) await TenantPackageApi.createTenantPackageTypeApi(data)
ElMessage.success(t('common.createSuccess')) ElMessage.success(t('common.createSuccess'))
console.log('new data')
} else { } else {
await TenantPackageApi.updateTenantPackageTypeApi(data) await TenantPackageApi.updateTenantPackageTypeApi(data)
ElMessage.success(t('common.updateSuccess')) ElMessage.success(t('common.updateSuccess'))
console.log('edit data')
} }
// //
dialogVisible.value = false dialogVisible.value = false