diff --git a/frontend-web/src/core/auth/external-login.ts b/frontend-web/src/core/auth/external-login.ts index 219db2b2..bd80d522 100644 --- a/frontend-web/src/core/auth/external-login.ts +++ b/frontend-web/src/core/auth/external-login.ts @@ -51,7 +51,14 @@ export function readExternalLoginParams(): Required { } } -/** 把 ``theme`` query 值映射成 ThemeContext 的 ``'dark' | 'light'`` */ +/** + * 把外部(URL query / 无界 props)的 ``theme`` 值映射成 ThemeContext 的 + * ``'dark' | 'light'``。已知取值: + * - ``dark-blue`` → 深色 + * - ``red`` → 浅色(当前默认红白主题) + * - ``blue`` → 暂无该主题,先用浅色代替(命中 else 分支) + * 其余/缺省同样回退浅色。将来若新增 blue 主题,在此扩展映射即可。 + */ export function resolveThemeFromQuery(themeParam: string | null | undefined): "dark" | "light" { return themeParam === "dark-blue" ? "dark" : "light"; } diff --git a/frontend-web/src/core/embed/wujie.ts b/frontend-web/src/core/embed/wujie.ts new file mode 100644 index 00000000..df4b977a --- /dev/null +++ b/frontend-web/src/core/embed/wujie.ts @@ -0,0 +1,78 @@ +/** + * 无界(wujie)子应用运行时 props 读取。 + * + * 本应用作为子应用被内网门户用无界嵌套时,父级通过 + * + * 注入参数,子应用在 `window.$wujie.props` 上拿到**同一个对象**(无界在子应用 + * bootstrap 之前就已注入,因此首帧即可读)。 + * + * 本期范围:只「读出来 + 存起来 + 打印」,**不接入对方鉴权**。读出的 + * ticket/userId/env 复用 external-login 的 localStorage 存储(与 URL 跳转登录 + * 同一套 key),theme 交给调用方(LoginPage)映射到 ThemeContext。 + */ +import { useEffect, useMemo } from "react"; + +import { persistExternalLoginParams } from "@/core/auth/external-login"; + +/** 无界父级注入的 props 形状。除已知字段外允许透传其余未知字段便于排查。 */ +export interface WujieProps { + /** 第三方鉴权 ticket(本期仅存储待用,不消费)。 */ + ticket?: string; + /** 外部用户 id。 */ + userId?: string; + /** 环境标识。 */ + env?: string; + /** 主题色标识(映射到深/浅色)。 */ + theme?: string; + [key: string]: unknown; +} + +interface WujieRuntime { + props?: WujieProps; + [key: string]: unknown; +} + +declare global { + interface Window { + /** 无界注入的子应用运行时;非无界环境下为 undefined。 */ + $wujie?: WujieRuntime; + /** 无界注入的环境标志(子应用内为 true)。 */ + __POWERED_BY_WUJIE__?: boolean; + } +} + +/** 当前是否运行在无界子应用环境中。 */ +export function isWujieEnv(): boolean { + return typeof window !== "undefined" && !!window.__POWERED_BY_WUJIE__; +} + +/** 读取无界父级注入的 props;不在无界环境 / 未注入时返回空对象。 */ +export function readWujieProps(): WujieProps { + if (typeof window === "undefined") return {}; + return window.$wujie?.props ?? {}; +} + +/** + * 全局读取无界父级注入的 props 的 hook。 + * + * - 首帧从 `window.$wujie.props` 读出(无界已在 bootstrap 前注入,引用稳定); + * - 读到后把 ticket/userId/env 持久化到 localStorage(复用 external-login 存储), + * 供后续任意页面/请求读取 —— 即「存到全局」; + * - 返回 props 供调用方消费(如 LoginPage 取 theme)。 + * + * 非无界环境(直接访问)下返回空对象且不做任何持久化,可安全无条件调用。 + */ +export function useWujieProps(): WujieProps { + const props = useMemo(() => readWujieProps(), []); + + useEffect(() => { + if (!props.ticket && !props.userId && !props.env) return; + persistExternalLoginParams({ + ticket: props.ticket, + userId: props.userId, + env: props.env, + }); + }, [props.ticket, props.userId, props.env]); + + return props; +} diff --git a/frontend-web/src/pages/LoginPage.tsx b/frontend-web/src/pages/LoginPage.tsx index 8e7bca92..05e695df 100644 --- a/frontend-web/src/pages/LoginPage.tsx +++ b/frontend-web/src/pages/LoginPage.tsx @@ -10,6 +10,7 @@ import { persistExternalLoginParams, resolveThemeFromQuery, } from "@/core/auth/external-login"; +import { isWujieEnv, useWujieProps } from "@/core/embed/wujie"; import { GENERAL_QA_ROUTE, loginByYUserId, @@ -49,11 +50,23 @@ export default function LoginPage() { const [status, setStatus] = useState("loading"); const [errorMessage, setErrorMessage] = useState(""); - // 主题切换:theme=dark-blue → 深色,其他值或缺省 → 浅色 + // 无界(wujie)父级通过 $wujie.props 注入的参数(ticket / userId / theme / env)。 + // hook 内部已把 ticket/userId/env 持久化到 localStorage(复用 external-login 存储)。 + const wujieProps = useWujieProps(); + + // 调试:把无界注入的 props 打印出来(仅在无界子应用环境下,避免独立访问时刷日志)。 useEffect(() => { - if (!externalParams.theme) return; - setTheme(resolveThemeFromQuery(externalParams.theme)); - }, [externalParams.theme, setTheme]); + if (!isWujieEnv()) return; + console.log("[wujie] sub-app props:", wujieProps); + }, [wujieProps]); + + // 主题切换:无界 props.theme 优先,其次 URL query theme。两者取值约定一致 + // (dark-blue → 深色;red → 浅色;blue 暂无该主题,先用浅色代替),统一走 resolveThemeFromQuery。 + useEffect(() => { + const themeValue = wujieProps.theme || externalParams.theme; + if (!themeValue) return; + setTheme(resolveThemeFromQuery(themeValue)); + }, [wujieProps.theme, externalParams.theme, setTheme]); // ticket / userId / env 持久化(与 login 请求并行做,互不阻塞) useEffect(() => {