diff --git a/quantdinger_vue/src/api/portfolio.js b/quantdinger_vue/src/api/portfolio.js index 06d02cc..732508e 100644 --- a/quantdinger_vue/src/api/portfolio.js +++ b/quantdinger_vue/src/api/portfolio.js @@ -80,8 +80,7 @@ export function runMonitor (id, params = {}) { return request({ url: `/api/portfolio/monitors/${id}/run`, method: 'post', - data: params, - timeout: 60000 // 60s timeout for monitor API (async mode should return immediately) + data: params }) } diff --git a/quantdinger_vue/src/permission.js b/quantdinger_vue/src/permission.js index 9f7bd23..32a64c3 100644 --- a/quantdinger_vue/src/permission.js +++ b/quantdinger_vue/src/permission.js @@ -67,7 +67,17 @@ router.beforeEach((to, from, next) => { } }) }) - .catch(() => { + .catch((err) => { + // If token is invalid/expired, clear local auth and redirect to login. + const status = err && err.response && err.response.status + if (status === 401) { + store.dispatch('Logout').finally(() => { + next({ path: loginRoutePath, query: { redirect: to.fullPath } }) + NProgress.done() + }) + return + } + // Do NOT hard-logout on transient failures (backend down, proxy issue, etc). // Instead, degrade gracefully with a default role and continue. store.commit('SET_ROLES', [{ id: 'default', permissionList: [] }]) diff --git a/quantdinger_vue/src/utils/request.js b/quantdinger_vue/src/utils/request.js index 93ab62e..b1d05cd 100644 --- a/quantdinger_vue/src/utils/request.js +++ b/quantdinger_vue/src/utils/request.js @@ -3,13 +3,16 @@ import axios from 'axios' import storage from 'store' import notification from 'ant-design-vue/es/notification' import { VueAxios } from './axios' -import { ACCESS_TOKEN } from '@/store/mutation-types' +import { ACCESS_TOKEN, USER_INFO, USER_ROLES } from '@/store/mutation-types' // PHPSESSID 存储键名 const PHPSESSID_KEY = 'PHPSESSID' // Locale storage key used by vue-i18n (see src/locales/index.js) const LOCALE_KEY = 'lang' +// Prevent multiple concurrent 401 redirects +let isRedirectingToLogin = false + /** * 获取 token,处理 token 可能是字符串或对象的情况 */ @@ -53,16 +56,28 @@ const errorHandler = (error) => { }) } if (error.response.status === 401 && !(data.result && data.result.isLogin)) { - notification.error({ - message: 'Unauthorized', - description: 'Authorization verification failed' - }) - // 不清理本地 token,避免刷新后丢失登录态;仅跳转到登录页 - // 项目使用 hash 模式,需要跳转到 /#/user/login - const curHash = window.location.hash || '' - if (!curHash.includes('/user/login')) { - const redirect = encodeURIComponent(curHash.replace('#', '') || '/') - window.location.assign(`/#/user/login?redirect=${redirect}`) + // Token invalid/expired: MUST clear local auth state, otherwise route guard will + // detect a stale token and immediately bounce user away from login page. + if (!isRedirectingToLogin) { + isRedirectingToLogin = true + try { + storage.remove(ACCESS_TOKEN) + storage.remove(USER_INFO) + storage.remove(USER_ROLES) + storage.remove(PHPSESSID_KEY) + } catch (e) {} + + notification.error({ + message: 'Unauthorized', + description: data.msg || data.message || 'Token invalid or expired, please login again.' + }) + + // 项目使用 hash 模式,需要跳转到 /#/user/login + const curHash = window.location.hash || '' + if (!curHash.includes('/user/login')) { + const redirect = encodeURIComponent(curHash.replace('#', '') || '/') + window.location.assign(`/#/user/login?redirect=${redirect}`) + } } } }