Commit b6c7d8685cb9ce98e96eb887d96659cae3a9dd86
1 parent
abb94eae
perf: refresh token && delete console
Showing
10 changed files
with
100 additions
and
63 deletions
yudao-ui-admin-vue3/src/api/system/user/profile/index.ts
| ... | ... | @@ -24,6 +24,6 @@ export const updateUserPwdApi = (oldPassword: string, newPassword: string) => { |
| 24 | 24 | } |
| 25 | 25 | |
| 26 | 26 | // 用户头像上传 |
| 27 | -export const uploadAvatarApi = (params) => { | |
| 28 | - return request.upload({ url: '/system/user/profile/update-avatar', params }) | |
| 27 | +export const uploadAvatarApi = (data) => { | |
| 28 | + return request.upload({ url: '/system/user/profile/update-avatar', data: data }) | |
| 29 | 29 | } | ... | ... |
yudao-ui-admin-vue3/src/components/Verifition/src/Verify.vue
| ... | ... | @@ -102,7 +102,6 @@ export default { |
| 102 | 102 | * @description 刷新 |
| 103 | 103 | * */ |
| 104 | 104 | const refresh = () => { |
| 105 | - console.log(instance.value) | |
| 106 | 105 | if (instance.value.refresh) { |
| 107 | 106 | instance.value.refresh() |
| 108 | 107 | } |
| ... | ... | @@ -271,7 +270,6 @@ export default { |
| 271 | 270 | -moz-box-sizing: content-box; |
| 272 | 271 | box-sizing: content-box; |
| 273 | 272 | border: 1px solid #ddd; |
| 274 | - -webkit-border-radius: 4px; | |
| 275 | 273 | } |
| 276 | 274 | |
| 277 | 275 | .verify-bar-area .verify-move-block { |
| ... | ... | @@ -284,7 +282,6 @@ export default { |
| 284 | 282 | -moz-box-sizing: content-box; |
| 285 | 283 | box-sizing: content-box; |
| 286 | 284 | box-shadow: 0 0 2px #888888; |
| 287 | - -webkit-border-radius: 1px; | |
| 288 | 285 | } |
| 289 | 286 | |
| 290 | 287 | .verify-bar-area .verify-move-block:hover { | ... | ... |
yudao-ui-admin-vue3/src/components/Verifition/src/Verify/VerifySlide.vue
| ... | ... | @@ -242,7 +242,6 @@ export default { |
| 242 | 242 | //兼容移动端 |
| 243 | 243 | var x = e.touches[0].pageX |
| 244 | 244 | } |
| 245 | - console.log(barArea) | |
| 246 | 245 | startLeft.value = Math.floor(x - barArea.value.getBoundingClientRect().left) |
| 247 | 246 | startMoveTime.value = +new Date() //开始滑动的时间 |
| 248 | 247 | if (isEnd.value == false) { | ... | ... |
yudao-ui-admin-vue3/src/config/axios/index.ts
| 1 | 1 | import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios' |
| 2 | -import { ElMessage, ElNotification } from 'element-plus' | |
| 2 | +import { ElMessage, ElMessageBox, ElNotification } from 'element-plus' | |
| 3 | 3 | import qs from 'qs' |
| 4 | 4 | import { config } from '@/config/axios/config' |
| 5 | -import { getAccessToken, getTenantId, removeToken } from '@/utils/auth' | |
| 5 | +import { getAccessToken, getRefreshToken, getTenantId, removeToken, setToken } from '@/utils/auth' | |
| 6 | 6 | import errorCode from './errorCode' |
| 7 | 7 | import { useI18n } from '@/hooks/web/useI18n' |
| 8 | +import { resetRouter } from '@/router' | |
| 8 | 9 | |
| 9 | 10 | const tenantEnable = import.meta.env.VITE_APP_TENANT_ENABLE |
| 10 | 11 | const BASE_URL = import.meta.env.VITE_BASE_URL |
| ... | ... | @@ -20,9 +21,9 @@ const ignoreMsgs = [ |
| 20 | 21 | export const isRelogin = { show: false } |
| 21 | 22 | // Axios 无感知刷新令牌,参考 https://www.dashingdog.cn/article/11 与 https://segmentfault.com/a/1190000020210980 实现 |
| 22 | 23 | // 请求队列 |
| 23 | -// const requestList = [] | |
| 24 | +let requestList: any[] = [] | |
| 24 | 25 | // 是否正在刷新中 |
| 25 | -// const isRefreshToken = false | |
| 26 | +let isRefreshToken = false | |
| 26 | 27 | |
| 27 | 28 | export const PATH_URL = base_url[import.meta.env.VITE_API_BASEPATH] |
| 28 | 29 | |
| ... | ... | @@ -91,6 +92,7 @@ service.interceptors.request.use( |
| 91 | 92 | service.interceptors.response.use( |
| 92 | 93 | async (response: AxiosResponse<Recordable>) => { |
| 93 | 94 | const { data } = response |
| 95 | + console.info(data) | |
| 94 | 96 | if (!data) { |
| 95 | 97 | // 返回“[HTTP]请求没有返回值”; |
| 96 | 98 | throw new Error() |
| ... | ... | @@ -112,16 +114,38 @@ service.interceptors.response.use( |
| 112 | 114 | return Promise.reject(msg) |
| 113 | 115 | } else if (code === 401) { |
| 114 | 116 | // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了 |
| 115 | - return handleAuthorized() | |
| 116 | - // if (!isRefreshToken) { | |
| 117 | - // isRefreshToken = true | |
| 118 | - // // 1. 如果获取不到刷新令牌,则只能执行登出操作 | |
| 119 | - // if (!getRefreshToken()) { | |
| 120 | - // return handleAuthorized() | |
| 121 | - // } | |
| 122 | - // // 2. 进行刷新访问令牌 | |
| 123 | - // // TODO: 引入refreshToken会循环依赖报错 | |
| 124 | - // } | |
| 117 | + if (!isRefreshToken) { | |
| 118 | + isRefreshToken = true | |
| 119 | + // 1. 如果获取不到刷新令牌,则只能执行登出操作 | |
| 120 | + if (!getRefreshToken()) { | |
| 121 | + return handleAuthorized() | |
| 122 | + } | |
| 123 | + // 2. 进行刷新访问令牌 | |
| 124 | + try { | |
| 125 | + const refreshTokenRes = await refreshToken() | |
| 126 | + // 2.1 刷新成功,则回放队列的请求 + 当前请求 | |
| 127 | + setToken(refreshTokenRes.data) | |
| 128 | + requestList.forEach((cb: any) => cb()) | |
| 129 | + return service(response.config) | |
| 130 | + } catch (e) { | |
| 131 | + // 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。 | |
| 132 | + // 2.2 刷新失败,只回放队列的请求 | |
| 133 | + requestList.forEach((cb: any) => cb()) | |
| 134 | + // 提示是否要登出。即不回放当前请求!不然会形成递归 | |
| 135 | + return handleAuthorized() | |
| 136 | + } finally { | |
| 137 | + requestList = [] | |
| 138 | + isRefreshToken = false | |
| 139 | + } | |
| 140 | + } else { | |
| 141 | + // 添加到队列,等待刷新获取到新的令牌 | |
| 142 | + return new Promise((resolve) => { | |
| 143 | + requestList.push(() => { | |
| 144 | + ;(config as Recordable).headers.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token 请根据实际情况自行修改 | |
| 145 | + resolve(service(response.config)) | |
| 146 | + }) | |
| 147 | + }) | |
| 148 | + } | |
| 125 | 149 | } else if (code === 500) { |
| 126 | 150 | ElMessage.error(t('sys.api.errMsg500')) |
| 127 | 151 | return Promise.reject(new Error(msg)) |
| ... | ... | @@ -165,14 +189,32 @@ service.interceptors.response.use( |
| 165 | 189 | return Promise.reject(error) |
| 166 | 190 | } |
| 167 | 191 | ) |
| 192 | + | |
| 193 | +const refreshToken = async () => { | |
| 194 | + return await service({ | |
| 195 | + url: '/system/auth/refresh-token?refreshToken=' + getRefreshToken(), | |
| 196 | + method: 'post' | |
| 197 | + }) | |
| 198 | +} | |
| 168 | 199 | const handleAuthorized = () => { |
| 169 | 200 | const { t } = useI18n() |
| 170 | 201 | if (!isRelogin.show) { |
| 171 | - removeToken() | |
| 172 | 202 | isRelogin.show = true |
| 173 | - ElNotification.error(t('sys.api.timeoutMessage')) | |
| 203 | + ElMessageBox.confirm(t('sys.api.timeoutMessage'), t('common.confirmTitle'), { | |
| 204 | + confirmButtonText: t('login.relogin'), | |
| 205 | + cancelButtonText: t('common.cancel'), | |
| 206 | + type: 'warning' | |
| 207 | + }) | |
| 208 | + .then(() => { | |
| 209 | + removeToken() | |
| 210 | + resetRouter() // 重置静态路由表 | |
| 211 | + isRelogin.show = false | |
| 212 | + location.href = '/' | |
| 213 | + }) | |
| 214 | + .catch(() => { | |
| 215 | + isRelogin.show = false | |
| 216 | + }) | |
| 174 | 217 | } |
| 175 | - location.href = '/' | |
| 176 | 218 | return Promise.reject(t('sys.api.timeoutMessage')) |
| 177 | 219 | } |
| 178 | 220 | export { service } | ... | ... |
yudao-ui-admin-vue3/src/router/index.ts
| ... | ... | @@ -10,6 +10,7 @@ import { createRouter, createWebHashHistory } from 'vue-router' |
| 10 | 10 | import { usePermissionStoreWithOut } from '@/store/modules/permission' |
| 11 | 11 | import { useDictStoreWithOut } from '@/store/modules/dict' |
| 12 | 12 | import { listSimpleDictDataApi } from '@/api/system/dict/dict.data' |
| 13 | +import { isRelogin } from '@/config/axios' | |
| 13 | 14 | |
| 14 | 15 | const permissionStore = usePermissionStoreWithOut() |
| 15 | 16 | |
| ... | ... | @@ -48,14 +49,14 @@ router.beforeEach(async (to, from, next) => { |
| 48 | 49 | next({ path: '/' }) |
| 49 | 50 | } else { |
| 50 | 51 | if (!dictStore.getIsSetDict) { |
| 52 | + isRelogin.show = true | |
| 51 | 53 | // 获取所有字典 |
| 52 | 54 | const res = await listSimpleDictDataApi() |
| 53 | - if (res) { | |
| 54 | - dictStore.setDictMap(res) | |
| 55 | - dictStore.setIsSetDict(true) | |
| 56 | - } | |
| 55 | + dictStore.setDictMap(res) | |
| 56 | + dictStore.setIsSetDict(true) | |
| 57 | 57 | } |
| 58 | 58 | if (permissionStore.getIsAddRouters) { |
| 59 | + isRelogin.show = false | |
| 59 | 60 | next() |
| 60 | 61 | return |
| 61 | 62 | } |
| ... | ... | @@ -77,7 +78,7 @@ router.beforeEach(async (to, from, next) => { |
| 77 | 78 | if (whiteList.indexOf(to.path) !== -1) { |
| 78 | 79 | next() |
| 79 | 80 | } else { |
| 80 | - next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页 | |
| 81 | + next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页 | |
| 81 | 82 | } |
| 82 | 83 | } |
| 83 | 84 | }) | ... | ... |
yudao-ui-admin-vue3/src/views/Profile/components/UserAvatar.vue
| ... | ... | @@ -6,12 +6,12 @@ import { ElRow, ElCol, ElUpload, ElMessage, ElDialog } from 'element-plus' |
| 6 | 6 | import { propTypes } from '@/utils/propTypes' |
| 7 | 7 | import { uploadAvatarApi } from '@/api/system/user/profile' |
| 8 | 8 | const cropper = ref() |
| 9 | +const dialogVisible = ref(false) | |
| 10 | +const cropperVisible = ref(false) | |
| 9 | 11 | const props = defineProps({ |
| 10 | 12 | img: propTypes.string.def('') |
| 11 | 13 | }) |
| 12 | -const state = reactive({ | |
| 13 | - dialogVisible: false, | |
| 14 | - cropperVisible: false, | |
| 14 | +const options = reactive({ | |
| 15 | 15 | dialogTitle: '编辑头像', |
| 16 | 16 | options: { |
| 17 | 17 | img: props.img, //裁剪图片的地址 |
| ... | ... | @@ -27,8 +27,11 @@ const state = reactive({ |
| 27 | 27 | }) |
| 28 | 28 | /** 编辑头像 */ |
| 29 | 29 | const editCropper = () => { |
| 30 | - state.dialogVisible = true | |
| 31 | - state.cropperVisible = true | |
| 30 | + dialogVisible.value = true | |
| 31 | +} | |
| 32 | +// 打开弹出层结束时的回调 | |
| 33 | +const modalOpened = () => { | |
| 34 | + cropperVisible.value = true | |
| 32 | 35 | } |
| 33 | 36 | /** 向左旋转 */ |
| 34 | 37 | const rotateLeft = () => { |
| ... | ... | @@ -44,7 +47,7 @@ const changeScale = (num: number) => { |
| 44 | 47 | cropper.value.changeScale(num) |
| 45 | 48 | } |
| 46 | 49 | // 覆盖默认的上传行为 |
| 47 | -const requestUpload = () => {} | |
| 50 | +const requestUpload: any = () => {} | |
| 48 | 51 | /** 上传预处理 */ |
| 49 | 52 | const beforeUpload = (file: Blob) => { |
| 50 | 53 | if (file.type.indexOf('image/') == -1) { |
| ... | ... | @@ -54,64 +57,65 @@ const beforeUpload = (file: Blob) => { |
| 54 | 57 | reader.readAsDataURL(file) |
| 55 | 58 | reader.onload = () => { |
| 56 | 59 | if (reader.result) { |
| 57 | - state.options.img = reader.result as string | |
| 60 | + options.options.img = reader.result as string | |
| 58 | 61 | } |
| 59 | 62 | } |
| 60 | 63 | } |
| 61 | 64 | } |
| 62 | 65 | /** 上传图片 */ |
| 63 | 66 | const uploadImg = () => { |
| 64 | - cropper.value.getCropBlob((data) => { | |
| 67 | + cropper.value.getCropBlob((data: any) => { | |
| 65 | 68 | let formData = new FormData() |
| 66 | 69 | formData.append('avatarfile', data) |
| 67 | 70 | uploadAvatarApi(formData) |
| 68 | 71 | }) |
| 69 | 72 | } |
| 70 | 73 | /** 实时预览 */ |
| 71 | -const realTime = (data) => { | |
| 72 | - state.previews = data | |
| 74 | +const realTime = (data: any) => { | |
| 75 | + options.previews = data | |
| 73 | 76 | } |
| 74 | 77 | watch( |
| 75 | 78 | () => props.img, |
| 76 | 79 | () => { |
| 77 | 80 | if (props.img) { |
| 78 | - state.options.img = props.img | |
| 79 | - state.previews.img = props.img | |
| 80 | - state.previews.url = props.img | |
| 81 | + options.options.img = props.img | |
| 82 | + options.previews.img = props.img | |
| 83 | + options.previews.url = props.img | |
| 81 | 84 | } |
| 82 | 85 | } |
| 83 | 86 | ) |
| 84 | 87 | </script> |
| 85 | 88 | <template> |
| 86 | 89 | <div class="user-info-head" @click="editCropper()"> |
| 87 | - <img :src="state.options.img" title="点击上传头像" class="img-circle img-lg" alt="" /> | |
| 90 | + <img :src="options.options.img" title="点击上传头像" class="img-circle img-lg" alt="" /> | |
| 88 | 91 | </div> |
| 89 | 92 | <el-dialog |
| 90 | - v-model="state.dialogVisible" | |
| 91 | - :title="state.dialogTitle" | |
| 92 | - width="50%" | |
| 93 | - :maxHeight="350" | |
| 93 | + v-model="dialogVisible" | |
| 94 | + :title="options.dialogTitle" | |
| 95 | + width="800px" | |
| 96 | + append-to-body | |
| 94 | 97 | style="padding: 30px 20px" |
| 98 | + @opened="modalOpened" | |
| 95 | 99 | > |
| 96 | 100 | <el-row> |
| 97 | 101 | <el-col :xs="24" :md="12" style="height: 350px"> |
| 98 | 102 | <VueCropper |
| 99 | 103 | ref="cropper" |
| 100 | - :img="state.options.img" | |
| 104 | + :img="options.options.img" | |
| 101 | 105 | :info="true" |
| 102 | - :autoCrop="state.options.autoCrop" | |
| 103 | - :autoCropWidth="state.options.autoCropWidth" | |
| 104 | - :autoCropHeight="state.options.autoCropHeight" | |
| 105 | - :fixedBox="state.options.fixedBox" | |
| 106 | + :autoCrop="options.options.autoCrop" | |
| 107 | + :autoCropWidth="options.options.autoCropWidth" | |
| 108 | + :autoCropHeight="options.options.autoCropHeight" | |
| 109 | + :fixedBox="options.options.fixedBox" | |
| 106 | 110 | @real-time="realTime" |
| 107 | - v-if="state.cropperVisible" | |
| 111 | + v-if="cropperVisible" | |
| 108 | 112 | /> |
| 109 | 113 | </el-col> |
| 110 | 114 | <el-col :xs="24" :md="12" style="height: 350px"> |
| 111 | 115 | <div class="avatar-upload-preview"> |
| 112 | 116 | <img |
| 113 | - :src="state.previews.url" | |
| 114 | - :style="state.previews.img" | |
| 117 | + :src="options.previews.url" | |
| 118 | + :style="options.previews.img" | |
| 115 | 119 | style="!max-width: 100%" |
| 116 | 120 | alt="" |
| 117 | 121 | /> | ... | ... |
yudao-ui-admin-vue3/src/views/system/menu/index.vue
| ... | ... | @@ -47,7 +47,6 @@ const getTree = async () => { |
| 47 | 47 | const res = await MenuApi.listSimpleMenusApi() |
| 48 | 48 | const menu = { id: 0, name: '主类目', children: [] as any[] } |
| 49 | 49 | menu.children = handleTree(res) |
| 50 | - console.info(menu) | |
| 51 | 50 | menuOptions.value = menu |
| 52 | 51 | } |
| 53 | 52 | // ========== 查询 ========== | ... | ... |
yudao-ui-admin-vue3/src/views/system/oauth2/client/index.vue
| ... | ... | @@ -155,7 +155,7 @@ getList() |
| 155 | 155 | </Table> |
| 156 | 156 | </ContentWrap> |
| 157 | 157 | |
| 158 | - <Dialog v-model="dialogVisible" :title="dialogTitle"> | |
| 158 | + <Dialog v-model="dialogVisible" :title="dialogTitle" width="60%" maxHeight="420px"> | |
| 159 | 159 | <!-- 对话框(添加 / 修改) --> |
| 160 | 160 | <Form |
| 161 | 161 | v-if="['create', 'update'].includes(actionType)" | ... | ... |
yudao-ui-admin-vue3/src/views/system/role/index.vue
| ... | ... | @@ -110,7 +110,7 @@ const defaultProps = { |
| 110 | 110 | label: 'name', |
| 111 | 111 | value: 'id' |
| 112 | 112 | } |
| 113 | -const treeOptions = ref([]) // 菜单树形结构 | |
| 113 | +const treeOptions = ref<any[]>([]) // 菜单树形结构 | |
| 114 | 114 | const treeRef = ref<InstanceType<typeof ElTree>>() |
| 115 | 115 | const dialogScopeVisible = ref(false) |
| 116 | 116 | const dialogScopeTitle = ref('数据权限') |
| ... | ... | @@ -133,7 +133,6 @@ const handleScope = async (type: string, row: RoleVO) => { |
| 133 | 133 | const menuRes = await listSimpleMenusApi() |
| 134 | 134 | treeOptions.value = handleTree(menuRes) |
| 135 | 135 | const role = await PermissionApi.listRoleMenusApi(row.id) |
| 136 | - console.info(role) | |
| 137 | 136 | if (role) { |
| 138 | 137 | // treeRef.value!.setCheckedKeys(role as unknown as Array<number>) |
| 139 | 138 | defaultCheckedKeys.value = role |
| ... | ... | @@ -142,7 +141,6 @@ const handleScope = async (type: string, row: RoleVO) => { |
| 142 | 141 | const deptRes = await listSimpleDeptApi() |
| 143 | 142 | treeOptions.value = handleTree(deptRes) |
| 144 | 143 | const role = await RoleApi.getRoleApi(row.id) |
| 145 | - console.info(role) | |
| 146 | 144 | dataScopeForm.dataScope = role.dataScope |
| 147 | 145 | if (role.dataScopeDeptIds) { |
| 148 | 146 | // treeRef.value!.setCheckedKeys(role.dataScopeDeptIds as unknown as Array<number>, false) |
| ... | ... | @@ -155,7 +153,6 @@ const handleScope = async (type: string, row: RoleVO) => { |
| 155 | 153 | // 保存权限 |
| 156 | 154 | const submitScope = async () => { |
| 157 | 155 | const keys = treeRef.value!.getCheckedKeys(false) as unknown as Array<number> |
| 158 | - console.info(keys) | |
| 159 | 156 | if ('data' === actionScopeType.value) { |
| 160 | 157 | const data = ref<PermissionAssignRoleDataScopeReqVO>({ |
| 161 | 158 | roleId: dataScopeForm.id, | ... | ... |
yudao-ui-admin-vue3/src/views/system/tenantPackage/index.vue
| ... | ... | @@ -19,7 +19,7 @@ const defaultProps = { |
| 19 | 19 | value: 'id' |
| 20 | 20 | } |
| 21 | 21 | // ========== 创建菜单树结构 ========== |
| 22 | -const menuOptions = ref([]) // 树形结构 | |
| 22 | +const menuOptions = ref<any[]>([]) // 树形结构 | |
| 23 | 23 | const treeRef = ref<InstanceType<typeof ElTree>>() |
| 24 | 24 | const treeNodeAll = ref(false) |
| 25 | 25 | // 全选/全不选 |
| ... | ... | @@ -84,11 +84,9 @@ const submitForm = async () => { |
| 84 | 84 | if (actionType.value === 'create') { |
| 85 | 85 | await TenantPackageApi.createTenantPackageTypeApi(data) |
| 86 | 86 | ElMessage.success(t('common.createSuccess')) |
| 87 | - console.log('new data') | |
| 88 | 87 | } else { |
| 89 | 88 | await TenantPackageApi.updateTenantPackageTypeApi(data) |
| 90 | 89 | ElMessage.success(t('common.updateSuccess')) |
| 91 | - console.log('edit data') | |
| 92 | 90 | } |
| 93 | 91 | // 操作成功,重新加载列表 |
| 94 | 92 | dialogVisible.value = false | ... | ... |