Skip to content

数据持久化

实现原理

数据持久化存储方案采用 vuex + localStorage,先将数据存储到 vuex,当页面刷新或者离开会触发对应的钩子 同步到 localStorage,实现数据持久化。

当重新访问页面时,从 localStorage 读取数据,实现数据恢复。在这个过程中会进行数据校验,保证数据的完整性。

vuex

vuex 模块介绍

bash
├── store
   ├── modules
       └── menu.ts           # 菜单
       └── setting.ts        # 设置中心
       └── user.ts           # 用户
       └── worktab.ts        # 多标签页

menu

  • menuList // 菜单列表
  • menuWidth // 菜单宽度

setting

  • systemThemeType // 全局主题类型 light dark
  • systemThemeMode // 全局主题模式 light dark auto
  • menuThemeType // 菜单主题类型
  • systemThemeColor // 系统主题颜色
  • boxBorderMode // 盒子模式 border | shadow
  • uniqueOpened // 是否开启手风琴模式
  • showMenuButton // 是否显示菜单展开按钮
  • showRefreshButton // 是否显示页面刷新按钮
  • showCrumbs // 是否显示全局面包屑
  • autoClose // 设置后是否自动关闭窗口
  • showWorkTab // 是否显示多标签
  • showLanguage // 是否显示多语言选择
  • showNprogress // 是否显示顶部进度条
  • colorWeak // 是否显示顶部进度条
  • showSettingGuide // 是否显示设置引导
  • pageTransition // 页面切换动画
  • menuOpen // 菜单是否展开
  • refresh

user

  • language // 语言
  • isLogin // 是否登录
  • info // 用户信息
  • searchHistory // 搜索历史

worktab

  • current // 当前标签页
  • opened // 打开的标签页

localStorage

使用版本号 + 数据校验进本地数据管理,当数据不正确的时候,会清空本地数据,并跳转至登录页面,保证用户正常访问。

如何保证数据完整性?

1、版本号管理

使用版本号管理 localStorage 数据,并将新数据合并到现有数据中

ts
// 数据持久化存储
function saveStoreStorage<T>(newData: T) {
  const version = import.meta.env.VITE_VERSION;
  initVersion(version);
  const vs = localStorage.getItem("version") || version;
  const storedData = JSON.parse(localStorage.getItem(`sys-v${vs}`) || "{}");

  // 合并新数据与现有数据
  const mergedData = { ...storedData, ...newData };
  localStorage.setItem(`sys-v${vs}`, JSON.stringify(mergedData));
}

2、数据校验

在 /src/utils/storage.ts 文件中封装了数据校验方法,在页面刷新时,会调用该方法,判断数据是否完整,不正确则清空本地存储,并跳转至登录页面。

ts
// 验证本地存储数据并处理异常
export function validateStorageData() {
  if (location.href.includes("/login")) return true;

  const schema = {
    user: {
      info: "object",
      isLogin: "boolean",
      language: "string",
      worktab: {
        current: {
          title: "string",
          title_en: "string",
          path: "string",
          params: "object",
          query: "object",
        },
        opened: "object",
      },
      setting: {
        systemThemeType: "string",
        systemThemeMode: "string",
        menuThemeType: "string",
        boxBorderMode: "boolean",
        uniqueOpened: "boolean",
        systemThemeColor: "string",
        showMenuButton: "boolean",
        showRefreshButton: "boolean",
        showCrumbs: "boolean",
        autoClose: "boolean",
        showWorkTab: "boolean",
        showLanguage: "boolean",
        showNprogress: "boolean",
        colorWeak: "boolean",
        showSettingGuide: "boolean",
        refresh: "boolean",
      },
    },
  };

  try {
    const data = JSON.parse(getSysStorage() || "{}");
    // 模拟本地数据类型错误
    // data.user.language = 2024

    if (Object.keys(data).length === 0) {
      logOut();
      return false;
    }

    if (!validate(data, schema)) {
      throw new Error("本地存储数据结构异常");
    }

    return true;
  } catch {
    handleError();
    return false;
  }
}

// 将 vuex 中的数据保存到 localStorage 中(在即将离开页面(刷新或关闭)时执行)
export function saveUserData() {
  const isiOS = /iPhone|iPad|iPod/i.test(navigator.userAgent);
  const eventType = isiOS ? "pagehide" : "beforeunload";

  window.addEventListener(eventType, () => {
    useUserStore().saveUserData();
  });
}

Released under the MIT License.