Appearance
title: 退出登录后跳转与请求是否会发出?(Vue Router 4 实战) date: 2025-09-05 tags: [Vue, Vue Router, Pinia, Axios, 登录, 退出]
TL;DR(先给结论)
router.push('/user')不会去加载@/views/(public)/layout.vue;/user是独立的公开路由,全局前置守卫会先跑,再决定是否进入目标路由,受保护布局组件不会被解析/执行。- 真正会“误发请求”的常见来源是:① 旧页中已发起但未取消的请求;②
<keep-alive>相关的激活/停用副作用;③ 你在 HTTP 层没有对“无 Token 的鉴权请求”做硬性拦截。 - 给你三道保险:彻底清 Token + 取消未决请求 + 请求层强拦(可选再加根路由
beforeEnter)。
最小修改(仅给需要改的代码片段)
说明:按你的偏好,只给需修改片段;复杂逻辑在上一行加注释。
1) auth Store:退出时同时清理本地存储 Token、取消所有请求、清默认头
文件: useAuthStore 所在文件
ts
// 新增导入
import storage from "store";
import { STORAGE_KEYS } from "@/constants";
const { ACCESS_TOKEN } = STORAGE_KEYS;ts
// 新增:统一清空本地 Token(Pinia + 本地存储)
function clearToken() {
// 复杂逻辑:双通道清理,避免路由守卫误判已登录
token.value = null;
refresh.value = null;
storage.remove(ACCESS_TOKEN);
}ts
// 调整 logout 函数(仅贴需要增加的片段)
async function logout() {
try {
await http.post("/api/pub/loginOut").catch(() => {});
// 复杂逻辑:优雅下线——取消所有还在飞行中的请求,防止“未登录也发请求”
http.cancelAll?.();
// 复杂逻辑:清掉默认鉴权头,防止后续请求夹带旧 Token
http.setAuthToken?.(null);
clearToken();
resetAllStores();
} catch (error) {
console.error(error);
throw error;
}
}你现在的调用
await authStore.logout(); router.push('/user')没问题;若不想留下历史记录,可换成:
ts
// 可选:避免回退到已登录页
await router.replace({ name: "user" });2) http 工具:加“全局取消池 + 无 Token 时硬拦需要鉴权的请求”
文件: 你的 @/utils/http(或创建一个轻量封装)
ts
// 新增:全局取消池
const abortPool = new Set<AbortController>();
export function cancelAll() {
// 复杂逻辑:退出登录时集中取消所有未决请求
abortPool.forEach((c) => c.abort());
abortPool.clear();
}
export function setAuthToken(token?: string | null) {
// 复杂逻辑:动态设置/清除默认鉴权头
if (token) {
axios.defaults.headers.common.Authorization = `Bearer ${token}`;
} else {
delete axios.defaults.headers.common.Authorization;
}
}ts
// 在请求拦截里增加以下片段(保留你已有逻辑)
http.interceptors.request.use((config) => {
// 复杂逻辑:为每个请求绑定 AbortController,纳入全局取消池
const ac = new AbortController();
(config as any).signal = ac.signal;
abortPool.add(ac);
// 复杂逻辑:对标记了 { auth: true } 的请求做硬性校验,无 Token 直接终止
const needAuth = (config as any).auth === true;
const token = storage.get(ACCESS_TOKEN);
if (needAuth && !token) {
ac.abort();
// 用标准错误中断;也可自定义错误码
throw new axios.Cancel("UNAUTHENTICATED");
}
return config;
});ts
// 在响应/错误拦截里释放对应的 controller(保持池干净)
const detach = (cfg?: any) => {
if (!cfg?.signal) return;
for (const ac of abortPool) {
if (cfg.signal === ac.signal) {
abortPool.delete(ac);
break;
}
}
};
http.interceptors.response.use(
(resp) => {
detach(resp.config);
return resp;
},
(err) => {
detach(err?.config);
return Promise.reject(err);
}
);使用方式:凡是需要登录才能访问的接口,在调用时带上
{ auth: true }:
ts
// 复杂逻辑:打上鉴权标记,无 Token 时在请求层被硬拦
http.get("/api/secure/profile", { auth: true });3) 根布局路由(可选加固):未登录禁止进入,阻止懒加载 layout.vue
这是“冗余但稳”的第四道锁:即使有人误配了守卫,根布局也进不去。
文件: 路由表所在文件
ts
// 新增导入(如果本文件里还没有)
import storage from "store";
import { STORAGE_KEYS } from "@/constants";
import { LOGIN_ROUTE } from "./constants";
const { ACCESS_TOKEN } = STORAGE_KEYS;ts
// 在 path: '/' 那个根路由对象上新增 beforeEnter(只贴新增的属性)
beforeEnter: (to, from, next) => {
// 复杂逻辑:未登录直接拒绝进入受保护根布局,避免解析/执行 layout.vue
const token = storage.get(ACCESS_TOKEN)
if (!token) return next({ name: LOGIN_ROUTE.name, replace: true })
next()
},为什么你会怀疑 layout.vue 被加载?
常见场景有三种(任选对号入座):
- 旧页未决请求:你还在“受保护页面”时就发出了请求,
push('/user')后这些请求仍会完成,除非你主动取消(上面第 2 步已解决)。 <keep-alive>激活/停用副作用:离开/进入时有数据拉取,需要在未登录时短路。- 预加载 Chunk:如果手动开启过
webpackPrefetch等,会看到静态资源拉取,但这不等同于“业务请求”发出。
排查清单(按优先级)
- 退出后
localStorage/store里是否还残留ACCESS_TOKEN?(第 1 步已处理) - 所有需要登录的接口是否统一加了
{ auth: true }并被请求拦截器“硬拦”?(第 2 步) - 页面里有没有在
onMounted直接拉数据但无 Token 校验的逻辑?(在组件中加早返回) - 是否使用了
<keep-alive>并在activated/deactivated发请求?(同样做登录短路) - 仍担心懒加载?给根布局加
beforeEnter(第 3 步“加固锁”)。
你最关心的那句直白话
- 不会:
router.push('/user')不会去跑@/views/(public)/layout.vue里的业务请求。 - 会:如果旧页的请求已经发出且你没有取消,它们会继续完成。上面给的三段修改能把这类“该停的请求”全部截住。