// @stago/sdk - Module Context // Rich context provided by Host to module components import React from 'react'; import type { HttpClient } from './http-client.js'; export type { HttpClient, HttpOptions, HttpQuery, HttpQueryValue, HttpResponseKind, } from './http-client.js'; export { HttpError } from './http-client.js'; // ========== User Context ========== /** * User information passed from Host to module. * 字段与 live.employees 一一对应:id / phone / name。 */ export interface ModuleUser { /** 员工 id(live.employees.id) */ id: number; /** 手机号(live.employees.phone) */ phone: string; /** 姓名(live.employees.name) */ name: string; /** User role */ role: 'admin' | 'user'; /** User permissions list */ permissions: string[]; } // ========== Theme Context ========== /** * Theme configuration and controls */ export interface ModuleTheme { /** Current theme mode */ mode: 'light' | 'dark'; /** Primary color (hex) */ primaryColor: string; /** Toggle theme mode */ toggle: () => void; } // ========== Router Context ========== /** * Router service for navigation */ export interface ModuleRouter { /** Current path within module */ currentPath: string; /** Dynamic route parameters (e.g., { id: '123' } from /detail/:id) */ params: Record; /** Query string parameters */ query: URLSearchParams; /** Navigate to a new path within module */ navigate: (path: string) => void; } // ========== Notification Context ========== /** * Notification service for user feedback */ export interface ModuleNotifications { /** Show success notification */ success: (message: string, description?: string) => void; /** Show error notification */ error: (message: string, description?: string) => void; /** Show warning notification */ warning: (message: string, description?: string) => void; /** Show info notification */ info: (message: string, description?: string) => void; } // ========== Global State Context ========== /** * Global state for cross-module communication * Simple state store - modules can share data through this */ export interface ModuleGlobalState { /** Get value by key */ get: (key: string) => T | undefined; /** Set value by key */ set: (key: string, value: T) => void; /** Subscribe to key changes, returns unsubscribe function */ subscribe: (key: string, callback: (value: unknown) => void) => () => void; /** Clear all state for current module namespace */ clear: () => void; } // ========== Platform Services ========== /** * Storage service with module namespace isolation */ export interface ModuleStorage { /** Get item */ get: (key: string) => T | null; /** Set item */ set: (key: string, value: T) => void; /** Remove item */ remove: (key: string) => void; /** Clear all items for this module */ clear: () => void; } /** * Platform configuration */ export interface AppConfig { /** Platform API base URL */ apiBaseUrl: string; /** Module base URL */ moduleBaseUrl: string; /** Platform version */ version: string; /** Environment: development | production */ environment: 'development' | 'production'; } /** * Platform services */ export interface PlatformServices { /** HTTP client with auto-authentication */ http: HttpClient; /** Module-isolated storage */ storage: ModuleStorage; /** Platform configuration */ config: AppConfig; } // ========== Full Module Context ========== /** * Complete Module Context * Provided by Host via ModuleProvider */ export interface ModuleContext { /** User information */ user: ModuleUser; /** Theme configuration */ theme: ModuleTheme; /** Router service */ router: ModuleRouter; /** Notification service */ notifications: ModuleNotifications; /** Global state (cross-module communication) */ globalState: ModuleGlobalState; /** Platform services */ platform: PlatformServices; } // ========== React Context ========== const ModuleContext = React.createContext(null); const GLOBAL_MODULE_CONTEXT_KEY = '__STAGO_MODULE_CONTEXT__'; function getGlobalModuleContext(): ModuleContext | null { if (typeof globalThis === 'undefined') { return null; } return ( ((globalThis as Record)[GLOBAL_MODULE_CONTEXT_KEY] as | ModuleContext | undefined) || null ); } /** * Get module context * Must be used within ModuleProvider */ export function useModule(): ModuleContext { const context = React.useContext(ModuleContext); if (context) { return context; } const globalContext = getGlobalModuleContext(); if (globalContext) { return globalContext; } throw new Error('useModule must be used within ModuleProvider'); } /** * ModuleProvider component * Host wraps module with this provider */ export function ModuleProvider({ children, value, publishToGlobal = true, }: { children: React.ReactNode; value: ModuleContext; /** 多 Tab keep-alive 时仅激活实例应写入全局,避免隐藏实例覆盖路径 */ publishToGlobal?: boolean; }): React.ReactElement { React.useLayoutEffect(() => { if (!publishToGlobal || typeof globalThis === 'undefined') { return; } (globalThis as Record)[GLOBAL_MODULE_CONTEXT_KEY] = value; return () => { if ((globalThis as Record)[GLOBAL_MODULE_CONTEXT_KEY] === value) { delete (globalThis as Record)[GLOBAL_MODULE_CONTEXT_KEY]; } }; }, [value, publishToGlobal]); return React.createElement(ModuleContext.Provider, { value }, children); } /** * 是否在底座注入的 `ModuleProvider` 中运行(即作为远程模块嵌在宿主里)。 * 为 `true` 时,宿主侧栏通常已根据 `menu.children` 负责子页导航,模块内应不要再画一层侧栏/菜单,只渲染业务内容区即可。 * 本地独立 `vite` 预览且未包 `ModuleProvider` 时为 `false`,可保留模块自带导航便于开发。 * * 必须在 React 组件或 Hook 中调用(内部使用 `useContext`)。 */ export function useIsInModuleContext(): boolean { return React.useContext(ModuleContext) !== null || getGlobalModuleContext() !== null; } /** * 获取宿主传入的当前子路径(用于将模块内 Router 与 Host 菜单点击保持同步)。 * * @param fallback 未在宿主上下文时的兜底路径,默认 '/' * @returns 当前模块子路径(如 '/dashboard') */ export function useHostCurrentPath(fallback = '/'): string { const context = React.useContext(ModuleContext); const globalContext = getGlobalModuleContext(); const currentPath = context?.router.currentPath || globalContext?.router.currentPath; if (!currentPath) { return fallback; } return currentPath.startsWith('/') ? currentPath : `/${currentPath}`; }