feat: 公开页背景色支持 iframe 外部配置 + CM助手旁排行榜入口
- 新增 usePublicBackground:公开页(排行榜/分享/会商分享/计划任务)支持 嵌套方通过 ?bg=/?text= 查询参数或 postMessage 覆盖背景/文字色 - Header 左上角 CM助手 旁新增「排行榜」按钮,新标签打开公开排行榜页 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
28098b3155
commit
da677f87d5
6 files changed
+136
-14
No files matched your search
@@ -0,0 +1,88 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
/**
|
||||
* 公开页(被第三方 iframe 嵌套)的可配置背景色。
|
||||
*
|
||||
* 这些 `/public/*`、`/share/*`、`/roundtable-share/*` 页面会被别的系统用
|
||||
* `<iframe>` 嵌进去。嵌套方希望背景色跟自己的站点保持一致,所以我们把
|
||||
* 「背景色 / 前景文字色」抽成可外部传入的参数,两种传法都支持:
|
||||
*
|
||||
* 1. URL 查询参数(嵌套时一次性指定,最常用):
|
||||
* `<iframe src=".../#/public/leaderboard?bg=%23ffffff&text=%23111111">`
|
||||
* —— `bg`/`background` 设背景,`text`/`fg` 设文字色。
|
||||
* 2. `postMessage`(嵌套后动态切换,比如宿主切主题时同步):
|
||||
* `iframe.contentWindow.postMessage({ type: "deerflow:set-bg", bg: "#fff", text: "#111" }, "*")`
|
||||
*
|
||||
* 不传时返回空对象,页面沿用自带的默认配色(Tailwind 类),互不影响。
|
||||
*/
|
||||
export type PublicBackground = {
|
||||
backgroundColor?: string;
|
||||
color?: string;
|
||||
};
|
||||
|
||||
const HEX_RE = /^#?(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
||||
// 允许的非 hex 颜色:rgb()/rgba()/hsl()/hsla() 与简单命名色。限定安全字符集,
|
||||
// 既能覆盖常见写法,又能挡掉任何想塞进内联样式的奇怪内容。
|
||||
const SAFE_COLOR_RE = /^[a-zA-Z]+$|^(?:rgb|rgba|hsl|hsla)\(\s*[0-9.,%\s/]+\)$/;
|
||||
|
||||
/** 把外部传入的颜色串规整成可用的 CSS 颜色;非法/为空时返回 undefined。 */
|
||||
export function normalizeCssColor(raw: string | null | undefined): string | undefined {
|
||||
if (!raw) return undefined;
|
||||
const value = raw.trim();
|
||||
if (!value) return undefined;
|
||||
if (HEX_RE.test(value)) return value.startsWith("#") ? value : `#${value}`;
|
||||
if (SAFE_COLOR_RE.test(value)) return value;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function readFromSearch(search: string): PublicBackground {
|
||||
const params = new URLSearchParams(search.startsWith("?") ? search : `?${search}`);
|
||||
const backgroundColor = normalizeCssColor(params.get("bg") ?? params.get("background"));
|
||||
const color = normalizeCssColor(params.get("text") ?? params.get("fg"));
|
||||
const result: PublicBackground = {};
|
||||
if (backgroundColor) result.backgroundColor = backgroundColor;
|
||||
if (color) result.color = color;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取公开页背景配置,可直接 `style={...}` 展开到页面根容器。
|
||||
*
|
||||
* 合并两处来源:HashRouter 路由内的查询串(`?bg=` 跟在 `#/路由` 后面)以及
|
||||
* `window.location.search`(`?bg=` 在 `#` 前面);路由内的更贴近当前页,优先级更高。
|
||||
* 同时监听 `postMessage` 以支持嵌套后动态改色。
|
||||
*/
|
||||
export function usePublicBackground(): PublicBackground {
|
||||
const location = useLocation();
|
||||
|
||||
const fromUrl = useMemo<PublicBackground>(() => {
|
||||
const outer = readFromSearch(typeof window !== "undefined" ? window.location.search : "");
|
||||
const inner = readFromSearch(location.search);
|
||||
return { ...outer, ...inner };
|
||||
}, [location.search]);
|
||||
|
||||
const [override, setOverride] = useState<PublicBackground>({});
|
||||
|
||||
useEffect(() => {
|
||||
function onMessage(event: MessageEvent) {
|
||||
const data = event.data as
|
||||
| { type?: string; bg?: string; background?: string; text?: string; fg?: string }
|
||||
| null;
|
||||
if (!data || typeof data !== "object") return;
|
||||
if (data.type !== "deerflow:set-bg" && data.type !== "deerflow:set-background") return;
|
||||
setOverride((prev) => {
|
||||
const next: PublicBackground = { ...prev };
|
||||
const bg = normalizeCssColor(data.bg ?? data.background);
|
||||
const text = normalizeCssColor(data.text ?? data.fg);
|
||||
if (bg) next.backgroundColor = bg;
|
||||
if (text) next.color = text;
|
||||
return next;
|
||||
});
|
||||
}
|
||||
window.addEventListener("message", onMessage);
|
||||
return () => window.removeEventListener("message", onMessage);
|
||||
}, []);
|
||||
|
||||
return useMemo(() => ({ ...fromUrl, ...override }), [fromUrl, override]);
|
||||
}
|
||||
Reference in new issue
Block a user