轻应用主题参数:随当前主题色映射传值
- 主题参数改为按系统主题(red/dark-blue)映射取目标应用对应的值 - 应用管理弹窗用结构化映射编辑器(逐主题色配置目标值,默认同名) - iframe 嵌套显式传入当前主题色,切主题时 src 重建 - 读取/保存时规范化映射串;解析支持 light/dark 等别名,取不到回退主题色名而非恒为 red Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
a7596001c5
commit
74324f4585
4 files changed
+41
-12
No files matched your search
@@ -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 立即重建、
|
||||
// 把新主题色传给被嵌套应用(不依赖 <html> 的 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"
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<AppThemeColor, string[]> = {
|
||||
red: ["red", "light"],
|
||||
blue: ["blue"],
|
||||
"dark-blue": ["dark-blue", "darkblue", "dark"],
|
||||
};
|
||||
|
||||
/**
|
||||
* 读取我方当前主题色,以 ThemeContext 写在 <html> 上的 `dark` class 为准
|
||||
* (含嵌入场景的强制主题)。当前:深色=dark-blue,浅色=red。
|
||||
@@ -73,8 +83,8 @@ export function stringifyThemeMap(map: Record<string, string>): 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<string, unknown> {
|
||||
export function resolveRouteParamValue(
|
||||
param: LightAppRouteParam,
|
||||
userInfo: Record<string, unknown> = 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<LightApp, "url" | "routeParams">,
|
||||
userInfo: Record<string, unknown> = 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("&")}`;
|
||||
|
||||
Reference in new issue
Block a user