import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react'; import type { ReactNode } from 'react'; import type { Platform, PlatformContext } from '@atlas/contracts'; import { api, apiErrorMessage } from '@/services/api'; /** 平台选择的本地记忆键:仅保存平台 id 这一非敏感 UI 偏好 */ const PLATFORM_STORAGE_KEY = 'atlas.platform_id'; interface PlatformContextValue { /** 已启用的平台目录;加载失败时为空数组并置 error */ platforms: Platform[]; /** 页头当前选中的平台;目录未加载完成或为空时为 null */ currentPlatform: Platform | null; /** 目录加载失败信息;非空时提供重试入口 */ error: string | null; /** 当前平台的默认门店上下文(GET /api/platform-context) */ context: PlatformContext | null; contextLoading: boolean; selectPlatform: (platformId: string) => void; /** 门店变更后刷新默认门店摘要 */ refreshContext: () => Promise; /** 目录加载失败后重试 */ reload: () => void; } const PlatformContextContext = createContext(null); /** 平台上下文:页头平台选择器的数据源,门店等模块据此确定当前平台(目录接口将 Ozon 排在最前) */ export function PlatformProvider({ children }: { children: ReactNode }) { const [platforms, setPlatforms] = useState([]); const [selectedId, setSelectedId] = useState(() => localStorage.getItem(PLATFORM_STORAGE_KEY) ); const [error, setError] = useState(null); const [context, setContext] = useState(null); const [contextLoading, setContextLoading] = useState(false); // 快速切换平台时丢弃过期响应与 loading:只接受最后一次请求的结果 const contextRequestIdRef = useRef(0); const loadPlatforms = useCallback(async () => { setError(null); try { const list = await api.get('/platforms'); setPlatforms(list); // 已保存的选择失效(平台下线或首次使用)时回退到目录第一家(Ozon 优先) setSelectedId((prev) => prev && list.some((item) => item.id === prev) ? prev : (list[0]?.id ?? null) ); } catch (err) { setError(apiErrorMessage(err)); } }, []); useEffect(() => { void loadPlatforms(); }, [loadPlatforms]); const currentPlatform = useMemo( () => platforms.find((item) => item.id === selectedId) ?? null, [platforms, selectedId] ); const loadContext = useCallback(async (platformId: string) => { const requestId = ++contextRequestIdRef.current; setContextLoading(true); try { const next = await api.get('/platform-context', { headers: { 'X-Atlas-Platform-Id': platformId }, }); if (contextRequestIdRef.current !== requestId) return; setContext(next); } catch { // 上下文读取失败不阻塞页面:门店页等业务接口自会报错 if (contextRequestIdRef.current !== requestId) return; setContext(null); } finally { if (contextRequestIdRef.current === requestId) { setContextLoading(false); } } }, []); useEffect(() => { if (!currentPlatform) { setContext(null); return; } void loadContext(currentPlatform.id); }, [currentPlatform, loadContext]); const selectPlatform = useCallback((platformId: string) => { setSelectedId(platformId); localStorage.setItem(PLATFORM_STORAGE_KEY, platformId); }, []); const refreshContext = useCallback(async () => { if (!currentPlatform) return; await loadContext(currentPlatform.id); }, [currentPlatform, loadContext]); const value = useMemo( () => ({ platforms, currentPlatform, error, context, contextLoading, selectPlatform, refreshContext, reload: () => void loadPlatforms(), }), [ platforms, currentPlatform, error, context, contextLoading, selectPlatform, refreshContext, loadPlatforms, ] ); return ( {children} ); } export function usePlatform(): PlatformContextValue { const context = useContext(PlatformContextContext); if (!context) { throw new Error('usePlatform 必须在 PlatformProvider 内使用'); } return context; }