确保刷新页面后用户登录状态不会失效,永久化存储用户登录信息

为了解决刷新页面后用户登录失效的问题,我们需要对以下三个文件进行修改:

  1. main.js :在应用初始化时从 localStorage 中恢复 Token。

  2. App.vue:监听路由变化,确保登录状态和动态路由加载正确。

  3. permission.js(路由守卫):优化路由守卫逻辑,确保 Token 和动态路由的正确处理。

1. 修改 main.js

在应用初始化时,从 localStorage 中恢复 Token,并更新到 Vuex 中。

javascript 复制代码
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 SvgIcon from '@/icons';
import '@/router/permission.js'; // 引入路由守卫
import zhCn from 'element-plus/es/locale/lang/zh-cn'; // 国际化中文
import request from "@/util/request";

const app = createApp(App);

// 从 localStorage 中恢复 Token
const token = localStorage.getItem('token');
if (token) {
  store.commit('SET_TOKEN', token); // 更新 Vuex 中的 Token
}

// 注册全局属性
app.config.globalProperties.$request = request; // 挂载请求方法
app.config.globalProperties.$baseUrl = process.env.VUE_APP_BASEURL; // 挂载基础 URL

// 使用插件
SvgIcon(app);
app.use(ElementPlus, {
    locale: zhCn,
});
app.use(store);
app.use(router);

// 挂载应用
app.mount('#app');

修改 App.vue

App.vue 中监听路由变化,确保登录状态和动态路由加载正确。

javascript 复制代码
<template>
  <router-view />
</template>

