news 2026/9/30 5:33:28

Vue登录功能全流程:axios封装、路由守卫与登录态管理实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vue登录功能全流程:axios封装、路由守卫与登录态管理实战

做前端这几年,后台管理系统做了不下十套,几乎每一套项目的第一个功能模块都是同一个东西:登录。Vue项目里的登录功能,表面上看就是"一个表单接一个接口",可真要动手做的时候,事情远没那么简单:axios要不要封装、token放在哪里、刷新页面登录态丢了怎么办、开发环境跨域怎么处理、生产环境接口路径怎么配,这些问题不提前想清楚,后面会反复踩坑。所以我把Vue实现登录功能的全流程整理了一遍,以Spring Boot + Vue前后端分离为背景,重点讲封装axios、路由守卫、登录态管理,以及联调部署阶段会遇到的问题。适合准备从零搭后台管理系统、或者刚接触Vue项目实战的同学参考,照着做基本能覆盖大部分业务场景。

1. 登录功能前后端分离下的整体设计思路

1.1 一次登录请求背后发生了什么

在前后端分离架构里,登录并不是简单地把账号密码发给后端就完事了。完整的一次登录流程,至少包含五个环节:

  • 用户在登录页输入账号密码,前端先做一轮格式校验,比如非空、手机号格式、密码长度。
  • 校验通过后,前端把账号密码以JSON格式发送到后端的登录接口,通常是POST /api/auth/login。
  • 后端校验账号密码,正确就返回token(令牌)和用户基本信息,错误就返回对应的错误码和提示文案。
  • 前端拿到token后,把它写入localStorage或sessionStorage,同时把用户信息放进Pinia或Vuex全局状态里。
  • 路由守卫在页面跳转前检查本地是否存在有效token,没有就直接弹回登录页。

这里最关键的一点是:登录成功之后,前端怎么向后端证明"接下来的每个请求都是你这个人在发"?答案就是token。后端在登录时签发token,前端后续请求的请求头里带上token,格式一般是Authorization: Bearer <token>,后端再校验这个token决定是否放行。这也是为什么一定要统一封装axios——你不可能在每个业务接口里都手动写一遍"带token、判断过期、处理错误"的逻辑,太容易漏。

1.2 为什么一定要统一封装axios

很多新手写登录功能,直接在组件里import axios from 'axios',然后axios.post('/api/auth/login')一把梭。登录接口这么写没问题,问题出在登录之后的接口上。

项目里的业务接口动辄几十上百个,每个接口都要做三件事:请求头里带token、统一处理错误提示、在登录态失效时跳回登录页。如果不封装,这些逻辑会在每个页面重复出现,代码不仅冗余,还容易出现一个得改、其他忘了改的情况。比如后端约定某个接口的错误码返回值变了,你就得全项目搜索所有调用这个接口的地方逐个改。

封装axios之后,这些通用逻辑只写一遍,集中在拦截器里。具体来说有四个好处:一是token自动注入,任何请求都不用关心headers;二是错误处理统一,后端返回什么格式、前端怎么提示,都在一个地方维护;三是登录态过期可以全局拦截,一次性处理401跳转;四是便于扩展,比如文件上传、接口取消、日志上报,都可以在封装层做增强。登录功能是第一个要接入这套机制的模块,所以封装axios这件事最好在做登录之前就完成。

1.3 技术选型:Vue3 + Pinia 还是 Vue2 + Vuex

这个问题的答案取决于你的项目背景。新项目我建议直接上Vue3 + Vite + Pinia,Vue生态现在的主流方向就是这套组合,组合式API写起来更灵活,Pinia比Vuex简单很多,没有mutation和getter这些概念绕来绕去。存量项目如果是Vue2,那就老老实实Vuex,没必要强行上Vue3,迁移成本不值当。

axios封装这一层在两个技术栈下几乎可以共用,主要差异在状态管理和路由守卫的写法。表里面整理了一下:

技术栈状态管理路由守卫环境变量
Vue3 + VitePinia,defineStore写法router.beforeEach,用法一致import.meta.env.VITE_XXX
Vue2 + VuexVuex,state/mutations/actionsrouter.beforeEach,用法一致process.env.VUE_APP_XXX
axios封装请求/响应拦截器,两版通用两版通用baseURL读取方式不同

