前端通用 Pinia Token 全流程操作指南(常见常用版)
本文梳理 Vue3 官方推荐状态管理库 Pinia 下的 Token 操作逻辑,剥离具体项目细节,聚焦「获取→存储→使用→过期→清除」的核心生命周期,结合 Pinia 「无 mutations、actions 直接异步、模块化更简单」的特性,每个步骤均标注「通用场景+Pinia 通用方案+注意事项」,可直接作为开发速查表。
前置说明:Pinia 与 Vuex 的核心区别(Token 操作相关)
| 修改状态的方式 | 必须通过 commit 调用 mutations | actions 中可直接修改 state |
| 异步处理 | actions 处理异步,mutations 同步 | actions 可直接写 async/await |
| 模块化 | 需开启 namespaced: true 隔离 | 天然模块化,无需额外配置 |
| TypeScript 支持 | 配置复杂 | 原生支持,类型推导友好 |
一、第一步:登录成功获取并存储 Token
通用场景
用户通过账号密码/验证码等方式登录,后端验证通过后返回 Token,需将 Token 存入 Pinia(全局共享)和本地存储(刷新不丢)。
Pinia 通用方案
代码示例
// 1. 定义 Pinia Store(src/stores/user.js)
import { defineStore } from 'pinia'
import { loginAPI } from '@/api/user' // 假设已封装好登录接口
export const useUserStore = defineStore('user', {
// state:必须是函数返回对象(避免组件间共享状态)
state: () => ({
token: localStorage.getItem('token') || '' // 初始化时从本地存储读取
}),
// actions:直接处理异步,可直接修改 state
actions: {
async login(loginForm) {
// 调用登录接口
const res = await loginAPI(loginForm)
// 从接口返回中提取 Token(具体字段看后端文档)
const token = res.data.token
// 直接修改 state(无需 commit!Pinia 核心优势)
this.token = token
// 同时存入本地存储
localStorage.setItem('token', token)
}
}
})
// 2. 组件中调用(Vue3 组合式 API 示例)
<script setup>
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
const handleLogin = async () => {
try {
await userStore.login({ username: 'admin', password: '123456' })
alert('登录成功')
// 跳转到首页(需结合路由)
} catch (err) {
alert('登录失败:' + (err.response?.data?.msg || '未知错误'))
}
}
</script>
注意事项
二、第二步:请求接口时自动携带 Token
通用场景
每次发起业务请求时,自动从 Pinia Store 中读取 Token 并添加到请求头。
Pinia 通用方案
代码示例
// src/utils/request.js(Axios 封装)
import axios from 'axios'
import { createPinia } from 'pinia'
import { useUserStore } from '@/stores/user'
// 初始化 Pinia(组件外使用 Store 必须)
const pinia = createPinia()
const userStore = useUserStore(pinia)
const service = axios.create({
baseURL: '/api',
timeout: 10000
})
// 请求拦截器:自动带 Token
service.interceptors.request.use(
(config) => {
// 从 Pinia Store 中读取 Token
const token = userStore.token
if (token) {
// 添加到请求头(具体字段看后端文档)
config.headers['Authorization'] = `Bearer ${token}`
}
return config
},
(error) => Promise.reject(error)
)
export default service
注意事项
三、第三步:处理 Token 过期
通用场景
Token 过期后,后端返回 401 状态码,需清除 Token 并跳转到登录页。
Pinia 通用方案
代码示例
// 1. Pinia Store 补充 logout action(src/stores/user.js)
export const useUserStore = defineStore('user', {
state: () => ({ token: localStorage.getItem('token') || '' }),
actions: {
async login(loginForm) { /* 登录逻辑 */ },
// 登出/清除 Token action
logout() {
// 直接修改 state
this.token = ''
// 清除本地存储
localStorage.removeItem('token')
}
}
})
// 2. 响应拦截器补充(src/utils/request.js)
import router from '@/router' // 需结合路由
service.interceptors.response.use(
(response) => response.data,
(error) => {
if (error.response && error.response.status === 401) {
alert('登录已过期,请重新登录')
// 调用 Pinia Store 的 logout action
userStore.logout()
// 跳转到登录页
router.replace('/login')
} else {
alert('请求失败:' + (error.response?.data?.msg || '网络错误'))
}
return Promise.reject(error)
}
)
注意事项
四、第四步:用户主动登出
通用场景
用户点击“退出登录”按钮,需清除所有登录相关数据。
Pinia 通用方案
代码示例
<script setup>
import { useUserStore } from '@/stores/user'
import { useRouter } from 'vue-router'
const userStore = useUserStore()
const router = useRouter()
const handleLogout = () => {
if (confirm('确定要退出登录吗?')) {
// 调用 Pinia Store 的 logout action
userStore.logout()
alert('退出成功')
router.replace('/login')
}
}
</script>
注意事项
五、第五步:路由权限控制(进阶但常用)
通用场景
未登录用户不能访问受保护页面(如首页、个人中心),已登录用户不能访问登录页。
Pinia 通用方案
代码示例
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import { createPinia } from 'pinia'
import { useUserStore } from '@/stores/user'
const routes = [
{ path: '/login', component: () => import('@/views/Login.vue') },
{ path: '/home', component: () => import('@/views/Home.vue'), meta: { requiresAuth: true } }
]
const router = createRouter({
history: createWebHistory(),
routes
})
// 初始化 Pinia(路由守卫中使用 Store 必须)
const pinia = createPinia()
// 全局前置路由守卫
router.beforeEach((to, from, next) => {
const userStore = useUserStore(pinia)
const token = userStore.token
const whiteList = ['/login', '/404']
if (token) {
to.path === '/login' ? next('/home') : next()
} else {
whiteList.includes(to.path) ? next() : next('/login')
}
})
export default router



