From 055523bf33db8dcd45a5547aee7cd293fadfec8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=AE=8F=E5=87=AF?= Date: Wed, 22 Jul 2026 15:43:06 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend-web/.env.example | 2 + frontend-web/public/runtime-config.js | 7 ++ frontend-web/src/App.tsx | 2 +- frontend-web/src/components/ui/sidebar.tsx | 30 +++-- .../workspace/appearance-controls.tsx | 6 + .../settings/appearance-settings-page.tsx | 95 +++++++++----- .../workspace/workspace-sidebar.tsx | 36 ++++-- frontend-web/src/contexts/ThemeContext.tsx | 34 ++++- .../src/core/branding/brand-assets.ts | 5 +- frontend-web/src/core/i18n/locales/en-US.ts | 6 +- frontend-web/src/core/i18n/locales/types.ts | 6 +- frontend-web/src/core/i18n/locales/zh-CN.ts | 6 +- frontend-web/src/core/system-settings/api.ts | 2 +- frontend-web/src/env.ts | 5 + frontend-web/src/pages/LightAppIframeView.tsx | 8 +- frontend-web/src/pages/LightAppListPage.tsx | 58 +++++++-- .../src/pages/LightAppManagementPage.tsx | 2 +- frontend-web/src/pages/LightAppNoteQAPage.tsx | 5 +- .../src/pages/NotebookAIWritingPage.tsx | 2 +- frontend-web/src/pages/PageRoutes.tsx | 14 ++- frontend-web/src/pages/WorkspaceRoutes.tsx | 19 ++- .../src/pages/light-app-management.css | 52 ++++++++ frontend-web/src/pages/light-app-pages.css | 5 + .../src/strategy-components/lib/light-app.ts | 5 +- frontend-web/src/styles/globals.css | 118 ++++++++++++++++++ frontend-web/src/styles/tdesign-theme.css | 31 +++++ .../app/gateway/routers/system_settings.py | 8 +- .../deerflow/config/system_settings.py | 6 +- 28 files changed, 483 insertions(+), 92 deletions(-) diff --git a/frontend-web/.env.example b/frontend-web/.env.example index 1b3d80bb..eea12c4c 100644 --- a/frontend-web/.env.example +++ b/frontend-web/.env.example @@ -50,4 +50,6 @@ # 可选 # VITE_CANVAS_LANGGRAPH_BASE_URL= # VITE_STATIC_WEBSITE_ONLY= +# 部署级紧凑导航:true 时固定收起工作台左侧栏,并隐藏换肤与全量/简洁模式切换。 +# VITE_FORCE_COLLAPSED_SIDEBAR=false # VITE_GITHUB_OAUTH_TOKEN= diff --git a/frontend-web/public/runtime-config.js b/frontend-web/public/runtime-config.js index 139e781d..eeed8fc9 100644 --- a/frontend-web/public/runtime-config.js +++ b/frontend-web/public/runtime-config.js @@ -19,12 +19,15 @@ window.__CM_RUNTIME_CONFIG__ = { VITE_BRAND_LOGO_LIGHT_URL: "brand-assets/brand-logo-light.png", // 浅蓝主题的顶栏系统 Logo 图片地址。 VITE_BRAND_LOGO_LIGHTBLUE_URL: "brand-assets/brand-logo-lightblue.png", + // 绿色主题可单独替换 Logo;默认复用浅色 Logo。 + VITE_BRAND_LOGO_LIGHTGREEN_URL: "brand-assets/brand-logo-light.png", // 深色主题的顶栏系统 Logo 图片地址。 VITE_BRAND_LOGO_DARK_URL: "brand-assets/brand-logo-dark.png", // 红白主题的欢迎页 Logo 图片地址:为空时显示原有 CM 文字和渐变背景。 VITE_BRAND_WELCOME_LIGHT_URL: "", // 浅蓝主题的欢迎页 Logo 图片地址:为空时显示原有 CM 文字和渐变背景。 VITE_BRAND_WELCOME_LIGHTBLUE_URL: "", + VITE_BRAND_WELCOME_LIGHTGREEN_URL: "", // 深色主题的欢迎页 Logo 图片地址:为空时显示原有 CM 文字和渐变背景。 VITE_BRAND_WELCOME_DARK_URL: "", @@ -112,6 +115,10 @@ window.__CM_RUNTIME_CONFIG__ = { // 静态站点模式开关;一般部署业务系统时保持 false。 VITE_STATIC_WEBSITE_ONLY: "false", + // 紧凑导航开关:true 时固定使用简洁布局,工作台左侧栏收起为菜单图标, + // 隐藏系统 Logo、换肤按钮以及“全量/简洁模式”切换。修改后刷新页面即可生效。 + VITE_FORCE_COLLAPSED_SIDEBAR: "false", + // GitHub OAuth Token;不使用 GitHub 相关能力时留空。 VITE_GITHUB_OAUTH_TOKEN: "", }; diff --git a/frontend-web/src/App.tsx b/frontend-web/src/App.tsx index a4a67c48..e3978689 100644 --- a/frontend-web/src/App.tsx +++ b/frontend-web/src/App.tsx @@ -86,7 +86,7 @@ function AppProviders({ children }: { children: React.ReactNode }) { attribute="class" storageKey="strategy-theme" defaultTheme="light" - themes={["light", "dark", "lightblue"]} + themes={["light", "dark", "lightblue", "lightgreen"]} enableSystem={false} disableTransitionOnChange forcedTheme={forcedTheme} diff --git a/frontend-web/src/components/ui/sidebar.tsx b/frontend-web/src/components/ui/sidebar.tsx index f544abf3..3f38c6ff 100644 --- a/frontend-web/src/components/ui/sidebar.tsx +++ b/frontend-web/src/components/ui/sidebar.tsx @@ -57,18 +57,21 @@ function useSidebarSafe() { return React.useContext(SidebarContext); } -function SidebarProvider({ - defaultOpen = true, - open: openProp, - onOpenChange: setOpenProp, - className, +function SidebarProvider({ + defaultOpen = true, + open: openProp, + onOpenChange: setOpenProp, + disableToggle = false, + className, style, children, ...props -}: React.ComponentProps<"div"> & { - defaultOpen?: boolean; - open?: boolean; - onOpenChange?: (open: boolean) => void; +}: React.ComponentProps<"div"> & { + defaultOpen?: boolean; + open?: boolean; + onOpenChange?: (open: boolean) => void; + /** Keep a controlled deployment-level rail from being reopened by hotkeys. */ + disableToggle?: boolean; }) { const isMobile = useIsMobile(); const [openMobile, setOpenMobile] = React.useState(false); @@ -92,10 +95,11 @@ function SidebarProvider({ [setOpenProp, open], ); - // Helper to toggle the sidebar. - const toggleSidebar = React.useCallback(() => { - return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open); - }, [isMobile, setOpen, setOpenMobile]); + // Helper to toggle the sidebar. + const toggleSidebar = React.useCallback(() => { + if (disableToggle) return; + return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open); + }, [disableToggle, isMobile, setOpen, setOpenMobile]); // Adds a keyboard shortcut to toggle the sidebar. React.useEffect(() => { diff --git a/frontend-web/src/components/workspace/appearance-controls.tsx b/frontend-web/src/components/workspace/appearance-controls.tsx index c11534b0..0f159a88 100644 --- a/frontend-web/src/components/workspace/appearance-controls.tsx +++ b/frontend-web/src/components/workspace/appearance-controls.tsx @@ -15,6 +15,7 @@ import { } from "@/components/ui/dropdown-menu"; import { useTheme, type ThemeType } from "@/contexts/ThemeContext"; import { useAppearanceSettings, useLayoutMode } from "@/core/system-settings/hooks"; +import { env } from "@/env"; import { cn } from "@/lib/utils"; import { buildLightAppUrl } from "@/strategy-components/lib/light-app"; @@ -29,6 +30,7 @@ export type AppearanceControlTone = "banner-dark" | "banner-light" | "content"; // 换肤色板:红色=红白主题(light),蓝色=浅蓝主题(lightblue),深蓝色=深色主题(dark) export const THEME_SWATCHES: { id: ThemeType; label: string; color: string }[] = [ { id: "light", label: "红色", color: "#cf1322" }, + { id: "lightgreen", label: "绿色", color: "#07c05f" }, { id: "lightblue", label: "蓝色", color: "#0062d9" }, { id: "dark", label: "深蓝色", color: "#002466" }, ]; @@ -95,6 +97,8 @@ export function ThemeSwitcher({ tone }: { tone: AppearanceControlTone }) { useEffect(() => () => cancelClose(), []); + if (env.FORCE_COLLAPSED_SIDEBAR) return null; + return (
diff --git a/frontend-web/src/components/workspace/settings/appearance-settings-page.tsx b/frontend-web/src/components/workspace/settings/appearance-settings-page.tsx index 162d710b..657cb6f9 100644 --- a/frontend-web/src/components/workspace/settings/appearance-settings-page.tsx +++ b/frontend-web/src/components/workspace/settings/appearance-settings-page.tsx @@ -7,6 +7,7 @@ import { ExternalLink, LayoutGridIcon, LayoutIcon, + LeafIcon, ListIcon, MoonIcon, PanelLeftIcon, @@ -70,7 +71,7 @@ const languageOptions: { value: Locale; label: string }[] = [ { value: "zh-CN", label: zhCN.locale.localName }, ]; -const DEFAULT_SHORTCUT_THEME_MAP = "red=red;blue=blue;dark-blue=dark-blue"; +const DEFAULT_SHORTCUT_THEME_MAP = "red=red;green=green;blue=blue;dark-blue=dark-blue"; function makeShortcutId() { if (typeof crypto !== "undefined" && "randomUUID" in crypto) { @@ -211,14 +212,20 @@ export function AppearanceSettingsPage({ onClose }: { onClose?: () => void }) { } }; - const handleSetGlobalDefault = async ( - value: "light" | "dark" | "lightblue", + const handleSetGlobalDefault = async ( + value: "light" | "dark" | "lightblue" | "lightgreen", ) => { if (value === globalDefault) return; try { await updateAppearance.mutateAsync({ default_theme: value }); - const label = - value === "light" ? "浅色" : value === "dark" ? "深色" : "浅蓝"; + const label = + value === "light" + ? "浅色" + : value === "dark" + ? "深色" + : value === "lightgreen" + ? "绿色" + : "浅蓝"; toast.success(`已将系统默认主题设置为${label}`); } catch (e) { toast.error(e instanceof Error ? e.message : "保存失败"); @@ -368,20 +375,28 @@ export function AppearanceSettingsPage({ onClose }: { onClose?: () => void }) { description: t.settings.appearance.darkDescription, icon: MoonIcon, }, - { - id: "lightblue", - label: t.settings.appearance.lightblue, - description: t.settings.appearance.lightblueDescription, - icon: DropletIcon, - }, + { + id: "lightblue", + label: t.settings.appearance.lightblue, + description: t.settings.appearance.lightblueDescription, + icon: DropletIcon, + }, + { + id: "lightgreen", + label: t.settings.appearance.lightgreen, + description: t.settings.appearance.lightgreenDescription, + icon: LeafIcon, + }, ], [ t.settings.appearance.dark, t.settings.appearance.darkDescription, t.settings.appearance.light, t.settings.appearance.lightDescription, - t.settings.appearance.lightblue, - t.settings.appearance.lightblueDescription, + t.settings.appearance.lightblue, + t.settings.appearance.lightblueDescription, + t.settings.appearance.lightgreen, + t.settings.appearance.lightgreenDescription, ], ); @@ -399,7 +414,7 @@ export function AppearanceSettingsPage({ onClose }: { onClose?: () => void }) { label={option.label} description={option.description} active={currentTheme === option.id} - mode={option.id as "light" | "dark" | "lightblue"} + mode={option.id as "light" | "dark" | "lightblue" | "lightgreen"} onSelect={(value) => setTheme(value)} /> ))} @@ -541,23 +556,35 @@ export function AppearanceSettingsPage({ onClose }: { onClose?: () => void }) { 深色 - + 浅蓝 + +

当前系统默认: {globalDefault === "light" ? "浅色" - : globalDefault === "dark" - ? "深色" - : "浅蓝"} + : globalDefault === "dark" + ? "深色" + : globalDefault === "lightgreen" + ? "绿色" + : "浅蓝"}

@@ -1074,8 +1101,8 @@ function ThemePreviewCard({ label: string; description: string; active: boolean; - mode: "light" | "dark" | "lightblue"; - onSelect: (mode: "light" | "dark" | "lightblue") => void; + mode: "light" | "dark" | "lightblue" | "lightgreen"; + onSelect: (mode: "light" | "dark" | "lightblue" | "lightgreen") => void; }) { const previewMode = mode; return ( @@ -1104,10 +1131,12 @@ function ThemePreviewCard({ className={cn( "relative overflow-hidden rounded-md border text-xs transition-colors", previewMode === "dark" - ? "border-neutral-800 bg-neutral-900 text-neutral-200" - : previewMode === "lightblue" - ? "border-blue-200 bg-white text-slate-900" - : "border-slate-200 bg-white text-slate-900", + ? "border-neutral-800 bg-neutral-900 text-neutral-200" + : previewMode === "lightblue" + ? "border-blue-200 bg-white text-slate-900" + : previewMode === "lightgreen" + ? "border-emerald-200 bg-white text-emerald-950" + : "border-slate-200 bg-white text-slate-900", )} >
@@ -1115,10 +1144,12 @@ function ThemePreviewCard({ className={cn( "h-2 w-2 rounded-full", previewMode === "dark" - ? "bg-emerald-400" - : previewMode === "lightblue" - ? "bg-blue-500" - : "bg-emerald-500", + ? "bg-emerald-400" + : previewMode === "lightblue" + ? "bg-blue-500" + : previewMode === "lightgreen" + ? "bg-[#07c05f]" + : "bg-[#cf1322]", )} />
diff --git a/frontend-web/src/components/workspace/workspace-sidebar.tsx b/frontend-web/src/components/workspace/workspace-sidebar.tsx index 39206d90..331b97b9 100644 --- a/frontend-web/src/components/workspace/workspace-sidebar.tsx +++ b/frontend-web/src/components/workspace/workspace-sidebar.tsx @@ -20,11 +20,15 @@ import { WorkspaceHeader } from "./workspace-header"; import { WorkspaceNavChatList } from "./workspace-nav-chat-list"; import { WorkspaceNavMenu } from "./workspace-nav-menu"; -export function WorkspaceSidebar({ - compactChrome = false, - className, - ...props -}: React.ComponentProps & { compactChrome?: boolean }) { +export function WorkspaceSidebar({ + compactChrome = false, + forceCollapsed = false, + className, + ...props +}: React.ComponentProps & { + compactChrome?: boolean; + forceCollapsed?: boolean; +}) { const { open: isSidebarOpen } = useSidebar(); const { theme } = useTheme(); const pathname = usePathname(); @@ -32,8 +36,26 @@ export function WorkspaceSidebar({ // 通用问答(全量模式)左栏:新建回答 → 常用智能体 → 聊天记录(需求 0616 #1)。 // 仅在通用问答(/page/workspace/chats*)下启用三块式布局;点击常用智能体后是 // 「原地嵌入」(停留在 /chats/new?agent=...,仅右侧问答区换成智能体对话),左侧栏不变。 - const isGeneralQa = pathname.startsWith("/page/workspace/chats"); - + const isGeneralQa = pathname.startsWith("/page/workspace/chats"); + + if (forceCollapsed) { + return ( + + + + + + ); + } + // 紧凑模式:用「外层定宽容器 + transition-[width]」做展开/收起过渡动画。 // collapsible="none" 的 Sidebar 是普通 in-flow 定宽块(非 absolute,故不受本布局对 // sidebar-container 的 absolute/left-0 覆盖影响),外层 overflow-hidden 把它从 diff --git a/frontend-web/src/contexts/ThemeContext.tsx b/frontend-web/src/contexts/ThemeContext.tsx index 004900f9..ece09321 100644 --- a/frontend-web/src/contexts/ThemeContext.tsx +++ b/frontend-web/src/contexts/ThemeContext.tsx @@ -10,7 +10,7 @@ import { useTheme as useNextTheme } from 'next-themes'; import { useAppearanceSettings } from '@/core/system-settings/hooks'; // 主题类型 -export type ThemeType = 'dark' | 'light' | 'lightblue'; +export type ThemeType = 'dark' | 'light' | 'lightblue' | 'lightgreen'; // 主题配置 export interface ThemeConfig { @@ -104,6 +104,28 @@ const themes: Record = { overlay: 'black/30', }, }, + lightgreen: { + type: 'lightgreen', + name: '绿色主题', + colors: { + background: 'white', + backgroundSecondary: 'emerald-50', + text: 'emerald-950', + textSecondary: 'emerald-800', + primary: 'emerald-600', + primaryHover: 'emerald-500', + border: 'emerald-200', + accent: 'emerald-700', + success: 'emerald-600', + warning: 'amber-500', + error: 'red-600', + card: 'white', + cardHover: 'emerald-50', + input: 'white', + inputBorder: 'emerald-200', + overlay: 'black/30', + }, + }, }; const STORAGE_KEY = 'strategy-theme'; @@ -121,6 +143,7 @@ function readPersistedTheme(): ThemeType { const saved = localStorage.getItem(STORAGE_KEY); if (saved === 'dark') return 'dark'; if (saved === 'lightblue') return 'lightblue'; + if (saved === 'lightgreen') return 'lightgreen'; return 'light'; } @@ -179,7 +202,8 @@ export const ThemeProvider: React.FC = ({ children }) => { current && current !== 'light' && current !== 'dark' && - current !== 'lightblue' + current !== 'lightblue' && + current !== 'lightgreen' ) { // Garbage value — wipe it so the boot-sync below can take over. localStorage.removeItem(STORAGE_KEY); @@ -216,6 +240,8 @@ export const ThemeProvider: React.FC = ({ children }) => { ? 'dark' : effectiveTheme === 'lightblue' ? 'lightblue' + : effectiveTheme === 'lightgreen' + ? 'lightgreen' : 'light' : readPersistedTheme(); @@ -234,8 +260,8 @@ export const ThemeProvider: React.FC = ({ children }) => { root.removeAttribute('theme-mode'); root.classList.remove('dark'); - if (theme === 'lightblue') { - root.setAttribute('data-theme', 'lightblue'); + if (theme === 'lightblue' || theme === 'lightgreen') { + root.setAttribute('data-theme', theme); } else { root.removeAttribute('data-theme'); } diff --git a/frontend-web/src/core/branding/brand-assets.ts b/frontend-web/src/core/branding/brand-assets.ts index b78cebb9..127075e5 100644 --- a/frontend-web/src/core/branding/brand-assets.ts +++ b/frontend-web/src/core/branding/brand-assets.ts @@ -3,23 +3,26 @@ import fallbackLogoLight from "@/assets/img/brand-logo-light.png"; import fallbackLogoLightblue from "@/assets/img/brand-logo-lightblue.png"; import { readRuntimeEnv } from "@/env"; -export type BrandTheme = "light" | "lightblue" | "dark"; +export type BrandTheme = "light" | "lightblue" | "lightgreen" | "dark"; const headerFallbackAssets: Record = { light: fallbackLogoLight, lightblue: fallbackLogoLightblue, + lightgreen: fallbackLogoLight, dark: fallbackLogoDark, }; const headerRuntimeKeys: Record = { light: "VITE_BRAND_LOGO_LIGHT_URL", lightblue: "VITE_BRAND_LOGO_LIGHTBLUE_URL", + lightgreen: "VITE_BRAND_LOGO_LIGHTGREEN_URL", dark: "VITE_BRAND_LOGO_DARK_URL", }; const welcomeRuntimeKeys: Record = { light: "VITE_BRAND_WELCOME_LIGHT_URL", lightblue: "VITE_BRAND_WELCOME_LIGHTBLUE_URL", + lightgreen: "VITE_BRAND_WELCOME_LIGHTGREEN_URL", dark: "VITE_BRAND_WELCOME_DARK_URL", }; diff --git a/frontend-web/src/core/i18n/locales/en-US.ts b/frontend-web/src/core/i18n/locales/en-US.ts index 3844d46e..cc28ffee 100644 --- a/frontend-web/src/core/i18n/locales/en-US.ts +++ b/frontend-web/src/core/i18n/locales/en-US.ts @@ -448,11 +448,13 @@ export const enUS: Translations = { system: "System", light: "Light", dark: "Dark", - lightblue: "Light Blue", + lightblue: "Light Blue", + lightgreen: "Green", systemDescription: "Match the operating system preference automatically.", lightDescription: "Bright palette with higher contrast for daytime.", darkDescription: "Dim palette that reduces glare for focus.", - lightblueDescription: "A crisp light palette built around blue.", + lightblueDescription: "A crisp light palette built around blue.", + lightgreenDescription: "A crisp light palette built around WeKnora green.", languageTitle: "Language", languageDescription: "Switch between languages.", }, diff --git a/frontend-web/src/core/i18n/locales/types.ts b/frontend-web/src/core/i18n/locales/types.ts index 6f21b24a..29783587 100644 --- a/frontend-web/src/core/i18n/locales/types.ts +++ b/frontend-web/src/core/i18n/locales/types.ts @@ -383,11 +383,13 @@ export interface Translations { system: string; light: string; dark: string; - lightblue: string; + lightblue: string; + lightgreen: string; systemDescription: string; lightDescription: string; darkDescription: string; - lightblueDescription: string; + lightblueDescription: string; + lightgreenDescription: string; languageTitle: string; languageDescription: string; }; diff --git a/frontend-web/src/core/i18n/locales/zh-CN.ts b/frontend-web/src/core/i18n/locales/zh-CN.ts index df437800..5e62a6d9 100644 --- a/frontend-web/src/core/i18n/locales/zh-CN.ts +++ b/frontend-web/src/core/i18n/locales/zh-CN.ts @@ -429,11 +429,13 @@ export const zhCN: Translations = { system: "系统", light: "浅色", dark: "深色", - lightblue: "浅蓝", + lightblue: "浅蓝", + lightgreen: "绿色", systemDescription: "自动跟随系统主题。", lightDescription: "更明亮的配色,适合日间使用。", darkDescription: "更暗的配色,减少眩光方便专注。", - lightblueDescription: "以蓝色为主色的清爽浅色配色。", + lightblueDescription: "以蓝色为主色的清爽浅色配色。", + lightgreenDescription: "采用 WeKnora 绿色的清爽浅色配色。", languageTitle: "语言", languageDescription: "在不同语言之间切换。", }, diff --git a/frontend-web/src/core/system-settings/api.ts b/frontend-web/src/core/system-settings/api.ts index d2c0564e..d1be600e 100644 --- a/frontend-web/src/core/system-settings/api.ts +++ b/frontend-web/src/core/system-settings/api.ts @@ -276,7 +276,7 @@ export async function updatePublishSquares( return data; } -export type ThemeMode = "light" | "dark" | "lightblue"; +export type ThemeMode = "light" | "dark" | "lightblue" | "lightgreen"; export type ViewMode = "card" | "list"; export type LayoutMode = "full" | "compact"; export type CompactShortcutParamType = "login" | "theme"; diff --git a/frontend-web/src/env.ts b/frontend-web/src/env.ts index 03e9af83..32d4844c 100644 --- a/frontend-web/src/env.ts +++ b/frontend-web/src/env.ts @@ -29,5 +29,10 @@ export const env = { NEXT_PUBLIC_NOTE_API_BASE_URL: readRuntimeEnv("VITE_NOTE_API_BASE_URL"), NEXT_PUBLIC_NOTEBOOK_API_BASE_URL: readRuntimeEnv("VITE_AIPI_BACKENP_URL"), NEXT_PUBLIC_STATIC_WEBSITE_ONLY: readRuntimeEnv("VITE_STATIC_WEBSITE_ONLY"), + /** + * Deployment-level compact navigation mode. This is intentionally a runtime + * flag so operations can switch the shell without rebuilding the frontend. + */ + FORCE_COLLAPSED_SIDEBAR: readRuntimeFlag("VITE_FORCE_COLLAPSED_SIDEBAR"), GITHUB_OAUTH_TOKEN: readRuntimeEnv("VITE_GITHUB_OAUTH_TOKEN"), }; diff --git a/frontend-web/src/pages/LightAppIframeView.tsx b/frontend-web/src/pages/LightAppIframeView.tsx index bc0cb0e5..aa687b50 100644 --- a/frontend-web/src/pages/LightAppIframeView.tsx +++ b/frontend-web/src/pages/LightAppIframeView.tsx @@ -21,7 +21,13 @@ export default function LightAppIframeView() { // 把新主题色传给被嵌套应用(不依赖 的 dark class,避免时序读旧值)。 // 深色=dark-blue,浅蓝=blue,其余(红白)=red。 const themeColor: AppThemeColor = - theme === "dark" ? "dark-blue" : theme === "lightblue" ? "blue" : "red"; + theme === "dark" + ? "dark-blue" + : theme === "lightgreen" + ? "green" + : theme === "lightblue" + ? "blue" + : "red"; const src = useMemo( () => (app ? buildLightAppUrl(app, undefined, themeColor) : ""), [app, themeColor], diff --git a/frontend-web/src/pages/LightAppListPage.tsx b/frontend-web/src/pages/LightAppListPage.tsx index 86d2cb05..b97ffe5e 100644 --- a/frontend-web/src/pages/LightAppListPage.tsx +++ b/frontend-web/src/pages/LightAppListPage.tsx @@ -34,7 +34,7 @@ import { toast } from "sonner"; import { Pagination } from "@/components/ui/pagination"; import { cn } from "@/lib/utils"; -import { useTheme } from "@/contexts/ThemeContext"; +import { useTheme, type ThemeType } from "@/contexts/ThemeContext"; import { useLightApps, type LightApp } from "@/strategy-components/api/light-apps"; import { buildLightAppUrl } from "@/strategy-components/lib/light-app"; @@ -64,12 +64,46 @@ function hashIndex(str: string, mod: number): number { return mod > 0 ? h % mod : 0; } +function getLightAccent(theme: ThemeType) { + if (theme === "lightgreen") { + return { + dot: "bg-[#07C05F]", + badge: "border-[#07C05F]/15 bg-[#E9F8EC] text-[#049B38]", + focus: "focus:border-[#07C05F]/60 focus:ring-[#07C05F]/20", + text: "text-[#049B38]", + hoverText: "group-hover:text-[#049B38]", + link: "text-[#049B38] hover:text-[#038626]", + button: "bg-[#07C05F] shadow-[0_1px_3px_rgba(7,192,95,0.2)] hover:bg-[#08DD6E]", + }; + } + if (theme === "lightblue") { + return { + dot: "bg-[#0062D9]", + badge: "border-[#0062D9]/15 bg-[#EAF4FF] text-[#0062D9]", + focus: "focus:border-[#0062D9]/60 focus:ring-[#0062D9]/20", + text: "text-[#0062D9]", + hoverText: "group-hover:text-[#0062D9]", + link: "text-[#0062D9] hover:text-[#0056C6]", + button: "bg-[#0062D9] shadow-[0_1px_3px_rgba(0,98,217,0.2)] hover:bg-[#0079FE]", + }; + } + return { + dot: "bg-[#CF1322]", + badge: "border-[#CF1322]/15 bg-[#FFF1F0] text-[#CF1322]", + focus: "focus:border-[#CF1322]/60 focus:ring-[#CF1322]/20", + text: "text-[#CF1322]", + hoverText: "group-hover:text-[#CF1322]", + link: "text-[#CF1322] hover:text-[#A8071A]", + button: "bg-[#CF1322] shadow-[0_1px_3px_rgba(207,19,34,0.2)] hover:bg-[#A8071A]", + }; +} + export default function LightAppListPage() { const [search, setSearch] = useState(""); const [page, setPage] = useState(1); const [copiedId, setCopiedId] = useState(null); const { theme } = useTheme(); - const isLightBlue = theme === 'lightblue'; + const lightAccent = getLightAccent(theme); const { data: apps = [], isLoading } = useLightApps({ appType: "window_open", @@ -118,13 +152,13 @@ export default function LightAppListPage() {
- - + +

应用列表

- + 导航工作台
@@ -142,7 +176,7 @@ export default function LightAppListPage() { value={search} onChange={(e) => setSearch(e.target.value)} placeholder="键入应用名称、目标地址、或描述,实时过滤检索…" - className={cn("w-full rounded-[5px] border border-zinc-200 bg-zinc-50/50 py-3 pl-10 pr-10 text-sm transition focus:bg-white focus:outline-none focus:ring-2 dark:border-[#1A2F5A] dark:bg-[#0C1836] dark:text-[#C5CFE0] dark:placeholder-[#4A6491] dark:focus:border-[#1677FF]/60 dark:focus:bg-[#122347] dark:focus:ring-[#1677FF]/25", isLightBlue ? "focus:border-[#0062D9]/60 focus:ring-[#0062D9]/20" : "focus:border-[#CF1322]/60 focus:ring-[#CF1322]/20")} + className={cn("w-full rounded-[5px] border border-zinc-200 bg-zinc-50/50 py-3 pl-10 pr-10 text-sm transition focus:bg-white focus:outline-none focus:ring-2 dark:border-[#1A2F5A] dark:bg-[#0C1836] dark:text-[#C5CFE0] dark:placeholder-[#4A6491] dark:focus:border-[#1677FF]/60 dark:focus:bg-[#122347] dark:focus:ring-[#1677FF]/25", lightAccent.focus)} /> {search && ( @@ -239,7 +273,7 @@ function AppCard({ onOpen: () => void; }) { const { theme } = useTheme(); - const isLightBlue = theme === 'lightblue'; + const lightAccent = getLightAccent(theme); const Icon = ICON_POOL[hashIndex(app.appId, ICON_POOL.length)]; const cardTheme = THEME_POOL[hashIndex(app.appId, THEME_POOL.length)]; @@ -256,7 +290,7 @@ function AppCard({
-

+

{app.name}

@@ -270,7 +304,7 @@ function AppCard({
拼接后目标路由终端: - + 自动拼接已激活参
@@ -312,7 +346,7 @@ function AppCard({