Merge branch 'dhk-1' of http://192.168.0.200/knowledge-base-bj/deerflow-server into dhk-1
This commit is contained in:
commit
db50493577
24 files changed
+1833
-181
No files matched your search
@@ -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);
|
||||
}
|
||||
@@ -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<Set<string>>(() => new Set(defaultExpandedIds));
|
||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
const [settingsDefaultSection, setSettingsDefaultSection] =
|
||||
useState<SettingsSection>("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 (
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
title={collapsed && depth === 0 ? item.label : undefined}
|
||||
onClick={() => item.path && navigate(item.path)}
|
||||
className={menuButtonClass(isActive, depth)}
|
||||
>
|
||||
{isActive && (
|
||||
<span
|
||||
className={`absolute left-0 inset-y-0 w-[3px] rounded-r ${
|
||||
isDark ? "bg-cyan-300" : "bg-red-600"
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
<Icon className={`${depth === 0 ? "h-[18px] w-[18px]" : "h-4 w-4"} flex-shrink-0 transition-transform duration-300 ease-out group-hover:scale-105`} />
|
||||
{(!collapsed || depth > 0) && (
|
||||
<span className={labelClass(false, depth > 0)}>
|
||||
{item.label}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<div key={item.id} className="space-y-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (collapsed && isTopLevel) {
|
||||
onToggleCollapsed();
|
||||
return;
|
||||
}
|
||||
toggleExpanded(item.id);
|
||||
}}
|
||||
title={collapsed && isTopLevel ? item.label : undefined}
|
||||
className={menuButtonClass(false, depth)}
|
||||
>
|
||||
<Icon
|
||||
className={`${isTopLevel ? "h-[18px] w-[18px]" : "h-4 w-4"} flex-shrink-0 transition-transform duration-300 ease-out group-hover:scale-105 ${
|
||||
expanded && depth > 0 ? "scale-105" : ""
|
||||
}`}
|
||||
/>
|
||||
{(!collapsed || !isTopLevel) && (
|
||||
<span className={labelClass(false, depth > 0)}>
|
||||
{item.label}
|
||||
</span>
|
||||
)}
|
||||
{(!collapsed || !isTopLevel) && (
|
||||
<ChevronRight
|
||||
className={`h-4 w-4 transition-transform duration-300 ease-out ${expanded ? "rotate-90" : ""}`}
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{(!collapsed || !isTopLevel) && item.children && (
|
||||
<div
|
||||
className={`${panelClass} overflow-hidden transition-all duration-300 ease-out ${
|
||||
expanded ? "max-h-[1200px] opacity-100" : "max-h-0 opacity-0"
|
||||
}`}
|
||||
>
|
||||
{item.children.map((child) =>
|
||||
child.children ? renderBranch(child, depth + 1) : renderLeaf(child, depth + 1),
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsDialog
|
||||
open={settingsOpen}
|
||||
onOpenChange={setSettingsOpen}
|
||||
defaultSection={settingsDefaultSection}
|
||||
/>
|
||||
<NotificationDialog
|
||||
open={notificationOpen}
|
||||
onOpenChange={setNotificationOpen}
|
||||
/>
|
||||
<aside
|
||||
className={`fixed left-0 top-16 z-40 flex h-[calc(100vh-4rem)] flex-col overflow-hidden shadow-sm theme-transition transition-all duration-300 ${
|
||||
isDark
|
||||
? "border-r border-blue-800/30 bg-slate-900"
|
||||
: "border-r border-red-100 bg-white"
|
||||
} ${collapsed ? "w-16" : "w-64"}`}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onToggleCollapsed}
|
||||
title={collapsed ? "展开侧边栏" : "收起侧边栏"}
|
||||
className={`absolute -right-3 top-6 z-50 flex h-6 w-6 items-center justify-center rounded-full shadow-md theme-transition ${
|
||||
isDark
|
||||
? "bg-slate-700 text-slate-300 hover:bg-slate-600"
|
||||
: "bg-white text-gray-500 hover:bg-red-50 hover:text-red-600 border border-red-100"
|
||||
}`}
|
||||
>
|
||||
{collapsed ? <ChevronRight className="h-3 w-3" /> : <ChevronLeft className="h-3 w-3" />}
|
||||
</button>
|
||||
|
||||
<nav className={`flex-1 overflow-y-auto ${collapsed ? "p-2" : "p-4"} space-y-2 transition-all duration-300 ease-out`}>
|
||||
{visibleMenuItems.map((item) =>
|
||||
item.children ? renderBranch(item) : renderLeaf(item),
|
||||
)}
|
||||
</nav>
|
||||
|
||||
<div
|
||||
className={`mt-auto shrink-0 theme-transition ${
|
||||
isDark ? "border-t border-blue-800/30" : "border-t border-red-100"
|
||||
} ${collapsed ? "p-2" : "p-4"}`}
|
||||
>
|
||||
{mounted ? (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
title={collapsed ? t.workspace.settingsAndMore : undefined}
|
||||
className={footerButtonClass}
|
||||
>
|
||||
<Settings className="h-5 w-5 flex-shrink-0" />
|
||||
{!collapsed && (
|
||||
<>
|
||||
<span className={labelClass(false)}>
|
||||
{t.workspace.settingsAndMore}
|
||||
</span>
|
||||
<ChevronsUpDown className="h-4 w-4 flex-shrink-0" />
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align={collapsed ? "start" : "end"}
|
||||
side={collapsed ? "right" : "top"}
|
||||
sideOffset={4}
|
||||
className="min-w-56 rounded-lg"
|
||||
>
|
||||
<DropdownMenuItem onClick={() => setNotificationOpen(true)}>
|
||||
<Bell className="size-4" />
|
||||
{t.notifications.title}
|
||||
{unreadCount > 0 && (
|
||||
<span
|
||||
className={`ml-auto flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[10px] font-medium leading-none text-white ${
|
||||
isDark ? "bg-cyan-500" : "bg-red-500"
|
||||
}`}
|
||||
>
|
||||
{unreadCount > 99 ? "99+" : unreadCount}
|
||||
</span>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => openSettings("tools")}>
|
||||
<Wrench className="size-4" />
|
||||
{t.settings.sections.tools}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => openSettings("account")}>
|
||||
<User className="size-4" />
|
||||
个人信息
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className={`${footerButtonClass} pointer-events-none`}
|
||||
>
|
||||
<Settings className="h-5 w-5 flex-shrink-0" />
|
||||
{!collapsed && (
|
||||
<span className={labelClass(false)}>
|
||||
{t.workspace.settingsAndMore}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`pointer-events-none absolute right-0 top-0 h-32 w-32 rounded-full blur-3xl theme-transition ${
|
||||
isDark ? "bg-blue-500/10" : "bg-red-500/5"
|
||||
}`}
|
||||
/>
|
||||
<div
|
||||
className={`pointer-events-none absolute bottom-20 right-0 h-24 w-24 rounded-full blur-3xl theme-transition ${
|
||||
isDark ? "bg-cyan-400/5" : "bg-red-400/5"
|
||||
}`}
|
||||
/>
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<LegacyAccordionSidebar
|
||||
collapsed={collapsed}
|
||||
onToggleCollapsed={onToggleCollapsed}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <SecondaryNavSidebar />;
|
||||
}
|
||||
|
||||
/**
|
||||
* 浅色二级导航侧边栏(图标卡片,红/蓝主题色经 --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<SettingsSection>("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 (
|
||||
<>
|
||||
<SettingsDialog
|
||||
open={settingsOpen}
|
||||
onOpenChange={setSettingsOpen}
|
||||
defaultSection={settingsDefaultSection}
|
||||
/>
|
||||
<NotificationDialog
|
||||
open={notificationOpen}
|
||||
onOpenChange={setNotificationOpen}
|
||||
/>
|
||||
<aside className={`light-sidebar ${expanded ? "light-sidebar--expanded" : ""}`}>
|
||||
{/* 一级列:二级图标卡片 + 底部设置 */}
|
||||
<div className="light-sidebar__primary">
|
||||
<nav className="light-sidebar__list" aria-label="二级菜单">
|
||||
{secondaryItems.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const active = isItemActive(item);
|
||||
return (
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
title={item.label}
|
||||
onClick={() => handleItemClick(item)}
|
||||
className={`light-sidebar-item ${active ? "light-sidebar-item--active" : ""}`}
|
||||
>
|
||||
<Icon className="light-sidebar-item__icon h-7 w-7" />
|
||||
<span className="light-sidebar-item__label">{item.label}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
|
||||
<div className="light-sidebar__footer">
|
||||
{mounted ? (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
title={t.workspace.settingsAndMore}
|
||||
className="light-sidebar-item"
|
||||
>
|
||||
<Settings className="light-sidebar-item__icon h-7 w-7" />
|
||||
<span className="light-sidebar-item__label">设置</span>
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="start"
|
||||
side="right"
|
||||
sideOffset={8}
|
||||
className="min-w-56 rounded-lg"
|
||||
>
|
||||
<DropdownMenuItem onClick={() => setNotificationOpen(true)}>
|
||||
<Bell className="size-4" />
|
||||
{t.notifications.title}
|
||||
{unreadCount > 0 && (
|
||||
<span className="ml-auto flex h-4 min-w-4 items-center justify-center rounded-full bg-[var(--brand-900)] px-1 text-[10px] font-medium leading-none text-white">
|
||||
{unreadCount > 99 ? "99+" : unreadCount}
|
||||
</span>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => openSettings("tools")}>
|
||||
<Wrench className="size-4" />
|
||||
{t.settings.sections.tools}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => openSettings("account")}>
|
||||
<User className="size-4" />
|
||||
个人信息
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 二级列:三级导航(激活二级项有三级子项时展开,整体动画见 css width transition) */}
|
||||
<div className="light-sidebar__secondary" aria-hidden={!expanded}>
|
||||
<nav className="light-sidebar-subnav" aria-label="三级菜单">
|
||||
{thirdItems.map((child) => {
|
||||
const ChildIcon = child.icon;
|
||||
const childActive = child.path
|
||||
? pathname === child.path
|
||||
: hasActiveChild(child, pathname);
|
||||
return (
|
||||
<button
|
||||
key={child.id}
|
||||
type="button"
|
||||
title={child.label}
|
||||
onClick={() => handleItemClick(child)}
|
||||
className={`light-sidebar-subitem ${childActive ? "light-sidebar-subitem--active" : ""}`}
|
||||
>
|
||||
<ChildIcon className="light-sidebar-subitem__icon h-4 w-4" />
|
||||
<span className="light-sidebar-subitem__label">{child.label}</span>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 旧版浅色整树手风琴侧边栏。仅用于 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";
|
||||
|
||||
@@ -227,11 +227,18 @@ export const ThemeProvider: React.FC<ThemeProviderProps> = ({ 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 = () => {
|
||||
|
||||
@@ -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<string> = 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
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
@@ -590,11 +590,6 @@ export default function ChatPage() {
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{compactLanding && (
|
||||
<div className="text-muted-foreground/70 w-full -translate-y-2 text-center text-[11px]">
|
||||
CM Assistant V1.2 · 深度推理可能会涉及数据同步 · 信息由AI自动生成,请审慎参考
|
||||
</div>
|
||||
)}
|
||||
{env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" && (
|
||||
<div className="text-muted-foreground/67 w-full translate-y-12 text-center text-xs">
|
||||
{t.common.notAvailableInDemoMode}
|
||||
|
||||
@@ -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<string | null>(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 (
|
||||
<div className="h-full overflow-y-auto bg-zinc-50/60 font-sans text-zinc-900 antialiased dark:bg-[#0C1836] dark:text-[#C5CFE0]">
|
||||
<div className="light-app-list-page h-full overflow-y-auto bg-zinc-50/60 font-sans text-zinc-900 antialiased dark:bg-[#0C1836] dark:text-[#C5CFE0]">
|
||||
<div className="mx-auto flex min-h-full w-full max-w-7xl flex-col gap-6 px-4 py-7 sm:px-6 lg:px-8">
|
||||
{/* 标题 */}
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<span className="relative flex size-3">
|
||||
<span className="absolute inline-flex size-full animate-ping rounded-full bg-[#CF1322] opacity-75 dark:bg-[#1677FF]" />
|
||||
<span className="relative inline-flex size-3 rounded-full bg-[#CF1322] dark:bg-[#1677FF]" />
|
||||
<span className={cn("absolute inline-flex size-full animate-ping rounded-full opacity-75", isLightBlue ? "bg-[#0062D9]" : "bg-[#CF1322]", "dark:bg-[#1677FF]")} />
|
||||
<span className={cn("relative inline-flex size-3 rounded-full", isLightBlue ? "bg-[#0062D9]" : "bg-[#CF1322]", "dark:bg-[#1677FF]")} />
|
||||
</span>
|
||||
<h1 className="text-2xl font-bold tracking-tight text-zinc-900 dark:text-[#E8EDF5]">
|
||||
应用列表
|
||||
</h1>
|
||||
<span className="rounded-[5px] border border-[#CF1322]/15 bg-[#FFF1F0] px-2 py-0.5 font-mono text-xs font-medium text-[#CF1322] dark:border-[#1677FF]/30 dark:bg-[#1677FF]/15 dark:text-[#4096FF]">
|
||||
<span className={cn("rounded-[5px] border px-2 py-0.5 font-mono text-xs font-medium dark:border-[#1677FF]/30 dark:bg-[#1677FF]/15 dark:text-[#4096FF]", isLightBlue ? "border-[#0062D9]/15 bg-[#EAF4FF] text-[#0062D9]" : "border-[#CF1322]/15 bg-[#FFF1F0] text-[#CF1322]")}>
|
||||
导航工作台
|
||||
</span>
|
||||
</div>
|
||||
@@ -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 && (
|
||||
<button
|
||||
@@ -156,13 +161,13 @@ export default function LightAppListPage() {
|
||||
<div className="-mt-2 flex items-center justify-between">
|
||||
<div className="text-xs font-medium text-zinc-500 dark:text-[#6E88B0]">
|
||||
检索到最终结果:{" "}
|
||||
<span className="font-bold text-[#CF1322] dark:text-[#4096FF]">{apps.length}</span> 个关联应用
|
||||
<span className={cn("font-bold dark:text-[#4096FF]", isLightBlue ? "text-[#0062D9]" : "text-[#CF1322]")}>{apps.length}</span> 个关联应用
|
||||
</div>
|
||||
{search && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSearch("")}
|
||||
className="text-xs font-medium text-[#CF1322] underline hover:text-[#A8071A] dark:text-[#4096FF] dark:hover:text-[#1677FF]"
|
||||
className={cn("text-xs font-medium underline dark:text-[#4096FF] dark:hover:text-[#1677FF]", isLightBlue ? "text-[#0062D9] hover:text-[#0056C6]" : "text-[#CF1322] hover:text-[#A8071A]")}
|
||||
>
|
||||
重置过滤条件
|
||||
</button>
|
||||
@@ -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({
|
||||
{/* 上:图标 + 标题 + 描述 */}
|
||||
<div>
|
||||
<div className="mb-3.5 flex items-start gap-4">
|
||||
<div className={cn("rounded-[5px] p-3 shadow-sm", theme.iconBg)}>
|
||||
<div className={cn("rounded-[5px] p-3 shadow-sm", cardTheme.iconBg)}>
|
||||
<Icon className="size-5.5" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 className="mb-2 line-clamp-1 text-base font-bold leading-snug text-zinc-900 transition-colors group-hover:text-[#CF1322] dark:text-[#E8EDF5] dark:group-hover:text-[#4096FF]">
|
||||
<h4 className={cn("mb-2 line-clamp-1 text-base font-bold leading-snug text-zinc-900 transition-colors dark:text-[#E8EDF5] dark:group-hover:text-[#4096FF]", isLightBlue ? "group-hover:text-[#0062D9]" : "group-hover:text-[#CF1322]")}>
|
||||
{app.name}
|
||||
</h4>
|
||||
|
||||
@@ -263,7 +270,7 @@ function AppCard({
|
||||
<div className="mb-4 rounded-[5px] bg-zinc-50/70 p-3 dark:bg-[#0C1836]">
|
||||
<div className="mb-1 flex items-center justify-between text-[10px] font-semibold text-zinc-400 dark:text-[#6E88B0]">
|
||||
<span>拼接后目标路由终端:</span>
|
||||
<span className="text-[9px] font-bold uppercase tracking-wider text-[#CF1322] dark:text-[#4096FF]">
|
||||
<span className={cn("text-[9px] font-bold uppercase tracking-wider dark:text-[#4096FF]", isLightBlue ? "text-[#0062D9]" : "text-[#CF1322]")}>
|
||||
自动拼接已激活参
|
||||
</span>
|
||||
</div>
|
||||
@@ -305,7 +312,7 @@ function AppCard({
|
||||
<button
|
||||
type="button"
|
||||
onClick={onOpen}
|
||||
className="flex items-center justify-center gap-1.5 rounded-[5px] bg-[#CF1322] px-3 py-2 text-xs font-semibold text-white shadow-[0_1px_3px_rgba(207,19,34,0.2)] transition hover:bg-[#A8071A] active:scale-[0.98] dark:bg-[#1677FF] dark:shadow-none dark:hover:bg-[#0958D9]"
|
||||
className={cn("flex items-center justify-center gap-1.5 rounded-[5px] px-3 py-2 text-xs font-semibold text-white transition active:scale-[0.98] dark:bg-[#1677FF] dark:shadow-none dark:hover:bg-[#0958D9]", isLightBlue ? "bg-[#0062D9] shadow-[0_1px_3px_rgba(0,98,217,0.2)] hover:bg-[#0079FE]" : "bg-[#CF1322] shadow-[0_1px_3px_rgba(207,19,34,0.2)] hover:bg-[#A8071A]")}
|
||||
>
|
||||
<span>进入应用</span>
|
||||
<ArrowUpRight className="size-3.5" />
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* 应用管理页 —— 注册 / 编辑轻应用(iframe 与 window.open)。
|
||||
*
|
||||
* 深蓝主题适配见 `./light-app-management.css`(《深蓝色 UI 规范》tokens.css)。
|
||||
*/
|
||||
import { useMemo, useState } from "react";
|
||||
import {
|
||||
AlertCircle,
|
||||
@@ -43,6 +48,8 @@ import {
|
||||
stringifyThemeMap,
|
||||
} from "@/strategy-components/lib/light-app";
|
||||
|
||||
import "./light-app-management.css";
|
||||
|
||||
interface FormState {
|
||||
name: string;
|
||||
description: string;
|
||||
@@ -63,7 +70,10 @@ const PARAM_TYPE_OPTIONS: { label: string; value: "login" | "theme" }[] = [
|
||||
{ label: "主题参数", value: "theme" },
|
||||
];
|
||||
|
||||
const DIALOG_SELECT_POPUP = { attach: () => document.body };
|
||||
const DIALOG_SELECT_POPUP = {
|
||||
attach: () => document.body,
|
||||
overlayClassName: "lam-select-popup",
|
||||
};
|
||||
|
||||
const emptyForm = (): FormState => ({
|
||||
name: "",
|
||||
@@ -213,14 +223,14 @@ export default function LightAppManagementPage() {
|
||||
const tableApps = useMemo(() => apps, [apps]);
|
||||
|
||||
return (
|
||||
<div className="h-full overflow-y-auto bg-neutral-50">
|
||||
<div className="light-app-management-page h-full overflow-y-auto bg-neutral-50 dark:bg-[#0C1836] dark:text-[#C5CFE0]">
|
||||
<div className="mx-auto flex min-h-full w-full max-w-7xl flex-col space-y-7 px-8 py-8">
|
||||
<div className="flex flex-col justify-between gap-6 md:flex-row md:items-center">
|
||||
<div className="space-y-1.5">
|
||||
<h1 className="flex items-center text-xl font-black tracking-tight text-neutral-900">
|
||||
<h1 className="lam-title flex items-center text-xl font-black tracking-tight text-neutral-900">
|
||||
应用管理
|
||||
</h1>
|
||||
<p className="max-w-2xl text-xs leading-relaxed text-neutral-500">
|
||||
<p className="lam-desc max-w-2xl text-xs leading-relaxed text-neutral-500">
|
||||
注册轻应用。
|
||||
<code className="mx-1 rounded-[5px] bg-neutral-100 px-1.5 py-0.5 font-mono text-[11px] text-neutral-600">
|
||||
window.open
|
||||
@@ -235,14 +245,14 @@ export default function LightAppManagementPage() {
|
||||
|
||||
<button
|
||||
onClick={openCreate}
|
||||
className="flex cursor-pointer items-center justify-center space-x-1.5 rounded-[5px] bg-[#A8071A] px-5 py-2.5 text-xs font-semibold text-white shadow-md shadow-rose-950/10 transition-all hover:bg-[#BC0D1E] hover:shadow-lg hover:shadow-rose-900/15 active:bg-[#820014]"
|
||||
className="lam-primary-btn flex cursor-pointer items-center justify-center space-x-1.5 rounded-[5px] bg-[#A8071A] px-5 py-2.5 text-xs font-semibold text-white shadow-md shadow-rose-950/10 transition-all hover:bg-[#BC0D1E] hover:shadow-lg hover:shadow-rose-900/15 active:bg-[#820014]"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
<span>注册应用</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center gap-3.5 rounded-[5px] bg-white p-3 shadow-[0_1px_3px_rgba(0,0,0,0.01)] md:flex-row">
|
||||
<div className="lam-toolbar flex flex-col items-center gap-3.5 rounded-[5px] bg-white p-3 shadow-[0_1px_3px_rgba(0,0,0,0.01)] md:flex-row">
|
||||
<div className="relative w-full flex-1">
|
||||
<Search className="absolute left-3.5 top-1/2 h-4 w-4 -translate-y-1/2 text-neutral-400" />
|
||||
<input
|
||||
@@ -250,7 +260,7 @@ export default function LightAppManagementPage() {
|
||||
value={search}
|
||||
onChange={(e) => 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 && (
|
||||
<button
|
||||
@@ -262,12 +272,12 @@ export default function LightAppManagementPage() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex shrink-0 items-center space-x-0.5 self-end rounded-[5px] border border-neutral-200/50 bg-neutral-100/60 p-0.5 md:self-auto">
|
||||
<div className="lam-view-toggle flex shrink-0 items-center space-x-0.5 self-end rounded-[5px] border border-neutral-200/50 bg-neutral-100/60 p-0.5 md:self-auto">
|
||||
<button
|
||||
onClick={() => setViewMode("table")}
|
||||
className={`cursor-pointer rounded-[5px] px-4 py-1.5 text-xs font-bold transition-all ${
|
||||
className={`lam-view-toggle-btn cursor-pointer rounded-[5px] px-4 py-1.5 text-xs font-bold transition-all ${
|
||||
viewMode === "table"
|
||||
? "bg-white text-[#F5222D] shadow-sm"
|
||||
? "is-active bg-white text-[#F5222D] shadow-sm"
|
||||
: "text-neutral-500 hover:text-neutral-700"
|
||||
}`}
|
||||
>
|
||||
@@ -275,9 +285,9 @@ export default function LightAppManagementPage() {
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode("cards")}
|
||||
className={`cursor-pointer rounded-[5px] px-4 py-1.5 text-xs font-bold transition-all ${
|
||||
className={`lam-view-toggle-btn cursor-pointer rounded-[5px] px-4 py-1.5 text-xs font-bold transition-all ${
|
||||
viewMode === "cards"
|
||||
? "bg-white text-[#F5222D] shadow-sm"
|
||||
? "is-active bg-white text-[#F5222D] shadow-sm"
|
||||
: "text-neutral-500 hover:text-neutral-700"
|
||||
}`}
|
||||
>
|
||||
@@ -287,14 +297,14 @@ export default function LightAppManagementPage() {
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="text-xs font-medium text-neutral-500">
|
||||
搜寻到 <span className="font-bold text-[#F5222D]">{tableApps.length}</span>{" "}
|
||||
<div className="lam-count text-xs font-medium text-neutral-500">
|
||||
搜寻到 <strong className="font-bold text-[#F5222D]">{tableApps.length}</strong>{" "}
|
||||
个注册完成的子模块轻应用
|
||||
</div>
|
||||
|
||||
{viewMode === "table" ? (
|
||||
<div className="overflow-visible rounded-[5px] bg-white shadow-[0_1px_3px_0_rgba(0,0,0,0.01)]">
|
||||
<table className="w-full table-fixed border-collapse text-left">
|
||||
<div className="lam-table-wrap overflow-visible rounded-[5px] bg-white shadow-[0_1px_3px_0_rgba(0,0,0,0.01)]">
|
||||
<table className="lam-table w-full table-fixed border-collapse text-left">
|
||||
<thead>
|
||||
<tr className="select-none border-b border-neutral-100/80 bg-neutral-50/40 text-[12.5px] font-semibold text-neutral-500">
|
||||
<th className="w-[15%] px-6 py-4">名称</th>
|
||||
@@ -310,19 +320,19 @@ export default function LightAppManagementPage() {
|
||||
{tableApps.map((app) => (
|
||||
<tr key={app.appId} className="group align-middle transition-colors hover:bg-neutral-50/40">
|
||||
<td className="px-6 py-[18px]">
|
||||
<span className="text-[14px] font-bold text-neutral-800 transition-colors group-hover:text-[#F5222D]">
|
||||
<span className="lam-row-name text-[14px] font-bold text-neutral-800 transition-colors group-hover:text-[#F5222D]">
|
||||
{app.name}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td className="px-4 py-[18px]">
|
||||
{app.appType === "window_open" ? (
|
||||
<span className="inline-flex select-none items-center space-x-1.5 rounded-[5px] border border-rose-100 bg-rose-50 px-3 py-1 text-[12px] font-semibold text-rose-800">
|
||||
<span className="lam-tag-window inline-flex select-none items-center space-x-1.5 rounded-[5px] border border-rose-100 bg-rose-50 px-3 py-1 text-[12px] font-semibold text-rose-800">
|
||||
<ExternalLink className="h-3.5 w-3.5" />
|
||||
<span>新页跳转</span>
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex select-none items-center space-x-1.5 rounded-[5px] border border-sky-100 bg-sky-50 px-3 py-1 text-[12px] font-semibold text-sky-800">
|
||||
<span className="lam-tag-iframe inline-flex select-none items-center space-x-1.5 rounded-[5px] border border-sky-100 bg-sky-50 px-3 py-1 text-[12px] font-semibold text-sky-800">
|
||||
<Monitor className="h-3.5 w-3.5" />
|
||||
<span>iframe 嵌套</span>
|
||||
</span>
|
||||
@@ -330,7 +340,7 @@ export default function LightAppManagementPage() {
|
||||
</td>
|
||||
|
||||
<td className="overflow-hidden px-4 py-[18px]">
|
||||
<div className="flex max-w-full items-center space-x-1.5 font-mono text-[12px] text-neutral-600 transition-colors group-hover:text-neutral-800">
|
||||
<div className="lam-url flex max-w-full items-center space-x-1.5 font-mono text-[12px] text-neutral-600 transition-colors group-hover:text-neutral-800">
|
||||
<a
|
||||
href={app.url}
|
||||
target="_blank"
|
||||
@@ -342,7 +352,7 @@ export default function LightAppManagementPage() {
|
||||
</a>
|
||||
<button
|
||||
onClick={() => handleCopyUrl(app.url)}
|
||||
className="shrink-0 rounded-[5px] p-1 text-neutral-400 transition-colors hover:bg-neutral-100 hover:text-neutral-600"
|
||||
className="lam-icon-btn shrink-0 rounded-[5px] p-1 text-neutral-400 transition-colors hover:bg-neutral-100 hover:text-neutral-600"
|
||||
title="复制网址"
|
||||
>
|
||||
<Copy className="h-3 w-3" />
|
||||
@@ -352,8 +362,8 @@ export default function LightAppManagementPage() {
|
||||
|
||||
<td className="px-4 py-[18px]">
|
||||
{app.appType === "iframe" && app.mountLocation ? (
|
||||
<span className="inline-flex max-w-full items-center space-x-1.5 rounded-[5px] bg-sky-50 px-2 py-0.5 text-[11px] font-medium text-sky-700">
|
||||
<span className="h-1.5 w-1.5 shrink-0 rounded-full bg-sky-500" />
|
||||
<span className="lam-mount-tag inline-flex max-w-full items-center space-x-1.5 rounded-[5px] bg-sky-50 px-2 py-0.5 text-[11px] font-medium text-sky-700">
|
||||
<span className="lam-mount-dot h-1.5 w-1.5 shrink-0 rounded-full bg-sky-500" />
|
||||
<span className="truncate">{mountLabel(app.mountLocation)}</span>
|
||||
</span>
|
||||
) : (
|
||||
@@ -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"
|
||||
>
|
||||
<span>{app.routeParams.length}个</span>
|
||||
{activeParamPopover === app.appId ? (
|
||||
@@ -389,14 +399,14 @@ export default function LightAppManagementPage() {
|
||||
className="fixed inset-0 z-40"
|
||||
onClick={() => setActiveParamPopover(null)}
|
||||
/>
|
||||
<div className="absolute left-4 top-12 z-50 w-[280px] animate-in fade-in rounded-[5px] border border-neutral-100 bg-white p-3.5 text-[12px] shadow-[0_8px_32px_rgba(34,34,34,0.16)]">
|
||||
<div className="mb-2.5 flex items-center justify-between border-b border-neutral-100 pb-2">
|
||||
<div className="lam-param-popover absolute left-4 top-12 z-50 w-[280px] animate-in fade-in rounded-[5px] border border-neutral-100 bg-white p-3.5 text-[12px] shadow-[0_8px_32px_rgba(34,34,34,0.16)]">
|
||||
<div className="lam-param-popover-head mb-2.5 flex items-center justify-between border-b border-neutral-100 pb-2">
|
||||
<span className="text-[11px] font-bold uppercase tracking-wider text-neutral-400">
|
||||
注入的路由参数
|
||||
</span>
|
||||
<button
|
||||
onClick={() => setActiveParamPopover(null)}
|
||||
className="text-[10px] font-semibold text-rose-700 hover:underline"
|
||||
className="lam-param-popover-close text-[10px] font-semibold text-rose-700 hover:underline"
|
||||
>
|
||||
关闭
|
||||
</button>
|
||||
@@ -405,11 +415,11 @@ export default function LightAppManagementPage() {
|
||||
{app.routeParams.map((p, idx) => (
|
||||
<div
|
||||
key={`${app.appId}-${idx}`}
|
||||
className="rounded-[5px] border border-neutral-100 bg-neutral-50/60 p-2 text-[11px] leading-relaxed"
|
||||
className="lam-param-item rounded-[5px] border border-neutral-100 bg-neutral-50/60 p-2 text-[11px] leading-relaxed"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-1 font-mono">
|
||||
<span className="truncate font-bold text-rose-700">{p.key}</span>
|
||||
<span className="truncate text-[10px] text-neutral-500">
|
||||
<span className="lam-param-key truncate font-bold text-rose-700">{p.key}</span>
|
||||
<span className="lam-param-val truncate text-[10px] text-neutral-500">
|
||||
{getParamSourceLabel(p)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -427,15 +437,15 @@ export default function LightAppManagementPage() {
|
||||
{app.enabled ? (
|
||||
<>
|
||||
<span className="relative flex h-2.5 w-2.5 shrink-0">
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75" />
|
||||
<span className="relative inline-flex h-2.5 w-2.5 rounded-full bg-emerald-500" />
|
||||
<span className="lam-status-on-ping absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75" />
|
||||
<span className="lam-status-on-dot relative inline-flex h-2.5 w-2.5 rounded-full bg-emerald-500" />
|
||||
</span>
|
||||
<span className="text-[12.5px] font-bold text-emerald-700">已启用</span>
|
||||
<span className="lam-status-on text-[12.5px] font-bold text-emerald-700">已启用</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="h-2.5 w-2.5 shrink-0 rounded-full bg-neutral-300" />
|
||||
<span className="text-[12.5px] font-medium text-neutral-400">已停用</span>
|
||||
<span className="lam-status-off-dot h-2.5 w-2.5 shrink-0 rounded-full bg-neutral-300" />
|
||||
<span className="lam-status-off text-[12.5px] font-medium text-neutral-400">已停用</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -445,14 +455,14 @@ export default function LightAppManagementPage() {
|
||||
<div className="flex items-center justify-center space-x-1">
|
||||
<button
|
||||
onClick={() => openEdit(app)}
|
||||
className="cursor-pointer rounded-[5px] p-1.5 text-neutral-500 transition-colors hover:bg-neutral-100 hover:text-emerald-600"
|
||||
className="lam-icon-btn cursor-pointer rounded-[5px] p-1.5 text-neutral-500 transition-colors hover:bg-neutral-100 hover:text-emerald-600"
|
||||
title="编辑配置"
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(app)}
|
||||
className="cursor-pointer rounded-[5px] p-1.5 text-neutral-400 transition-colors hover:bg-rose-50 hover:text-rose-600"
|
||||
className="lam-icon-btn lam-icon-btn-danger cursor-pointer rounded-[5px] p-1.5 text-neutral-400 transition-colors hover:bg-rose-50 hover:text-rose-600"
|
||||
title="删除配置"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
@@ -466,15 +476,15 @@ export default function LightAppManagementPage() {
|
||||
<tr>
|
||||
<td colSpan={7} className="py-12 text-center">
|
||||
<div className="flex flex-col items-center justify-center space-y-3">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-neutral-100 text-neutral-300">
|
||||
<div className="lam-empty-icon flex h-12 w-12 items-center justify-center rounded-full bg-neutral-100 text-neutral-300">
|
||||
<Info className="h-6 w-6" />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<p className="text-[14px] font-semibold text-neutral-400">
|
||||
<p className="lam-empty-title text-[14px] font-semibold text-neutral-400">
|
||||
{isLoading ? "加载中..." : "暂无匹配的组件应用"}
|
||||
</p>
|
||||
{!isLoading && (
|
||||
<p className="text-[12px] text-neutral-300">
|
||||
<p className="lam-empty-sub text-[12px] text-neutral-300">
|
||||
{search ? "请确认搜索关键词输入是否正确" : "点击右上角“注册应用”添加第一款轻应用"}
|
||||
</p>
|
||||
)}
|
||||
@@ -487,15 +497,15 @@ export default function LightAppManagementPage() {
|
||||
</table>
|
||||
</div>
|
||||
) : tableApps.length === 0 ? (
|
||||
<div className="rounded-[5px] bg-white py-20 text-center">
|
||||
<div className="mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-full bg-neutral-100 text-neutral-300">
|
||||
<div className="lam-table-wrap rounded-[5px] bg-white py-20 text-center">
|
||||
<div className="lam-empty-icon mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-full bg-neutral-100 text-neutral-300">
|
||||
<Info className="h-6 w-6" />
|
||||
</div>
|
||||
<p className="text-[14px] font-semibold text-neutral-400">
|
||||
<p className="lam-empty-title text-[14px] font-semibold text-neutral-400">
|
||||
{isLoading ? "加载中..." : "暂无匹配的组件应用"}
|
||||
</p>
|
||||
{!isLoading && (
|
||||
<p className="mt-1 text-[12px] text-neutral-300">
|
||||
<p className="lam-empty-sub mt-1 text-[12px] text-neutral-300">
|
||||
{search ? "请确认搜索关键词输入是否正确" : "点击右上角“注册应用”添加第一款轻应用"}
|
||||
</p>
|
||||
)}
|
||||
@@ -505,7 +515,7 @@ export default function LightAppManagementPage() {
|
||||
{tableApps.map((app) => (
|
||||
<div
|
||||
key={app.appId}
|
||||
className="group flex flex-col justify-between overflow-hidden rounded-[5px] bg-white shadow-[0_1px_3px_0_rgba(0,0,0,0.01)] transition-all hover:shadow-md"
|
||||
className="lam-card group flex flex-col justify-between overflow-hidden rounded-[5px] bg-white shadow-[0_1px_3px_0_rgba(0,0,0,0.01)] transition-all hover:shadow-md"
|
||||
>
|
||||
<div
|
||||
className={`h-1.5 w-full bg-gradient-to-r ${
|
||||
@@ -518,38 +528,38 @@ export default function LightAppManagementPage() {
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0 flex-1 space-y-1.5">
|
||||
{app.appType === "window_open" ? (
|
||||
<span className="inline-flex items-center space-x-1 rounded-[5px] border border-rose-100 bg-rose-50 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wider text-rose-700">
|
||||
<span className="lam-tag-window inline-flex items-center space-x-1 rounded-[5px] border border-rose-100 bg-rose-50 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wider text-rose-700">
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
<span>新页跳转</span>
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center space-x-1 rounded-[5px] border border-sky-100 bg-sky-50 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wider text-sky-700">
|
||||
<span className="lam-tag-iframe inline-flex items-center space-x-1 rounded-[5px] border border-sky-100 bg-sky-50 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wider text-sky-700">
|
||||
<Monitor className="h-3 w-3" />
|
||||
<span>iframe 嵌套</span>
|
||||
</span>
|
||||
)}
|
||||
<h4 className="line-clamp-1 text-sm font-bold leading-snug text-neutral-800 transition-colors group-hover:text-[#F5222D]">
|
||||
<h4 className="lam-card-title line-clamp-1 text-sm font-bold leading-snug text-neutral-800 transition-colors group-hover:text-[#F5222D]">
|
||||
{app.name}
|
||||
</h4>
|
||||
</div>
|
||||
{app.enabled ? (
|
||||
<span className="inline-flex shrink-0 items-center space-x-1 text-[11px] font-bold text-emerald-700">
|
||||
<span className="h-2 w-2 rounded-full bg-emerald-500" />
|
||||
<span className="lam-status-on inline-flex shrink-0 items-center space-x-1 text-[11px] font-bold text-emerald-700">
|
||||
<span className="lam-status-on-dot h-2 w-2 rounded-full bg-emerald-500" />
|
||||
<span>已启用</span>
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex shrink-0 items-center space-x-1 text-[11px] font-medium text-neutral-400">
|
||||
<span className="h-2 w-2 rounded-full bg-neutral-300" />
|
||||
<span className="lam-status-off inline-flex shrink-0 items-center space-x-1 text-[11px] font-medium text-neutral-400">
|
||||
<span className="lam-status-off-dot h-2 w-2 rounded-full bg-neutral-300" />
|
||||
<span>已停用</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="line-clamp-2 min-h-[2.25rem] text-xs leading-relaxed text-neutral-500">
|
||||
<p className="lam-card-desc line-clamp-2 min-h-[2.25rem] text-xs leading-relaxed text-neutral-500">
|
||||
{app.description || "暂无应用详细描述说明。"}
|
||||
</p>
|
||||
|
||||
<div className="flex items-center space-x-1.5 rounded-[5px] bg-neutral-50/60 p-2 font-mono text-[11px] text-neutral-600">
|
||||
<div className="lam-card-url-box flex items-center space-x-1.5 rounded-[5px] bg-neutral-50/60 p-2 font-mono text-[11px] text-neutral-600">
|
||||
<Globe className="h-3.5 w-3.5 shrink-0 text-neutral-400" />
|
||||
<span className="flex-1 truncate" title={app.url}>
|
||||
{app.url}
|
||||
@@ -568,7 +578,7 @@ export default function LightAppManagementPage() {
|
||||
{app.routeParams.map((p, idx) => (
|
||||
<span
|
||||
key={`${app.appId}-card-${idx}`}
|
||||
className="rounded-[5px] border border-neutral-200/40 bg-neutral-100 px-1.5 py-0.5 font-mono text-[10px] text-neutral-500"
|
||||
className="lam-card-param rounded-[5px] border border-neutral-200/40 bg-neutral-100 px-1.5 py-0.5 font-mono text-[10px] text-neutral-500"
|
||||
>
|
||||
{p.key}
|
||||
</span>
|
||||
@@ -577,24 +587,24 @@ export default function LightAppManagementPage() {
|
||||
)}
|
||||
|
||||
{app.appType === "iframe" && app.mountLocation && (
|
||||
<div className="inline-flex items-center gap-1.5 rounded-[5px] bg-sky-50/60 p-2 text-[11px] font-medium leading-none text-sky-700">
|
||||
<div className="lam-card-mount inline-flex items-center gap-1.5 rounded-[5px] bg-sky-50/60 p-2 text-[11px] font-medium leading-none text-sky-700">
|
||||
<span>挂载分类:</span>
|
||||
<code className="font-semibold">{mountLabel(app.mountLocation)}</code>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end space-x-1 border-t border-neutral-100/60 bg-neutral-50/30 px-5 py-3">
|
||||
<div className="lam-card-footer flex items-center justify-end space-x-1 border-t border-neutral-100/60 bg-neutral-50/30 px-5 py-3">
|
||||
<button
|
||||
onClick={() => openEdit(app)}
|
||||
className="cursor-pointer rounded-[5px] p-1.5 text-neutral-500 transition-colors hover:bg-neutral-100 hover:text-emerald-600"
|
||||
className="lam-icon-btn cursor-pointer rounded-[5px] p-1.5 text-neutral-500 transition-colors hover:bg-neutral-100 hover:text-emerald-600"
|
||||
title="编辑配置"
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(app)}
|
||||
className="cursor-pointer rounded-[5px] p-1.5 text-neutral-400 transition-colors hover:bg-rose-50 hover:text-rose-600"
|
||||
className="lam-icon-btn lam-icon-btn-danger cursor-pointer rounded-[5px] p-1.5 text-neutral-400 transition-colors hover:bg-rose-50 hover:text-rose-600"
|
||||
title="删除配置"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
@@ -607,21 +617,21 @@ export default function LightAppManagementPage() {
|
||||
</div>
|
||||
|
||||
{viewMode === "table" && (
|
||||
<div className="select-none rounded-[5px] bg-amber-50/40 p-5 shadow-[0_1px_2px_rgba(0,0,0,0.005)]">
|
||||
<div className="flex items-center space-x-2 border-b border-amber-100/50 pb-3 text-amber-800">
|
||||
<div className="lam-tip-box select-none rounded-[5px] bg-amber-50/40 p-5 shadow-[0_1px_2px_rgba(0,0,0,0.005)]">
|
||||
<div className="lam-tip-head flex items-center space-x-2 border-b border-amber-100/50 pb-3 text-amber-800">
|
||||
<AlertCircle className="h-4 w-4 shrink-0 text-amber-600" />
|
||||
<h4 className="text-[13px] font-bold tracking-tight">智能多维挂载规则:</h4>
|
||||
</div>
|
||||
<ul className="mt-3.5 space-y-3 pl-1 text-[12px] leading-relaxed text-neutral-600">
|
||||
<ul className="lam-tip-list mt-3.5 space-y-3 pl-1 text-[12px] leading-relaxed text-neutral-600">
|
||||
<li className="flex items-start space-x-2">
|
||||
<span className="mt-1 shrink-0 text-amber-600">•</span>
|
||||
<span className="lam-tip-bullet mt-1 shrink-0 text-amber-600">•</span>
|
||||
<span>
|
||||
当您注册 <strong className="font-bold text-neutral-800">iframe 嵌套</strong>{" "}
|
||||
应用时,您需要在菜单一栏指定分类(如“资源管理”),组件将直接作为新导航菜单项加入该分类。
|
||||
</span>
|
||||
</li>
|
||||
<li className="flex items-start space-x-2">
|
||||
<span className="mt-1 shrink-0 text-amber-600">•</span>
|
||||
<span className="lam-tip-bullet mt-1 shrink-0 text-amber-600">•</span>
|
||||
<span>
|
||||
当路由挂载成功,点击左侧注入的子栏名称可在主区域调起{" "}
|
||||
<strong className="font-bold text-neutral-800">真实 Iframe 预览器</strong>
|
||||
@@ -636,23 +646,23 @@ export default function LightAppManagementPage() {
|
||||
{dialogOpen && (
|
||||
<div className="fixed inset-0 z-[100] flex items-center justify-center overflow-y-auto select-none">
|
||||
<div
|
||||
className="fixed inset-0 bg-[#3D3D3D]/40 backdrop-blur-[1px]"
|
||||
className="lam-dialog-overlay fixed inset-0 bg-[#3D3D3D]/40 backdrop-blur-[1px]"
|
||||
onClick={() => setDialogOpen(false)}
|
||||
/>
|
||||
|
||||
<div className="relative z-[110] m-4 flex max-h-[90vh] w-full max-w-[580px] flex-col rounded-[5px] border border-[#E8E8E8] bg-white p-6 shadow-[0_8px_32px_rgba(34,34,34,0.16)]">
|
||||
<div className="flex flex-shrink-0 items-center justify-between border-b border-[#E8E8E8] pb-4">
|
||||
<div className="lam-dialog relative z-[110] m-4 flex max-h-[90vh] w-full max-w-[580px] flex-col rounded-[5px] border border-[#E8E8E8] bg-white p-6 shadow-[0_8px_32px_rgba(34,34,34,0.16)]">
|
||||
<div className="lam-dialog-header flex flex-shrink-0 items-center justify-between border-b border-[#E8E8E8] pb-4">
|
||||
<div className="flex items-center space-x-2.5">
|
||||
<span className="rounded-[5px] bg-rose-50 p-1.5 text-[#A8071A]">
|
||||
<span className="lam-dialog-icon rounded-[5px] bg-rose-50 p-1.5 text-[#A8071A]">
|
||||
<Layers className="h-5 w-5" />
|
||||
</span>
|
||||
<h2 className="text-[17px] font-bold text-[#222222]">
|
||||
<h2 className="lam-dialog-title text-[17px] font-bold text-[#222222]">
|
||||
{editing ? "修改轻应用" : "注册应用"}
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setDialogOpen(false)}
|
||||
className="cursor-pointer rounded-[5px] p-1 text-[#A6A6A6] transition-colors hover:bg-[#FAFAFA] hover:text-[#F5222D]"
|
||||
className="lam-dialog-close cursor-pointer rounded-[5px] p-1 text-[#A6A6A6] transition-colors hover:bg-[#FAFAFA] hover:text-[#F5222D]"
|
||||
>
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
@@ -660,8 +670,8 @@ export default function LightAppManagementPage() {
|
||||
|
||||
<div className="flex-1 space-y-4 overflow-y-auto py-4 pr-1 text-[#222222]">
|
||||
<div className="space-y-1.5">
|
||||
<label className="flex items-center space-x-1.5 text-[14px] font-medium">
|
||||
<span className="font-bold text-[#F5222D]">*</span>
|
||||
<label className="lam-form-label flex items-center space-x-1.5 text-[14px] font-medium">
|
||||
<span className="lam-required font-bold text-[#F5222D]">*</span>
|
||||
<Layers className="h-4 w-4 text-[#A6A6A6]" />
|
||||
<span>应用类型</span>
|
||||
</label>
|
||||
@@ -672,7 +682,7 @@ export default function LightAppManagementPage() {
|
||||
popupProps={DIALOG_SELECT_POPUP}
|
||||
onChange={(v) => patchForm({ appType: v as LightAppType })}
|
||||
/>
|
||||
<p className="text-[11.5px] leading-relaxed text-[#A6A6A6]">
|
||||
<p className="lam-form-hint text-[11.5px] leading-relaxed text-[#A6A6A6]">
|
||||
{form.appType === "window_open"
|
||||
? "在新标签页或卡片式路由容器打开。多用于外部高度复杂的系统跳转。"
|
||||
: "通过标准 iframe 嵌入页面,可直接挂载到左侧导航树相应分类内。"}
|
||||
@@ -680,8 +690,8 @@ export default function LightAppManagementPage() {
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="flex items-center space-x-1.5 text-[14px] font-medium">
|
||||
<span className="font-bold text-[#F5222D]">*</span>
|
||||
<label className="lam-form-label flex items-center space-x-1.5 text-[14px] font-medium">
|
||||
<span className="lam-required font-bold text-[#F5222D]">*</span>
|
||||
<Tag className="h-4 w-4 text-[#A6A6A6]" />
|
||||
<span>应用名称</span>
|
||||
</label>
|
||||
@@ -689,13 +699,13 @@ export default function LightAppManagementPage() {
|
||||
value={form.name}
|
||||
onChange={(e) => patchForm({ name: e.target.value })}
|
||||
placeholder="如: 舆情看板"
|
||||
className="h-9 rounded-[5px] border-[#DBDBDB] text-[14px]"
|
||||
className="lam-input h-9 rounded-[5px] border-[#DBDBDB] text-[14px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="flex items-center space-x-1.5 text-[14px] font-medium">
|
||||
<span className="font-bold text-[#F5222D]">*</span>
|
||||
<label className="lam-form-label flex items-center space-x-1.5 text-[14px] font-medium">
|
||||
<span className="lam-required font-bold text-[#F5222D]">*</span>
|
||||
<Link2 className="h-4 w-4 text-[#A6A6A6]" />
|
||||
<span>应用地址</span>
|
||||
</label>
|
||||
@@ -703,14 +713,14 @@ export default function LightAppManagementPage() {
|
||||
value={form.url}
|
||||
onChange={(e) => patchForm({ url: e.target.value })}
|
||||
placeholder="https://example.com/page 或 http://47.88.25.99:3000/"
|
||||
className="h-9 rounded-[5px] border-[#DBDBDB] font-mono text-[14px]"
|
||||
className="lam-input h-9 rounded-[5px] border-[#DBDBDB] font-mono text-[14px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{form.appType === "iframe" && (
|
||||
<div className="animate-in fade-in space-y-1.5">
|
||||
<label className="flex items-center space-x-1.5 text-[14px] font-medium">
|
||||
<span className="font-bold text-[#F5222D]">*</span>
|
||||
<label className="lam-form-label flex items-center space-x-1.5 text-[14px] font-medium">
|
||||
<span className="lam-required font-bold text-[#F5222D]">*</span>
|
||||
<FolderTree className="h-4 w-4 text-[#A6A6A6]" />
|
||||
<span>挂载菜单</span>
|
||||
</label>
|
||||
@@ -726,7 +736,7 @@ export default function LightAppManagementPage() {
|
||||
)}
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="flex items-center space-x-1.5 text-[14px] font-medium">
|
||||
<label className="lam-form-label flex items-center space-x-1.5 text-[14px] font-medium">
|
||||
<span className="w-2 shrink-0" />
|
||||
<AlignLeft className="h-4 w-4 text-[#A6A6A6]" />
|
||||
<span>应用描述 (可选)</span>
|
||||
@@ -736,13 +746,13 @@ export default function LightAppManagementPage() {
|
||||
onChange={(e) => patchForm({ description: e.target.value })}
|
||||
placeholder="简要说明该应用的用途"
|
||||
rows={2}
|
||||
className="resize-none rounded-[5px] border-[#DBDBDB] text-[14px]"
|
||||
className="lam-input resize-none rounded-[5px] border-[#DBDBDB] text-[14px]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2.5 pt-1.5">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="flex items-center space-x-1.5 text-[14px] font-medium">
|
||||
<span className="lam-form-label flex items-center space-x-1.5 text-[14px] font-medium">
|
||||
<Sliders className="h-4 w-4 text-[#A6A6A6]" />
|
||||
<span>路由参数</span>
|
||||
<span
|
||||
@@ -755,7 +765,7 @@ export default function LightAppManagementPage() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={addParam}
|
||||
className="flex h-7 cursor-pointer items-center space-x-1 rounded-[5px] border border-[#DBDBDB] bg-white px-2.5 text-[12px] font-bold text-[#575757] transition-colors hover:border-[#F5222D] hover:text-[#F5222D]"
|
||||
className="lam-add-param-btn flex h-7 cursor-pointer items-center space-x-1 rounded-[5px] border border-[#DBDBDB] bg-white px-2.5 text-[12px] font-bold text-[#575757] transition-colors hover:border-[#F5222D] hover:text-[#F5222D]"
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
<span>添加路由参数</span>
|
||||
@@ -763,14 +773,14 @@ export default function LightAppManagementPage() {
|
||||
</div>
|
||||
|
||||
{form.routeParams.length > 0 ? (
|
||||
<div className="max-h-[220px] space-y-2 overflow-y-auto rounded-[5px] border border-[#E8E8E8] bg-[#FAFAFA] p-1">
|
||||
<div className="lam-param-panel max-h-[220px] space-y-2 overflow-y-auto rounded-[5px] border border-[#E8E8E8] bg-[#FAFAFA] p-1">
|
||||
{form.routeParams.map((p, idx) => {
|
||||
const themeMap =
|
||||
p.paramType === "theme" ? parseThemeMap(p.themeValue) : {};
|
||||
return (
|
||||
<div
|
||||
key={idx}
|
||||
className="relative rounded-[5px] border border-[#E8E8E8] bg-white p-2"
|
||||
className="lam-param-row relative rounded-[5px] border border-[#E8E8E8] bg-white p-2"
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex-1">
|
||||
@@ -779,7 +789,7 @@ export default function LightAppManagementPage() {
|
||||
value={p.key}
|
||||
onChange={(e) => patchParam(idx, { key: e.target.value })}
|
||||
placeholder="参数名 key"
|
||||
className="h-8 w-full rounded-[5px] border border-[#DBDBDB] bg-white px-2 font-mono text-[12px] focus:border-[#F5222D] focus:outline-none focus:ring-px focus:ring-[#F5222D]"
|
||||
className="lam-input h-8 w-full rounded-[5px] border border-[#DBDBDB] bg-white px-2 font-mono text-[12px] focus:border-[#F5222D] focus:outline-none focus:ring-px focus:ring-[#F5222D]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -817,7 +827,7 @@ export default function LightAppManagementPage() {
|
||||
patchParam(idx, { customValue: e.target.value })
|
||||
}
|
||||
placeholder="自定义值"
|
||||
className="h-8 w-[120px] rounded-[5px] border border-[#DBDBDB] px-2 text-[12px] focus:border-[#F5222D] focus:outline-none"
|
||||
className="lam-input h-8 w-[120px] rounded-[5px] border border-[#DBDBDB] px-2 text-[12px] focus:border-[#F5222D] focus:outline-none"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -826,7 +836,7 @@ export default function LightAppManagementPage() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeParam(idx)}
|
||||
className="cursor-pointer rounded-[5px] p-1 text-[#A6A6A6] transition-colors hover:bg-[#FEEDED] hover:text-[#F5222D]"
|
||||
className="lam-icon-btn lam-icon-btn-danger cursor-pointer rounded-[5px] p-1 text-[#A6A6A6] transition-colors hover:bg-[#FEEDED] hover:text-[#F5222D]"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
@@ -834,8 +844,8 @@ export default function LightAppManagementPage() {
|
||||
|
||||
{/* 主题参数:逐主题色配置传给目标应用的值(左=系统主题色,右=目标应用值,可改) */}
|
||||
{p.paramType === "theme" && (
|
||||
<div className="mt-2 space-y-1.5 rounded-[5px] bg-[#FAFAFA] p-2">
|
||||
<p className="text-[11px] leading-relaxed text-[#A6A6A6]">
|
||||
<div className="lam-param-theme-box mt-2 space-y-1.5 rounded-[5px] bg-[#FAFAFA] p-2">
|
||||
<p className="lam-form-hint text-[11px] leading-relaxed text-[#A6A6A6]">
|
||||
各系统主题色跳转/嵌套时传给该应用的值(默认同名,可按对方命名修改)
|
||||
</p>
|
||||
{APP_THEME_COLORS.map((c) => (
|
||||
@@ -862,7 +872,7 @@ export default function LightAppManagementPage() {
|
||||
})
|
||||
}
|
||||
placeholder={c.key}
|
||||
className="h-7 flex-1 rounded-[5px] border border-[#DBDBDB] bg-white px-2 font-mono text-[12px] focus:border-[#F5222D] focus:outline-none focus:ring-px focus:ring-[#F5222D]"
|
||||
className="lam-input h-7 flex-1 rounded-[5px] border border-[#DBDBDB] bg-white px-2 font-mono text-[12px] focus:border-[#F5222D] focus:outline-none focus:ring-px focus:ring-[#F5222D]"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
@@ -873,19 +883,19 @@ export default function LightAppManagementPage() {
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-[5px] border border-dashed border-[#E8E8E8] bg-[#FAFAFA] p-4 text-center text-[12px] text-[#A6A6A6]">
|
||||
<div className="lam-param-empty rounded-[5px] border border-dashed border-[#E8E8E8] bg-[#FAFAFA] p-4 text-center text-[12px] text-[#A6A6A6]">
|
||||
暂无注入的路由参数,加载地址时不传递多余拼接。
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between border-t border-[#E8E8E8] py-2.5">
|
||||
<span className="text-[14px] font-medium text-[#222222]">立即启用该应用</span>
|
||||
<div className="lam-dialog-footer flex items-center justify-between border-t border-[#E8E8E8] py-2.5">
|
||||
<span className="lam-form-label text-[14px] font-medium text-[#222222]">立即启用该应用</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => patchForm({ enabled: !form.enabled })}
|
||||
className={`relative h-[22px] w-10 cursor-pointer rounded-full p-0.5 transition-colors duration-200 ${
|
||||
form.enabled ? "bg-[#F5222D]" : "bg-[#DBDBDB]"
|
||||
form.enabled ? "lam-switch-on bg-[#F5222D]" : "lam-switch-off bg-[#DBDBDB]"
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
@@ -897,12 +907,12 @@ export default function LightAppManagementPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-shrink-0 items-center justify-end space-x-3 border-t border-[#E8E8E8] pt-4">
|
||||
<div className="lam-dialog-footer flex flex-shrink-0 items-center justify-end space-x-3 border-t border-[#E8E8E8] pt-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
disabled={saving}
|
||||
onClick={() => setDialogOpen(false)}
|
||||
className="h-9 rounded-[5px] border-[#DBDBDB] px-[18px] text-[14px] font-semibold"
|
||||
className="lam-dialog-cancel h-9 rounded-[5px] border-[#DBDBDB] px-[18px] text-[14px] font-semibold"
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
@@ -910,7 +920,7 @@ export default function LightAppManagementPage() {
|
||||
type="button"
|
||||
disabled={saving}
|
||||
onClick={handleSave}
|
||||
className="h-9 cursor-pointer rounded-[5px] bg-[#F5222D] px-[18px] text-[14px] font-bold text-white shadow-[0_2px_8px_rgba(34,34,34,0.08)] transition-colors hover:bg-[#CF1322] active:bg-[#A8071A] disabled:cursor-not-allowed disabled:opacity-70"
|
||||
className="lam-dialog-save h-9 cursor-pointer rounded-[5px] bg-[#F5222D] px-[18px] text-[14px] font-bold text-white shadow-[0_2px_8px_rgba(34,34,34,0.08)] transition-colors hover:bg-[#CF1322] active:bg-[#A8071A] disabled:cursor-not-allowed disabled:opacity-70"
|
||||
>
|
||||
{saving ? "保存中..." : editing ? "保存修改" : "立即注册"}
|
||||
</button>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { cn } from "@/lib/utils";
|
||||
export default function LightAppNoteQAPage() {
|
||||
const { theme } = useTheme();
|
||||
const isDark = theme === "dark";
|
||||
const isLightBlue = theme === 'lightblue';
|
||||
const [question, setQuestion] = useState("");
|
||||
|
||||
const handleSubmit = () => {
|
||||
@@ -111,7 +112,9 @@ export default function LightAppNoteQAPage() {
|
||||
"flex h-7 w-7 shrink-0 items-center justify-center rounded-full transition-colors",
|
||||
isDark
|
||||
? "bg-sky-500 text-white hover:bg-sky-400"
|
||||
: "bg-[#F5222D] text-white hover:bg-[#CF1322]",
|
||||
: isLightBlue
|
||||
? "bg-[#0062D9] text-white hover:bg-[#0079FE]"
|
||||
: "bg-[#F5222D] text-white hover:bg-[#CF1322]",
|
||||
)}
|
||||
aria-label="发送"
|
||||
>
|
||||
|
||||
@@ -5,8 +5,10 @@ import { PageSidebar } from "@/components/page-sidebar";
|
||||
import { PageSidebarDark } from "@/components/page-sidebar-dark";
|
||||
import { isChatsIframeWorkspacePath } from "@/core/page-layout/chats-iframe-route";
|
||||
import { readSidebarEnabled } from "@/core/page-layout/sidebar-enabled";
|
||||
import { isWujieEnv } from "@/core/embed/wujie";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useActiveThirdLevel } from "@/core/page-layout/use-sidebar-menu";
|
||||
import { useLayoutMode } from "@/core/system-settings/hooks";
|
||||
|
||||
import WorkspaceRoutes from "./WorkspaceRoutes";
|
||||
@@ -23,6 +25,12 @@ function PageLayout() {
|
||||
const location = useLocation();
|
||||
const { theme } = useTheme();
|
||||
const isDark = theme === "dark";
|
||||
// 浅色非嵌入:侧边栏为新版「二级导航图标卡片」窄栏(96px);wujie 嵌入仍走旧版手风琴(64/256px)。
|
||||
const inWujie = useMemo(() => isWujieEnv(), []);
|
||||
const lightNarrowSidebar = !isDark && !inWujie;
|
||||
// 浅色窄栏在右侧展开「三级列」时整体变宽(96→236px),主内容左内边距随之过渡。
|
||||
const thirdLevel = useActiveThirdLevel();
|
||||
const lightExpanded = lightNarrowSidebar && thirdLevel.length > 0;
|
||||
// 界面模式(外观设置):用户显式选「全量/简洁」时以其为准;否则跟随管理员系统默认
|
||||
// (default_layout_mode);两者都没有才回退到会话级 sidebarEnabled(外部门户/yUserId
|
||||
// 登录时置位)这一旧默认。这样无论是否走门户登录,「全量↔简洁」都能真正控制顶栏+侧边栏。
|
||||
@@ -62,8 +70,11 @@ function PageLayout() {
|
||||
// 只剩左下角浮动按钮(fixed,叠在 main 上不影响布局)。
|
||||
// 不加 transition:transition + sidebar 立刻 unmount 不同步会抽搐
|
||||
? (collapsed ? "pl-0" : "pl-[118px]")
|
||||
// 浅色仍保留过渡动画(pl-16 ↔ pl-64 平滑收展)
|
||||
: `transition-[padding] duration-300 ${collapsed ? "pl-16" : "pl-64"}`
|
||||
: lightNarrowSidebar
|
||||
// 浅色新版窄栏:展开三级列时 96→236px,主内容左内边距随之过渡
|
||||
? `transition-[padding] duration-300 ${lightExpanded ? "pl-[236px]" : "pl-[96px]"}`
|
||||
// 浅色旧版手风琴(wujie 嵌入):pl-16 ↔ pl-64 平滑收展
|
||||
: `transition-[padding] duration-300 ${collapsed ? "pl-16" : "pl-64"}`
|
||||
}`}
|
||||
>
|
||||
<Routes>
|
||||
|
||||
@@ -0,0 +1,675 @@
|
||||
@import "../../docs/ui规范-深蓝/tokens.css";
|
||||
@import "./light-app-pages.css";
|
||||
|
||||
/**
|
||||
* 应用管理页 —— 深蓝色主题适配(《深蓝色 UI 规范》)
|
||||
* 浅色样式仍由组件内 Tailwind 承担;.dark 下本文件覆盖为 ZS-004 / ZS-002 令牌。
|
||||
*/
|
||||
.dark .light-app-management-page {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
/* ── 页头 / 文案 ─────────────────────────────────────────────────────── */
|
||||
.dark .light-app-management-page .lam-title {
|
||||
color: var(--color-text-strong);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-desc {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-desc code {
|
||||
background: rgba(22, 119, 255, 0.12);
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-primary-btn {
|
||||
background: var(--color-primary);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-primary-btn:hover {
|
||||
background: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-primary-btn:active {
|
||||
background: var(--color-primary-active);
|
||||
}
|
||||
|
||||
/* ── 工具条 / 搜索 / 视图切换 ─────────────────────────────────────────── */
|
||||
.dark .light-app-management-page .lam-toolbar {
|
||||
background: var(--color-bg-card);
|
||||
border: 1px solid var(--color-border);
|
||||
box-shadow: var(--shadow-1);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-search-input {
|
||||
background: var(--color-bg-page);
|
||||
border-color: var(--color-border);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-search-input::placeholder {
|
||||
color: var(--color-text-disabled);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-search-input:hover {
|
||||
background: var(--color-bg-card);
|
||||
border-color: var(--color-link);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-search-input:focus {
|
||||
background: var(--color-bg-card);
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.16);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-view-toggle {
|
||||
background: var(--color-bg-page);
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-view-toggle-btn.is-active {
|
||||
background: var(--color-bg-card);
|
||||
color: var(--color-link);
|
||||
box-shadow: var(--shadow-1);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-view-toggle-btn:not(.is-active) {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-view-toggle-btn:not(.is-active):hover {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-count {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-count strong {
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
/* ── 表格(§28)──────────────────────────────────────────────────────── */
|
||||
.dark .light-app-management-page .lam-table-wrap {
|
||||
background: var(--color-bg-page);
|
||||
border: 1px solid var(--color-border);
|
||||
box-shadow: var(--shadow-1);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-table thead tr {
|
||||
background: var(--color-bg-card);
|
||||
border-color: var(--color-border);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-table tbody {
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-table tbody tr {
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-table tbody tr:hover {
|
||||
background: rgba(22, 119, 255, 0.08);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-row-name {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-table tbody tr:hover .lam-row-name {
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-tag-window {
|
||||
border-color: rgba(245, 34, 45, 0.35);
|
||||
background: var(--func-error-bg);
|
||||
color: #ff7875;
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-tag-iframe {
|
||||
border-color: rgba(22, 119, 255, 0.35);
|
||||
background: var(--func-info-bg);
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-url {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-table tbody tr:hover .lam-url {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-url a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-url a:hover {
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-mount-tag {
|
||||
background: var(--func-info-bg);
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-mount-dot {
|
||||
background: var(--color-primary);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-param-btn {
|
||||
border-color: var(--color-border);
|
||||
background: var(--color-bg-page);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-param-btn:hover {
|
||||
background: rgba(22, 119, 255, 0.08);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-param-popover {
|
||||
border-color: var(--color-border);
|
||||
background: var(--color-bg-card);
|
||||
box-shadow: var(--shadow-3);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-param-popover-head {
|
||||
border-color: var(--color-border);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-param-popover-close {
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-param-item {
|
||||
border-color: var(--color-border);
|
||||
background: var(--color-bg-page);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-param-key {
|
||||
color: #ff7875;
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-param-val {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-status-on {
|
||||
color: var(--func-success);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-status-on-dot {
|
||||
background: var(--func-success);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-status-on-ping {
|
||||
background: var(--func-success);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-status-off {
|
||||
color: var(--color-text-disabled);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-status-off-dot {
|
||||
background: var(--color-text-disabled);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-icon-btn {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-icon-btn:hover {
|
||||
background: rgba(22, 119, 255, 0.08);
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-icon-btn-danger:hover {
|
||||
background: var(--func-error-bg);
|
||||
color: var(--func-error);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-empty-icon {
|
||||
background: var(--color-bg-page);
|
||||
border: 1px solid var(--color-border);
|
||||
color: var(--color-text-disabled);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-empty-title {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-empty-sub {
|
||||
color: var(--color-text-disabled);
|
||||
}
|
||||
|
||||
/* ── 卡片视图(§22 / §43)────────────────────────────────────────────── */
|
||||
.dark .light-app-management-page .lam-card {
|
||||
background: var(--color-bg-card);
|
||||
border: 1px solid var(--color-border);
|
||||
box-shadow: var(--shadow-1);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-card:hover {
|
||||
box-shadow: var(--shadow-2);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-card-title {
|
||||
color: var(--color-text-strong);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-card:hover .lam-card-title {
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-card-desc {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-card-url-box {
|
||||
background: var(--color-bg-page);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-card-param {
|
||||
border-color: var(--color-border);
|
||||
background: var(--color-bg-page);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-card-mount {
|
||||
background: var(--func-info-bg);
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-card-footer {
|
||||
border-color: var(--color-border);
|
||||
background: rgba(12, 24, 54, 0.5);
|
||||
}
|
||||
|
||||
/* ── 底部提示(§33 警告)────────────────────────────────────────────── */
|
||||
.dark .light-app-management-page .lam-tip-box {
|
||||
background: var(--func-warning-bg);
|
||||
border: 1px solid rgba(250, 173, 20, 0.25);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-tip-head {
|
||||
border-color: rgba(250, 173, 20, 0.2);
|
||||
color: var(--func-warning);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-tip-list {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-tip-list strong {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-tip-bullet {
|
||||
color: var(--func-warning);
|
||||
}
|
||||
|
||||
/* ── 弹窗(§21 Modal)────────────────────────────────────────────────── */
|
||||
.dark .light-app-management-page .lam-dialog-overlay {
|
||||
background: rgba(6, 14, 36, 0.6);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-dialog {
|
||||
border-color: var(--color-border);
|
||||
background: linear-gradient(180deg, var(--color-bg-card), var(--color-bg-page));
|
||||
box-shadow: var(--shadow-3);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-dialog-header {
|
||||
border-color: var(--color-border);
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-dialog-header::after {
|
||||
content: "";
|
||||
display: block;
|
||||
height: 1px;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 0;
|
||||
background: var(--gradient-title-line);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-dialog-icon {
|
||||
background: var(--func-info-bg);
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-dialog-title {
|
||||
color: var(--color-text-strong);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-dialog-close {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-dialog-close:hover {
|
||||
background: rgba(22, 119, 255, 0.08);
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-form-label {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-form-label .lam-required {
|
||||
color: var(--func-error);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-form-label svg {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-form-hint {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-dialog .lam-input,
|
||||
.dark .light-app-management-page .lam-dialog input[type="text"] {
|
||||
background: var(--color-bg-page);
|
||||
border-color: var(--color-border);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-dialog .lam-input::placeholder,
|
||||
.dark .light-app-management-page .lam-dialog input[type="text"]::placeholder {
|
||||
color: var(--color-text-disabled);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-dialog .lam-input:focus,
|
||||
.dark .light-app-management-page .lam-dialog input[type="text"]:focus {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.16);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-param-panel {
|
||||
border-color: var(--color-border);
|
||||
background: var(--color-bg-page);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-param-row {
|
||||
border-color: var(--color-border);
|
||||
background: var(--color-bg-card);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-param-theme-box {
|
||||
background: var(--color-bg-page);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-param-empty {
|
||||
border-color: var(--color-border);
|
||||
background: var(--color-bg-page);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-add-param-btn {
|
||||
border-color: var(--color-border-strong);
|
||||
background: var(--color-bg-card);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-add-param-btn:hover {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-dialog-footer {
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-dialog-cancel {
|
||||
border-color: var(--color-border-strong);
|
||||
background: transparent;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-dialog-cancel:hover {
|
||||
border-color: var(--color-link);
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-dialog-save {
|
||||
background: var(--color-primary);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-dialog-save:hover {
|
||||
background: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-dialog-save:active {
|
||||
background: var(--color-primary-active);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-switch-on {
|
||||
background: var(--color-primary);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-switch-off {
|
||||
background: var(--color-text-disabled);
|
||||
}
|
||||
|
||||
/* ── TDesign Select(§15)──────────────────────────────────────────── */
|
||||
.dark .light-app-management-page .t-select .t-input,
|
||||
.dark .light-app-management-page .t-select .t-input__wrap {
|
||||
background: var(--color-bg-page);
|
||||
border-color: var(--color-border);
|
||||
border-radius: var(--radius-input);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .t-select .t-input__inner {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .t-select .t-input__inner::placeholder {
|
||||
color: var(--color-text-disabled);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .t-select:hover .t-input,
|
||||
.dark .light-app-management-page .t-select:hover .t-input__wrap {
|
||||
border-color: var(--color-link);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .t-select.t-is-focused .t-input,
|
||||
.dark .light-app-management-page .t-select.t-is-focused .t-input__wrap {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.16);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .t-select .t-fake-arrow {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .t-select__dropdown {
|
||||
background: var(--color-bg-card);
|
||||
border-color: var(--color-border);
|
||||
box-shadow: var(--shadow-2);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .t-select-option {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .t-select-option:hover {
|
||||
background: rgba(22, 119, 255, 0.08);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .t-select-option.t-is-selected {
|
||||
background: rgba(22, 119, 255, 0.16);
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
/* shadcn Input / Textarea in dialog */
|
||||
.dark .light-app-management-page .lam-dialog [data-slot="input"],
|
||||
.dark .light-app-management-page .lam-dialog textarea {
|
||||
background: var(--color-bg-page);
|
||||
border-color: var(--color-border);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-dialog [data-slot="input"]:focus-visible,
|
||||
.dark .light-app-management-page .lam-dialog textarea:focus-visible {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.16);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-param-theme-box span {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* TDesign 下拉挂载到 body 时的深蓝样式 */
|
||||
.dark .lam-select-popup.t-popup {
|
||||
background: var(--color-bg-card);
|
||||
border-color: var(--color-border);
|
||||
box-shadow: var(--shadow-2);
|
||||
}
|
||||
|
||||
.dark .lam-select-popup .t-select-option {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.dark .lam-select-popup .t-select-option:hover {
|
||||
background: rgba(22, 119, 255, 0.08);
|
||||
}
|
||||
|
||||
.dark .lam-select-popup .t-select-option.t-is-selected {
|
||||
background: rgba(22, 119, 255, 0.16);
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-search-input + button,
|
||||
.dark .light-app-management-page .lam-toolbar .absolute.text-neutral-400 {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-table .text-neutral-300 {
|
||||
color: var(--color-text-disabled);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-card-url-box button {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.dark .light-app-management-page .lam-card-url-box button:hover {
|
||||
background: rgba(22, 119, 255, 0.08);
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════
|
||||
浅蓝主题覆盖([data-theme="lightblue"])
|
||||
══════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* ── 注册应用按钮 ─────────────────────────────────────────────────────── */
|
||||
[data-theme="lightblue"] .light-app-management-page .lam-primary-btn {
|
||||
background-color: #0062D9;
|
||||
box-shadow: 0 2px 6px rgba(0, 98, 217, 0.2);
|
||||
}
|
||||
[data-theme="lightblue"] .light-app-management-page .lam-primary-btn:hover {
|
||||
background-color: #0079FE;
|
||||
}
|
||||
[data-theme="lightblue"] .light-app-management-page .lam-primary-btn:active {
|
||||
background-color: #0056C6;
|
||||
}
|
||||
|
||||
/* ── 搜索框 focus ────────────────────────────────────────────────────── */
|
||||
[data-theme="lightblue"] .light-app-management-page .lam-search-input:focus {
|
||||
border-color: rgba(0, 98, 217, 0.4);
|
||||
box-shadow: 0 0 0 4px rgba(0, 98, 217, 0.1);
|
||||
}
|
||||
|
||||
/* ── 视图切换激活态 ──────────────────────────────────────────────────── */
|
||||
[data-theme="lightblue"] .light-app-management-page .lam-view-toggle-btn.is-active {
|
||||
color: #0062D9;
|
||||
}
|
||||
|
||||
/* ── 结果计数 ───────────────────────────────────────────────────────── */
|
||||
[data-theme="lightblue"] .light-app-management-page .lam-count strong {
|
||||
color: #0062D9;
|
||||
}
|
||||
|
||||
/* ── 表格行名 hover ──────────────────────────────────────────────────── */
|
||||
[data-theme="lightblue"] .light-app-management-page .lam-table tr:hover .lam-row-name {
|
||||
color: #0062D9;
|
||||
}
|
||||
|
||||
/* ── 参数弹窗 ───────────────────────────────────────────────────────── */
|
||||
[data-theme="lightblue"] .light-app-management-page .lam-param-popover-close {
|
||||
color: #0062D9;
|
||||
}
|
||||
[data-theme="lightblue"] .light-app-management-page .lam-param-key {
|
||||
color: #0062D9;
|
||||
}
|
||||
|
||||
/* ── 卡片标题 hover ──────────────────────────────────────────────────── */
|
||||
[data-theme="lightblue"] .light-app-management-page .lam-card:hover .lam-card-title {
|
||||
color: #0062D9;
|
||||
}
|
||||
|
||||
/* ── 弹窗图标 / 关闭按钮 ─────────────────────────────────────────────── */
|
||||
[data-theme="lightblue"] .light-app-management-page .lam-dialog-icon {
|
||||
background-color: #EAF4FF;
|
||||
color: #0062D9;
|
||||
}
|
||||
[data-theme="lightblue"] .light-app-management-page .lam-dialog-close:hover {
|
||||
color: #0062D9;
|
||||
}
|
||||
|
||||
/* ── 添加路由参数按钮 hover ───────────────────────────────────────────── */
|
||||
[data-theme="lightblue"] .light-app-management-page .lam-add-param-btn:hover {
|
||||
border-color: #0062D9;
|
||||
color: #0062D9;
|
||||
}
|
||||
|
||||
/* ── 表单输入框 focus ────────────────────────────────────────────────── */
|
||||
[data-theme="lightblue"] .light-app-management-page .lam-input:focus,
|
||||
[data-theme="lightblue"] .light-app-management-page .lam-dialog input[type="text"]:focus {
|
||||
border-color: #0062D9;
|
||||
box-shadow: 0 0 0 1px #0062D9;
|
||||
}
|
||||
[data-theme="lightblue"] .light-app-management-page .lam-dialog [data-slot="input"]:focus-visible,
|
||||
[data-theme="lightblue"] .light-app-management-page .lam-dialog textarea:focus-visible {
|
||||
border-color: #0062D9;
|
||||
box-shadow: 0 0 0 2px rgba(0, 98, 217, 0.16);
|
||||
}
|
||||
|
||||
/* ── TDesign Select focus / hover ────────────────────────────────────── */
|
||||
[data-theme="lightblue"] .light-app-management-page .t-select:hover .t-input,
|
||||
[data-theme="lightblue"] .light-app-management-page .t-select:hover .t-input__wrap {
|
||||
border-color: rgba(0, 98, 217, 0.5);
|
||||
}
|
||||
[data-theme="lightblue"] .light-app-management-page .t-select.t-is-focused .t-input,
|
||||
[data-theme="lightblue"] .light-app-management-page .t-select.t-is-focused .t-input__wrap {
|
||||
border-color: #0062D9;
|
||||
box-shadow: 0 0 0 2px rgba(0, 98, 217, 0.16);
|
||||
}
|
||||
|
||||
/* ── 启用开关 ───────────────────────────────────────────────────────── */
|
||||
[data-theme="lightblue"] .light-app-management-page .lam-switch-on {
|
||||
background-color: #0062D9;
|
||||
}
|
||||
|
||||
/* ── 保存按钮 ───────────────────────────────────────────────────────── */
|
||||
[data-theme="lightblue"] .light-app-management-page .lam-dialog-save {
|
||||
background-color: #0062D9;
|
||||
box-shadow: 0 2px 8px rgba(0, 98, 217, 0.2);
|
||||
}
|
||||
[data-theme="lightblue"] .light-app-management-page .lam-dialog-save:hover {
|
||||
background-color: #0079FE;
|
||||
}
|
||||
[data-theme="lightblue"] .light-app-management-page .lam-dialog-save:active {
|
||||
background-color: #0056C6;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
@import "../../docs/ui规范-深蓝/tokens.css";
|
||||
|
||||
/** 轻应用中心列表 / 管理页 —— 深色模式共用页面底图(与 workspace 深蓝主题一致) */
|
||||
/* ── 浅蓝主题:页面底色轻微蓝染 ──────────────────────────────────────── */
|
||||
[data-theme="lightblue"] .light-app-management-page,
|
||||
[data-theme="lightblue"] .light-app-list-page {
|
||||
background-color: #F9FCFF;
|
||||
}
|
||||
|
||||
.dark .light-app-management-page,
|
||||
.dark .light-app-list-page {
|
||||
background-color: var(--color-bg-page);
|
||||
background-image: url("../assets/img/layout-bg-theme-dark-blue.svg");
|
||||
background-size: cover;
|
||||
background-position: center top;
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
@@ -488,9 +488,12 @@ export async function streamMultiAgent(
|
||||
let frameSeq = 0;
|
||||
|
||||
const debugLabel = `[stream:${req.agentType}:${req.agentName}]`;
|
||||
// Stream 帧日志(frame# / delta)仅 opt-in 开启。默认关闭,避免 Step 2 流式期间
|
||||
// 刷屏(含 wujie 嵌入时子应用 devtools);需要排查 SSE 时在控制台执行:
|
||||
// `window.__MULTI_AGENT_DEBUG__ = true`
|
||||
const debugEnabled =
|
||||
typeof window !== "undefined" &&
|
||||
(window as unknown as { __MULTI_AGENT_DEBUG__?: boolean }).__MULTI_AGENT_DEBUG__ !== false;
|
||||
(window as unknown as { __MULTI_AGENT_DEBUG__?: boolean }).__MULTI_AGENT_DEBUG__ === true;
|
||||
|
||||
const result: StreamMultiAgentResult = {
|
||||
dispatched: [],
|
||||
|
||||
@@ -198,6 +198,14 @@ export default function RoundtablePlanningPage() {
|
||||
const prefillClearedRef = useRef(false);
|
||||
// 共识达成后只向父页面(无界)回传一次交付结果。
|
||||
const consensusEmittedRef = useRef(false);
|
||||
/** 上一帧 hasConsensus,用于识别 false→true 的 live 过渡(回显不算)。 */
|
||||
const prevHasConsensusRef = useRef(false);
|
||||
const markRestoredConsensusAsEmitted = useCallback((hasConsensus: boolean) => {
|
||||
prevHasConsensusRef.current = hasConsensus;
|
||||
if (hasConsensus && inWujieEmbed) {
|
||||
consensusEmittedRef.current = true;
|
||||
}
|
||||
}, [inWujieEmbed]);
|
||||
// 带 taskId 时,先按任务回显最新草稿再决定是否 autoStart。用 state 而非 ref:
|
||||
// 解析完成需触发重渲染让 C2 effect 重跑(否则 intentThreadId 先就绪会错过 autoStart)。
|
||||
// 无 taskId 时初始即「已解析」,autoStart 照常。
|
||||
@@ -518,8 +526,9 @@ export default function RoundtablePlanningPage() {
|
||||
setDagExecMode(run.dagExecMode ?? "fast");
|
||||
setStep3Report(run.step3 ?? null);
|
||||
step2.hydrateFromDraft(run as unknown as Step2Snapshot);
|
||||
markRestoredConsensusAsEmitted(Boolean(run.hasConsensus));
|
||||
},
|
||||
[step2],
|
||||
[markRestoredConsensusAsEmitted, step2],
|
||||
);
|
||||
|
||||
const handleSelectStep2Run = useCallback(
|
||||
@@ -579,6 +588,7 @@ export default function RoundtablePlanningPage() {
|
||||
setOrchestrationPlan(snapshot.orchestrationPlan ?? null);
|
||||
setDagExecMode(snapshot.dagExecMode ?? "fast");
|
||||
step2.hydrateFromDraft(snapshot as unknown as Step2Snapshot);
|
||||
markRestoredConsensusAsEmitted(Boolean(snapshot.hasConsensus));
|
||||
},
|
||||
cancelAllStreams: () => {
|
||||
step2.abortAllInFlight();
|
||||
@@ -744,6 +754,7 @@ export default function RoundtablePlanningPage() {
|
||||
drafts.loadedFromDraftRef.current = false;
|
||||
// 新一轮研讨 → 允许共识达成后再次向父页面回传。
|
||||
consensusEmittedRef.current = false;
|
||||
prevHasConsensusRef.current = false;
|
||||
// result.mode 决定 Step 2 编排:recommend = 总控自由派活;chain = 按链条顺序派活;
|
||||
// dag = 分层并行(来源链条带 stages,携带 result.plan)。
|
||||
setOrchestrationMode(result.mode);
|
||||
@@ -898,11 +909,17 @@ export default function RoundtablePlanningPage() {
|
||||
triggerToast("⚙️ 系统管理面板拉取中(暂不开放外部审计)");
|
||||
}, [buildWujieConsensusPayload, emitConsensusToParent, inWujieEmbed, triggerToast]);
|
||||
|
||||
// C4(无界回传):第二步达成共识后,把总控结论 + 各智能体交付一次性广播给父页面。
|
||||
// 用 ref 防重(共识态会多次触发渲染);非无界环境静默跳过。
|
||||
// C4(无界回传):第二步**本次 live 研讨** newly 达成共识时,把总控结论 + 各智能体交付广播给父页面。
|
||||
// 草稿/任务回显(抽屉重开)已在 hydrate 时 markRestoredConsensusAsEmitted,不再重复 emit。
|
||||
useEffect(() => {
|
||||
if (!step2.hasConsensus || consensusEmittedRef.current) return;
|
||||
const prev = prevHasConsensusRef.current;
|
||||
const now = step2.hasConsensus;
|
||||
prevHasConsensusRef.current = now;
|
||||
|
||||
if (!now || prev) return;
|
||||
if (consensusEmittedRef.current) return;
|
||||
if (!inWujieEmbed) return;
|
||||
|
||||
consensusEmittedRef.current = true;
|
||||
emitConsensusToParent();
|
||||
}, [step2.hasConsensus, inWujieEmbed, emitConsensusToParent]);
|
||||
@@ -1105,6 +1122,7 @@ export default function RoundtablePlanningPage() {
|
||||
drafts.setCurrentDraftId(null);
|
||||
// 重置后新一轮共识仍需回传父页面。
|
||||
consensusEmittedRef.current = false;
|
||||
prevHasConsensusRef.current = false;
|
||||
setCurrentStep(1);
|
||||
// 同步触发 Step 1 init,而不是依赖 useEffect —— 已经在 Step 1 时
|
||||
// currentStep 不变,useEffect 不会 re-fire。
|
||||
@@ -1348,7 +1366,7 @@ export default function RoundtablePlanningPage() {
|
||||
)}
|
||||
|
||||
{/* HEADER */}
|
||||
<header className="flex shrink-0 items-center justify-between gap-4 border-b border-slate-100 bg-white px-6 py-3 shadow-[0_1px_3px_rgba(0,0,0,0.02)] dark:border-border dark:bg-card dark:shadow-none">
|
||||
<header className="relative z-[2000] flex shrink-0 items-center justify-between gap-4 border-b border-slate-100 bg-white px-6 py-3 shadow-[0_1px_3px_rgba(0,0,0,0.02)] dark:border-border dark:bg-card dark:shadow-none">
|
||||
<StepNavigation
|
||||
currentStep={currentStep}
|
||||
intentReady={step1.intentReady}
|
||||
@@ -1404,6 +1422,7 @@ export default function RoundtablePlanningPage() {
|
||||
|
||||
<HistoryDropdown
|
||||
innerRef={drafts.historyDropdownRef}
|
||||
isDark={isDark}
|
||||
isOpen={drafts.isHistoryOpen}
|
||||
setIsOpen={drafts.setIsHistoryOpen}
|
||||
drafts={drafts.drafts}
|
||||
@@ -1442,7 +1461,7 @@ export default function RoundtablePlanningPage() {
|
||||
sidebarExpanded={step2SidebarExpanded}
|
||||
setSidebarExpanded={setStep2SidebarExpanded}
|
||||
/>
|
||||
<main className="flex-1 overflow-hidden p-4 flex gap-4 min-h-0">
|
||||
<main className="relative z-0 flex-1 overflow-hidden p-4 flex gap-4 min-h-0">
|
||||
{/* LEFT SIDEBAR */}
|
||||
<LeftSidebar
|
||||
currentStep={currentStep}
|
||||
@@ -1728,6 +1747,7 @@ function StepButton({
|
||||
|
||||
interface HistoryDropdownProps {
|
||||
innerRef: React.MutableRefObject<HTMLDivElement | null>;
|
||||
isDark: boolean;
|
||||
isOpen: boolean;
|
||||
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
drafts: ReturnType<typeof useDraftPersistence>["drafts"];
|
||||
@@ -1749,6 +1769,7 @@ interface HistoryDropdownProps {
|
||||
function HistoryDropdown(props: HistoryDropdownProps) {
|
||||
const {
|
||||
innerRef,
|
||||
isDark,
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
drafts,
|
||||
@@ -1764,6 +1785,8 @@ function HistoryDropdown(props: HistoryDropdownProps) {
|
||||
onCommitRename,
|
||||
onCancelRename,
|
||||
} = props;
|
||||
const panelBg = isDark ? "#122347" : "#ffffff";
|
||||
const currentBg = isDark ? "#1a2f5a" : "#ecfdf5";
|
||||
|
||||
return (
|
||||
<div className="relative" ref={innerRef}>
|
||||
@@ -1787,8 +1810,28 @@ function HistoryDropdown(props: HistoryDropdownProps) {
|
||||
</button>
|
||||
|
||||
{isOpen && (
|
||||
<div className="absolute right-0 top-full mt-2 w-[360px] max-h-[60vh] overflow-y-auto bg-white border border-slate-200 rounded-2xl shadow-xl z-50 dark:bg-[#0d1d3b] dark:border-border">
|
||||
<div className="px-4 py-3 border-b border-slate-100 flex items-center justify-between sticky top-0 bg-white z-10 dark:bg-[#0d1d3b] dark:border-border">
|
||||
<div
|
||||
className="rt-history-dropdown-panel absolute right-0 top-full mt-2 w-[360px] max-h-[60vh] overflow-y-auto border border-slate-200 rounded-2xl shadow-xl z-[1000] dark:border-border"
|
||||
style={{
|
||||
background: panelBg,
|
||||
backgroundColor: panelBg,
|
||||
backgroundImage: "none",
|
||||
opacity: 1,
|
||||
backdropFilter: "none",
|
||||
WebkitBackdropFilter: "none",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="rt-history-dropdown-header px-4 py-3 border-b border-slate-100 flex items-center justify-between sticky top-0 z-10 dark:border-border"
|
||||
style={{
|
||||
background: panelBg,
|
||||
backgroundColor: panelBg,
|
||||
backgroundImage: "none",
|
||||
opacity: 1,
|
||||
backdropFilter: "none",
|
||||
WebkitBackdropFilter: "none",
|
||||
}}
|
||||
>
|
||||
<span className="text-xs font-black text-slate-800 tracking-wide dark:text-foreground">历史草稿</span>
|
||||
<span className="text-[10px] font-mono text-slate-400">{drafts.length} 条</span>
|
||||
</div>
|
||||
@@ -1807,7 +1850,11 @@ function HistoryDropdown(props: HistoryDropdownProps) {
|
||||
const openOrLoad = () =>
|
||||
isActiveJob && d.jobId ? onOpenJob(d.jobId) : onLoad(d.id);
|
||||
return (
|
||||
<li key={d.id} className={`px-3 py-2.5 ${isCurrent ? "bg-emerald-50/40" : ""}`}>
|
||||
<li
|
||||
key={d.id}
|
||||
className="px-3 py-2.5"
|
||||
style={isCurrent ? { backgroundColor: currentBg } : undefined}
|
||||
>
|
||||
<div className="flex items-start gap-2">
|
||||
<div className="flex-1 min-w-0">
|
||||
{isRenaming ? (
|
||||
|
||||
@@ -110,10 +110,13 @@
|
||||
--rt-primary-hover: var(--primary);
|
||||
--rt-primary-soft: var(--accent);
|
||||
color: var(--foreground);
|
||||
/* 整页背景换深蓝主题底图(仅深色;浅色保持红白主题不受影响)。
|
||||
* background-image 叠在 background-color 上 —— 底色作为图加载前/未覆盖区域的回落;
|
||||
* cover 铺满、fixed 固定不随内容滚动(大屏背板观感,与应用渲染大屏一致)。
|
||||
* url() 里的 SVG 由 Vite 当独立资源输出(非内联),浏览器缓存。 */
|
||||
}
|
||||
|
||||
/* 整页背景换深蓝主题底图(仅页面根 .roundtable-planning;浅色不受影响)。
|
||||
* background-image 叠在 background-color 上 —— 底色作为图加载前/未覆盖区域的回落;
|
||||
* cover 铺满、fixed 固定不随内容滚动(大屏背板观感,与应用渲染大屏一致)。
|
||||
* url() 里的 SVG 由 Vite 当独立资源输出(非内联),浏览器缓存。 */
|
||||
.dark .roundtable-planning {
|
||||
background-color: var(--background);
|
||||
background-image: url("../../assets/img/layout-bg-theme-dark-blue.svg");
|
||||
background-size: cover;
|
||||
@@ -122,6 +125,19 @@
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
/* Portal 弹窗(RecommendAgentsDialog 等挂 .roundtable-planning--dark 但不在页面根内):
|
||||
* 不透明实底、无底图 —— 对齐 §21 Modal 容器色 ZS-004-08。 */
|
||||
.roundtable-planning--dark:not(.roundtable-planning) {
|
||||
--background: #122347;
|
||||
--card: #0c1836;
|
||||
--popover: #122347;
|
||||
--muted: #0f1c3d;
|
||||
--secondary: #1a2f5a;
|
||||
--accent: #1a2f5a;
|
||||
background-color: var(--background);
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.dark .roundtable-planning ::-webkit-scrollbar-thumb,
|
||||
.roundtable-planning--dark ::-webkit-scrollbar-thumb {
|
||||
background: color-mix(in oklch, var(--primary) 45%, var(--border));
|
||||
@@ -388,7 +404,7 @@
|
||||
/* 顶栏 */
|
||||
.dark .roundtable-planning > header,
|
||||
.roundtable-planning--dark > header {
|
||||
background-color: var(--card) !important;
|
||||
background-color: #122347 !important;
|
||||
border-color: var(--border) !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
@@ -407,6 +423,16 @@
|
||||
color: var(--foreground) !important;
|
||||
}
|
||||
|
||||
/* 历史记录下拉面板:深色下改为不透明实底(避免 token 半透明透出底图)。 */
|
||||
:is(.dark .roundtable-planning, .roundtable-planning--dark) .rt-history-dropdown-panel {
|
||||
background-color: #122347 !important;
|
||||
background-image: none !important;
|
||||
}
|
||||
|
||||
:is(.dark .roundtable-planning, .roundtable-planning--dark) .rt-history-dropdown-header {
|
||||
background-color: #122347 !important;
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
* 派活链路流程图连接线(DispatchChainModal)。
|
||||
*
|
||||
|
||||
@@ -139,6 +139,10 @@ const Header = ({ collapsed }: HeaderProps) => {
|
||||
return email ? email.split("@")[0] : "用户";
|
||||
}, [auth?.email]);
|
||||
const showTopNav = isDark && !collapsed && !isWujieEnv();
|
||||
// 浅色(红/蓝)顶栏:一级导航正常横排进顶栏,不做深色那种围绕基座的特殊处理。
|
||||
// 与 collapsed 解耦(折叠只影响侧边栏,一级导航始终可见);wujie 嵌入下隐藏,
|
||||
// 此时一级导航回落到侧边栏(见 page-sidebar.tsx)。
|
||||
const showLightTopNav = !isDark && !isWujieEnv();
|
||||
|
||||
const hasActiveChild = (item: MenuItem): boolean => {
|
||||
if (item.path && pathname === item.path) {
|
||||
@@ -194,6 +198,20 @@ const Header = ({ collapsed }: HeaderProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const renderLightTopItem = (item: MenuItem) => {
|
||||
const isSelected = item.path ? pathname === item.path : hasActiveChild(item);
|
||||
return (
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
className={`light-topnav-item ${isSelected ? "light-topnav-item--active" : ""}`}
|
||||
onClick={() => handleNavItemClick(item)}
|
||||
>
|
||||
{item.label}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
// 右侧操作区(换肤/通知/用户)—— 深浅两套布局共用
|
||||
const actionCluster = (
|
||||
<>
|
||||
@@ -276,7 +294,7 @@ const Header = ({ collapsed }: HeaderProps) => {
|
||||
open={notificationOpen}
|
||||
onOpenChange={setNotificationOpen}
|
||||
/>
|
||||
<header className="strategy-app-header theme-transition relative flex h-16 items-center justify-between px-4">
|
||||
<header className="strategy-app-header theme-transition relative z-[2101] flex h-16 items-center justify-between px-4">
|
||||
{showTopNav ? (
|
||||
<>
|
||||
{/* 左分区(flex-1):菜单靠右贴近中间基座 */}
|
||||
@@ -309,6 +327,27 @@ const Header = ({ collapsed }: HeaderProps) => {
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : showLightTopNav ? (
|
||||
<>
|
||||
{/* 浅色顶栏:logo + 一级导航横排(白字落在 banner 上) */}
|
||||
<div className="flex h-full min-w-0 flex-1 items-center">
|
||||
<div className="flex flex-shrink-0 items-center gap-2 pr-1">
|
||||
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-white/20">
|
||||
<FileText className="h-4 w-4 text-white" />
|
||||
</div>
|
||||
<span className="whitespace-nowrap text-base font-semibold text-white">
|
||||
CM助手
|
||||
</span>
|
||||
</div>
|
||||
<nav className="light-topnav" aria-label="一级菜单">
|
||||
{visibleMenuItems.map(renderLightTopItem)}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div className="flex shrink-0 items-center gap-2 pl-2">
|
||||
{actionCluster}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex h-full min-w-0 flex-1 items-center gap-3">
|
||||
@@ -335,6 +374,7 @@ const Header = ({ collapsed }: HeaderProps) => {
|
||||
</>
|
||||
)}
|
||||
</header>
|
||||
{/* 三级菜单横条仅深色用;浅色三级已改到侧边栏右侧列(见 page-sidebar.tsx) */}
|
||||
{showTopNav && thirdSubNavItems.length ? (
|
||||
<nav className="third-submenu-bar" aria-label="三级菜单">
|
||||
<div className="third-submenu-list">
|
||||
|
||||
@@ -23,7 +23,8 @@ export const ActionAnalyzes: React.FC<ActionAnalyzesProps> = ({ title, category,
|
||||
const isDark = theme === 'dark';
|
||||
// 主题颜色变量
|
||||
const cardBg = isDark ? 'bg-slate-800/30' : 'bg-white';
|
||||
const cardBorder = isDark ? 'border-blue-800/30' : 'border-red-100';
|
||||
const isLightBlue = theme === 'lightblue';
|
||||
const cardBorder = isDark ? 'border-blue-800/30' : isLightBlue ? 'border-[#D4E9FF]' : 'border-red-100';
|
||||
const textPrimary = isDark ? 'text-white' : 'text-gray-900';
|
||||
const textSecondary = isDark ? 'text-slate-400' : 'text-gray-600';
|
||||
const textMuted = isDark ? 'text-slate-500' : 'text-gray-400';
|
||||
@@ -137,7 +138,7 @@ export const ActionAnalyzes: React.FC<ActionAnalyzesProps> = ({ title, category,
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onKeyDown={(e) => handleKeyDown(e)}
|
||||
className={`w-full pl-10 pr-4 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-red-500/20 transition-colors ${inputBg} ${cardBorder} ${textPrimary} placeholder:${textMuted}`}
|
||||
className={`w-full pl-10 pr-4 py-2 border rounded-lg text-sm focus:outline-none focus:ring-2 ${isLightBlue ? 'focus:ring-[#0062D9]/20' : 'focus:ring-red-500/20'} transition-colors ${inputBg} ${cardBorder} ${textPrimary} placeholder:${textMuted}`}
|
||||
/>
|
||||
</div>
|
||||
{/* 任务列表 */}
|
||||
|
||||
@@ -27,7 +27,8 @@ export const ActionColumn: React.FC<ActionColumnProps> = ({ title, category, sub
|
||||
const isDark = theme === 'dark';
|
||||
// 主题颜色变量
|
||||
const cardBg = isDark ? 'bg-slate-800/30' : 'bg-white';
|
||||
const cardBorder = isDark ? 'border-blue-800/30' : 'border-red-100';
|
||||
const isLightBlue = theme === 'lightblue';
|
||||
const cardBorder = isDark ? 'border-blue-800/30' : isLightBlue ? 'border-[#D4E9FF]' : 'border-red-100';
|
||||
const textPrimary = isDark ? 'text-white' : 'text-gray-900';
|
||||
const textSecondary = isDark ? 'text-slate-400' : 'text-gray-600';
|
||||
const textMuted = isDark ? 'text-slate-500' : 'text-gray-400';
|
||||
|
||||
@@ -28,6 +28,7 @@ export const KanbanColumn: React.FC<KanbanColumnProps> = ({
|
||||
}) => {
|
||||
const { theme } = useTheme();
|
||||
const isDark = theme === 'dark';
|
||||
const isLightBlue = theme === 'lightblue';
|
||||
const [pageNum, setPageNum] = useState(1);
|
||||
|
||||
const handlePageChange = async (value: number) => {
|
||||
@@ -37,7 +38,9 @@ export const KanbanColumn: React.FC<KanbanColumnProps> = ({
|
||||
|
||||
const columnClass = isDark
|
||||
? 'border border-[rgba(67,89,124,0.46)] bg-[linear-gradient(180deg,rgba(18,36,67,0.96),rgba(12,28,54,0.96))] shadow-[0_18px_40px_rgba(3,10,22,0.18)]'
|
||||
: 'border border-[#E8E8E8] bg-[linear-gradient(180deg,#FFFFFF_0%,#FFF9F9_100%)] shadow-[0_2px_8px_rgba(34,34,34,0.08)]';
|
||||
: isLightBlue
|
||||
? 'border border-[#E8E8E8] bg-[linear-gradient(180deg,#FFFFFF_0%,#F4F9FF_100%)] shadow-[0_2px_8px_rgba(34,34,34,0.08)]'
|
||||
: 'border border-[#E8E8E8] bg-[linear-gradient(180deg,#FFFFFF_0%,#FFF9F9_100%)] shadow-[0_2px_8px_rgba(34,34,34,0.08)]';
|
||||
|
||||
return (
|
||||
<section className={`flex h-full flex-col rounded p-4 ${columnClass}`}>
|
||||
@@ -54,7 +57,7 @@ export const KanbanColumn: React.FC<KanbanColumnProps> = ({
|
||||
</h2>
|
||||
<span
|
||||
className={`inline-flex rounded-sm px-2 py-0.5 text-xs ${
|
||||
isDark ? 'bg-[rgba(77,141,255,0.14)] text-[#9CCBFF]' : 'bg-[#FFF1F0] text-[#CF1322]'
|
||||
isDark ? 'bg-[rgba(77,141,255,0.14)] text-[#9CCBFF]' : isLightBlue ? 'bg-[#EAF4FF] text-[#0062D9]' : 'bg-[#FFF1F0] text-[#CF1322]'
|
||||
}`}
|
||||
>
|
||||
{count}
|
||||
@@ -86,7 +89,7 @@ export const KanbanColumn: React.FC<KanbanColumnProps> = ({
|
||||
className={`flex h-full min-h-[220px] flex-col items-center justify-center rounded border border-dashed ${
|
||||
isDark
|
||||
? 'border-[rgba(87,111,149,0.42)] bg-[rgba(12,24,44,0.46)] text-slate-400'
|
||||
: 'border-[#F3C4C8] bg-[#FFF7F7] text-[#A6A6A6]'
|
||||
: isLightBlue ? 'border-[#AAD2FF] bg-[#F4F9FF] text-[#A6A6A6]' : 'border-[#F3C4C8] bg-[#FFF7F7] text-[#A6A6A6]'
|
||||
}`}
|
||||
>
|
||||
<FileText className="h-8 w-8 opacity-70" />
|
||||
|
||||
+15
-12
@@ -33,6 +33,7 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
|
||||
const { theme } = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const isDark = theme === 'dark';
|
||||
const isLightBlue = theme === 'lightblue';
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [showAnalysisModal, setShowAnalysisModal] = useState(false);
|
||||
@@ -222,7 +223,9 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
|
||||
|
||||
const shellClass = isDark
|
||||
? 'strategy-data h-full overflow-y-auto'
|
||||
: 'strategy-data h-full overflow-y-auto bg-[linear-gradient(180deg,#FFFDFD_0%,#FFF7F7_46%,#FAFAFA_100%)]';
|
||||
: isLightBlue
|
||||
? 'strategy-data h-full overflow-y-auto bg-[linear-gradient(180deg,#F9FCFF_0%,#F4F9FF_46%,#FAFAFA_100%)]'
|
||||
: 'strategy-data h-full overflow-y-auto bg-[linear-gradient(180deg,#FFFDFD_0%,#FFF7F7_46%,#FAFAFA_100%)]';
|
||||
|
||||
const toolbarClass = isDark
|
||||
? 'border border-[rgba(43,143,254,0.55)] bg-[rgba(3,38,80,0.82)] text-white shadow-[0_16px_36px_rgba(3,10,22,0.2)]'
|
||||
@@ -238,8 +241,8 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
|
||||
|
||||
const metrics = [
|
||||
{ label: '全部任务', value: unanalyzedTotal + runningTotal + completedTotal, icon: Layers, fg: '#222222', chipBg: '#F5F5F5', chipFg: '#7F7F7F' },
|
||||
{ label: '未分析', value: unanalyzedTotal, icon: Inbox, fg: '#F5222D', chipBg: '#FEEDED', chipFg: '#F5222D' },
|
||||
{ label: '分析中', value: runningTotal, icon: Loader, fg: '#1677FF', chipBg: '#EAF4FF', chipFg: '#1677FF' },
|
||||
{ label: '未分析', value: unanalyzedTotal, icon: Inbox, fg: isLightBlue ? '#0062D9' : '#F5222D', chipBg: isLightBlue ? '#EAF4FF' : '#FEEDED', chipFg: isLightBlue ? '#0062D9' : '#F5222D' },
|
||||
{ label: '分析中', value: runningTotal, icon: Loader, fg: isLightBlue ? '#0079FE' : '#1677FF', chipBg: isLightBlue ? '#F4F9FF' : '#EAF4FF', chipFg: isLightBlue ? '#0079FE' : '#1677FF' },
|
||||
{ label: '已完成', value: completedTotal, icon: CircleCheck, fg: '#52C41A', chipBg: '#EEFCF7', chipFg: '#189D72' },
|
||||
];
|
||||
|
||||
@@ -250,11 +253,11 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
|
||||
<div className="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
|
||||
<div className="space-y-2.5">
|
||||
<div className="flex items-center gap-1.5 text-xs">
|
||||
<span className={`rounded-sm border px-2 py-0.5 font-medium ${isDark ? 'border-[rgba(108,161,255,0.24)] bg-[rgba(77,141,255,0.16)] text-[#9CCBFF]' : 'border-[#FFD6D6] bg-[#FFF1F0] text-[#CF1322]'}`}>
|
||||
<span className={`rounded-sm border px-2 py-0.5 font-medium ${isDark ? 'border-[rgba(108,161,255,0.24)] bg-[rgba(77,141,255,0.16)] text-[#9CCBFF]' : isLightBlue ? 'border-[#AAD2FF] bg-[#EAF4FF] text-[#0062D9]' : 'border-[#FFD6D6] bg-[#FFF1F0] text-[#CF1322]'}`}>
|
||||
{category}
|
||||
</span>
|
||||
<ArrowRight className={`h-3 w-3 ${isDark ? 'text-slate-500' : 'text-[#A6A6A6]'}`} />
|
||||
<span className={`rounded-sm border px-2 py-0.5 font-medium ${isDark ? 'border-[rgba(103,232,165,0.2)] bg-[rgba(34,197,94,0.14)] text-[#89E8B8]' : 'border-[#FFE2B8] bg-[#FFF6E8] text-[#D46B08]'}`}>
|
||||
<span className={`rounded-sm border px-2 py-0.5 font-medium ${isDark ? 'border-[rgba(103,232,165,0.2)] bg-[rgba(34,197,94,0.14)] text-[#89E8B8]' : isLightBlue ? 'border-[#C1EBFB] bg-[#EFFAFE] text-[#2F9ACC]' : 'border-[#FFE2B8] bg-[#FFF6E8] text-[#D46B08]'}`}>
|
||||
{subCategory}
|
||||
</span>
|
||||
</div>
|
||||
@@ -293,7 +296,7 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={`inline-flex items-center gap-2 rounded-sm px-4 py-2 text-sm ${isDark ? 'bg-[rgba(77,141,255,0.16)] text-[#9CCBFF]' : 'bg-[#F5222D] text-white hover:bg-[#CF1322]'}`}
|
||||
className={`inline-flex items-center gap-2 rounded-sm px-4 py-2 text-sm ${isDark ? 'bg-[rgba(77,141,255,0.16)] text-[#9CCBFF]' : isLightBlue ? 'bg-[#0062D9] text-white hover:bg-[#0079FE]' : 'bg-[#F5222D] text-white hover:bg-[#CF1322]'}`}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
新建任务
|
||||
@@ -336,7 +339,7 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
|
||||
<KanbanColumn
|
||||
title="未分析"
|
||||
count={unanalyzedTotal}
|
||||
color={isDark ? 'bg-[#7CC9FF]' : 'bg-[#F5222D]'}
|
||||
color={isDark ? 'bg-[#7CC9FF]' : isLightBlue ? 'bg-[#0062D9]' : 'bg-[#F5222D]'}
|
||||
tasks={unanalyzedList}
|
||||
taskType="unanalyzed"
|
||||
taskDataType="sentiment"
|
||||
@@ -396,7 +399,7 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAnalysisModal(false)}
|
||||
className={`rounded-sm p-2 ${isDark ? 'hover:bg-[rgba(21,42,72,0.9)]' : 'hover:bg-[#FFF1F0]'}`}
|
||||
className={`rounded-sm p-2 ${isDark ? 'hover:bg-[rgba(21,42,72,0.9)]' : isLightBlue ? 'hover:bg-[#EAF4FF]' : 'hover:bg-[#FFF1F0]'}`}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
@@ -406,7 +409,7 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleOpenTaskDetail}
|
||||
className={`flex w-full items-center justify-between rounded border px-5 py-4 text-left transition ${isDark ? 'border-[rgba(67,89,124,0.46)] bg-[rgba(15,33,60,0.92)] hover:border-[rgba(110,168,255,0.46)]' : 'border-[#E8E8E8] bg-[#FFF9F9] hover:border-[#F6B0B4]'}`}
|
||||
className={`flex w-full items-center justify-between rounded border px-5 py-4 text-left transition ${isDark ? 'border-[rgba(67,89,124,0.46)] bg-[rgba(15,33,60,0.92)] hover:border-[rgba(110,168,255,0.46)]' : isLightBlue ? 'border-[#E8E8E8] bg-[#F4F9FF] hover:border-[#80BCFF]' : 'border-[#E8E8E8] bg-[#FFF9F9] hover:border-[#F6B0B4]'}`}
|
||||
>
|
||||
<div>
|
||||
<div className="font-medium">查看任务详情</div>
|
||||
@@ -420,10 +423,10 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleOpenConsultation}
|
||||
className={`flex w-full items-center justify-between rounded border px-5 py-4 text-left transition ${isDark ? 'border-[rgba(67,89,124,0.46)] bg-[rgba(15,33,60,0.92)] hover:border-[rgba(103,232,165,0.42)]' : 'border-[#E8E8E8] bg-[#FFF9F9] hover:border-[#F6B0B4]'}`}
|
||||
className={`flex w-full items-center justify-between rounded border px-5 py-4 text-left transition ${isDark ? 'border-[rgba(67,89,124,0.46)] bg-[rgba(15,33,60,0.92)] hover:border-[rgba(103,232,165,0.42)]' : isLightBlue ? 'border-[#E8E8E8] bg-[#F4F9FF] hover:border-[#80BCFF]' : 'border-[#E8E8E8] bg-[#FFF9F9] hover:border-[#F6B0B4]'}`}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<Users className={`mt-0.5 h-5 w-5 ${isDark ? 'text-[#89E8B8]' : 'text-[#CF1322]'}`} />
|
||||
<Users className={`mt-0.5 h-5 w-5 ${isDark ? 'text-[#89E8B8]' : isLightBlue ? 'text-[#0062D9]' : 'text-[#CF1322]'}`} />
|
||||
<div>
|
||||
<div className="font-medium">多智能体会商</div>
|
||||
<div className={`mt-1 text-sm ${isDark ? 'text-slate-300' : 'text-[#575757]'}`}>
|
||||
@@ -437,7 +440,7 @@ const SentimentAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCa
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSingleAgentAnalysis}
|
||||
className={`flex w-full items-center justify-between rounded border px-5 py-4 text-left transition ${isDark ? 'border-[rgba(67,89,124,0.46)] bg-[rgba(15,33,60,0.92)] hover:border-[rgba(167,139,250,0.46)]' : 'border-[#E8E8E8] bg-[#FFF9F9] hover:border-[#F6B0B4]'}`}
|
||||
className={`flex w-full items-center justify-between rounded border px-5 py-4 text-left transition ${isDark ? 'border-[rgba(67,89,124,0.46)] bg-[rgba(15,33,60,0.92)] hover:border-[rgba(167,139,250,0.46)]' : isLightBlue ? 'border-[#E8E8E8] bg-[#F4F9FF] hover:border-[#80BCFF]' : 'border-[#E8E8E8] bg-[#FFF9F9] hover:border-[#F6B0B4]'}`}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
<Bot className={`mt-0.5 h-5 w-5 ${isDark ? 'text-[#C4B5FD]' : 'text-[#7C3AED]'}`} />
|
||||
|
||||
@@ -33,6 +33,7 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
|
||||
const { theme } = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const isDark = theme === 'dark';
|
||||
const isLightBlue = theme === 'lightblue';
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [showAnalysisModal, setShowAnalysisModal] = useState(false);
|
||||
@@ -222,7 +223,9 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
|
||||
|
||||
const shellClass = isDark
|
||||
? 'strategy-data h-full overflow-y-auto text-slate-100'
|
||||
: 'strategy-data h-full overflow-y-auto bg-[radial-gradient(circle_at_top_left,rgba(245,34,45,0.08),transparent_26%),linear-gradient(180deg,#fffdfd_0%,#fff7f7_42%,#fafafa_100%)] text-[#222222]';
|
||||
: isLightBlue
|
||||
? 'strategy-data h-full overflow-y-auto bg-[radial-gradient(circle_at_top_left,rgba(0,98,217,0.06),transparent_26%),linear-gradient(180deg,#F9FCFF_0%,#F4F9FF_42%,#FAFAFA_100%)] text-[#222222]'
|
||||
: 'strategy-data h-full overflow-y-auto bg-[radial-gradient(circle_at_top_left,rgba(245,34,45,0.08),transparent_26%),linear-gradient(180deg,#fffdfd_0%,#fff7f7_42%,#fafafa_100%)] text-[#222222]';
|
||||
|
||||
const toolbarClass = isDark
|
||||
? 'border border-[rgba(43,143,254,0.55)] bg-[rgba(3,38,80,0.82)] text-slate-100 shadow-[0_16px_36px_rgba(3,10,22,0.2)]'
|
||||
@@ -242,8 +245,8 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
|
||||
|
||||
const metrics = [
|
||||
{ label: '全部任务', value: unanalyzedTotal + runningTotal + completedTotal, icon: Layers, fg: '#222222', chipBg: '#F5F5F5', chipFg: '#7F7F7F' },
|
||||
{ label: '未分析', value: unanalyzedTotal, icon: Inbox, fg: '#F5222D', chipBg: '#FEEDED', chipFg: '#F5222D' },
|
||||
{ label: '分析中', value: runningTotal, icon: Loader, fg: '#1677FF', chipBg: '#EAF4FF', chipFg: '#1677FF' },
|
||||
{ label: '未分析', value: unanalyzedTotal, icon: Inbox, fg: isLightBlue ? '#0062D9' : '#F5222D', chipBg: isLightBlue ? '#EAF4FF' : '#FEEDED', chipFg: isLightBlue ? '#0062D9' : '#F5222D' },
|
||||
{ label: '分析中', value: runningTotal, icon: Loader, fg: isLightBlue ? '#0079FE' : '#1677FF', chipBg: isLightBlue ? '#F4F9FF' : '#EAF4FF', chipFg: isLightBlue ? '#0079FE' : '#1677FF' },
|
||||
{ label: '已完成', value: completedTotal, icon: CircleCheck, fg: '#52C41A', chipBg: '#EEFCF7', chipFg: '#189D72' },
|
||||
];
|
||||
|
||||
@@ -254,11 +257,11 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
|
||||
<div className="flex flex-wrap items-center gap-x-3 gap-y-2">
|
||||
<h1 className={`text-[24px] font-semibold leading-8 ${isDark ? 'text-slate-50' : 'text-[#222222]'}`}>{title}</h1>
|
||||
<div className="flex items-center gap-1.5 text-xs">
|
||||
<span className={`rounded-sm border px-2 py-0.5 font-medium ${isDark ? 'border-[rgba(108,161,255,0.24)] bg-[rgba(77,141,255,0.16)] text-[#9CCBFF]' : 'border-[#FFD6D6] bg-[#FFF1F0] text-[#CF1322]'}`}>
|
||||
<span className={`rounded-sm border px-2 py-0.5 font-medium ${isDark ? 'border-[rgba(108,161,255,0.24)] bg-[rgba(77,141,255,0.16)] text-[#9CCBFF]' : isLightBlue ? 'border-[#AAD2FF] bg-[#EAF4FF] text-[#0062D9]' : 'border-[#FFD6D6] bg-[#FFF1F0] text-[#CF1322]'}`}>
|
||||
{category}
|
||||
</span>
|
||||
<ArrowRight className={`h-3 w-3 ${isDark ? 'text-slate-500' : 'text-[#A6A6A6]'}`} />
|
||||
<span className={`rounded-sm border px-2 py-0.5 font-medium ${isDark ? 'border-[rgba(103,232,165,0.2)] bg-[rgba(34,197,94,0.14)] text-[#89E8B8]' : 'border-[#FFE2B8] bg-[#FFF6E8] text-[#D46B08]'}`}>
|
||||
<span className={`rounded-sm border px-2 py-0.5 font-medium ${isDark ? 'border-[rgba(103,232,165,0.2)] bg-[rgba(34,197,94,0.14)] text-[#89E8B8]' : isLightBlue ? 'border-[#C1EBFB] bg-[#EFFAFE] text-[#2F9ACC]' : 'border-[#FFE2B8] bg-[#FFF6E8] text-[#D46B08]'}`}>
|
||||
{subCategory}
|
||||
</span>
|
||||
</div>
|
||||
@@ -290,7 +293,7 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className={`inline-flex shrink-0 items-center gap-2 whitespace-nowrap rounded-sm px-4 py-2 text-sm ${isDark ? 'bg-[rgba(77,141,255,0.16)] text-[#9CCBFF]' : 'bg-[#F5222D] text-white hover:bg-[#CF1322]'}`}
|
||||
className={`inline-flex shrink-0 items-center gap-2 whitespace-nowrap rounded-sm px-4 py-2 text-sm ${isDark ? 'bg-[rgba(77,141,255,0.16)] text-[#9CCBFF]' : isLightBlue ? 'bg-[#0062D9] text-white hover:bg-[#0079FE]' : 'bg-[#F5222D] text-white hover:bg-[#CF1322]'}`}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
新建任务
|
||||
@@ -332,7 +335,7 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
|
||||
<KanbanColumn
|
||||
title="未分析"
|
||||
count={unanalyzedTotal}
|
||||
color={isDark ? 'bg-[#7CC9FF]' : 'bg-[#F5222D]'}
|
||||
color={isDark ? 'bg-[#7CC9FF]' : isLightBlue ? 'bg-[#0062D9]' : 'bg-[#F5222D]'}
|
||||
tasks={unanalyzedList}
|
||||
taskType="unanalyzed"
|
||||
pageIndexSearch={pageIndexChange}
|
||||
@@ -389,7 +392,7 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAnalysisModal(false)}
|
||||
className={`rounded-sm p-2 transition ${isDark ? 'hover:bg-[rgba(15,41,74,0.88)]' : 'hover:bg-[#FFF1F0]'}`}
|
||||
className={`rounded-sm p-2 transition ${isDark ? 'hover:bg-[rgba(15,41,74,0.88)]' : isLightBlue ? 'hover:bg-[#EAF4FF]' : 'hover:bg-[#FFF1F0]'}`}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
@@ -399,7 +402,7 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleOpenTaskDetail}
|
||||
className={`group flex items-center justify-between rounded border px-5 py-4 text-left transition ${isDark ? 'border-[rgba(110,136,176,0.24)] bg-[rgba(10,30,54,0.9)] hover:border-[rgba(96,165,250,0.48)] hover:bg-[rgba(12,38,69,0.96)]' : 'border-[#E8E8E8] bg-[#FFF9F9] hover:border-[#F6B0B4]'}`}
|
||||
className={`group flex items-center justify-between rounded border px-5 py-4 text-left transition ${isDark ? 'border-[rgba(110,136,176,0.24)] bg-[rgba(10,30,54,0.9)] hover:border-[rgba(96,165,250,0.48)] hover:bg-[rgba(12,38,69,0.96)]' : isLightBlue ? 'border-[#E8E8E8] bg-[#F4F9FF] hover:border-[#80BCFF]' : 'border-[#E8E8E8] bg-[#FFF9F9] hover:border-[#F6B0B4]'}`}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div
|
||||
@@ -421,12 +424,12 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleOpenConsultation}
|
||||
className={`group flex items-center justify-between rounded border px-5 py-4 text-left transition ${isDark ? 'border-[rgba(110,136,176,0.24)] bg-[rgba(10,30,54,0.9)] hover:border-[rgba(45,212,191,0.48)] hover:bg-[rgba(10,42,58,0.96)]' : 'border-[#E8E8E8] bg-[#FFF9F9] hover:border-[#F6B0B4]'}`}
|
||||
className={`group flex items-center justify-between rounded border px-5 py-4 text-left transition ${isDark ? 'border-[rgba(110,136,176,0.24)] bg-[rgba(10,30,54,0.9)] hover:border-[rgba(45,212,191,0.48)] hover:bg-[rgba(10,42,58,0.96)]' : isLightBlue ? 'border-[#E8E8E8] bg-[#F4F9FF] hover:border-[#80BCFF]' : 'border-[#E8E8E8] bg-[#FFF9F9] hover:border-[#F6B0B4]'}`}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div
|
||||
className={`flex h-10 w-10 items-center justify-center rounded-sm ${isDark ? 'bg-[linear-gradient(180deg,#14B8A6,#22C55E)] text-white' : ''}`}
|
||||
style={isDark ? undefined : { backgroundColor: '#FEEDED', color: '#CF1322' }}
|
||||
style={isDark ? undefined : isLightBlue ? { backgroundColor: '#EAF4FF', color: '#0062D9' } : { backgroundColor: '#FEEDED', color: '#CF1322' }}
|
||||
>
|
||||
<Users className="h-5 w-5" />
|
||||
</div>
|
||||
@@ -443,7 +446,7 @@ const TaskAnalyzes: React.FC<TaskListPageProps> = ({ title, category, subCategor
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSingleAgentAnalysis}
|
||||
className={`group flex items-center justify-between rounded border px-5 py-4 text-left transition ${isDark ? 'border-[rgba(110,136,176,0.24)] bg-[rgba(10,30,54,0.9)] hover:border-[rgba(167,139,250,0.48)] hover:bg-[rgba(28,24,58,0.96)]' : 'border-[#E8E8E8] bg-[#FFF9F9] hover:border-[#F6B0B4]'}`}
|
||||
className={`group flex items-center justify-between rounded border px-5 py-4 text-left transition ${isDark ? 'border-[rgba(110,136,176,0.24)] bg-[rgba(10,30,54,0.9)] hover:border-[rgba(167,139,250,0.48)] hover:bg-[rgba(28,24,58,0.96)]' : isLightBlue ? 'border-[#E8E8E8] bg-[#F4F9FF] hover:border-[#80BCFF]' : 'border-[#E8E8E8] bg-[#FFF9F9] hover:border-[#F6B0B4]'}`}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div
|
||||
|
||||
@@ -55,6 +55,7 @@ export const TaskCard: React.FC<TaskCardProps> = ({
|
||||
const { setStartConfig } = useChatContext();
|
||||
const navigate = useNavigate();
|
||||
const isDark = theme === 'dark';
|
||||
const isLightBlue = theme === 'lightblue';
|
||||
|
||||
const typeConfig = getTaskTypeConfig(task.taskType);
|
||||
const TypeIcon = typeConfig.icon;
|
||||
@@ -118,11 +119,13 @@ export const TaskCard: React.FC<TaskCardProps> = ({
|
||||
|
||||
const panelClass = isDark
|
||||
? 'border border-[rgba(67,89,124,0.46)] bg-[rgba(19,39,72,0.9)] shadow-[0_14px_34px_rgba(3,10,22,0.2)] hover:border-[rgba(110,168,255,0.52)]'
|
||||
: 'border border-[#E8E8E8] bg-white shadow-[0_2px_8px_rgba(34,34,34,0.08)] hover:border-[#F6B0B4] hover:shadow-[0_4px_16px_rgba(34,34,34,0.12)]';
|
||||
: isLightBlue
|
||||
? 'border border-[#E8E8E8] bg-white shadow-[0_2px_8px_rgba(34,34,34,0.08)] hover:border-[#80BCFF] hover:shadow-[0_4px_16px_rgba(34,34,34,0.12)]'
|
||||
: 'border border-[#E8E8E8] bg-white shadow-[0_2px_8px_rgba(34,34,34,0.08)] hover:border-[#F6B0B4] hover:shadow-[0_4px_16px_rgba(34,34,34,0.12)]';
|
||||
|
||||
const typeBadgeClass = isDark
|
||||
? 'bg-[rgba(77,141,255,0.14)] text-[#A9D2FF] border border-[rgba(108,161,255,0.24)]'
|
||||
: 'bg-[#FFF1F0] text-[#CF1322] border border-[#FFD6D6]';
|
||||
: isLightBlue ? 'bg-[#EAF4FF] text-[#0062D9] border border-[#AAD2FF]' : 'bg-[#FFF1F0] text-[#CF1322] border border-[#FFD6D6]';
|
||||
|
||||
const levelClass = isDark
|
||||
? 'text-slate-400'
|
||||
@@ -139,15 +142,15 @@ export const TaskCard: React.FC<TaskCardProps> = ({
|
||||
: 'bg-[#EAF4FF] text-[#1677FF] border border-[#CDE2FF]'
|
||||
: isDark
|
||||
? 'bg-[rgba(96,165,250,0.12)] text-[#8FD1FF] border border-[rgba(143,209,255,0.2)]'
|
||||
: 'bg-[#FFF1F0] text-[#F5222D] border border-[#FFD6D6]';
|
||||
: isLightBlue ? 'bg-[#EAF4FF] text-[#0062D9] border border-[#AAD2FF]' : 'bg-[#FFF1F0] text-[#F5222D] border border-[#FFD6D6]';
|
||||
|
||||
const actionClass = isCompleted
|
||||
? isDark
|
||||
? 'bg-[rgba(12,48,82,0.95)] text-white hover:bg-[rgba(15,60,104,0.98)]'
|
||||
: 'bg-[#FFF1F0] text-[#CF1322] hover:bg-[#FFE4E6]'
|
||||
: isLightBlue ? 'bg-[#EAF4FF] text-[#0062D9] hover:bg-[#D4E9FF]' : 'bg-[#FFF1F0] text-[#CF1322] hover:bg-[#FFE4E6]'
|
||||
: isDark
|
||||
? 'bg-[linear-gradient(90deg,#1677FF,#22A8F0)] text-white hover:brightness-110'
|
||||
: 'bg-[linear-gradient(90deg,#F5222D,#FF6B73)] text-white hover:brightness-105';
|
||||
: isLightBlue ? 'bg-[linear-gradient(90deg,#0062D9,#2B8FFE)] text-white hover:brightness-105' : 'bg-[linear-gradient(90deg,#F5222D,#FF6B73)] text-white hover:brightness-105';
|
||||
|
||||
return (
|
||||
<article
|
||||
|
||||
+13
-1
@@ -5,7 +5,7 @@
|
||||
* Used by TaskAnalyzes, SentimentAnalyzes, ActionAnalyzes and related children.
|
||||
*/
|
||||
|
||||
/* ── CSS variables (light) ─────────────────────────────────────────────── */
|
||||
/* ── CSS variables (light - red) ─────────────────────────────────────── */
|
||||
.strategy-data {
|
||||
--color-main: #cf1322;
|
||||
--td-brand-color: #ce2d39;
|
||||
@@ -20,6 +20,18 @@
|
||||
--input-border: 0 76% 89%;
|
||||
}
|
||||
|
||||
/* ── CSS variables (light - blue) ────────────────────────────────────── */
|
||||
[data-theme="lightblue"] .strategy-data {
|
||||
--color-main: #0062D9;
|
||||
--td-brand-color: #0062D9;
|
||||
--bg-color: #EAF4FF;
|
||||
--card-border: #80BCFF;
|
||||
|
||||
--card-hover: 211 100% 96%;
|
||||
--border-color: 211 100% 92%;
|
||||
--input-border: 211 100% 92%;
|
||||
}
|
||||
|
||||
/* ── CSS variables (dark) ────────────────────────────────────────────── */
|
||||
.dark .strategy-data {
|
||||
--color-main: var(--color-primary);
|
||||
|
||||
Reference in new issue
Block a user