Files
atlas-seller-kit/apps/web/src/services/platform.tsx
T
2026-09-04 15:57:16 +08:00

144 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}