<script setup>
import { watch, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import store from './store';

const route = useRoute();
const whitePath = ['/adminlogin', '/index', '/'];

// 监听路由变化,动态添加标签页
watch(
  route,
  (to) => {
    if (whitePath.indexOf(to.path) === -1) {
      const tab = {
        name: to.name,
        path: to.path
      };
      store.commit("ADD_TABS", tab); // 添加标签页
    }
  },
  { deep: true, immediate: true }
);

// 在组件挂载时检查 Token
onMounted(() => {
  const token = localStorage.getItem('token');
  if (token) {
    store.commit('SET_TOKEN', token); // 更新 Vuex 中的 Token
  }
});
</script>

<style>
/* 全局样式 */
</style>

修改 permission.js(路由守卫)

优化路由守卫逻辑,确保 Token 和动态路由的正确处理。

javascript 复制代码
import router from "@/router";
import store from "@/store";

router.beforeEach(async (to, from, next) => {
    console.log('Navigating from', from.path, 'to', to.path);

    // 白名单路径(无需登录即可访问)
    const whiteList = ['/adminlogin', '/login', '/register', '/forgetpassword'];

    // 从 localStorage 中获取 Token
    const token = localStorage.getItem('token') || store.getters.GET_TOKEN;
    const hasRoutes = store.state.hasRoutes;
    const menuList = store.getters.GET_MENULIST;

    // 设置页面标题
    document.title = store.getters.GET_WEBNAME;

    if (token) {
        // 有 Token 的情况
        if (!hasRoutes) {
            // 如果动态路由未加载,加载动态路由
            try {
                await bindRoute(menuList); // 动态加载路由
                store.commit("SET_ROUTES_STATE", true); // 标记路由已加载
                next({ ...to, replace: true }); // 重新跳转到目标路由
            } catch (error) {
                console.error('动态路由加载失败:', error);
                next('/login'); // 如果加载失败,跳转到登录页
            }
        } else {
            // 动态路由已加载,直接放行
            next();
        }
    } else {
        // 无 Token 的情况
        if (whiteList.includes(to.path)) {
            // 如果目标路径在白名单中,直接放行
            next();
        } else {
            // 否则跳转到登录页
            next("/login");
        }
    }
});

// 动态绑定路由
const bindRoute = (menuList) => {
    return new Promise((resolve) => {
        let newRoutes = router.options.routes;
        menuList.forEach(menu => {
            if (menu.children) {
                menu.children.forEach(m => {
                    // 将菜单转换为路由
                    let route = menuToRoute(m, menu.name);
                    if (route) {
                        newRoutes[0].children.push(route); // 添加到路由管理
                    }
                });
            }
        });
        // 重新添加到路由
        newRoutes.forEach(route => {
            router.addRoute(route);
        });
        resolve();
    });
};

// 菜单转成路由
const menuToRoute = (menu, parentName) => {
    if (!menu.component) {
        return null;
    } else {
        let route = {
            name: menu.name,
            path: menu.path,
            meta: {
                parentName: parentName
            }
        };
        route.component = () => import(`@/views${menu.component}.vue`); // 动态加载组件
        return route;
    }
};

修改 store 中的 Token 逻辑

确保在 store 中正确设置和清除 Token。

  1. 持久化存储 :将 tokenmenuListwebNameuserInfo 存储在 localStorage 中,而不是 sessionStorage,因为 sessionStorage 在页面刷新后会丢失。

  2. 初始化状态 :在 store 初始化时,从 localStorage 中恢复状态。

  3. 退出登录逻辑 :在 logout 操作中,清除 localStoragesessionStorage,并重置 state

javascript 复制代码
import router from '@/router';
import { createStore } from 'vuex';

export default createStore({
  state: {
    // 存储 token
    token: localStorage.getItem('token') || "", // 从 localStorage 中恢复 token
    userName: localStorage.getItem('userName') || "", // 从 localStorage 中恢复 userName
    hasRoutes: false,
    editableTabsValue: '/index',
    webName: localStorage.getItem('webName') || "", // 从 localStorage 中恢复 webName
    editableTabs: [
      {
        title: '首页',
        name: '/index'
      }
    ],
    menuList: JSON.parse(localStorage.getItem('menuList')) || [] // 从 localStorage 中恢复 menuList
  },
  getters: {
    GET_TOKEN(state) {
      return state.token;
    },
    GET_MENULIST(state) {
      return state.menuList;
    },
    GET_WEBNAME(state) {
      return state.webName;
    },
    GET_USERINFO(state) {
      return {
        username: state.userName
      };
    }
  },
  mutations: {
    SET_TOKEN(state, token) {
      state.token = token;
      localStorage.setItem('token', token); // 存储到 localStorage
    },
    SET_MENULIST(state, menuList) {
      state.menuList = menuList;
      localStorage.setItem('menuList', JSON.stringify(menuList)); // 存储到 localStorage
    },
    SET_WEBNAME(state, webName) {
      state.webName = webName;
      localStorage.setItem('webName', webName); // 存储到 localStorage
    },
    SET_USERINFO(state, userInfo) {
      state.userName = userInfo.username;
      localStorage.setItem('userName', userInfo.username); // 存储到 localStorage
    },
    SET_ROUTES_STATE(state, hasRoutes) {
      state.hasRoutes = hasRoutes;
    },
    ADD_TABS(state, tab) {
      if (state.editableTabs.findIndex(e => e.name === tab.path) === -1) {
        if (tab.path !== '/login' && tab.path !== '/register' && tab.path !== '/forgetpassword') {
          state.editableTabs.push({
            title: tab.name,
            name: tab.path
          });
        }
      }
      state.editableTabsValue = tab.path;
    },
    RESET_TABS(state) {
      state.editableTabsValue = '/index';
      state.editableTabs = [
        {
          title: '首页',
          name: '/index'
        }
      ];
    },
    // 定义重置 state 的 mutation 函数
    RESET_STATE(state) {
      // 重置各个属性值
      state.token = "";
      state.userName = "";
      state.hasRoutes = false;
      state.editableTabsValue = '/index';
      state.editableTabs = [
        {
          title: '首页',
          name: '/index'
        }
      ];
      state.menuList = [];
      // 清除 localStorage
      localStorage.removeItem('token');
      localStorage.removeItem('userName');
      localStorage.removeItem('webName');
      localStorage.removeItem('menuList');
    }
  },
  actions: {
    // 安全退出
    logout({ commit }) {
      // 调用 mutation 函数来重置 state
      commit('RESET_STATE');
      // 清除 sessionStorage
      sessionStorage.clear();
      // 跳转到登录页
      router.replace("/login");
      // 强制刷新页面以确保所有状态被清除
      window.location.reload();
    }
  },
  modules: {
  }
});
  1. 持久化存储

    • tokenuserNamewebNamemenuList 存储在 localStorage 中,确保刷新页面后数据不会丢失。

    • state 初始化时,从 localStorage 中恢复数据。

  2. 退出登录逻辑

    • logout 操作中,调用 RESET_STATE 重置 state,并清除 localStoragesessionStorage

    • 使用 router.replace("/login") 跳转到登录页,并强制刷新页面以确保所有状态被清除。

  3. 状态恢复

    • state 初始化时,从 localStorage 中恢复 tokenuserNamewebNamemenuList,确保刷新页面后状态不会丢失。
相关推荐
dsyyyyy11013 分钟前
Typescript装饰器
前端·javascript·typescript
一拳不是超人1 小时前
一个没测暗色模式的 Bug,吃掉了我一半 谷歌扩展用户
前端·javascript·程序员
liangshanbo12152 小时前
React useState 函数式更新面试题整理
前端·react.js·前端框架
涛涛ing2 小时前
为什么你的页面在 Safari 上总出问题?Interop 2027 正在解决这个 20 年老毛病
前端
不一样的少年_2 小时前
WebP 压缩到底在干嘛?小白也能看懂的原理拆解
前端·后端·图片资源
问心无愧05132 小时前
ctf show web 177
前端·笔记
不一样的少年_2 小时前
PNG/JPG 如何变成 WebP?真相不是改后缀!
前端·后端·图片资源
杉氧2 小时前
RN 性能调优指南:重渲染(Re-renders)控制与长列表(FlatList)优化
android·前端·react native
一_个前端2 小时前
[JS] 一站式搞定 PDF、图片、Dom弹窗、表格的浏览器打印功能
前端
JavaGuide2 小时前
阿里 Qoder 又开源了一个专门给 Claude Code、Codex 做“体检”的项目
前端·后端