/* ============================================================ NAV CONFIG — 按设备 MAC 下发的导航入口隐藏配置 - 连接设备拿到 deviceState.mac 后请求 /audio/getNavConfig - localStorage 缓存(30min):首屏/离线即时生效,后台再刷新 - fail-open:接口失败或无 mac 时隐藏集为空,默认全部显示, 绝不因后端异常导致导航不可用 ============================================================ */ import { createContext, useContext, useEffect, useMemo, useState, type ReactNode, } from "react"; import { useDevice } from "@/contexts/DeviceContext"; import { getNavConfig } from "@/lib/luxsinApi"; /** nav key ↔ 路由 path 映射;隐藏入口时据此同步拦截路由。 */ export const NAV_KEY_TO_PATH: Record = { community: "/community", hpEq: "/eq", home: "/", effects: "/effects", myAi: "/ai", }; const CACHE_PREFIX = "luxsin:navConfig:"; const CACHE_TTL_MS = 30 * 60 * 1000; type NavConfigContextValue = { /** 需要隐藏的 nav key 集合 */ hiddenKeys: Set; /** 某个 nav key 是否被隐藏 */ isNavHidden: (key: string) => boolean; /** 某个路由 path 是否因入口隐藏而被拦截 */ isPathHidden: (path: string) => boolean; }; const NavConfigContext = createContext(null); function cacheKey(mac: string): string { return CACHE_PREFIX + mac.toUpperCase(); } function readCache(mac: string): string[] | null { try { const raw = localStorage.getItem(cacheKey(mac)); if (!raw) return null; const parsed = JSON.parse(raw) as { at?: number; hidden?: string[] }; if (!parsed || typeof parsed.at !== "number" || !Array.isArray(parsed.hidden)) { return null; } if (Date.now() - parsed.at > CACHE_TTL_MS) { localStorage.removeItem(cacheKey(mac)); return null; } return parsed.hidden; } catch { return null; } } function writeCache(mac: string, hidden: string[]): void { try { localStorage.setItem(cacheKey(mac), JSON.stringify({ at: Date.now(), hidden })); } catch { /* ignore — 隐私模式/配额不足时静默降级 */ } } export function NavConfigProvider({ children }: { children: ReactNode }) { const { deviceState } = useDevice(); const mac = (deviceState?.mac ?? "").trim(); const [hidden, setHidden] = useState([]); useEffect(() => { if (!mac) { setHidden([]); return; } // 先用本地缓存立即生效(避免首屏闪烁 / 离线可用),再后台刷新 const cached = readCache(mac); if (cached) setHidden(cached); let cancelled = false; (async () => { try { const res = await getNavConfig(mac); const list = Array.isArray(res.data?.hidden) ? res.data!.hidden! : []; if (!cancelled) { setHidden(list); writeCache(mac, list); } } catch { // fail-open:保留缓存或空数组,默认全部显示 } })(); return () => { cancelled = true; }; }, [mac]); const value = useMemo(() => { const keySet = new Set(hidden); const pathSet = new Set( hidden.map((k) => NAV_KEY_TO_PATH[k]).filter((p): p is string => !!p), ); return { hiddenKeys: keySet, isNavHidden: (key: string) => keySet.has(key), isPathHidden: (path: string) => pathSet.has(path), }; }, [hidden]); return ( {children} ); } export function useNavConfig(): NavConfigContextValue { const ctx = useContext(NavConfigContext); if (!ctx) { throw new Error("useNavConfig must be used within NavConfigProvider"); } return ctx; }