144 lines
4.8 KiB
TypeScript
144 lines
4.8 KiB
TypeScript
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<void>;
|
||
/** 目录加载失败后重试 */
|
||
reload: () => void;
|
||
}
|
||
|
||
const PlatformContextContext = createContext<PlatformContextValue | null>(null);
|
||
|
||
/** 平台上下文:页头平台选择器的数据源,门店等模块据此确定当前平台(目录接口将 Ozon 排在最前) */
|
||
export function PlatformProvider({ children }: { children: ReactNode }) {
|
||
const [platforms, setPlatforms] = useState<Platform[]>([]);
|
||
const [selectedId, setSelectedId] = useState<string | null>(() =>
|
||
localStorage.getItem(PLATFORM_STORAGE_KEY)
|
||
);
|
||
const [error, setError] = useState<string | null>(null);
|
||
const [context, setContext] = useState<PlatformContext | null>(null);
|
||
const [contextLoading, setContextLoading] = useState(false);
|
||
// 快速切换平台时丢弃过期响应与 loading:只接受最后一次请求的结果
|
||
const contextRequestIdRef = useRef(0);
|
||
|
||
const loadPlatforms = useCallback(async () => {
|
||
setError(null);
|
||
try {
|
||
const list = await api.get<Platform[]>('/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<PlatformContext>('/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 (
|
||
<PlatformContextContext.Provider value={value}>{children}</PlatformContextContext.Provider>
|
||
);
|
||
}
|
||
|
||
export function usePlatform(): PlatformContextValue {
|
||
const context = useContext(PlatformContextContext);
|
||
if (!context) {
|
||
throw new Error('usePlatform 必须在 PlatformProvider 内使用');
|
||
}
|
||
return context;
|
||
}
|