所以后面讲的核心代码,除了状态管理部分我以Pinia为主,其余对于Vue2项目同样适用。登录功能的核心从来不是框架多新,而是token管理逻辑是不是清晰、错误处理是不是统一,这个思路跨版本都很稳定。

2. 封装axios从零到一:拦截器与错误处理

2.1 创建axios实例:baseURL与timeout怎么定

先新建一个src/utils/request.js文件,专门放axios实例相关代码。不建议直接在业务组件里引入原始的axios,统一走这个封装好的实例。

// src/utils/request.js import axios from 'axios' import { ElMessage } from 'element-plus' const service = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 15000 }) export default service

baseURL我用的是Vite的环境变量VITE_API_BASE_URL,开发环境一般设成/api,生产环境设成/prod-api之类的路径,具体怎么和转发配置联动,在第5章细说。timeout我习惯设15秒,不是越长越好,登录接口如果超过15秒没响应,大概率是网络或者服务端出了问题,继续等下去只会让用户干着急。这个时间可以根据项目实际情况调整,但别设成0(无限等待),线上出问题排查起来很麻烦。

另外提一句,如果是Vue2 + webpack项目,这里要改成process.env.VUE_APP_API_BASE_URL,读取环境变量的方式不同,其他逻辑一模一样。

2.2 请求拦截器:自动注入token与公共参数

请求拦截器在每次请求发出去之前执行,我们在这里做token注入。

service.interceptors.request.use( (config) => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }, (error) => { return Promise.reject(error) } )

这里有两个小细节。一是token的key最好统一定义成一个常量,比如const TOKEN_KEY = 'admin_token',然后封装getToken、setToken、removeToken三个函数放到单独文件里。不然项目里到处都是localStorage.getItem('token'),哪天想改成sessionStorage,全项目都得动。

二是Authorization: Bearer <token>这个格式,一定要跟后端约定好。Bearer是HTTP认证规范里的一种令牌类型,后端大概率也是用Spring Security这类框架解析的,如果不带Bearer,有些后端框架会直接拒绝。前后端联调的时候,这个格式必须提前确认,等接口全写完再改就麻烦了。

如果项目里有公共参数,比如渠道号、客户端版本号,也可以在这个拦截器里统一加到config.params上,业务代码里就不用每个接口单独传了。

2.3 响应拦截器:业务码判断与HTTP错误分开处理

响应拦截器是整个封装里最核心的部分,也是最容易写乱的地方。先看代码:

service.interceptors.response.use( (response) => { const res = response.data if (res.code !== 200) { if (res.code === 401) { handleUnauthorized() } else { ElMessage.error(res.message || '请求失败') } return Promise.reject(new Error(res.message || '请求失败')) } return res }, (error) => { const status = error.response?.status if (status === 401) { handleUnauthorized() } else { ElMessage.error(error.response?.data?.message || error.message || '网络异常') } return Promise.reject(error) } )

这里有个关键概念必须理解:HTTP状态码和业务码是两回事。很多后端设计是登录成功返回HTTP 200 + JSON数据,参数错误也返回HTTP 200,只是JSON里的业务码是400。这个设计下,请求永远走响应拦截器的第一个回调(success分支),所以你要在success分支里判断业务码。另一些后端会把参数错误直接返回HTTP 400,这时候请求会走第二个回调(error分支)。两种风格在开发中都很常见,所以封装的思路是:success分支处理业务码,error分支处理HTTP状态码,两者都要写,并且都要兼容401这种情况。

业务码的约定需要和后端对齐,常见的设计大概是:

业务码含义前端处理
200成功正常返回数据
400参数错误提示后端返回的message
401未登录/token过期清登录态,跳登录页
403权限不足提示无权限
500服务器内部错误提示服务器异常

响应拦截器里我习惯直接return res,也就是后端返回的完整body,而不是return response。这样业务层拿到的是{ code, message, data },只需要res.data就能取到真正的数据,少一层嵌套。很多同学刚接触时习惯return response,结果业务代码里得写response.data.data,看着就难受。

