//! localStorage 持久化 ref — 读初始化 + 写回 localStorage(JSON) //! //! 背景:Tasks/Projects/Ideas 等列表页的筛选/分页状态散布在多个 ref,各自手写 //! localStorage.getItem/setItem 易漏且风格不一。本 composable 收敛为声明式一行: //! const page = usePersistedRef('tasks.page', 1) //! //! 行为: //! - 初始化时读 localStorage,存在且 JSON 解析成功则用其值,否则用 default //! - watch ref 深度变化,JSON.stringify 写回 localStorage(try/catch 防脏数据/配额) //! - SSR/无 localStorage 环境(SSR 不适用于 Tauri webview,但防御性兜底)返回纯 ref //! //! 设计取舍: //! - 不做跨 tab 同步(storage 事件)——单实例 Tauri 应用无此需求 //! - 不做类型校验(解析出的值类型可能漂移)——调用方 default 决定类型,JSON 解析成功即采用, //! 若需严格类型校验可在调用方业务层判断(当前所有调用点均为 string/number,无需求) import { ref, watch, type Ref } from 'vue' /** * 创建一个持久化到 localStorage 的 ref。 * * @param key localStorage 键名(建议带模块前缀,如 'tasks.page') * @param defaultValue 默认值(localStorage 无值或解析失败时使用) * @returns 与普通 ref 行为一致的 Ref */ export function usePersistedRef(key: string, defaultValue: T): Ref { // 初始化:读 localStorage,失败/不存在回落到 default const initial = readPersisted(key, defaultValue) const r = ref(initial) as Ref // 写回:深度 watch,JSON.stringify 失败静默(防循环引用等异常) watch( r, (val) => { try { localStorage.setItem(key, JSON.stringify(val)) } catch { /* 忽略:配额溢出 / 序列化失败 / 隐私模式禁用 localStorage */ } }, { deep: true }, ) return r } /** 读 localStorage 并 JSON 解析,失败/不存在返回 defaultValue。仅本模块内部使用。 */ function readPersisted(key: string, defaultValue: T): T { try { const raw = localStorage.getItem(key) if (raw === null) return defaultValue return JSON.parse(raw) as T } catch { return defaultValue } }