feat: 实时消息与管理端运营能力
新增群聊/私信与公共 WebSocket 总线,站长用户内容档案,并将通知与待审角标改为推送驱动;同步精简管理仪表盘并修复主题换肤 DOM 冲突。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -190,10 +190,15 @@ export function deriveAccentPair(input: string): AccentDerivation {
|
||||
}
|
||||
|
||||
// 生成可直接注入的 CSS 文本(覆盖 globals.css 默认 accent 令牌);
|
||||
// 未配置或等于默认色时返回空串,完全使用内置样式
|
||||
// 未配置或等于默认色时返回空串,完全使用内置样式(SSR 场景)
|
||||
export function accentStyleCSS(input: string): string {
|
||||
if (!input || input.toLowerCase() === DEFAULT_ACCENT) return "";
|
||||
const { light, dark } = deriveAccentPair(input);
|
||||
return buildAccentCSS(input);
|
||||
}
|
||||
|
||||
/** 始终输出一套 accent 覆盖(含默认色),供客户端 runtime 盖住 SSR 自定义 style */
|
||||
function buildAccentCSS(input: string): string {
|
||||
const { light, dark } = deriveAccentPair(input || DEFAULT_ACCENT);
|
||||
const hi = mix(light, "#000000", 0.1);
|
||||
const darkHi = mix(dark, "#ffffff", 0.14);
|
||||
return [
|
||||
@@ -222,22 +227,44 @@ export function accentStyleCSS(input: string): string {
|
||||
// 客户端:实时换肤(后台预览 / 保存后即时生效)
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
const STYLE_ELEMENT_ID = "j13-accent-vars";
|
||||
/** SSR 注入的 style(React 托管,客户端禁止 remove/改写) */
|
||||
const SSR_STYLE_ID = "j13-accent-vars";
|
||||
/** 客户端专用覆盖节点,挂在 body 末尾以压过 SSR */
|
||||
const RUNTIME_STYLE_ID = "j13-accent-runtime";
|
||||
|
||||
// 将主题色应用到当前文档;传 null 移除覆盖、恢复内置默认
|
||||
function ensureRuntimeStyle(): HTMLStyleElement {
|
||||
let el = document.getElementById(RUNTIME_STYLE_ID) as HTMLStyleElement | null;
|
||||
if (el) return el;
|
||||
el = document.createElement("style");
|
||||
el.id = RUNTIME_STYLE_ID;
|
||||
el.setAttribute("data-accent-source", "runtime");
|
||||
document.body.appendChild(el);
|
||||
return el;
|
||||
}
|
||||
|
||||
function removeRuntimeStyle(): void {
|
||||
const el = document.getElementById(RUNTIME_STYLE_ID);
|
||||
// 只移除我们自己创建的节点,绝不碰 React 的 SSR <style>
|
||||
el?.remove();
|
||||
}
|
||||
|
||||
// 将主题色应用到当前文档;传 null/默认色时恢复内置令牌(不拆 SSR 节点)
|
||||
export function applyAccentTheme(input: string | null): void {
|
||||
if (typeof document === "undefined") return;
|
||||
let el = document.getElementById(STYLE_ELEMENT_ID) as HTMLStyleElement | null;
|
||||
const css = input === null ? "" : accentStyleCSS(input);
|
||||
if (!css) {
|
||||
el?.remove();
|
||||
const raw = (input || "").trim().toLowerCase();
|
||||
const isDefault = !raw || raw === DEFAULT_ACCENT.toLowerCase();
|
||||
|
||||
if (isDefault) {
|
||||
const ssr = document.getElementById(SSR_STYLE_ID);
|
||||
const ssrHasCustom = !!(ssr && ssr.textContent && ssr.textContent.trim());
|
||||
if (ssrHasCustom) {
|
||||
// SSR 仍挂着旧自定义色:用 runtime 写回默认覆盖,等 refresh 后再卸
|
||||
ensureRuntimeStyle().textContent = buildAccentCSS(DEFAULT_ACCENT);
|
||||
} else {
|
||||
removeRuntimeStyle();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!el) {
|
||||
el = document.createElement("style");
|
||||
el.id = STYLE_ELEMENT_ID;
|
||||
document.head.appendChild(el);
|
||||
}
|
||||
el.setAttribute("data-accent-source", "runtime");
|
||||
el.textContent = css;
|
||||
|
||||
ensureRuntimeStyle().textContent = buildAccentCSS(raw);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user