2.4 token过期处理与重复请求取消

token过期是登录功能最典型的场景。后端返回401,前端要做的不是弹个"未授权"就完事,而是要清理本地登录态再跳回登录页。这里要注意一个问题:页面里可能有多个接口同时发出,如果每个接口都返回401,就会触发多次"弹提示+跳转",用户体验很糟糕。

处理方式是用一个标志位控制,401只处理一次:

let isHandling401 = false function handleUnauthorized() { if (isHandling401) return isHandling401 = true localStorage.removeItem(TOKEN_KEY) router.push({ path: '/login', query: { redirect: router.currentRoute.value.fullPath } }) setTimeout(() => { isHandling401 = false }, 1000) }

再加一层防御:跳转登录页之后,把跳转前的路径放在query参数里,用户重新登录后可以自动回到之前想访问的页面,这个细节在后台管理系统里很加分。

另外还有一个容易被忽略的场景:用户连续点击提交按钮,同一个登录请求可能被发出去两次。axios有取消请求的机制,老版本用CancelToken,新版本推荐AbortController。简单可行的方案是用一个Map存当前在途请求的controller,重复请求先取消前一个:

const pendingMap = new Map() function getRequestKey(config) { const { method, url, params, data } = config return [method, url, JSON.stringify(params), JSON.stringify(data)].join('&') } // 请求拦截器里 const requestKey = getRequestKey(config) if (pendingMap.has(requestKey)) { pendingMap.get(requestKey).abort() } const controller = new AbortController() config.signal = controller.signal pendingMap.set(requestKey, controller) // 响应拦截器里, 无论成功失败都要删除记录 const requestKey = getRequestKey(response.config) pendingMap.delete(requestKey)

取消请求这个功能登录页不一定用得上,但等到做列表页搜索的时候非常有用:用户快速调整筛选条件,网络慢的时候会产生多个并发请求,后面的响应把前面的覆盖掉,页面数据就乱了。既然封装axios是为了"一套逻辑到处用",这个能力建议提前放进去。

3. 登录页开发:表单校验、登录接口调用与记住密码

3.1 表单验证规则的设计:前端校验只是体验优化

登录页的主流做法是用UI库的表单组件。Element Plus的el-form配合rules规则,可以很容易实现"必填、格式校验、密码长度"这类基础验证。

const rules = { username: [ { required: true, message: '请输入用户名', trigger: 'blur' }, { min: 3, max: 20, message: '长度在3到20个字符之间', trigger: 'blur' } ], password: [ { required: true, message: '请输入密码', trigger: 'blur' }, { min: 6, max: 32, message: '密码长度在6到32位之间', trigger: 'blur' } ] }

如果字段需要更复杂的规则,比如手机号,可以写自定义validator:

const validatePhone = (rule, value, callback) => { if (!value) { callback(new Error('请输入手机号')) } else if (!/^1[3-9]\d{9}$/.test(value)) { callback(new Error('手机号格式不正确')) } else { callback() } }

但是有一点必须说清楚:前端校验只是提升交互体验,不能作为安全依据。真正防刷、防暴力破解靠的是后端的校验和限流。就算前端校验全通过,接口该防的还是要防。

表单校验的触发时机也有讲究。我一般把校验放在提交时统一触发,而不是每个字段失去焦点就弹红色提示。一进去还没输入就满屏报错,用户体感很差。可以设置el-form的validate-on-rule-change为false,防止初始化时rule变化触发无意义的校验。

3.2 调用登录接口与loading状态管理

表单校验通过后,就可以调用登录接口了。这里有一个经常被提到的最佳实践:把API请求集中封装成一个一个的函数,而不是在组件里直接写axios请求。新建src/api/auth.js:

// src/api/auth.js import request from '@/utils/request' export function login(data) { return request({ url: '/auth/login', method: 'post', data }) } export function getUserInfo() { return request({ url: '/auth/me', method: 'get' }) } export function logout() { return request({ url: '/auth/logout', method: 'post' }) }

组件里调用:

