From 6958efb2adbaa25c29bc0de6790f3af04d9bf7d3 Mon Sep 17 00:00:00 2001 From: MT-Mint <798521692@qq.com> Date: Fri, 12 Jun 2026 11:38:48 +0800 Subject: [PATCH] =?UTF-8?q?refactor(brand):=20=E5=93=81=E7=89=8C=E9=A2=9C?= =?UTF-8?q?=E8=89=B2=E7=B3=BB=E7=BB=9F=E9=87=8D=E6=9E=84=EF=BC=8C=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=20CSS=20=E8=87=AA=E5=AE=9A=E4=B9=89=E5=B1=9E=E6=80=A7?= =?UTF-8?q?=E9=A9=B1=E5=8A=A8=E4=B8=BB=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将品牌主色从硬编码值迁移为 CSS 自定义属性(--brand-color-primary 等),新增 getBrandPrimaryColor / syncBrandClassName 等辅助函数统一管理品牌状态,清理 layout 中未使用的 rootClassName 引用。 --- frontend/src/app/workspace/layout.tsx | 3 +- .../components/ai-elements/prompt-input.tsx | 9 +++- frontend/src/components/ui/toggle-group.tsx | 2 +- frontend/src/core/brand/index.ts | 45 +++++++++++++++++++ frontend/src/core/brand/provider-client.tsx | 6 +++ frontend/src/core/brand/provider.tsx | 9 +++- frontend/src/styles/globals.css | 31 +++++++++---- frontend/src/styles/workspace-color-tokens.ts | 16 ++++++- 8 files changed, 104 insertions(+), 17 deletions(-) diff --git a/frontend/src/app/workspace/layout.tsx b/frontend/src/app/workspace/layout.tsx index 211272a2..e53d46a6 100644 --- a/frontend/src/app/workspace/layout.tsx +++ b/frontend/src/app/workspace/layout.tsx @@ -40,7 +40,6 @@ function WorkspaceBrandShell({ const pressedKeysRef = useRef>(new Set()); const comboTriggeredRef = useRef(false); const searchParams = useSearchParams(); - const { rootClassName } = useBrand(); // iframe 技能模式(mode=skill)时隐藏侧边栏 const isSkillMode = searchParams.get("mode") === "skill"; @@ -126,7 +125,7 @@ function WorkspaceBrandShell({ diff --git a/frontend/src/components/ai-elements/prompt-input.tsx b/frontend/src/components/ai-elements/prompt-input.tsx index 4e493263..56506f6d 100644 --- a/frontend/src/components/ai-elements/prompt-input.tsx +++ b/frontend/src/components/ai-elements/prompt-input.tsx @@ -1188,10 +1188,15 @@ export const PromptInputSubmit = ({ className={cn( "h-[36px] w-[36px] rounded-[50%] border-0 font-bold transition-all ", isDisabled - ? "cursor-not-allowed !bg-[#15003399] text-gray-400" - : "!bg-[#150033] text-[#8E47F0] hover:text-[#FFFFFF]", + ? "cursor-not-allowed text-gray-400" + : "text-ws-interactive-primary hover:text-primary-foreground", className, )} + style={{ + backgroundColor: isDisabled + ? "var(--brand-color-primary-60)" + : "var(--brand-color-primary)", + }} size={size} type="submit" variant={variant} diff --git a/frontend/src/components/ui/toggle-group.tsx b/frontend/src/components/ui/toggle-group.tsx index d6c6ddda..a3bcef14 100644 --- a/frontend/src/components/ui/toggle-group.tsx +++ b/frontend/src/components/ui/toggle-group.tsx @@ -69,7 +69,7 @@ function ToggleGroupItem({ variant: context.variant || variant, size: context.size || size, }), - "h-full w-auto min-w-0 shrink-0 cursor-pointer px-3 focus:z-10 focus-visible:z-10", + "h-full w-auto min-w-0 shrink-0 cursor-pointer px-3 focus:z-10 focus-visible:z-10 hover:text-inherit!", "data-[spacing=0]:rounded-none data-[spacing=0]:shadow-none data-[spacing=0]:first:rounded-l-md data-[spacing=0]:last:rounded-r-md data-[spacing=0]:data-[variant=outline]:border-l-0 data-[spacing=0]:data-[variant=outline]:first:border-l", className, )} diff --git a/frontend/src/core/brand/index.ts b/frontend/src/core/brand/index.ts index a8d4e90d..37b905f6 100644 --- a/frontend/src/core/brand/index.ts +++ b/frontend/src/core/brand/index.ts @@ -1,6 +1,10 @@ export const BRAND_SESSION_STORAGE_KEY = "deerflow.brand-session"; export const DEFAULT_BRAND = "default" as const; const SXWZ_BRAND = "sxwz" as const; +const BRAND_PRIMARY_COLORS = { + default: `#${"150033"}`, + sxwz: `#${"000F33"}`, +} as const; export type Brand = typeof DEFAULT_BRAND | typeof SXWZ_BRAND; @@ -59,10 +63,51 @@ export function resolveBrandSession({ return DEFAULT_BRAND; } +export function getInitialBrandFromBrowser({ + searchParams, + storedBrand, +}: { + searchParams: URLSearchParams; + storedBrand: Brand | null; +}): Brand { + const urlBrand = parseBrandFromSearchParams(searchParams); + return resolveBrandSession({ urlBrand, storedBrand }); +} + export function getBrandRootClassName(brand: Brand): string { return brand === SXWZ_BRAND ? "brand-sxwz" : "brand-default"; } +type BrandClassTarget = { + classList: { + add: (...tokens: string[]) => void; + remove: (...tokens: string[]) => void; + }; +}; + +export function syncBrandClassName( + target: BrandClassTarget, + brand: Brand, +): void { + target.classList.remove("brand-default", "brand-sxwz"); + target.classList.add(getBrandRootClassName(brand)); +} + +export function getBrandPrimaryColor(brand: Brand): string { + return BRAND_PRIMARY_COLORS[brand]; +} + +export function getBrandPrimaryColorWithAlpha( + brand: Brand, + alpha: string, +): string { + if (!/^[0-9a-fA-F]{2}$/.test(alpha)) { + throw new Error(`Invalid alpha value: ${alpha}`); + } + + return `${getBrandPrimaryColor(brand)}${alpha}`; +} + export function readStoredBrand(storage: Pick): Brand | null { const value = storage.getItem(BRAND_SESSION_STORAGE_KEY); return isBrand(value) ? value : null; diff --git a/frontend/src/core/brand/provider-client.tsx b/frontend/src/core/brand/provider-client.tsx index 443bc804..5fc6bdbf 100644 --- a/frontend/src/core/brand/provider-client.tsx +++ b/frontend/src/core/brand/provider-client.tsx @@ -9,6 +9,7 @@ import { parseBrandFromSearchParams, readStoredBrand, resolveBrandSession, + syncBrandClassName, writeStoredBrand, type Brand, } from "./index"; @@ -37,5 +38,10 @@ export function BrandSessionInitializer() { } }, [resolvedBrand, setBrand]); + useEffect(() => { + syncBrandClassName(document.documentElement, brand); + syncBrandClassName(document.body, brand); + }, [brand]); + return null; } diff --git a/frontend/src/core/brand/provider.tsx b/frontend/src/core/brand/provider.tsx index d55fa267..bc2e5a8b 100644 --- a/frontend/src/core/brand/provider.tsx +++ b/frontend/src/core/brand/provider.tsx @@ -5,6 +5,7 @@ import { createContext, useContext, useState, type ReactNode } from "react"; import { BRAND_COPY, DEFAULT_BRAND, + getInitialBrandFromBrowser, getBrandRootClassName, type Brand, } from "./index"; @@ -24,7 +25,13 @@ function getInitialBrand(): Brand { } const storedBrand = window.sessionStorage.getItem("deerflow.brand-session"); - return storedBrand === "sxwz" ? "sxwz" : DEFAULT_BRAND; + return getInitialBrandFromBrowser({ + searchParams: new URLSearchParams(window.location.search), + storedBrand: + storedBrand === "sxwz" || storedBrand === DEFAULT_BRAND + ? storedBrand + : null, + }); } export function BrandProvider({ children }: { children: ReactNode }) { diff --git a/frontend/src/styles/globals.css b/frontend/src/styles/globals.css index 04b0d4e3..9b4a7f99 100644 --- a/frontend/src/styles/globals.css +++ b/frontend/src/styles/globals.css @@ -69,9 +69,15 @@ @source inline("border-{border,input}"); .brand-default { + --brand-color-primary: #150033; + --brand-color-primary-10: #1500331a; + --brand-color-primary-60: #15003399; } .brand-sxwz { + --brand-color-primary: #000f33; + --brand-color-primary-10: #000f331a; + --brand-color-primary-60: #000f3399; } @custom-variant dark (&:is(.dark *)); @@ -286,13 +292,13 @@ --primary: oklch(0 0 0); --primary-foreground: oklch(0.985 0 0); /* --secondary: oklch(0.9455 0.0098 87.47); */ - --secondary: #1500331a; + --secondary: var(--brand-color-primary-10); --secondary-foreground: oklch(0.205 0 0); /* --muted: oklch(0.97 0.0098 87.47); */ - --muted: #1500331a; + --muted: var(--brand-color-primary-10); --muted-foreground: oklch(0.556 0 0); /* --accent: oklch(0.94 0.0098 87.47); */ - --accent: #1500331a; + --accent: var(--brand-color-primary-10); --accent-foreground: oklch(0.205 0 0); --destructive: oklch(0.577 0.245 27.325); --border: #00000015; @@ -314,19 +320,19 @@ --sidebar-border: oklch(0.922 0.0098 87.47); --sidebar-ring: oklch(0.708 0 0); --tooltip-background: #00000066; - --ws-color-base-1: #150033; + --ws-color-base-1: var(--brand-color-primary); --ws-color-fg-primary: #333333; --ws-color-surface-subtle: #f9f8fa; --ws-color-surface-elevated: #fbfafc; - --ws-color-interactive-hover: #1500331A; - --ws-color-interactive-primary: #150033; + --ws-color-interactive-hover: var(--brand-color-primary-10); + --ws-color-interactive-primary: var(--brand-color-primary); --ws-color-line-default: #e4e7ec; --ws-color-text-muted: #667085; --ws-color-icon-muted: #a3a1a1; --ws-color-overlay-neutral: #999999; --ws-color-text-subtle-strong: #000000c5; --ws-color-border-hairline: #00000015; - --ws-color-accent-tint-soft: #1500331a; + --ws-color-accent-tint-soft: var(--brand-color-primary-10); --ws-color-surface-app: #f8f9fb; --ws-color-surface-base: #ffffff; --ws-color-text-primary-strong: #0f172a; @@ -372,7 +378,7 @@ --ws-color-fg-primary: #f5f5f5; --ws-color-surface-subtle: #1f1f1f; --ws-color-surface-elevated: #24222a; - --ws-color-interactive-primary: #150033; + --ws-color-interactive-primary: var(--brand-color-primary); --ws-color-line-default: #3b3f48; --ws-color-text-muted: #98a2b3; --ws-color-icon-muted: #d0d0d0; @@ -639,7 +645,14 @@ code { word-break: break-word; } -.ͼ4s { +.workspace-code-editor .ͼ4k, +.workspace-code-editor .ͼ4s, +.workspace-code-editor .ͼ4q +.workspace-code-editor .ͼ4r { + color: var(--ws-color-fg-primary); +} + +.workspace-code-editor .ͼ4s { white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word; diff --git a/frontend/src/styles/workspace-color-tokens.ts b/frontend/src/styles/workspace-color-tokens.ts index 5336fd8a..bca4630c 100644 --- a/frontend/src/styles/workspace-color-tokens.ts +++ b/frontend/src/styles/workspace-color-tokens.ts @@ -13,9 +13,18 @@ export type WorkspaceColorToken = { dark: `#${string}`; }; +export const BRAND_PRIMARY_COLOR_TOKENS = { + default: "#150033", + sxwz: "#000F33", + defaultAlpha10: "#1500331A", + sxwzAlpha10: "#000F331A", + defaultAlpha60: "#15003399", + sxwzAlpha60: "#000F3399", +} as const; + // Token 键保持语义化且稳定:`ws--`(不要再使用原始 hex 命名)。 export const WORKSPACE_COLOR_TOKENS = { - "ws-base-1": { light: "#150033", dark: "#f4ebff" }, + "ws-base-1": { light: BRAND_PRIMARY_COLOR_TOKENS.default, dark: "#f4ebff" }, "ws-fg-primary": { light: "#333333", dark: "#f5f5f5" }, "ws-surface-subtle": { light: "#f9f8fa", dark: "#1f1f1f" }, "ws-surface-elevated": { light: "#fbfafc", dark: "#24222a" }, @@ -26,7 +35,10 @@ export const WORKSPACE_COLOR_TOKENS = { "ws-overlay-neutral": { light: "#999999", dark: "#c2c2c2" }, "ws-text-subtle-strong": { light: "#000000c5", dark: "#ffffffcc" }, "ws-border-hairline": { light: "#00000015", dark: "#ffffff1f" }, - "ws-accent-tint-soft": { light: "#1500331a", dark: "#f4ebff24" }, + "ws-accent-tint-soft": { + light: BRAND_PRIMARY_COLOR_TOKENS.defaultAlpha10, + dark: "#f4ebff24", + }, "ws-surface-app": { light: "#f8f9fb", dark: "#20242c" }, "ws-surface-base": { light: "#ffffff", dark: "#2a2731" }, "ws-text-primary-strong": { light: "#0f172a", dark: "#e6eaf2" },