主题色修改
This commit is contained in:
1 parent
1214ea0d91
commit
00d6bd8595
4 files changed
+157
-61
No files matched your search
@@ -17,7 +17,8 @@ export default function LightAppIframeView() {
|
||||
const isDark = theme === "dark";
|
||||
const { data: app, isLoading, error } = useLightApp(appId);
|
||||
|
||||
const src = useMemo(() => (app ? buildLightAppUrl(app) : ""), [app]);
|
||||
// 依赖 theme:切换主题色时重建 src,把对应主题色重新传给被嵌套应用。
|
||||
const src = useMemo(() => (app ? buildLightAppUrl(app) : ""), [app, theme]);
|
||||
|
||||
const centerClass = `flex h-full w-full items-center justify-center text-sm ${
|
||||
isDark ? "text-slate-400" : "text-slate-500"
|
||||
|
||||
@@ -23,9 +23,12 @@ import {
|
||||
type LightAppType,
|
||||
} from "@/strategy-components/api/light-apps";
|
||||
import {
|
||||
APP_THEME_COLORS,
|
||||
LOGIN_SOURCE_OPTIONS,
|
||||
MOUNT_OPTIONS,
|
||||
mountLabel,
|
||||
parseThemeMap,
|
||||
stringifyThemeMap,
|
||||
} from "@/strategy-components/lib/light-app";
|
||||
|
||||
interface FormState {
|
||||
@@ -53,7 +56,7 @@ const newParam = (): LightAppRouteParam => ({
|
||||
paramType: "login",
|
||||
source: "accessToken",
|
||||
customValue: "",
|
||||
themeValue: "dark-blue",
|
||||
themeValue: "red=red;blue=blue;dark-blue=dark-blue",
|
||||
});
|
||||
|
||||
function formFromApp(app: LightApp): FormState {
|
||||
@@ -69,7 +72,7 @@ function formFromApp(app: LightApp): FormState {
|
||||
paramType: p.paramType,
|
||||
source: p.source ?? "accessToken",
|
||||
customValue: p.customValue ?? "",
|
||||
themeValue: p.themeValue ?? "dark-blue",
|
||||
themeValue: p.themeValue ?? "",
|
||||
})),
|
||||
};
|
||||
}
|
||||
@@ -512,10 +515,12 @@ export default function LightAppManagementPage() {
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="flex items-center text-[14px] font-medium">
|
||||
<span>路由参数</span>
|
||||
<HelpCircle
|
||||
className="ml-1 h-3.5 w-3.5 text-[#A6A6A6]"
|
||||
<span
|
||||
className="ml-1 inline-flex"
|
||||
title="参数会在加载此应用时作为 query 参数拼接在 URL 的尾部"
|
||||
/>
|
||||
>
|
||||
<HelpCircle className="h-3.5 w-3.5 text-[#A6A6A6]" />
|
||||
</span>
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
@@ -529,41 +534,44 @@ export default function LightAppManagementPage() {
|
||||
|
||||
{form.routeParams.length > 0 ? (
|
||||
<div className="max-h-[220px] space-y-2 overflow-y-auto rounded-sm border border-[#E8E8E8] bg-[#FAFAFA] p-1">
|
||||
{form.routeParams.map((p, idx) => (
|
||||
{form.routeParams.map((p, idx) => {
|
||||
const themeMap =
|
||||
p.paramType === "theme" ? parseThemeMap(p.themeValue) : {};
|
||||
return (
|
||||
<div
|
||||
key={`${p.key}-${idx}`}
|
||||
className="relative flex items-center space-x-2 rounded-sm border border-[#E8E8E8] bg-white p-2"
|
||||
key={idx}
|
||||
className="relative rounded-sm border border-[#E8E8E8] bg-white p-2"
|
||||
>
|
||||
<div className="flex-1 space-y-1">
|
||||
<input
|
||||
type="text"
|
||||
value={p.key}
|
||||
onChange={(e) => patchParam(idx, { key: e.target.value })}
|
||||
placeholder="参数名 key"
|
||||
className="h-8 w-full rounded-[2px] border border-[#DBDBDB] bg-white px-2 font-mono text-[12px] focus:border-[#F5222D] focus:outline-none focus:ring-px focus:ring-[#F5222D]"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex-1">
|
||||
<input
|
||||
type="text"
|
||||
value={p.key}
|
||||
onChange={(e) => patchParam(idx, { key: e.target.value })}
|
||||
placeholder="参数名 key"
|
||||
className="h-8 w-full rounded-[2px] border border-[#DBDBDB] bg-white px-2 font-mono text-[12px] focus:border-[#F5222D] focus:outline-none focus:ring-px focus:ring-[#F5222D]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="w-[110px]">
|
||||
<select
|
||||
value={p.paramType}
|
||||
onChange={(e) =>
|
||||
patchParam(idx, {
|
||||
paramType: e.target.value as "login" | "theme",
|
||||
source:
|
||||
e.target.value === "login" ? (p.source || "accessToken") : undefined,
|
||||
})
|
||||
}
|
||||
className="h-8 w-full rounded-[2px] border border-[#DBDBDB] bg-[#FAFAFA] px-1 text-[12px] focus:border-[#F5222D] focus:outline-none"
|
||||
>
|
||||
<option value="login">登录参数</option>
|
||||
<option value="theme">主题参数</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="w-[110px]">
|
||||
<select
|
||||
value={p.paramType}
|
||||
onChange={(e) =>
|
||||
patchParam(idx, {
|
||||
paramType: e.target.value as "login" | "theme",
|
||||
source:
|
||||
e.target.value === "login" ? (p.source || "accessToken") : undefined,
|
||||
})
|
||||
}
|
||||
className="h-8 w-full rounded-[2px] border border-[#DBDBDB] bg-[#FAFAFA] px-1 text-[12px] focus:border-[#F5222D] focus:outline-none"
|
||||
>
|
||||
<option value="login">登录参数</option>
|
||||
<option value="theme">主题参数</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="flex-[1.2] text-[11px]">
|
||||
{p.paramType === "login" ? (
|
||||
<div className="flex items-center gap-2">
|
||||
{p.paramType === "login" && (
|
||||
<div className="flex flex-[1.2] items-center gap-2 text-[11px]">
|
||||
<select
|
||||
value={p.source || "accessToken"}
|
||||
onChange={(e) => patchParam(idx, { source: e.target.value })}
|
||||
@@ -587,26 +595,56 @@ export default function LightAppManagementPage() {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<input
|
||||
type="text"
|
||||
value={p.themeValue || ""}
|
||||
onChange={(e) => patchParam(idx, { themeValue: e.target.value })}
|
||||
placeholder="主题值,如 dark-blue"
|
||||
className="h-8 w-full rounded-[2px] border border-[#DBDBDB] bg-white px-2 text-[12px] focus:border-[#F5222D] focus:outline-none focus:ring-px focus:ring-[#F5222D]"
|
||||
/>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeParam(idx)}
|
||||
className="cursor-pointer rounded-sm p-1 text-[#A6A6A6] transition-colors hover:bg-[#FEEDED] hover:text-[#F5222D]"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeParam(idx)}
|
||||
className="cursor-pointer rounded-sm p-1 text-[#A6A6A6] transition-colors hover:bg-[#FEEDED] hover:text-[#F5222D]"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
{/* 主题参数:逐主题色配置传给目标应用的值(左=系统主题色,右=目标应用值,可改) */}
|
||||
{p.paramType === "theme" && (
|
||||
<div className="mt-2 space-y-1.5 rounded-sm bg-[#FAFAFA] p-2">
|
||||
<p className="text-[11px] leading-relaxed text-[#A6A6A6]">
|
||||
各系统主题色跳转/嵌套时传给该应用的值(默认同名,可按对方命名修改)
|
||||
</p>
|
||||
{APP_THEME_COLORS.map((c) => (
|
||||
<div key={c.key} className="flex items-center gap-2">
|
||||
<span className="flex w-[72px] shrink-0 items-center gap-1.5 text-[12px] text-[#575757]">
|
||||
<span
|
||||
className="h-2.5 w-2.5 shrink-0 rounded-full"
|
||||
style={{ backgroundColor: c.dot }}
|
||||
/>
|
||||
{c.label}
|
||||
</span>
|
||||
<span className="shrink-0 font-mono text-[11px] text-[#A6A6A6]">
|
||||
{c.key} =
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
value={themeMap[c.key] ?? c.key}
|
||||
onChange={(e) =>
|
||||
patchParam(idx, {
|
||||
themeValue: stringifyThemeMap({
|
||||
...themeMap,
|
||||
[c.key]: e.target.value,
|
||||
}),
|
||||
})
|
||||
}
|
||||
placeholder={c.key}
|
||||
className="h-7 flex-1 rounded-[2px] border border-[#DBDBDB] bg-white px-2 font-mono text-[12px] focus:border-[#F5222D] focus:outline-none focus:ring-px focus:ring-[#F5222D]"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-sm border border-dashed border-[#E8E8E8] bg-[#FAFAFA] p-4 text-center text-[12px] text-[#A6A6A6]">
|
||||
@@ -669,6 +707,7 @@ function cleanParams(params: LightAppRouteParam[]): LightAppRouteParam[] {
|
||||
base.customValue = (p.customValue ?? "").trim();
|
||||
}
|
||||
} else {
|
||||
// theme:存映射串(或纯值),运行时按当前主题解析取值
|
||||
base.themeValue = (p.themeValue ?? "").trim();
|
||||
}
|
||||
return base;
|
||||
|
||||
@@ -20,7 +20,7 @@ import { getBackendBaseURL } from "@/core/config";
|
||||
export type LightAppType = "window_open" | "iframe";
|
||||
export type RouteParamType = "login" | "theme";
|
||||
|
||||
/** 路由参数项。登录参数运行时从 localStorage.userInfo 解析;主题参数用 themeValue。 */
|
||||
/** 路由参数项。登录参数运行时从 localStorage.userInfo 解析;主题参数随当前系统主题自动注入。 */
|
||||
export interface LightAppRouteParam {
|
||||
key: string;
|
||||
paramType: RouteParamType;
|
||||
@@ -28,7 +28,7 @@ export interface LightAppRouteParam {
|
||||
source?: string | null;
|
||||
/** 登录参数且 source==="other" 时的字面值。 */
|
||||
customValue?: string | null;
|
||||
/** 主题参数的值(dark-blue / red / blue,可改)。 */
|
||||
/** 主题参数配置:映射串 `light=red;dark=darkBlue`(或纯值);运行时按当前系统主题取对应值(见 resolveThemeParamValue)。 */
|
||||
themeValue?: string | null;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,13 +27,68 @@ export const LOGIN_SOURCE_OPTIONS: { label: string; value: string }[] = [
|
||||
{ label: "其他(自定义值)", value: "other" },
|
||||
];
|
||||
|
||||
/** 主题参数示例值(深蓝 / 红 / 蓝),可改。 */
|
||||
export const THEME_EXAMPLE_OPTIONS: { label: string; value: string }[] = [
|
||||
{ label: "深蓝色 dark-blue", value: "dark-blue" },
|
||||
{ label: "红色 red", value: "red" },
|
||||
{ label: "蓝色 blue", value: "blue" },
|
||||
/**
|
||||
* 我方主题色键。当前启用 red(浅色) / dark-blue(深色);blue 为后续将新增的主题。
|
||||
* 主题参数的映射以这些键为左侧 key(如 `dark-blue=darkBlue`)。
|
||||
*/
|
||||
export type AppThemeColor = "red" | "blue" | "dark-blue";
|
||||
|
||||
/** 我方全部主题色(「应用管理」主题参数逐项配置目标值时遍历;dot 为配色小圆点)。 */
|
||||
export const APP_THEME_COLORS: { key: AppThemeColor; label: string; dot: string }[] = [
|
||||
{ key: "red", label: "红色", dot: "#F5222D" },
|
||||
{ key: "blue", label: "蓝色", dot: "#1677FF" },
|
||||
{ key: "dark-blue", label: "深蓝色", dot: "#002B5C" },
|
||||
];
|
||||
|
||||
/**
|
||||
* 读取我方当前主题色,以 ThemeContext 写在 <html> 上的 `dark` class 为准
|
||||
* (含嵌入场景的强制主题)。当前:深色=dark-blue,浅色=red。
|
||||
* TODO: 后续新增 blue 主题后,在此区分 blue 与 dark-blue。
|
||||
*/
|
||||
export function getCurrentTheme(): AppThemeColor {
|
||||
if (typeof document !== "undefined" && document.documentElement.classList.contains("dark")) {
|
||||
return "dark-blue";
|
||||
}
|
||||
return "red";
|
||||
}
|
||||
|
||||
/** 解析主题映射串(`red=red;dark-blue=darkBlue`)为 { 主题色键: 目标值 }。值不做 trim,便于表单回显。 */
|
||||
export function parseThemeMap(raw: string | null | undefined): Record<string, string> {
|
||||
const map: Record<string, string> = {};
|
||||
for (const pair of (raw ?? "").split(/[;,]/)) {
|
||||
const idx = pair.search(/[=:]/);
|
||||
if (idx <= 0) continue;
|
||||
const k = pair.slice(0, idx).trim().toLowerCase();
|
||||
map[k] = pair.slice(idx + 1);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/** 把主题映射对象序列化为映射串,按 APP_THEME_COLORS 顺序输出,缺省走同名(identity)。 */
|
||||
export function stringifyThemeMap(map: Record<string, string>): string {
|
||||
return APP_THEME_COLORS.map((c) => `${c.key}=${map[c.key] ?? c.key}`).join(";");
|
||||
}
|
||||
|
||||
/**
|
||||
* 按我方当前主题,从配置中取目标轻应用对应的值(跳转 / iframe 嵌套时注入)。
|
||||
*
|
||||
* 配置支持两种写法(管理员在「应用管理」主题参数里配置):
|
||||
* 1) 映射串:`red=red;blue=blue;dark-blue=darkBlue` —— 按当前主题取对应值;
|
||||
* 缺当前主题映射时回退到 red,再回退到首个值,最后回退到主题色名本身。
|
||||
* 2) 纯值:`red` —— 不区分主题,原样返回。
|
||||
*/
|
||||
export function resolveThemeParamValue(
|
||||
raw: string | null | undefined,
|
||||
theme: AppThemeColor = getCurrentTheme(),
|
||||
): string {
|
||||
const value = (raw ?? "").trim();
|
||||
if (!value) return theme;
|
||||
// 不含键值分隔符 → 纯值,不区分主题原样返回
|
||||
if (!/[=:]/.test(value)) return value;
|
||||
const map = parseThemeMap(value);
|
||||
return (map[theme] ?? map.red ?? Object.values(map)[0] ?? theme).trim();
|
||||
}
|
||||
|
||||
/** 读取并解析 localStorage 中的 userInfo。 */
|
||||
export function readUserInfo(): Record<string, unknown> {
|
||||
try {
|
||||
@@ -52,7 +107,8 @@ export function resolveRouteParamValue(
|
||||
userInfo: Record<string, unknown> = readUserInfo(),
|
||||
): string {
|
||||
if (param.paramType === "theme") {
|
||||
return param.themeValue ?? "";
|
||||
// 主题参数:按我方当前系统主题,从管理员配置的映射中取目标应用对应的值。
|
||||
return resolveThemeParamValue(param.themeValue);
|
||||
}
|
||||
// login
|
||||
if (param.source === "other") {
|
||||
|
||||
Reference in new issue
Block a user