const handleLogin = async () => { if (!loginFormRef.value) return const valid = await loginFormRef.value.validate().catch(() => false) if (!valid) return loading.value = true try { const res = await login(loginForm.value) setToken(res.data.token) userStore.setUserInfo(res.data.userInfo) router.push(route.query.redirect || '/') } finally { loading.value = false } }

几个关键点。第一,登录按钮要绑定loading状态,防止用户重复点击,同时给用户一个"正在提交"的反馈。第二,接口报错时不要在这里try catch里catch后静默处理,错误提示已经在响应拦截器统一弹过了,组件层只需要在finally里恢复loading状态,然后让错误自然抛出即可。第三,登录成功后的跳转目标要优先取route.query.redirect,因为路由守卫里可能存了用户原本想访问的页面,没有的话再默认跳首页。

3.3 记住账号与密码:localStorage还是sessionStorage

很多系统登录页会有一个"记住账号"的复选框。先说结论:token建议用sessionStorage还是localStorage,取决于产品需求。希望关闭浏览器后用户不需要重新登录,就存localStorage;希望更安全、关闭浏览器就失效,就存sessionStorage。两个方案都可行,但要跟产品和后端确认清楚,因为sessionStorage在多标签页之间是不共享的,这是个隐藏坑。

"记住账号"功能,我个人的做法是只存用户名,不存密码。存密码风险太高,本地存储任何内容都面临XSS攻击的风险,密码一旦被窃取,后果远比丢失一个token严重。如果产品经理坚持要"记住密码",至少要提醒他安全风险,加上"需要再次输入密码"之类的二次验证方案。折中的做法是:勾了记住账号,把用户名存进localStorage,下次进入登录页自动带出;没勾,就什么都别存。

顺带提一个体验点:用户输错密码或者账号不存在时,后端会返回"用户名或密码错误",这个提示要原样展示,不要在前端自作聪明地改成模糊提示。但接口失败时如果直接弹后端返回的英文或原始报错信息,体验也很差,所以后端在message字段里返回用户友好的文案,前端只管展示,这是最省心也最规范的做法。

4. 路由守卫与全局登录态:Pinia/Vuex状态管理实战

4.1 路由守卫:beforeEach如何拦住未登录用户

登录态管理不只是存token,还要让路由感知登录状态,这就是路由守卫的作用。Vue Router的全局前置守卫beforeEach每次路由跳转前都会执行,在这里做登录拦截再合适不过。

// src/router/index.js import router from './router' import { getToken } from '@/utils/auth' const whiteList = ['/login', '/404', '/register'] router.beforeEach((to, from, next) => { const token = getToken() if (token) { if (to.path === '/login') { next('/') } else { next() } } else { if (whiteList.includes(to.path)) { next() } else { next(`/login?redirect=${encodeURIComponent(to.fullPath)}`) } } })

whiteList白名单的意思是这些页面就算没登录也可以访问,通常是登录页、注册页、404页。如果不在白名单里且没有token,就强制跳登录页,同时把用户原本想去的路径塞进query,登录成功后跳回去。

这里有一个容易写错的地方:判断路由是否匹配白名单,不要直接用whiteList.includes(to.path)去匹配动态路由,比如/user/123这种带参数的路径,应该用to.path和to.matched结合判断,或者用正则匹配。后台管理系统最常见的情况是权限路由还没加载完就跳转了,后面动态路由部分会专门讲到。

4.2 登录态存放:Pinia/Vuex与本地存储各司其职

登录态的完整管理,要分两层:全局状态里存一份,供所有组件响应式读取;本地存储里也存一份,保证刷新页面后状态不丢。两者各司其职,不要互相替代。

以Pinia为例:

// src/stores/user.js import { defineStore } from 'pinia' import { getToken, setToken, removeToken } from '@/utils/auth' export const useUserStore = defineStore('user', { state: () => ({ token: getToken() || '', userInfo: {} }), getters: { isLoggedIn: (state) => !!state.token, displayName: (state) => state.userInfo?.name || '' }, actions: { setToken(token) { this.token = token setToken(token) }, setUserInfo(info) { this.userInfo = info }, logout() { this.token = '' this.userInfo = {} removeToken() } } })

