找回密码
 立即注册
首页 业界区 安全 nprogress插件的安装和使用,登录业务的代码编写,统一设 ...

nprogress插件的安装和使用,登录业务的代码编写,统一设置token,

涅牵 2025-5-30 19:21:30
nprogress插件的安装和使用,登录业务的代码编写,统一设置token,处理页面访问登录状态和登录过期。pinia-plugin-persistedstate插件的安装和使用。
  1. npm install nprogress npm i --save-dev @types/nprogress
复制代码
新建services/user.ts mkdir src/services && touch src/services/user.ts
  1. /** * 用户相关API & 业务逻辑(service层) * - 只负责异步API、数据转换、复杂流程 * - 类型全部解耦,只用import type { UserInfo } from ... */ import http from '@/utils/http' import type { UserInfo } from '@/store/types/user' /** * 用户登录 * @param username 用户名 * @param password 密码 * @returns 用户信息 */ export function loginApi(username: string, password: string) { return http.post('/api/login', { username, password }) } /** * 获取当前用户信息 */ export function fetchUserApi() { return http.get('/api/user') } /** * 用户登出 */ export function logoutApi() { return http.post('/api/logout') }
复制代码
对应的store层代码
  1. /** * 用户Pinia Store(只做状态与同步变更) * - Google标准:全部类型解耦,最大注释 * - 只做本地状态管理/变更/展示,异步交由service */ import { defineStore } from 'pinia' import type { UserInfo } from '../types/user' import { loginApi, fetchUserApi, logoutApi } from '@/services/user' export const useUserStore = defineStore('user', { /** * 1. State:本地用户信息 */ state: (): UserInfo => ({ id: '', name: '访客', email: '', plugin.php?id=ror_grab_cnblogs&act=pic&url=avatarUrl: '', isLoggedIn: false }), /** * 2. Actions:同步变更+异步业务(推荐复杂业务分离出service层) */ actions: { /** * 本地登录变更(仅存数据,不调接口) * @param info 登录成功后的用户信息 */ setLogin(info: Omit) { this.id = info.id this.name = info.name this.email = info.email this.plugin.php?id=ror_grab_cnblogs&act=pic&url=avatarUrl = info.plugin.php?id=ror_grab_cnblogs&act=pic&url=avatarUrl ?? '' this.isLoggedIn = true }, /** * 本地登出变更(重置所有状态) */ setLogout() { this.id = '' this.name = '访客' this.email = '' this.plugin.php?id=ror_grab_cnblogs&act=pic&url=avatarUrl = '' this.isLoggedIn = false }, /** * 登录流程(调API + 本地写入) */ async login(username: string, password: string) { const user = await loginApi(username, password) this.setLogin(user) // 可选:本地存储token等 }, /** * 拉取当前用户信息并写入本地 */ async fetchUser() { const user = await fetchUserApi() this.setLogin(user) }, /** * 登出流程(调API + 本地清理) */ async logout() { await logoutApi() this.setLogout() // 可选:清理token } }, /** * 3. Getters:只做本地展示/状态派生 */ getters: { /** * 用户名首字母大写 */ displayName: (state): string => state.name ? state.name.charAt(0).toUpperCase() + state.name.slice(1) : '访客' } persist: true // 开启自动持久化,所有state字段自动同步到本地 })
复制代码
layout层代码
mkdir src/layouts && touch src/layouts/MainLayout.vue && touch src/layouts/AuthLayout.vue
  1. [/code] [code]
复制代码
对应componets层代码
mkdir src/components && touch src/components/AppHeader.vue && touch src/components/AppSidebar.vue && touch src/components/AppFooter.vue && touch src/components/UserAvatar.vue
  1. [/code] [code]
复制代码
  1. [/code] [code]
复制代码
mkdir src/router && mkdir src/router/modules && touch src/router/index.ts && touch src/router/modules/other.ts && touch src/router/modules/base.ts
  1. /** * 路由主入口(router/index.ts) * - 自动聚合所有模块路由 * - 全局守卫:nprogress、鉴权、动态layout等 */ import { createRouter, createWebHistory } from 'vue-router' import NProgress from 'nprogress' import 'nprogress/nprogress.css' import { baseRoutes } from './modules/base' import { otherRoutes } from './modules/other' import { useUserStore } from '@/store/modules/user' // 合并所有路由 const routes = [...baseRoutes, ...otherRoutes] const router = createRouter({ history: createWebHistory(), routes }) // 全局前置守卫:进度条 + 登录拦截 router.beforeEach((to, _from, next) => { NProgress.start() const userStore = useUserStore() if (to.meta.requiresAuth && !userStore.isLoggedIn) { next('/login') } else { next() } }) router.afterEach(() => { NProgress.done() }) export default router
复制代码
  1. /** * 其它业务模块路由(router/modules/other.ts) */ import type { RouteRecordRaw } from 'vue-router' export const otherRoutes: RouteRecordRaw[] = [ { path: '/about', name: 'About', component: () => import('@/views/About.vue'), meta: { layout: 'main', requiresAuth: true, title: '关于我们' } } ]
复制代码
  1. /** * 基础路由模块(router/modules/base.ts) * - 管理无需权限的公共页面路由 */ import type { RouteRecordRaw } from 'vue-router' export const baseRoutes: RouteRecordRaw[] = [ { path: '/login', name: 'Login', component: () => import('@/views/Login.vue'), meta: { layout: 'auth', public: true, title: '登录' } }, { path: '/', name: 'Home', component: () => import('@/views/Home.vue'), meta: { layout: 'main', requiresAuth: true, title: '首页' } } // 可扩展更多公共页面 ]
复制代码
mkdir src/views && touch src/views/AppHome.vue && touch src/views/AppLogin.vue && touch src/views/AppAbout.vue
  1. [/code] [code]
复制代码
  1. [/code]
  2. 修改src/main.ts
  3. [code]/** * 应用主入口(main.ts) * - 注册Pinia、Router、ElementPlus等 * - 最大化注释,团队友好 */ import { createApp } from 'vue' import App from './App.vue' import router from '@/router' import { pinia } from '@/store' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' // 创建vue app const app = createApp(App) // 注册全局插件 app.use(pinia) app.use(router) app.use(ElementPlus) // 挂载应用 app.mount('#app')
复制代码
懒得写后端接口,postman可以定义伪接口。参照:https://www.cnblogs.com/surpassme/p/16489009.html
修改.env
  1. VITE_API_BASE_URL=/ VITE_APP_TITLE=My Vite App
复制代码
看数据变化的插件,Vue.js devtools
安装pinia-plugin-persistedstate
  1. npm install pinia-plugin-persistedstate
复制代码
修改pinia单例文件
  1. // 引入并创建Pinia实例,便于主入口集成 import { createPinia } from 'pinia' import piniaPersist from 'pinia-plugin-persistedstate' // 导出Pinia实例 export const pinia = createPinia() pinia.use(piniaPersist)
复制代码
token过期,修改https
  1. import axios from 'axios' import type { AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios' import router from '@/router' // 若用SPA跳转,否则可用location.href import { useUserStore } from '@/store/modules/user' const http = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 10000, headers: { 'Content-Type': 'application/json' } }) http.interceptors.request.use( config => config, error => Promise.reject(error) ) http.interceptors.response.use( (response: AxiosResponse) => { if (response.data && typeof response.data === 'object' && 'data' in response.data) { return response.data.data } return response.data }, (error: AxiosError) => { // === 统一处理token过期/失效 === // 1. 如果是http层401 if (error.response && error.response.status === 401) { // 2. 清理用户store,自动登出 const userStore = useUserStore() userStore.setLogout?.() // 3. 跳转登录页,防止死循环/多次重定向用replace router.replace('/login') // 或 window.location.href = '/login' } // 4. 也可以根据后端返回自定义错误码处理 // if (error.response && error.response.data && error.response.data.code === 'TOKEN_EXPIRED') { // ... // } return Promise.reject(error) }) // 重点是这一行的泛型 const get = (url: string, config?: AxiosRequestConfig): Promise => { return http.get(url, config) } const post = (url: string, data?: unknown, config?: AxiosRequestConfig): Promise => { return http.post(url, data, config) } export default { get, post }
复制代码

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册