From 74324f4585df19729c0d6e12cf62c0a723a2c50a Mon Sep 17 00:00:00 2001 From: fengxiangdebaozi <10845145+fengxiangdebaozi@user.noreply.gitee.com> Date: Fri, 12 Jun 2026 22:30:05 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BD=BB=E5=BA=94=E7=94=A8=E4=B8=BB=E9=A2=98?= =?UTF-8?q?=E5=8F=82=E6=95=B0=EF=BC=9A=E9=9A=8F=E5=BD=93=E5=89=8D=E4=B8=BB?= =?UTF-8?q?=E9=A2=98=E8=89=B2=E6=98=A0=E5=B0=84=E4=BC=A0=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 主题参数改为按系统主题(red/dark-blue)映射取目标应用对应的值 - 应用管理弹窗用结构化映射编辑器(逐主题色配置目标值,默认同名) - iframe 嵌套显式传入当前主题色,切主题时 src 重建 - 读取/保存时规范化映射串;解析支持 light/dark 等别名,取不到回退主题色名而非恒为 red Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend-web/src/pages/LightAppIframeView.tsx | 12 ++++++-- .../src/pages/LightAppManagementPage.tsx | 4 +-- .../src/strategy-components/api/light-apps.ts | 8 ++++- .../src/strategy-components/lib/light-app.ts | 29 +++++++++++++++---- 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/frontend-web/src/pages/LightAppIframeView.tsx b/frontend-web/src/pages/LightAppIframeView.tsx index c4ce4237..7f6b63ed 100644 --- a/frontend-web/src/pages/LightAppIframeView.tsx +++ b/frontend-web/src/pages/LightAppIframeView.tsx @@ -9,7 +9,7 @@ import { useParams } from "react-router-dom"; import { useTheme } from "@/contexts/ThemeContext"; import { useLightApp } from "@/strategy-components/api/light-apps"; -import { buildLightAppUrl } from "@/strategy-components/lib/light-app"; +import { buildLightAppUrl, type AppThemeColor } from "@/strategy-components/lib/light-app"; export default function LightAppIframeView() { const { appId } = useParams<{ appId: string }>(); @@ -17,8 +17,14 @@ export default function LightAppIframeView() { const isDark = theme === "dark"; const { data: app, isLoading, error } = useLightApp(appId); - // 依赖 theme:切换主题色时重建 src,把对应主题色重新传给被嵌套应用。 - const src = useMemo(() => (app ? buildLightAppUrl(app) : ""), [app, theme]); + // 由 useTheme 的 theme 直接换算主题色并显式传入,切主题时 src 立即重建、 + // 把新主题色传给被嵌套应用(不依赖 的 dark class,避免时序读旧值)。 + // TODO: 后续接入 blue 主题后,这里区分 blue 与 dark-blue。 + const themeColor: AppThemeColor = isDark ? "dark-blue" : "red"; + const src = useMemo( + () => (app ? buildLightAppUrl(app, undefined, themeColor) : ""), + [app, themeColor], + ); const centerClass = `flex h-full w-full items-center justify-center text-sm ${ isDark ? "text-slate-400" : "text-slate-500" diff --git a/frontend-web/src/pages/LightAppManagementPage.tsx b/frontend-web/src/pages/LightAppManagementPage.tsx index bab529e4..fd2b16c5 100644 --- a/frontend-web/src/pages/LightAppManagementPage.tsx +++ b/frontend-web/src/pages/LightAppManagementPage.tsx @@ -707,8 +707,8 @@ function cleanParams(params: LightAppRouteParam[]): LightAppRouteParam[] { base.customValue = (p.customValue ?? "").trim(); } } else { - // theme:存映射串(或纯值),运行时按当前主题解析取值 - base.themeValue = (p.themeValue ?? "").trim(); + // theme:规范化为完整映射串(red=..;blue=..;dark-blue=..),运行时按当前主题取值 + base.themeValue = stringifyThemeMap(parseThemeMap(p.themeValue)); } return base; }); diff --git a/frontend-web/src/strategy-components/api/light-apps.ts b/frontend-web/src/strategy-components/api/light-apps.ts index 9f476e7a..7e16c5a5 100644 --- a/frontend-web/src/strategy-components/api/light-apps.ts +++ b/frontend-web/src/strategy-components/api/light-apps.ts @@ -16,6 +16,7 @@ import { import { apiFetch } from "@/core/api/fetch-client"; import { getBackendBaseURL } from "@/core/config"; +import { parseThemeMap, stringifyThemeMap } from "@/strategy-components/lib/light-app"; export type LightAppType = "window_open" | "iframe"; export type RouteParamType = "login" | "theme"; @@ -104,7 +105,12 @@ function toApp(s: ServerApp): LightApp { paramType: p.paramType, source: p.source ?? null, customValue: p.customValue ?? null, - themeValue: p.themeValue ?? null, + // 主题参数读取时规范化为完整映射串(red=..;blue=..;dark-blue=..), + // 让旧的纯值/部分映射也能随主题切换,无需重新保存。 + themeValue: + p.paramType === "theme" + ? stringifyThemeMap(parseThemeMap(p.themeValue)) + : (p.themeValue ?? null), })), mountLocation: s.mount_location ?? null, icon: s.icon ?? null, diff --git a/frontend-web/src/strategy-components/lib/light-app.ts b/frontend-web/src/strategy-components/lib/light-app.ts index 5b8e66ac..df47982a 100644 --- a/frontend-web/src/strategy-components/lib/light-app.ts +++ b/frontend-web/src/strategy-components/lib/light-app.ts @@ -40,6 +40,16 @@ export const APP_THEME_COLORS: { key: AppThemeColor; label: string; dot: string { key: "dark-blue", label: "深蓝色", dot: "#002B5C" }, ]; +/** + * 主题键别名:兼容历史/手填里用 light/dark 等写法的映射键,归一到当前主题色键。 + * 查映射时按别名顺序尝试,避免「键对不上 → 回退到 red」的恒红 bug。 + */ +const THEME_KEY_ALIASES: Record = { + red: ["red", "light"], + blue: ["blue"], + "dark-blue": ["dark-blue", "darkblue", "dark"], +}; + /** * 读取我方当前主题色,以 ThemeContext 写在 上的 `dark` class 为准 * (含嵌入场景的强制主题)。当前:深色=dark-blue,浅色=red。 @@ -73,8 +83,8 @@ export function stringifyThemeMap(map: Record): string { * 按我方当前主题,从配置中取目标轻应用对应的值(跳转 / iframe 嵌套时注入)。 * * 配置支持两种写法(管理员在「应用管理」主题参数里配置): - * 1) 映射串:`red=red;blue=blue;dark-blue=darkBlue` —— 按当前主题取对应值; - * 缺当前主题映射时回退到 red,再回退到首个值,最后回退到主题色名本身。 + * 1) 映射串:`red=red;blue=blue;dark-blue=darkBlue` —— 按当前主题(含别名)取对应值; + * 取不到时回退到主题色名本身(identity),而非盲目回退首个值(会恒为 red)。 * 2) 纯值:`red` —— 不区分主题,原样返回。 */ export function resolveThemeParamValue( @@ -86,7 +96,11 @@ export function resolveThemeParamValue( // 不含键值分隔符 → 纯值,不区分主题原样返回 if (!/[=:]/.test(value)) return value; const map = parseThemeMap(value); - return (map[theme] ?? map.red ?? Object.values(map)[0] ?? theme).trim(); + for (const key of THEME_KEY_ALIASES[theme] ?? [theme]) { + if (map[key] != null) return map[key].trim(); + } + // 映射里没有当前主题(含别名)→ 退回主题色名本身,避免恒为 red + return theme; } /** 读取并解析 localStorage 中的 userInfo。 */ @@ -105,10 +119,12 @@ export function readUserInfo(): Record { export function resolveRouteParamValue( param: LightAppRouteParam, userInfo: Record = readUserInfo(), + theme?: AppThemeColor, ): string { if (param.paramType === "theme") { - // 主题参数:按我方当前系统主题,从管理员配置的映射中取目标应用对应的值。 - return resolveThemeParamValue(param.themeValue); + // 主题参数:按我方当前主题,从管理员配置的映射中取目标应用对应的值。 + // theme 显式传入时优先(避免切主题瞬间 DOM 的 dark class 还没更新导致读旧值)。 + return resolveThemeParamValue(param.themeValue, theme); } // login if (param.source === "other") { @@ -130,10 +146,11 @@ export function resolveRouteParamValue( export function buildLightAppUrl( app: Pick, userInfo: Record = readUserInfo(), + theme?: AppThemeColor, ): string { const pairs = (app.routeParams ?? []) .filter((p) => p.key) - .map((p) => `${encodeURIComponent(p.key)}=${encodeURIComponent(resolveRouteParamValue(p, userInfo))}`); + .map((p) => `${encodeURIComponent(p.key)}=${encodeURIComponent(resolveRouteParamValue(p, userInfo, theme))}`); if (pairs.length === 0) return app.url; const sep = app.url.includes("?") ? "&" : "?"; return `${app.url}${sep}${pairs.join("&")}`;