注意state里初始化token时直接读了一遍本地存储,这样刷新页面后store能马上恢复登录态。很多同学踩过"刷新后登录态丢失"的坑,根源就是store只在运行时被登录逻辑赋值,刷新之后store重新初始化,数据全丢了。解决思路就是上面这个:store初始化时从本地存储读。如果项目用了pinia的持久化插件,比如pinia-plugin-persistedstate,那可以自动做这层恢复,但手写getToken初始化也不复杂,值得掌握。

Vue2 + Vuex写法虽然长一点,但核心逻辑完全一致:state存token,mutations里同步本地存储,actions里调接口和触发mutations。

4.3 动态路由与权限扩展

很多后台系统的菜单是跟权限挂钩的,登录成功后拿到当前用户的角色和菜单列表,把菜单对应的路由动态注册进去,而不是一次性在路由表里写死所有页面。Vue Router提供了router.addRoute方法,用来在运行期添加路由。

基本思路是:登录成功后,调用getUserInfo接口获取用户信息和权限码列表,再根据权限动态注册路由:

// 登录成功之后 const res = await getUserInfo() userStore.setUserInfo(res.data.userInfo) // 根据后端返回的菜单结构生成路由对象 const dynamicRoutes = generateRoutes(res.data.menus) dynamicRoutes.forEach((route) => { router.addRoute(route) }) // 跳到目标页; 如果直接addRoute后立即跳转, 需要处理"路由未匹配"的问题 next({ ...to, replace: true })

这一步设置好了,beforeEach里的逻辑也要跟着调整:如果用户已登录且用户信息为空,先调接口拉取信息并注册动态路由再放行。这样刷新页面后,动态路由重建的流程不会断。第一次做会绕一点,但权限系统是后台管理系统的标配,建议把这段逻辑和登录流程一起梳理清楚。

这里也顺带回答很多朋友关心的"vue router pinia eslint + prettier vitest单元测试选什么"的问题:单元测试不是登录功能的必要组成部分,但如果你在搭团队项目脚手架,Vitest + Vue Test Utils是Vue3生态的推荐组合,可以把登录相关的工具函数(比如token存取、校验规则)写几个单测,后面改起来放心很多。

5. 联调部署必坑:开发环境接口转发与生产环境nginx配置

5.1 Vite开发环境接口转发配置

前后端分离开发时,前端跑在localhost:5173,后端跑在localhost:8080,浏览器直接跨域,接口根本调不通。解决办法不是让后端开CORS,而是在开发环境用Vite的server.proxy把请求转发到后端。

// vite.config.js export default defineConfig({ server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') } } } })

这段配置的意思是:前端发起的、以/api开头的请求,都会被Vite开发服务器转发到http://localhost:8080,同时把路径里的/api前缀去掉。配合axios的baseURL: '/api',前端代码里写的/auth/login,实际请求变成/api/auth/login,经转发后到达后端是/auth/login。

有一个常见困惑:为什么要有/api这个前缀?如果后端接口本身没有/api路径,rewrite去掉它;如果后端有,就把rewrite那行去掉。前缀的作用是让Vite能区分"哪些请求要转发、哪些请求是静态资源",也能配合nginx做生产环境转发。前后端要提前统一这个前缀规则,不然开发环境通了、生产环境又断了。

5.2 环境变量联动:VITE_API_BASE_URL怎么用

axios的baseURL是从环境变量里读的,Vite约定只有以VITE_开头的变量才会暴露给前端代码。项目根目录下建.env.development和.env.production两个文件:

# .env.development VITE_API_BASE_URL=/api # .env.production VITE_API_BASE_URL=/prod-api

开发环境里baseURL是/api,Vite启动后自动转发。构建生产包时,Vite会把VITE_API_BASE_URL=/prod-api编译进去,生产代码里所有请求都发到/prod-api开头,再由部署环境的nginx来转发。这样前端代码里永远只写相对路径,不需要区分"我到底该请求哪个域名",打包产物也能一份走天下,部署到哪套环境就由nginx决定转发到哪个后端。

