登录交互的实现:基于 Vuex 与 localStorage 的表单校验与 axios 请求链路拆解

发布时间:2026/10/2 6:47:57
登录交互的实现:基于 Vuex 与 localStorage 的表单校验与 axios 请求链路拆解 1. 登录交互到底难在哪从表单校验到 axios 请求链路登录交互这件事看起来就是「输入账号密码点按钮跳页面」但真正落到 Vue 项目里它其实是一条完整的链路表单校验规则怎么设计、校验通过后怎么发请求、请求怎么二次封装、登录态怎么存、刷新页面怎么不丢、失效了怎么跳回登录页。任何一个环节没接好都会出现「点了没反应」「刷新就退出」「报 401 但不知道去哪」这类问题。我这次要拆的就是这条链路关键词是登录交互、表单校验、axios、vuex、localstorage。适合谁看适合已经会写 Vue 组件、但登录这块总是拼不完整的前端同学也适合想把「校验 → 请求 → 状态 → 持久化」串成一条线的项目实践者。下面会给出可复制的校验规则配置、Vuex store 模块代码、axios 拦截器示例以及登录态刷新与失效跳转的验证步骤。先说清楚整体结构避免你写着写着迷路。一个完整的登录交互通常分四层第一层是视图层也就是 Login.vue负责数据绑定、表单校验、按钮点击。第二层是请求层也就是 api/index.js 加 utils/request.js负责把接口调用统一收口。第三层是状态层也就是 Vuex 的 store负责保存 userInfo 这类全局登录态。第四层是持久层也就是 localStorage 封装负责让刷新页面后登录态还在。这四层的关系是视图层校验通过后调请求层请求层拿到数据交给状态层状态层写入的同时通过持久层落盘。理解了这个流向后面每一步你都能对上号。很多人卡住的地方在于「校验和请求混在一起」。比如把 validate 的回调里塞一大堆逻辑接口调用、状态提交、路由跳转全堆在组件里。短期能跑但一旦要加「记住我」「token 过期刷新」「多角色跳转」组件就会变成一团。所以我的建议是校验只负责判断能不能提交请求只负责拿数据状态只负责存跳转只负责导航。职责分开后面才好扩展。还有一个高频误区是「只存 Vuex 不存 localStorage」。Vuex 是内存状态页面一刷新就回到初始值用户会发现自己明明登录了刷新一下又回到登录页。这就是为什么必须配合 localStorage 做持久化。反过来只存 localStorage 不存 Vuex 也不行因为组件里到处读 localStorage 会很散而且响应式更新拿不到。正确做法是两者结合Vuex 作为运行时状态localStorage 作为持久化备份初始化时从 localStorage 读回 Vuex。下面我会按「前置准备 → 可复制配置 → 验证请求 → 常见报错 → 收尾」的顺序展开每一步都给到能直接用的代码。你可以边看边对照自己的项目改。2. 前置准备TaoToken 接入与项目环境确认在正式写登录链路之前先把「请求要发到哪」这件事定下来。如果你的项目需要调用大模型能力或者你想用一个统一的 API 网关来管理请求可以先把 TaoToken 的接入准备好。官网地址是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 注意 API 这个地址后面不加 UTM 参数。这里要强调一点TaoToken 是合规的 API 服务入口不是所谓的「中转」工具我们只是把它当作一个标准的请求目标来配置。你需要在控制台创建 API Key然后把它写进项目的环境变量里不要硬编码到源码中提交到仓库。具体操作路径是这样的先打开 https://taotoken.net/api-keys 创建密钥拿到形如 sk-xxxx 的 Key然后打开接入文档 https://taotoken.net/doc 对照参数说明如果你要验证模型是否通可以用模型对话页面 https://taotoken.net/chat 先手动发一条消息确认如果是长期编码或 Agent 场景可以看 Coding Plan https://taotoken.net/coding-plan 。Claude Code 相关的接入说明在 https://taotoken.net/ClaudeCodeAnthropic 。项目环境这边你需要确认几件事。第一Vue 版本本文以 Vue 3 Vite 为主Vue 2 的同学把 createStore 换成 new Vuex.Store 即可。第二是否装了 Vuex没装的话执行npm install vuexnext第三是否装了 axiosnpm install axios第四UI 库本文示例用 Element Plus 的表单组件因为它自带 validate 方法和登录表单校验配合最顺npm install element-plus装完之后在 main.js 里全局挂载import { createApp } from vue import App from ./App.vue import router from ./router import store from ./store import ElementPlus from element-plus import element-plus/dist/index.css import api from ./api const app createApp(App) app.config.globalProperties.$api api app.use(router).use(ElementPlus).use(store).mount(#app)注意这里 app.config.globalProperties.$api api 这一行它把 api 模块挂到了全局组件里就能用 this.$api.login(...) 调用不用每个组件都 import 一遍。这是登录交互里很实用的一步能减少重复引入。环境变量方面建议在项目根目录建 .env.development 和 .env.production分别写# .env.development VITE_API_BASE_URL/api # .env.production VITE_API_BASE_URLhttps://taotoken.net/api然后在 request.js 里用 import.meta.env.VITE_API_BASE_URL 读取。这样开发环境走本地代理生产环境走正式地址切换不用改代码。最后确认一下目录结构建议长这样src/ api/ index.js utils/ request.js storage.js store/ index.js mutations.js views/ Login.vue Welcome.vue router/ index.js这个结构不是强制的但把请求、存储、状态、视图分开后面排查问题会轻松很多。前置准备好之后我们就可以进入可复制配置环节了。3. 可复制配置校验规则、Vuex store 与 axios 拦截器这一节是全文的核心我会把三块配置完整给出来表单校验规则、Vuex store 模块、axios 拦截器。你可以直接复制到项目里改。3.1 表单校验规则配置先看 Login.vue 里的数据绑定和校验规则。表单数据对象data() { return { user: { userName: , userPwd: }, rules: { userName: [ { required: true, message: 请输入用户名, trigger: blur }, { min: 3, max: 12, message: 用户名长度 3 到 12 位, trigger: blur } ], userPwd: [ { required: true, message: 请输入密码, trigger: blur }, { pattern: /^[A-Za-z0-9_]{6,20}$/, message: 密码为 6 到 20 位字母数字下划线, trigger: blur } ] } } }模板里给表单绑定 ref 和 rulesel-form refuserForm :modeluser :rulesrules label-width80px el-form-item label用户名 propuserName el-input v-modeluser.userName placeholder请输入用户名 / /el-form-item el-form-item label密码 propuserPwd el-input v-modeluser.userPwd typepassword placeholder请输入密码 / /el-form-item el-form-item el-button typeprimary clicklogin登录/el-button /el-form-item /el-form注意 prop 必须和 rules 里的字段名一致否则校验不生效。这是最常见的坑之一。3.2 Vuex store 模块配置store/index.jsimport { createStore } from vuex import mutations from ./mutations import storage from ../utils/storage const state { userInfo: || storage.getItem(userInfo) } export default createStore({ state, mutations })store/mutations.jsimport storage from ../utils/storage export default { saveUserInfo(state, userInfo) { state.userInfo userInfo storage.setItem(userInfo, userInfo) }, clearUserInfo(state) { state.userInfo storage.removeItem(userInfo) } }utils/storage.jsexport default { setItem(key, value) { localStorage.setItem(key, JSON.stringify(value)) }, getItem(key) { const value localStorage.getItem(key) try { return value ? JSON.parse(value) : } catch (e) { return } }, removeItem(key) { localStorage.removeItem(key) } }这里有个细节localStorage 只能存字符串所以存的时候要 JSON.stringify取的时候要 JSON.parse。如果直接存对象取出来会是 [object Object]这是新手常踩的坑。3.3 axios 拦截器配置utils/request.jsimport axios from axios import store from ../store import router from ../router const service axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || /api, timeout: 10000 }) service.interceptors.request.use( config { const userInfo store.state.userInfo if (userInfo userInfo.token) { config.headers.Authorization Bearer ${userInfo.token} } return config }, error Promise.reject(error) ) service.interceptors.response.use( response { const res response.data if (res.code 401) { store.commit(clearUserInfo) router.push(/login) return Promise.reject(new Error(登录已失效)) } return res }, error { if (error.response error.response.status 401) { store.commit(clearUserInfo) router.push(/login) } return Promise.reject(error) } ) export default serviceapi/index.jsimport request from ../utils/request export default { login(params) { return request({ url: /users/login, method: post, data: params }) } }这三块配置连起来就形成了「校验通过 → 发请求 → 拦截器带 token → 响应拦截处理 401 → 状态和持久化同步」的完整闭环。下面我们验证一下请求能不能跑通。4. 验证请求与成功结果登录态刷新与失效跳转配置写完了得验证它真的能跑。这一步我分成三个验证点登录请求是否成功、刷新后登录态是否还在、失效后是否跳回登录页。4.1 登录请求验证Login.vue 里的 login 方法methods: { login() { this.$refs.userForm.validate((valid) { if (!valid) return this.$api.login(this.user).then((res) { console.log(登录数据, res) this.$store.commit(saveUserInfo, res.data) this.$router.push(/welcome) }).catch((err) { console.error(登录失败, err) }) }) } }点击登录后打开浏览器控制台 Network 面板应该能看到一条 POST 请求路径是 /api/users/login请求体里带着 userName 和 userPwd。如果返回 200 且 data 里有 token 和用户信息说明请求链路通了。4.2 刷新后登录态验证登录成功后打开 Application 面板Local Storage 里应该能看到 userInfo 这个 key值是 JSON 字符串。然后按 F5 刷新页面观察 Vuex 里的 userInfo 是否还在。因为 store/index.js 里初始化时写了 storage.getItem(userInfo)所以刷新后 Vuex 会从 localStorage 读回数据登录态不会丢。你可以加一个简单的守卫来验证router.beforeEach((to, from, next) { const userInfo store.state.userInfo if (to.path ! /login !userInfo) { next(/login) } else { next() } })刷新后如果还能停在 /welcome说明持久化生效了。4.3 失效跳转验证手动把 localStorage 里的 userInfo 删掉或者把 token 改成一个无效值然后触发一个需要鉴权的请求。响应拦截器检测到 401 后会执行 clearUserInfo 并 router.push(/login)。你可以在控制台看到页面自动跳回登录页同时 localStorage 里的 userInfo 被清空。这一步验证的是「失效跳转」的闭环。很多项目只做了登录没做失效处理结果 token 过期后用户卡在页面上一直报错。加上响应拦截器之后这个问题就解决了。4.4 后端接口联调要点如果你要连后端注意几个点。后端路由如果加了 router.prefix(/api)前端代理就写proxy: { /api: { target: http://localhost:3000, changeOrigin: true } }如果后端没加 /api 前缀代理里要加 rewriteproxy: { /api: { target: http://localhost:3000, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ) } }数据库连接这块用 mongoose 连 MongoDB配置文件里写module.exports { URL: mongodb://127.0.0.1:27017/manager }db.js 里 mongoose.connect(config.URL)监听 open 和 error 事件打印日志。模型定义用 mongoose.Schema导出时 mongoose.model(users, userSchema, users)。这些和登录交互是配套的后端通了前端才能拿到真实数据。验证通过后我们来看常见报错。5. 常见报错排查401、代理失败与 choices 读取异常这一节我按真实报错来对照你遇到哪个就查哪个。5.1 401 Unauthorized现象请求返回 401控制台提示登录已失效。原因通常是 token 没带上、token 过期、或者后端校验失败。排查步骤先看 Network 里请求头有没有 Authorization 字段再看 store.state.userInfo 里有没有 token如果都有检查后端是否要求 Bearer 前缀。响应拦截器里已经处理了 401 跳转确认 router.push(/login) 能正常执行。5.2 local proxy failed / 代理失败现象请求发不出去控制台报 proxy error 或 ECONNREFUSED。原因一般是后端没启动、端口不对、或者代理配置写错。排查确认后端在 3000 端口跑着确认 vite.config.js 里 target 写的是 http://localhost:3000确认 baseURL 和代理前缀一致。如果后端加了 /api 前缀前端 baseURL 也写 /api两边要对齐。5.3 reading choices 报错现象调用模型接口时控制台报 Cannot read properties of undefined (reading choices)。这通常是因为响应结构和你预期的不一样比如返回的是 error 对象而不是正常结果。排查先 console.log 完整响应确认 data 结构检查请求参数里的 model ID 是否正确确认 API Key 有效。如果你用的是 TaoToken 的模型对话可以先去 https://taotoken.net/chat 手动验证一次确认 Key 和模型 ID 没问题再回到代码里调。5.4 OAuth 相关报错现象接入第三方登录或 Claude Code 时报 OAuth 认证失败。排查确认回调地址配置正确确认 client_id 和 client_secret 匹配确认授权范围包含所需权限。Claude Code 的接入细节可以参考 https://taotoken.net/ClaudeCodeAnthropic 里面有完整的配置说明。5.5 校验不生效现象点了登录按钮没输入内容也能提交。原因通常是 el-form 的 prop 和 rules 字段名不一致或者 ref 名字写错。排查确认 refuserForm 和 this.$refs.userForm 一致确认 propuserName 和 rules.userName 一致确认 validate 回调里 valid 为 false 时 return 了。5.6 刷新后登录态丢失现象登录后刷新页面又回到登录页。原因通常是只存了 Vuex 没存 localStorage或者 store 初始化时没从 storage 读。排查确认 mutations 里调用了 storage.setItem确认 state 初始化时写了 storage.getItem(userInfo)确认 storage.js 的 JSON.parse 没报错。5.7 三件套配置检查如果你在项目里用到了 CC Switch、Cline MCP 或 Codex auth.json记得把三件套写全Base URL、Key、Model ID。Base URL 用 https://taotoken.net/api Key 用控制台创建的 sk-xxxxModel ID 按文档填。缺任何一个都会导致请求失败。排查完这些基本能覆盖登录交互里 90% 的问题。6. 收尾把登录链路当成一个整体来维护写到这里登录交互的完整链路已经拆完了。从表单校验规则设计到 Vuex 状态管理再到 axios 请求封装和 localStorage 持久化每一层都有它明确的职责。我自己的经验是登录这块最怕的不是写不出来而是写得太散——校验、请求、状态、跳转全堆在一个组件里后面加个「记住我」或者「token 刷新」就要大改。所以我的建议是一开始就把 api、utils、store 分好目录把拦截器写进 request.js把持久化写进 storage.js。这样后面不管加什么需求都只是在这几个文件里改组件层几乎不用动。如果你需要验证模型接口是否通可以先去 https://taotoken.net/chat 手动发一条消息如果是长期编码或 Agent 场景可以看 https://taotoken.net/coding-plan 接入文档在 https://taotoken.net/doc API Key 在 https://taotoken.net/api-keys 。把这些入口收藏好下次配环境就不用到处找了。最后留一个实用技巧在 router.beforeEach 里做登录守卫时记得把 /login 本身排除掉否则会出现「跳登录页又被守卫拦回登录页」的死循环。这个坑我见过太多次加一行 to.path ! /login 就能解决。