diff --git a/frontend-web/src/components/page-sdebar.css b/frontend-web/src/components/page-sdebar.css index 7e22e50c..e6eb7677 100644 --- a/frontend-web/src/components/page-sdebar.css +++ b/frontend-web/src/components/page-sdebar.css @@ -498,3 +498,248 @@ background: linear-gradient(90deg, rgba(62, 178, 255, 0) 0%, #4ee6ff 50%, rgba(62, 178, 255, 0) 100%); box-shadow: 0 0 10px rgba(78, 230, 255, 0.72); } + +/* ══════════════════════════════════════════════════════════════════════ + 浅色主题导航(红/蓝两套,颜色经 --brand-* 变量自动切换) + 逻辑同深色:一级导航在顶栏、二级导航在侧边栏、三级导航在顶栏下方横条。 + 但一级导航不做深色那种围绕基座的特殊处理,正常横排即可。 + ══════════════════════════════════════════════════════════════════════ */ + +/* ── 顶栏一级导航(白字落在 red/blue-header banner 上)──────────────────── */ +.light-topnav { + display: flex; + align-items: center; + height: 64px; + min-width: 0; + gap: 6px; + /* 整体往右移(用百分比,随容器宽度自适应;约等于宽屏下的 200px 量级) */ + margin-left: 10%; + overflow-x: auto; + overflow-y: hidden; +} + +.light-topnav-item { + position: relative; + flex: 0 0 auto; + display: inline-flex; + align-items: center; + justify-content: center; + height: 60px; + padding: 0 18px; + border: 0; + background: transparent; + color: rgba(255, 255, 255, 0.85); + /* 复用全局字体栈(与「多智能体会商」等页面一致):Source Han Sans SC 在 Windows 上 + 常按字重分家族,放栈首会导致 font-weight 选不到粗体;用 --font-sans(Noto Sans SC + 领先)才能正常加粗。 */ + font-family: var(--font-sans); + font-size: 16px; + /* 普通项用 500(雅黑兜底下渲染为常规字面),选中项 700 才能看出粗体对比 */ + font-weight: 500; + line-height: 1; + white-space: nowrap; + cursor: pointer; + transition: color 0.18s ease; +} + +.light-topnav-item:hover { + color: #ffffff; +} + +/* 选中态:不改背景,文字加粗变亮 + 贴近导航栏底部一条粗白线 */ +.light-topnav-item--active, +.light-topnav-item--active:hover { + color: #ffffff; + font-weight: 700; +} + +.light-topnav-item--active::after { + content: ''; + position: absolute; + left: 12px; + right: 12px; + bottom: 0; + height: 4px; + border-radius: 3px 3px 0 0; + background: #ffffff; +} + +/* ── 侧边栏:二级图标卡片列(96px)+ 右侧三级列(展开时整体变宽,带过渡)──────── */ +.light-sidebar { + position: fixed; + left: 0; + top: 4rem; + z-index: 40; + width: 96px; + height: calc(100vh - 4rem); + display: flex; + flex-direction: row; + background: #ffffff; + border-right: 1px solid var(--border); + box-sizing: border-box; + overflow: hidden; + /* 展开三级列时整体变宽的过渡动画 */ + transition: width 0.3s ease; +} + +/* 展开三级列:96(二级) + 140(三级) = 236px */ +.light-sidebar--expanded { + width: 236px; +} + +/* 二级列:固定 96px,竖排图标卡片 + 底部设置 */ +.light-sidebar__primary { + flex: 0 0 96px; + width: 96px; + height: 100%; + display: flex; + flex-direction: column; + /* 横向 padding 置 0:让选中态浅红背景能铺满与列宽一致 */ + padding: 10px 0; + box-sizing: border-box; +} + +/* 三级列:固定 140px,随侧边栏展开淡入 */ +.light-sidebar__secondary { + flex: 0 0 140px; + width: 140px; + height: 100%; + border-left: 1px solid var(--border); + /* 横向 padding 置 0:让三级选中背景能铺满与列宽一致(文字内缩由 subitem 自身 padding 保证) */ + padding: 12px 0; + box-sizing: border-box; + overflow-y: auto; + overflow-x: hidden; + background: #ffffff; + opacity: 0; + transition: opacity 0.2s ease; +} + +.light-sidebar--expanded .light-sidebar__secondary { + opacity: 1; +} + +.light-sidebar__list { + flex: 1; + display: flex; + flex-direction: column; + gap: 6px; + overflow-y: auto; + overflow-x: hidden; +} + +.light-sidebar-item { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + width: 100%; + padding: 14px 4px; + border: 0; + border-radius: 0; + background: transparent; + color: #222222; + /* 复用全局字体栈,确保加粗生效(详见 .light-topnav-item 注释) */ + font-family: var(--font-sans); + font-size: 16px; + font-weight: 500; + line-height: 1.3; + text-align: center; + cursor: pointer; + transition: background-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease; +} + +.light-sidebar-item__icon { + flex-shrink: 0; + /* 图标统一用主题色(红主题红、蓝主题蓝) */ + color: var(--brand-900); + transition: color 0.18s ease, transform 0.18s ease; +} + +.light-sidebar-item__label { + display: block; + width: 100%; + word-break: break-word; +} + +.light-sidebar-item:hover { + background: var(--brand-200); +} + +.light-sidebar-item:hover .light-sidebar-item__icon { + color: var(--brand-900); + transform: scale(1.05); +} + +.light-sidebar-item--active { + background: var(--brand-200); + color: var(--brand-900); + font-weight: 600; + /* 选中态:去圆角去边框,浅红底铺满父元素宽度,右侧一条粗主题色边(用 inset 阴影避免挤压内容) */ + box-shadow: inset -4px 0 0 0 var(--brand-900); +} + +.light-sidebar-item--active .light-sidebar-item__icon { + color: var(--brand-900); +} + +/* 侧边栏底部:设置入口(浅色模式不支持收起,故无收起按钮) */ +.light-sidebar__footer { + margin-top: auto; + display: flex; + flex-direction: column; + gap: 4px; + padding-top: 8px; + border-top: 1px solid var(--border); +} + +/* ── 三级列条目(左对齐图标 + 文字,参考 xiugai-1.png)─────────────────────── */ +.light-sidebar-subnav { + display: flex; + flex-direction: column; + gap: 2px; +} + +.light-sidebar-subitem { + position: relative; + display: flex; + align-items: center; + gap: 8px; + width: 100%; + padding: 10px; + border: 0; + background: transparent; + color: #222222; + font-family: var(--font-sans); + font-size: 14px; + font-weight: 500; + line-height: 1.3; + text-align: left; + cursor: pointer; + transition: background-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease; +} + +.light-sidebar-subitem__icon { + flex-shrink: 0; + /* 图标用主题色(红主题红、蓝主题蓝) */ + color: var(--brand-900); +} + +.light-sidebar-subitem__label { + flex: 1; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.light-sidebar-subitem:hover { + background: var(--brand-200); +} + +.light-sidebar-subitem--active { + background: var(--brand-200); + color: var(--brand-900); + font-weight: 700; + box-shadow: inset -4px 0 0 0 var(--brand-900); +} diff --git a/frontend-web/src/components/page-sidebar-v1.tsx b/frontend-web/src/components/page-sidebar-v1.tsx new file mode 100644 index 00000000..58e59a72 --- /dev/null +++ b/frontend-web/src/components/page-sidebar-v1.tsx @@ -0,0 +1,303 @@ +import { useEffect, useState } from "react"; +import { useNavigate, useLocation } from "react-router-dom"; +import { + Bell, + ChevronLeft, + ChevronRight, + ChevronsUpDown, + Folder, + FolderOpen, + Settings, + User, + Wrench, +} from "lucide-react"; + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { NotificationDialog } from "@/components/workspace/notification-dialog"; +import { SettingsDialog } from "@/components/workspace/settings"; +import { useTheme } from "@/contexts/ThemeContext"; +import { getStoredAuth } from "@/core/auth"; +import { useI18n } from "@/core/i18n/hooks"; +import { useUnreadCount } from "@/core/notifications"; +import { + defaultExpandedIds, + type MenuItem, +} from "@/core/page-layout/sidebar-menu"; +import { useSidebarMenuItems } from "@/core/page-layout/use-sidebar-menu"; + +type PageSidebarProps = { + collapsed: boolean; + onToggleCollapsed: () => void; +}; + +type SettingsSection = "account" | "appearance" | "memory" | "tools" | "about"; + +export function PageSidebar({ + collapsed, + onToggleCollapsed, +}: PageSidebarProps) { + const { theme } = useTheme(); + const isDark = theme === "dark"; + const isAdmin = getStoredAuth()?.system_role === "admin"; + const visibleMenuItems = useSidebarMenuItems(!!isAdmin); + const { t } = useI18n(); + const { data: unreadCount = 0 } = useUnreadCount(); + // defaultExpandedIds 是共享的 ReadonlySet,复制一份给 useState(避免外部突变) + const [expandedIds, setExpandedIds] = useState>(() => new Set(defaultExpandedIds)); + const [settingsOpen, setSettingsOpen] = useState(false); + const [settingsDefaultSection, setSettingsDefaultSection] = + useState("appearance"); + const [notificationOpen, setNotificationOpen] = useState(false); + const [mounted, setMounted] = useState(false); + const navigate = useNavigate(); + const { pathname } = useLocation(); + + useEffect(() => { + setMounted(true); + }, []); + + const openSettings = (section: SettingsSection) => { + setSettingsDefaultSection(section); + setSettingsOpen(true); + }; + + const iconButtonClass = isDark + ? "rounded-lg p-2 text-slate-400 transition-all duration-300 ease-out hover:scale-105 hover:bg-slate-800/50 hover:text-white" + : "rounded-lg p-2 text-gray-500 transition-all duration-300 ease-out hover:scale-105 hover:bg-red-50 hover:text-red-600"; + const panelClass = isDark + ? "ml-4 space-y-1 border-l border-blue-800/30 pl-4" + : "ml-4 space-y-1 border-l border-red-100 pl-4"; + const labelClass = (compact: boolean, small = false) => + `overflow-hidden whitespace-nowrap transition-all duration-300 ease-out ${ + compact ? "max-w-0 opacity-0" : "max-w-[220px] opacity-100" + } ${small ? "text-xs" : "text-sm"} flex-1 font-medium`; + const menuButtonClass = (isActive: boolean, depth = 0) => + `group relative flex w-full items-center rounded-[2px] py-2 text-left transition-all duration-300 ease-out theme-transition ${ + collapsed && depth === 0 ? "justify-center px-2" : "gap-3 px-3" + } ${ + isActive + ? isDark + ? "bg-blue-500/15 text-cyan-300 font-semibold" + : "bg-red-50 text-red-600 font-semibold" + : isDark + ? "text-slate-400 hover:bg-slate-800/50 hover:text-white" + : "text-gray-600 hover:bg-red-50 hover:text-red-600" + }`; + const footerButtonClass = menuButtonClass(false, 0); + + const toggleExpanded = (id: string) => { + setExpandedIds((prev) => { + const next = new Set(prev); + if (next.has(id)) { + next.delete(id); + } else { + next.add(id); + } + return next; + }); + }; + + const renderLeaf = (item: MenuItem, depth = 0) => { + const Icon = item.icon; + const isActive = !!item.path && pathname === item.path; + + return ( + + ); + }; + + const renderBranch = (item: MenuItem, depth = 0) => { + const expanded = expandedIds.has(item.id); + const isTopLevel = depth === 0; + const Icon = depth > 0 && item.icon === Folder && expanded ? FolderOpen : item.icon; + + return ( +
+ + + {(!collapsed || !isTopLevel) && item.children && ( +
+ {item.children.map((child) => + child.children ? renderBranch(child, depth + 1) : renderLeaf(child, depth + 1), + )} +
+ )} +
+ ); + }; + + return ( + <> + + + + + ); +} diff --git a/frontend-web/src/components/page-sidebar.tsx b/frontend-web/src/components/page-sidebar.tsx index 58e59a72..42b556fe 100644 --- a/frontend-web/src/components/page-sidebar.tsx +++ b/frontend-web/src/components/page-sidebar.tsx @@ -22,14 +22,18 @@ import { NotificationDialog } from "@/components/workspace/notification-dialog"; import { SettingsDialog } from "@/components/workspace/settings"; import { useTheme } from "@/contexts/ThemeContext"; import { getStoredAuth } from "@/core/auth"; +import { isWujieEnv } from "@/core/embed/wujie"; import { useI18n } from "@/core/i18n/hooks"; import { useUnreadCount } from "@/core/notifications"; import { defaultExpandedIds, + getActiveThirdLevelItems, type MenuItem, } from "@/core/page-layout/sidebar-menu"; import { useSidebarMenuItems } from "@/core/page-layout/use-sidebar-menu"; +import "@/components/page-sdebar.css"; + type PageSidebarProps = { collapsed: boolean; onToggleCollapsed: () => void; @@ -37,7 +41,201 @@ type PageSidebarProps = { type SettingsSection = "account" | "appearance" | "memory" | "tools" | "about"; -export function PageSidebar({ +const hasActiveChild = (item: MenuItem, pathname: string): boolean => { + if (item.path && pathname === item.path) { + return true; + } + return item.children?.some((child) => hasActiveChild(child, pathname)) ?? false; +}; + +const findFirstRoutePath = (items?: MenuItem[]): string | null => { + if (!items?.length) { + return null; + } + for (const child of items) { + if (child.path) { + return child.path; + } + const nested = findFirstRoutePath(child.children); + if (nested) { + return nested; + } + } + return null; +}; + +/** + * 浅色侧边栏入口。 + * + * - 普通桌面环境:渲染「二级导航图标卡片」(一级在顶栏、二级在侧边栏,参考 导航-1.png)。 + * - wujie 嵌入环境:顶栏被宿主隐藏,一级导航无处安放,故回落到原手风琴整树菜单 + * (保证嵌入模式下所有层级仍可达,零回归)。 + */ +export function PageSidebar({ collapsed, onToggleCollapsed }: PageSidebarProps) { + if (isWujieEnv()) { + return ( + + ); + } + return ; +} + +/** + * 浅色二级导航侧边栏(图标卡片,红/蓝主题色经 --brand-* 变量自动切换)。 + * 浅色模式不支持侧边栏收起,故无收起按钮、不接收 collapsed 相关 props。 + */ +function SecondaryNavSidebar() { + const isAdmin = getStoredAuth()?.system_role === "admin"; + const visibleMenuItems = useSidebarMenuItems(!!isAdmin); + const { t } = useI18n(); + const { data: unreadCount = 0 } = useUnreadCount(); + const [settingsOpen, setSettingsOpen] = useState(false); + const [settingsDefaultSection, setSettingsDefaultSection] = + useState("appearance"); + const [notificationOpen, setNotificationOpen] = useState(false); + const [mounted, setMounted] = useState(false); + const navigate = useNavigate(); + const { pathname } = useLocation(); + + useEffect(() => { + setMounted(true); + }, []); + + const openSettings = (section: SettingsSection) => { + setSettingsDefaultSection(section); + setSettingsOpen(true); + }; + + // 当前激活的一级菜单(与顶栏一级导航联动);其 children 即侧边栏要渲染的二级导航。 + const activeTopItem = + visibleMenuItems.find((item) => hasActiveChild(item, pathname)) ?? + visibleMenuItems[0]; + const secondaryItems = activeTopItem?.children ?? []; + + const isItemActive = (item: MenuItem) => + item.path ? pathname === item.path : hasActiveChild(item, pathname); + + const handleItemClick = (item: MenuItem) => { + const path = item.path ?? findFirstRoutePath(item.children); + if (path) { + navigate(path); + } + }; + + // 当前激活二级项的三级子项;非空时侧边栏在右侧展开「三级列」并整体变宽(见 css 过渡)。 + const thirdItems = getActiveThirdLevelItems(visibleMenuItems, pathname); + const expanded = thirdItems.length > 0; + + return ( + <> + + + + + ); +} + +/** + * 旧版浅色整树手风琴侧边栏。仅用于 wujie 嵌入环境(顶栏被宿主隐藏时保证所有层级可达)。 + */ +function LegacyAccordionSidebar({ collapsed, onToggleCollapsed, }: PageSidebarProps) { @@ -66,9 +264,6 @@ export function PageSidebar({ setSettingsOpen(true); }; - const iconButtonClass = isDark - ? "rounded-lg p-2 text-slate-400 transition-all duration-300 ease-out hover:scale-105 hover:bg-slate-800/50 hover:text-white" - : "rounded-lg p-2 text-gray-500 transition-all duration-300 ease-out hover:scale-105 hover:bg-red-50 hover:text-red-600"; const panelClass = isDark ? "ml-4 space-y-1 border-l border-blue-800/30 pl-4" : "ml-4 space-y-1 border-l border-red-100 pl-4"; diff --git a/frontend-web/src/contexts/ThemeContext.tsx b/frontend-web/src/contexts/ThemeContext.tsx index 0c7fc748..004900f9 100644 --- a/frontend-web/src/contexts/ThemeContext.tsx +++ b/frontend-web/src/contexts/ThemeContext.tsx @@ -227,11 +227,18 @@ export const ThemeProvider: React.FC = ({ children }) => { if (theme === 'dark') { root.setAttribute('theme-mode', 'dark'); root.classList.add('dark'); + root.removeAttribute('data-theme'); return; } root.removeAttribute('theme-mode'); root.classList.remove('dark'); + + if (theme === 'lightblue') { + root.setAttribute('data-theme', 'lightblue'); + } else { + root.removeAttribute('data-theme'); + } }, [theme]); const toggleTheme = () => { diff --git a/frontend-web/src/core/page-layout/sidebar-menu.ts b/frontend-web/src/core/page-layout/sidebar-menu.ts index 908e596a..7e3ccbf5 100644 --- a/frontend-web/src/core/page-layout/sidebar-menu.ts +++ b/frontend-web/src/core/page-layout/sidebar-menu.ts @@ -9,15 +9,16 @@ */ import type { ElementType } from "react"; import { + Activity, BookOpen, Bot, Brain, CalendarClock, CircleHelp, + ClipboardList, Database, FileBox, FileText, - Folder, Gauge, Grid3X3, HelpCircle, @@ -29,6 +30,7 @@ import { ListChecks, ListTodo, Network, + Radar, Settings, Sparkles, Target, @@ -77,7 +79,7 @@ export const sidebarMenuItems: MenuItem[] = [ { id: "xk-center-task", label: "XK中任务", - icon: Folder, + icon: ClipboardList, children: [ { id: "xk-task-jc", @@ -96,7 +98,7 @@ export const sidebarMenuItems: MenuItem[] = [ { id: "coh-center-task", label: "COH中任务", - icon: Folder, + icon: Activity, children: [ { id: "coh-task-3qfx", @@ -121,7 +123,7 @@ export const sidebarMenuItems: MenuItem[] = [ { id: "pg-center-task", label: "PG中任务", - icon: Folder, + icon: Gauge, children: [ { id: "pg-task-pg", @@ -146,7 +148,7 @@ export const sidebarMenuItems: MenuItem[] = [ { id: "ty-center-task", label: "TY中任务", - icon: Folder, + icon: Radar, children: [ { id: "ty-task-xdty", @@ -296,6 +298,31 @@ export const defaultExpandedIds: ReadonlySet = new Set([ "comprehensive-management", ]); +/** 该菜单项自身或任意后代的 path 是否等于当前路由 */ +export function menuItemContainsPath(item: MenuItem, pathname: string): boolean { + if (item.path && item.path === pathname) { + return true; + } + return item.children?.some((child) => menuItemContainsPath(child, pathname)) ?? false; +} + +/** + * 在「一级 → 二级 → 三级」菜单中,取当前路由所在的「激活二级项」的三级子项。 + * 浅色侧边栏据此在二级右侧展开三级列;无三级则返回空数组(侧边栏保持窄栏)。 + */ +export function getActiveThirdLevelItems( + items: MenuItem[], + pathname: string, +): MenuItem[] { + const activeTop = + items.find((item) => menuItemContainsPath(item, pathname)) ?? items[0]; + const secondary = activeTop?.children ?? []; + const activeSecond = secondary.find( + (item) => (item.children?.length ?? 0) > 0 && menuItemContainsPath(item, pathname), + ); + return activeSecond?.children ?? []; +} + /** 按 admin 角色过滤菜单:非 admin 不展示 ``adminOnly: true`` 的项及其子项 */ export function filterMenuItemsByRole(items: MenuItem[], isAdmin: boolean): MenuItem[] { return items diff --git a/frontend-web/src/core/page-layout/use-sidebar-menu.ts b/frontend-web/src/core/page-layout/use-sidebar-menu.ts index 1f2ec6b0..006c767e 100644 --- a/frontend-web/src/core/page-layout/use-sidebar-menu.ts +++ b/frontend-web/src/core/page-layout/use-sidebar-menu.ts @@ -8,12 +8,15 @@ * 浅色 PageSidebar 与深色 PageSidebarDark 共用此 hook,保证两套皮肤同步。 */ import { useMemo } from "react"; +import { useLocation } from "react-router-dom"; import { Layout } from "lucide-react"; +import { getStoredAuth } from "@/core/auth"; import { useLightApps, type LightApp } from "@/strategy-components/api/light-apps"; import { filterMenuItemsByRole, + getActiveThirdLevelItems, sidebarMenuItems, type MenuItem, } from "./sidebar-menu"; @@ -61,3 +64,14 @@ export function useSidebarMenuItems(isAdmin: boolean): MenuItem[] { [iframeApps, isAdmin], ); } + +/** + * 当前路由所在「激活二级项」的三级子项(浅色侧边栏右侧列用)。 + * 供 PageRoutes(主内容左内边距随展开自适应)与浅色侧边栏共用同一份判定逻辑。 + */ +export function useActiveThirdLevel(): MenuItem[] { + const isAdmin = getStoredAuth()?.system_role === "admin"; + const items = useSidebarMenuItems(!!isAdmin); + const { pathname } = useLocation(); + return useMemo(() => getActiveThirdLevelItems(items, pathname), [items, pathname]); +} diff --git a/frontend-web/src/pages/ChatPage.tsx b/frontend-web/src/pages/ChatPage.tsx index c755455b..930dac06 100644 --- a/frontend-web/src/pages/ChatPage.tsx +++ b/frontend-web/src/pages/ChatPage.tsx @@ -590,11 +590,6 @@ export default function ChatPage() { )} /> )} - {compactLanding && ( -
- CM Assistant V1.2 · 深度推理可能会涉及数据同步 · 信息由AI自动生成,请审慎参考 -
- )} {env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" && (
{t.common.notAvailableInDemoMode} diff --git a/frontend-web/src/pages/LightAppListPage.tsx b/frontend-web/src/pages/LightAppListPage.tsx index b5145b83..86d2cb05 100644 --- a/frontend-web/src/pages/LightAppListPage.tsx +++ b/frontend-web/src/pages/LightAppListPage.tsx @@ -34,9 +34,12 @@ import { toast } from "sonner"; import { Pagination } from "@/components/ui/pagination"; import { cn } from "@/lib/utils"; +import { useTheme } from "@/contexts/ThemeContext"; import { useLightApps, type LightApp } from "@/strategy-components/api/light-apps"; import { buildLightAppUrl } from "@/strategy-components/lib/light-app"; +import "./light-app-pages.css"; + const PAGE_SIZE = 12; /** 卡片图标池(按 appId 哈希取一个,仅作视觉区分)。 */ @@ -65,6 +68,8 @@ 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 { data: apps = [], isLoading } = useLightApps({ appType: "window_open", @@ -107,19 +112,19 @@ export default function LightAppListPage() { }; return ( -
+
{/* 标题 */}
- - + +

应用列表

- + 导航工作台
@@ -137,7 +142,7 @@ export default function LightAppListPage() { value={search} onChange={(e) => setSearch(e.target.value)} placeholder="键入应用名称、目标地址、或描述,实时过滤检索…" - className="w-full rounded-[5px] border border-zinc-200 bg-zinc-50/50 py-3 pl-10 pr-10 text-sm transition focus:border-[#CF1322]/60 focus:bg-white focus:outline-none focus:ring-2 focus:ring-[#CF1322]/20 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" + 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")} /> {search && ( @@ -233,8 +238,10 @@ function AppCard({ onCopy: () => void; onOpen: () => void; }) { + const { theme } = useTheme(); + const isLightBlue = theme === 'lightblue'; const Icon = ICON_POOL[hashIndex(app.appId, ICON_POOL.length)]; - const theme = THEME_POOL[hashIndex(app.appId, THEME_POOL.length)]; + const cardTheme = THEME_POOL[hashIndex(app.appId, THEME_POOL.length)]; const parsedUrl = buildLightAppUrl(app); const hasParams = (app.routeParams ?? []).some((p) => p.key) && parsedUrl !== app.url; @@ -244,12 +251,12 @@ function AppCard({ {/* 上:图标 + 标题 + 描述 */}
-
+
-

+

{app.name}

@@ -263,7 +270,7 @@ function AppCard({
拼接后目标路由终端: - + 自动拼接已激活参
@@ -305,7 +312,7 @@ function AppCard({
-
+
setSearch(e.target.value)} placeholder="搜索应用名称 / 描述..." - className="w-full rounded-[5px] border border-neutral-100 bg-neutral-50/60 py-2 pl-10 pr-10 text-xs text-neutral-800 placeholder-neutral-400 transition-all hover:bg-white focus:border-[#F5222D]/40 focus:outline-none focus:ring-4 focus:ring-[#F5222D]/10" + className="lam-search-input w-full rounded-[5px] border border-neutral-100 bg-neutral-50/60 py-2 pl-10 pr-10 text-xs text-neutral-800 placeholder-neutral-400 transition-all hover:bg-white focus:border-[#F5222D]/40 focus:outline-none focus:ring-4 focus:ring-[#F5222D]/10" /> {search && (
-
- 搜寻到 {tableApps.length}{" "} +
+ 搜寻到 {tableApps.length}{" "} 个注册完成的子模块轻应用
{viewMode === "table" ? ( -
- +
+
@@ -310,19 +320,19 @@ export default function LightAppManagementPage() { {tableApps.map((app) => (
名称
- + {app.name} {app.appType === "window_open" ? ( - + 新页跳转 ) : ( - + iframe 嵌套 @@ -330,7 +340,7 @@ export default function LightAppManagementPage() { - {app.appType === "iframe" && app.mountLocation ? ( - - + + {mountLabel(app.mountLocation)} ) : ( @@ -370,7 +380,7 @@ export default function LightAppManagementPage() { activeParamPopover === app.appId ? null : app.appId ) } - className="inline-flex cursor-pointer items-center space-x-1.5 rounded-[5px] border border-neutral-200/60 bg-neutral-50/60 px-2.5 py-1 text-xs font-medium text-neutral-500 transition-colors hover:bg-neutral-100" + className="lam-param-btn inline-flex cursor-pointer items-center space-x-1.5 rounded-[5px] border border-neutral-200/60 bg-neutral-50/60 px-2.5 py-1 text-xs font-medium text-neutral-500 transition-colors hover:bg-neutral-100" > {app.routeParams.length}个 {activeParamPopover === app.appId ? ( @@ -389,14 +399,14 @@ export default function LightAppManagementPage() { className="fixed inset-0 z-40" onClick={() => setActiveParamPopover(null)} /> -
-
+
+
注入的路由参数 @@ -405,11 +415,11 @@ export default function LightAppManagementPage() { {app.routeParams.map((p, idx) => (
- {p.key} - + {p.key} + {getParamSourceLabel(p)}
@@ -427,15 +437,15 @@ export default function LightAppManagementPage() { {app.enabled ? ( <> - - + + - 已启用 + 已启用 ) : ( <> - - 已停用 + + 已停用 )}
@@ -445,14 +455,14 @@ export default function LightAppManagementPage() {
-
+
-

+

{isLoading ? "加载中..." : "暂无匹配的组件应用"}

{!isLoading && ( -

+

{search ? "请确认搜索关键词输入是否正确" : "点击右上角“注册应用”添加第一款轻应用"}

)} @@ -487,15 +497,15 @@ export default function LightAppManagementPage() {
) : tableApps.length === 0 ? ( -
-
+
+
-

+

{isLoading ? "加载中..." : "暂无匹配的组件应用"}

{!isLoading && ( -

+

{search ? "请确认搜索关键词输入是否正确" : "点击右上角“注册应用”添加第一款轻应用"}

)} @@ -505,7 +515,7 @@ export default function LightAppManagementPage() { {tableApps.map((app) => (
{app.appType === "window_open" ? ( - + 新页跳转 ) : ( - + iframe 嵌套 )} -

+

{app.name}

{app.enabled ? ( - - + + 已启用 ) : ( - - + + 已停用 )}
-

+

{app.description || "暂无应用详细描述说明。"}

-
+
{app.url} @@ -568,7 +578,7 @@ export default function LightAppManagementPage() { {app.routeParams.map((p, idx) => ( {p.key} @@ -577,24 +587,24 @@ export default function LightAppManagementPage() { )} {app.appType === "iframe" && app.mountLocation && ( -
+
挂载分类: {mountLabel(app.mountLocation)}
)}
-
+
{viewMode === "table" && ( -
-
+
+

智能多维挂载规则:

-
    +
    • - • + • 当您注册 iframe 嵌套{" "} 应用时,您需要在菜单一栏指定分类(如“资源管理”),组件将直接作为新导航菜单项加入该分类。
    • - • + • 当路由挂载成功,点击左侧注入的子栏名称可在主区域调起{" "} 真实 Iframe 预览器 @@ -636,23 +646,23 @@ export default function LightAppManagementPage() { {dialogOpen && (
      setDialogOpen(false)} /> -
      -
      +
      +
      - + -

      +

      {editing ? "修改轻应用" : "注册应用"}

      @@ -660,8 +670,8 @@ export default function LightAppManagementPage() {
      -
      -
      -
      {form.appType === "iframe" && (
      -