踩过的坑提醒:改了.env文件必须重启Vite开发服务器,改完不重启拿不到新变量。另外不要把后端地址直接写进生产环境变量,比如VITE_API_BASE_URL=http://x.x.x.x:8080,这样打包产物里会暴露后端服务地址,而且浏览器直连后端还会跨域,生产环境必须走nginx转发。

5.3 生产部署:nginx与history路由配置

前端项目构建后是一堆静态文件,部署时通常用nginx托管。这里有两个关键配置:SPA路由的history模式和接口转发。

server { listen 80; server_name yourdomain.com; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } location /prod-api/ { proxy_pass http://localhost:8080/; } }

第一段try_files $uri $uri/ /index.html是给Vue Router history模式用的。用户在地址栏直接输入/user/list访问,nginx在磁盘上找不到这个文件,就要回退到index.html,让前端路由接管。如果没有这行,刷新页面就会404。

第二段是接口转发:所有/prod-api/开头的请求,nginx转发到后端的http://localhost:8080/,后面的/表示把/prod-api/前缀去掉再拼接后端路径。这和开发环境Vite的rewrite逻辑是对应的,开发环境用/api,生产环境用/prod-api,只是前缀名字不同,整个链路就通起来了。部署时记得检查nginx的proxy_pass有没有配好,以及新版nginx配置语法是否支持proxy_pass http://localhost:8080/;这种带上末尾斜杠的写法。

6. 常见问题与排查实录:登录场景的坑我都替你踩过了

6.1 跨域报错:请求根本没出去

现象是浏览器控制台报CORS policy相关的错,Network里看到请求状态是(failed),或者Provisional headers are shown字样。这是我见过最多的问题,原因通常有三个:

  • 开发环境:axios的baseURL和Vite proxy的路径对不上。比如baseURL写死了http://localhost:8080,绕过了Vite的转发,浏览器直连后端就跨域。正确做法是baseURL只写/api,让Vite去转发。
  • 开发环境:改了vite.config.js没重启服务。proxy配置只在服务启动时读一次。
  • 生产环境:nginx没配置接口转发,前端请求打到静态服务器上没有对应接口。

排查思路很简单:先看Network里的完整请求URL。如果URL是http://localhost:5173/api/auth/login且报CORS,说明Vite转发没生效;如果是http://localhost:8080/auth/login,说明你的baseURL配的不对,绕开了转发。

6.2 刷新后登录态丢失

现象:登录成功后一切正常,一刷新页面就跳回登录页。原因基本是store里只存了内存态,刷新后store重新初始化,token没了。解决办法就是前面4.2节说的:Pinia初始化state时从localStorage同步读取token,或者用持久化插件。还有另一个变体:token存了,但路由守卫里读取token的key和存储token的key不一致,最常见的就是一个用了token,另一个用了access_token。这种bug很隐蔽,建议token存取工具函数统一收口到一个src/utils/auth.js,全项目只从这里面读写key。

6.3 并发请求重复弹出401

一个页面同时发三个请求,token过期后三个请求全部返回401,响应拦截器弹三次错误、跳转三次登录页。用户体验很糟糕,还会出现路由重复跳转的警告。解法前面讲过:用一个isHandling401的标志位,只有第一次401才执行清理和跳转逻辑。更严谨的做法是用请求队列,把401期间的其他请求拦截住,等token刷新后再重新发送,这是token刷新机制的内容,多数系统可以先不做,加个标志位就够了。

6.4 常见问题速查表

问题原因解决办法
登录接口跨域baseURL写死后端地址 / proxy配置没生效 / nginx未转发统一用/api前缀 + Vite/nginx转发
请求头没带token请求拦截器没注入 / token key不匹配检查拦截器代码和token存取函数
刷新后跳登录页store内存态丢失 / 读写key不一致state初始化时同步localStorage
多个401重复提示多个请求同时过期isHandling401标志位/请求队列
登录成功跳转不生效路由跳转被守卫拦截next里传redirect,处理动态路由合并
打包后接口404baseURL和生产环境值不一致 / nginx没配转发检查.env.production和nginx配置
表单一直提示校验失败rules字段名和model字段名不一致检查v-model绑定的属性名

6.5 实践补充:登录态校验放在后端,不要只信任前端

最后提醒一句和安全相关的事:前端判断是否登录的token存在localStorage里,这个token的本质是后端签发的凭据,它是否有效、是否过期,必须由后端判断。前端路由守卫的作用只是改善体验,不具备安全校验能力。用户完全可以绕过前端路由守卫直接调后端接口,所以后端每一个需要登录态的接口都要校验token。登录功能整套做完之后,建议做一次自测:用无痕窗口、直接改掉localStorage里的token、伪造一个过期token分别测一遍,看看后端能不能拦得住,这才算完整。

登录功能做完之后往深一步走,可以选择的方向很多:token自动刷新、动态路由权限、多角色菜单、单点登录,甚至配合WebAuthn做无密码登录。但不管扩展什么,封装axios + 路由守卫 + 登录态管理这套骨架都不会变。我个人的习惯是项目初始阶段就先把这三个部分搭扎实,宁可多花半天,后面每个业务模块都能吃到红利。如果你也在搭后台管理系统,我建议把登录这块当作第一个完整样板来打磨,它几乎涵盖了前端工程化最核心的链路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 5:33:24

Win7口令登录调试:从内核断点到SAM校验的完整排查

简介&#xff1a;一份关于Windows 7系统口令登录过程调试的实战型文档&#xff0c;面向Windows安全调试与系统分析人员&#xff0c;聚焦于通过Windbg追踪Win7登录流程&#xff0c;理清Winlogon、Lsass、LogonUI之间的RPC调用与密码验证关键环节。资源包内仅含1个docx格式说明文…

作者头像 李华
网站建设 2026/9/30 5:32:06

TraeAI + UE5:自然语言驱动的横版跳跃游戏开发实战

1. 背景与核心概念最近朋友圈和开发者社区里讨论最热烈的话题&#xff0c;就是 AI 对游戏开发流程的冲击。以前我们想做一个横版跳跃游戏&#xff0c;哪怕只是原型&#xff0c;也要经历&#xff1a;场景建模、角色动画、输入系统、碰撞检测、关卡设计这一整套流程&#xff0c;少…

作者头像 李华
网站建设 2026/9/30 5:31:43

DeepSeek低显存CT智能诊断:量化部署与特征提取实战

简介&#xff1a;面向希望在有限显存条件下借助DeepSeek实现CT片智能诊断的开发者与医学影像技术人员&#xff0c;这是一份系统讲解医疗影像分析落地方案的PDF文档。文档共二十页&#xff0c;先剖析医疗影像数据与模型显存占用等核心挑战&#xff0c;再详解DeepSeek轻量化架构&…

作者头像 李华
网站建设 2026/9/30 5:31:42

AI Agent Harness 七子系统:从零搭建稳定智能体骨架

1. 拆开 AI Agent 的“驾驶舱”&#xff1a;Harness 到底管什么很多人第一次听到 Harness 这个词&#xff0c;脑子里浮现的是汽车线束或者测试框架。放在 AI Agent 的语境里&#xff0c;它其实更接近“驾驶舱”或者“总装线”——模型是发动机&#xff0c;工具是车轮&#xff0…

作者头像 李华
网站建设 2026/9/30 5:31:27

PyTorch实验可复现性实战:从随机种子到依赖锁定与配置归档

有件事我印象极深&#xff1a;去年跑一个图像分类实验&#xff0c;本地训练出来的准确率是91.2%&#xff0c;兴致勃勃把代码原样发到另一台服务器&#xff0c;结果成了88.7%&#xff0c;换到第三台机器又变成了90.1%。训练脚本一字没改&#xff0c;数据是同一份预处理好的&…

作者头像 李华
网站建设 2026/9/30 5:31:18

6个C++控制台小游戏代码:Dev-C++可运行可复制练手项目

玩C的人大概都有这么一段经历&#xff1a;语法书翻了一本又一本&#xff0c;指针、引用、虚函数背得滚瓜烂熟&#xff0c;可真让你写点东西&#xff0c;脑子里却一片空白。我当年也是这样&#xff0c;直到有人甩给我一句话——别啃书了&#xff0c;写几个小游戏&#xff0c;一个…

作者头像 李华