🛠 1. Storage 失效检测(比如:防止无痕模式 localStorage 崩溃)

问题背景:
某些浏览器(尤其是 iOS Safari 无痕模式)下,localStorage 是存在但不可写的。直接写会抛异常导致页面崩掉!

解决方案:
在 helper 初始化时,检测 localStorage 是否可用,如果不可用就降级(比如直接不做持久化)。


🛠 2. 存储超时机制(比如:24小时后过期清除)

问题背景:
即使 tab 关闭,localStorage 可能残留。需要自动设置超时时间,过期后自己清掉!

解决方案:
保存数据时附带一个 timestamp,每次取数据时检测,如果超时了,自动清除。


🚀 全新升级版:utils/tab-storage-helper.ts

import { nanoid } from 'nanoid';

// ========== 配置区 ==========

// 是否开启“每个 TAB 独立存储”
const ENABLE_TAB_ISOLATION = true;

// tabId 存储到 sessionStorage 的 key
const SESSION_TAB_ID_KEY = 'tab-storage-helper-tab-id';

// 本地存储过期时间(毫秒),例如 24小时 = 24 * 60 * 60 * 1000
const STORAGE_EXPIRE_MS = 24 * 60 * 60 * 1000;

// ========== 工具函数 ==========

/** 检测 localStorage 是否可用 */
function isStorageAvailable() {
  try {
    const testKey = '__storage_test__';
    localStorage.setItem(testKey, '1');
    localStorage.removeItem(testKey);
    return true;
  } catch (e) {
    console.warn('[TabStorageHelper] localStorage 不可用', e);
    return false;
  }
}

/** 生成 tabId */
function generateTabId() {
  return nanoid(10);
}

/** 获取当前 tabId */
function getTabId() {
  let tabId = sessionStorage.getItem(SESSION_TAB_ID_KEY);
  if (!tabId) {
    tabId = generateTabId();
    sessionStorage.setItem(SESSION_TAB_ID_KEY, tabId);
  }
  return tabId;
}

/** 计算完整的存储 key */
function getStorageKey(baseKey: string) {
  if (ENABLE_TAB_ISOLATION) {
    const tabId = getTabId();
    return `${baseKey}-${tabId}`;
  }
  return baseKey;
}

/** 自动清除当前 tab 存储 */
function clearStorage(baseKey: string) {
  try {
    const fullKey = getStorageKey(baseKey);
    localStorage.removeItem(fullKey);
  } catch (e) {
    console.warn('[TabStorageHelper] 清理 localStorage 失败', e);
  }
}

/** devtools 日志 */
function devLog(message: string, ...args: any[]) {
  if (import.meta.env.DEV) {
    console.log(`%c[TabStorageHelper]%c ${message}`, 'color:#00c4b6;font-weight:bold;', 'color:inherit;', ...args);
  }
}

// ========== 核心导出 ==========

/** 创建 persist 配置 */
export function createPersistConfig(baseKey: string) {
  const isAvailable = isStorageAvailable();
  const storageKey = getStorageKey(baseKey);

  // 初始化:监听 tab 关闭清理
  if (ENABLE_TAB_ISOLATION && isAvailable) {
    window.addEventListener('beforeunload', () => {
      clearStorage(baseKey);
    });
  }

  if (import.meta.env.DEV && ENABLE_TAB_ISOLATION) {
    devLog(`TAB 隔离已启用,当前 tabId: ${sessionStorage.getItem(SESSION_TAB_ID_KEY)}`);
  }

  if (!isAvailable) {
    devLog('localStorage 不可用,将禁用 persist 功能');
    return undefined;
  }

  return {
    key: storageKey,
    storage: {
      getItem: (key: string) => {
        try {
          const raw = localStorage.getItem(key);
          if (!raw) return null;
          const data = JSON.parse(raw);
          if (data.__expireAt && Date.now() > data.__expireAt) {
            // 已过期
            localStorage.removeItem(key);
            devLog(`检测到存储已过期,已清理: ${key}`);
            return null;
          }
          return JSON.stringify(data.value);
        } catch (e) {
          console.warn('[TabStorageHelper] 读取失败', e);
          return null;
        }
      },
      setItem: (key: string, value: string) => {
        try {
          const data = {
            value: JSON.parse(value),
            __expireAt: Date.now() + STORAGE_EXPIRE_MS,
          };
          localStorage.setItem(key, JSON.stringify(data));
        } catch (e) {
          console.warn('[TabStorageHelper] 写入失败', e);
        }
      },
      removeItem: (key: string) => {
        try {
          localStorage.removeItem(key);
        } catch (e) {
          console.warn('[TabStorageHelper] 移除失败', e);
        }
      },
    },
  };
}

✨ 新版的亮点

能力说明
localStorage 检测防止无痕模式崩溃
自动存活检测超过24小时,数据失效自动清理
依然保留每个tab独立副本、tab关闭清理、统一配置
异常保护localStorage 全程 try-catch 包裹,稳
兼容性强任何浏览器都能优雅降级

🛠 用法依旧简单

比如 store/app-graph.ts 继续这么写就行:

import { defineStore } from 'pinia';
import { createPersistConfig } from '@/utils/tab-storage-helper';

export const useAppGraphStore = defineStore('app-graph', {
  state: () => ({
    shapes: [],
    selectedShapeId: null,
    canvasZoom: 1,
  }),
  persist: createPersistConfig('app-graph'), // 自带隔离+超时检测
});

如果 localStorage 不可用,persist会自动禁用。