This commit is contained in:
xuhd committed 2026-06-01 14:21:20 +08:00
commit e30b44dae1
68 files changed
+10813 -3976

No files matched your search

+178 -41
View File
@@ -1,6 +1,16 @@
.sidebar-contnet {
position: fixed;
left: 0;
top: 4rem;
z-index: 40;
width: 98px;
height: calc(100vh - 4rem);
flex-shrink: 0;
}
.sidebar-contents {
width: 98px;
min-height: 986px;
min-height: calc(100% - 20px);
background: rgba(3, 38, 80, 0.8);
border: 1px solid;
border-color: rgba(43, 143, 254, 0.6);
@@ -11,16 +21,30 @@
align-items: center;
padding: 12px 10px;
position: relative;
height: calc(100% - 20px);
box-sizing: border-box;
transform: translate(10px, 10px);
}
.item-nav-group {
width: 100%;
}
.item-nav-wrap {
position: relative;
width: 100%;
}
.item-nav-group:first-child .item-nav {
margin-top: 0;
}
.item-nav {
width: 100%;
height: 70px;
background: #ffffff1a;
border-color: #fefafa4d;
height: 75px;
margin-top: 13px;
font-family: Noto Sans S Chinese;
font-weight: 550;
font-weight: 500;
color: #fff;
font-size: 20px;
display: flex;
@@ -35,9 +59,46 @@
position: relative;
}
.item-nav:hover {
background: rgba(85, 166, 254, 0.12);
.item-nav:hover,
.item-nav-active,
.item-nav-hovered {
color: #ffffff;
background: linear-gradient(134.25deg,
rgba(0, 98, 217, 0.59) 2.28%,
rgba(0, 98, 217, 0.3) 99.59%) !important;
background-size: cover;
border: 1px solid;
border-color: rgba(255, 255, 255, 0.2);
position: relative;
}
.item-nav:hover::before,
.item-nav-active::before,
.item-nav-hovered::before {
content: '';
position: absolute;
right: -10px;
top: 50%;
transform: translateY(-50%);
width: 0;
height: 0;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
border-left: 10px solid rgba(0, 98, 217, 0.45);
pointer-events: none;
}
.item-nav:hover::after,
.item-nav-active::after,
.item-nav-hovered::after {
content: '';
position: absolute;
left: 0;
top: 29px;
width: 77px;
height: 45px;
background: url('../assets/icon/nav-line-logo.svg') no-repeat;
background-size: cover;
}
.item-nav__label {
@@ -52,53 +113,47 @@
.item-nav-content {
width: 155px;
min-height: 970px;
height: calc(100vh - 4rem - 20px);
min-height: calc(100vh - 4rem - 20px);
background: rgba(3, 38, 80, 0.8);
border: 1px solid;
border-color: rgba(43, 143, 254, 0.6);
border-radius: 4px;
backdrop-filter: blur(2px);
position: absolute;
left: 97px;
left: 108px;
top: 0;
z-index: 1000;
padding: 14px 13px 18px 16px;
overflow-y: auto;
}
.item-nav-active {
color: #ffffff;
background: linear-gradient(134.25deg,
rgba(0, 98, 217, 0.59) 2.28%,
rgba(0, 98, 217, 0.3) 99.59%) !important;
background-size: cover;
border: 1px solid;
border-color: rgba(255, 255, 255, 0.2);
position: relative;
}
.item-nav-active::before {
content: '';
position: absolute;
right: -10px;
top: 50%;
transform: translateY(-50%);
width: 0;
height: 0;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
border-left: 10px solid rgba(0, 98, 217, 0.45);
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: opacity 0.2s ease, visibility 0.2s ease;
}
.item-nav-active::after {
.item-nav-content::before {
content: '';
position: absolute;
left: 0;
top: 24px;
width: 77px;
height: 45px;
background: url('../assets/icon/nav-line-logo.svg') no-repeat;
background-size: cover;
left: -30px;
top: 0;
width: 30px;
height: 100%;
}
.item-nav-hover-bridge {
position: absolute;
right: -30px;
top: 0;
width: 30px;
height: 100%;
z-index: 999;
}
.item-nav-content--visible {
opacity: 1;
visibility: visible;
pointer-events: auto;
}
.sidebar-flyout-list {
@@ -140,7 +195,7 @@
padding: 3px 0;
font-family: Noto Sans S Chinese;
font-size: 18px;
line-height: 28px;
line-height: 16px;
font-weight: 500;
letter-spacing: 0;
color: rgba(255, 255, 255, 0.8);
@@ -182,4 +237,86 @@
.sidebar-flyout-label {
flex: 1;
white-space: nowrap;
}
.sidebar-collapse-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
border-radius: 8px;
cursor: pointer;
}
.sidebar-collapse-btn__icon {
width: 16px;
height: 16px;
flex: 0 0 16px;
color: #80e9ff;
}
.sidebar-collapse-btn__label {
font-size: 14px;
line-height: 1;
white-space: nowrap;
}
.sidebar-collapse-btn--inline {
width: calc(100% - 6px);
min-height: 58px;
margin-top: auto;
margin-bottom: 2px;
flex-direction: column;
gap: 6px;
border: 1px solid #55a6fe33;
background: #000c1c26;
color: rgba(255, 255, 255, 0.9);
box-shadow: inset 0 0 16px #00b9ff1f;
transition: background-color 0.2s ease, border-color 0.2s ease;
}
.sidebar-collapse-btn--inline .sidebar-collapse-btn__label {
font-size: 16px;
line-height: 1.1;
}
.sidebar-collapse-btn--inline:hover {
background: rgba(0, 98, 217, 0.3);
border-color: rgba(255, 255, 255, 0.25);
}
.sidebar-collapse-btn--floating {
position: fixed;
left: 10px;
bottom: 10px;
z-index: 60;
width: 36px;
height: 36px;
min-height: 36px;
padding: 0 10px;
border: 1px solid rgba(128, 233, 255, 0.35);
background: rgba(3, 38, 80, 0.82);
color: rgba(205, 245, 255, 0.95);
backdrop-filter: blur(2px);
transition: width 0.22s ease, opacity 0.2s ease, background-color 0.2s ease, border-color 0.2s ease;
overflow: hidden;
}
.sidebar-collapse-btn--floating:hover {
width: 104px;
background: rgba(7, 52, 110, 0.9);
border-color: rgba(128, 233, 255, 0.65);
}
.sidebar-collapse-btn--floating .sidebar-collapse-btn__label {
max-width: 0;
opacity: 0;
transform: translateX(-4px);
transition: max-width 0.22s ease, opacity 0.16s ease, transform 0.22s ease;
}
.sidebar-collapse-btn--floating:hover .sidebar-collapse-btn__label {
max-width: 64px;
opacity: 1;
transform: translateX(0);
}
+138 -321
View File
@@ -1,312 +1,66 @@
import { useEffect, useMemo, useState } from "react";
import type { ElementType } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { useNavigate, useLocation } from "react-router-dom";
import {
Bell,
Bot,
Brain,
CalendarClock,
ChevronLeft,
ChevronRight,
ChevronsUpDown,
CircleHelp,
Database,
FileBox,
FileText,
Folder,
FolderOpen,
Gauge,
Grid3X3,
HelpCircle,
History,
Layout,
Library,
List,
ListChecks,
ListTodo,
MessageSquareCode,
MessageSquarePlus,
Network,
NotebookPen,
Plus,
Settings,
Sparkles,
Target,
User,
Users,
Wrench,
} from "lucide-react";
import { PanelLeftOpen, PanelLeftClose } 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,
filterMenuItemsByRole,
sidebarMenuItems,
type MenuItem,
} from "@/core/page-layout/sidebar-menu";
import './page-sdebar.css'
type MenuItem = {
id: string;
label: string;
icon: ElementType;
path?: string;
adminOnly?: boolean;
children?: MenuItem[];
};
type PageSidebarDarkProps = {
/** 是否收起(true 时只渲染左下角浮动入口,sidebar 主体隐藏) */
collapsed: boolean;
onToggleCollapsed: () => void;
};
type SettingsSection = "account" | "appearance" | "memory" | "tools" | "about";
const menuItems: MenuItem[] = [
{
id: "qa-center",
label: "问答中心",
icon: HelpCircle,
children: [
{ id: "general-qa", label: "通用问答", icon: CircleHelp, path: "/page/workspace/chats/new" },
{ id: "expert-qa", label: "专家问答", icon: Brain, path: "/page/strategy/qa/expert-qa" },
{ id: "qa-zzjc", label: "zzjc支持", icon: Bot },
{ id: "qa-zzxd", label: "zzXD应用", icon: Layout },
{ id: "qa-research", label: "课题研究", icon: FileText, path: "/page/canvas/ai-writing" },
],
},
{
id: "light-app-center",
label: "轻应用",
icon: Grid3X3,
children: [
{
id: "light-app-note-management",
label: "笔记管理",
icon: NotebookPen,
children: [
{
id: "light-app-new-note",
label: "新建笔记",
icon: Plus,
path: "/page/strategy/light-app/new-note",
},
{
id: "light-app-note-list",
label: "笔记列表",
icon: List,
path: "/page/strategy/light-app/notes",
},
{
id: "light-app-note-qa",
label: "笔记问答",
icon: MessageSquareCode,
path: "/page/strategy/light-app/note-qa",
},
],
},
{ id: "light-app-square", label: "应用广场", icon: Layout },
{ id: "light-app-records", label: "运行记录", icon: History },
],
},
{
id: "task-management",
label: "任务管理",
icon: Target,
children: [
{
id: "xk-center-task",
label: "XK中任务",
icon: Folder,
children: [
{ id: "xk-task-jc", label: "任务JC", icon: ListTodo, path: "/page/strategy/task-management/xk-task-jc" },
{ id: "xk-task-dispatch", label: "任务调度", icon: ListTodo, path: "/page/strategy/task-management/xk-task-dispatch" },
],
},
{
id: "coh-center-task",
label: "COH中任务",
icon: Folder,
children: [
{
id: "coh-task-3qfx",
label: "3QFX",
icon: ListTodo,
path: "/page/strategy/task-management/plan-task-sentiment",
},
{
id: "coh-task-fx",
label: "任务FX",
icon: ListTodo,
path: "/page/strategy/task-management/plan-task/plan-task-fx",
},
{
id: "coh-task-xdfx",
label: "XDFX",
icon: ListTodo,
path: "/page/strategy/task-management/plan-task/plan-task-action",
},
],
},
{
id: "pg-center-task",
label: "PG中任务",
icon: Folder,
children: [
{ id: "pg-task-pg", label: "任务PG", icon: ListTodo },
{ id: "pg-task-xdpg", label: "XDPG", icon: ListTodo },
{ id: "pg-task-info-pg", label: "信息PG", icon: ListTodo },
],
},
{
id: "ty-center-task",
label: "TY中任务",
icon: Folder,
children: [
{ id: "ty-task-xdty", label: "XDTY任务", icon: ListTodo },
{ id: "ty-task-plan", label: "方案TY任务", icon: ListTodo },
{ id: "ty-task-xxty", label: "XXTY任务", icon: ListTodo },
],
},
],
},
{
id: "comprehensive-management",
label: "综合管理",
icon: Settings,
children: [
{ id: "mgmt-agents", label: "智能体管理", icon: Bot, path: "/page/strategy/agents" },
{ id: "mgmt-skills", label: "技能管理", icon: Sparkles, path: "/page/strategy/skills" },
{ id: "mgmt-memory", label: "记忆管理", icon: Brain, path: "/page/strategy/memory" },
{
id: "mgmt-tasks",
label: "任务管理",
icon: ListChecks,
path: "/page/strategy/scheduled/tasks",
},
{
id: "mgmt-recommended-questions",
label: "推荐问题配置",
icon: MessageSquarePlus,
path: "/page/strategy/settings/recommended-questions",
adminOnly: true,
},
{
id: "mgmt-prompt-prefix",
label: "问答前缀配置",
icon: MessageSquareCode,
path: "/page/strategy/settings/prompt-prefix",
adminOnly: true,
},
{
id: "mgmt-llm-metrics",
label: "大模型调用统计",
icon: Gauge,
path: "/page/strategy/settings/llm-metrics",
adminOnly: true,
},
{
id: "mgmt-users",
label: "用户管理",
icon: Users,
path: "/page/strategy/admin/users",
adminOnly: true,
},
{
id: "mgmt-admin-scheduled-tasks",
label: "定时任务管理",
icon: CalendarClock,
path: "/page/strategy/admin/scheduled-tasks",
adminOnly: true,
},
{
id: "mgmt-user-activity",
label: "用户活动审计",
icon: History,
path: "/page/strategy/admin/user-activity",
adminOnly: true,
},
],
},
{
id: "resource-management",
label: "资源管理",
icon: Database,
children: [
{
id: "resource-network-xwt",
label: "网络xwt资源管理",
icon: Network,
path: "/page/strategy/resource-management/resource-network-actor",
},
{
id: "resource-knowledge-base",
label: "知识库资源管理",
icon: Library,
path: "/page/strategy/resource-management/resource-knowledge-base",
},
{
id: "resource-evaluation-index",
label: "作战资源管理",
icon: FileBox,
path: "/page/strategy/resource-management/resource-evaluation-index",
},
],
},
];
const defaultExpandedIds = new Set([
"qa-center",
"light-app-center",
"light-app-note-management",
"task-management",
"xk-center-task",
]);
function filterMenuItemsByRole(items: MenuItem[], isAdmin: boolean): MenuItem[] {
return items
.filter((item) => !item.adminOnly || isAdmin)
.map((item) =>
item.children
? { ...item, children: filterMenuItemsByRole(item.children, isAdmin) }
: item,
);
}
export function PageSidebarDark({
collapsed,
onToggleCollapsed,
}: PageSidebarDarkProps) {
const { theme } = useTheme();
const isDark = theme === "dark";
export function PageSidebarDark({ collapsed, onToggleCollapsed }: PageSidebarDarkProps) {
const isAdmin = getStoredAuth()?.system_role === "admin";
const visibleMenuItems = useMemo(
() => filterMenuItemsByRole(menuItems, !!isAdmin),
() => filterMenuItemsByRole(sidebarMenuItems, !!isAdmin),
[isAdmin],
);
const { t } = useI18n();
const { data: unreadCount = 0 } = useUnreadCount();
const [expandedIds, setExpandedIds] = useState<Set<string>>(defaultExpandedIds);
const [settingsOpen, setSettingsOpen] = useState(false);
const [settingsDefaultSection, setSettingsDefaultSection] =
useState<SettingsSection>("appearance");
const [notificationOpen, setNotificationOpen] = useState(false);
const [mounted, setMounted] = useState(false);
// defaultExpandedIds 是共享的 ReadonlySet,复制一份给 useState(避免外部突变)
const [expandedIds, setExpandedIds] = useState<Set<string>>(() => new Set(defaultExpandedIds));
const navigate = useNavigate();
const { pathname } = useLocation();
// pathname 变化时取消任何未触发的 hover 关闭计时(cleanup 在卸载时也跑)
useEffect(() => {
setMounted(true);
return () => {
if (hoverCloseTimerRef.current !== null) {
window.clearTimeout(hoverCloseTimerRef.current);
}
};
}, []);
const openSettings = (section: SettingsSection) => {
setSettingsDefaultSection(section);
setSettingsOpen(true);
const handleNavHoverEnter = (id: string) => {
if (hoverCloseTimerRef.current !== null) {
window.clearTimeout(hoverCloseTimerRef.current);
hoverCloseTimerRef.current = null;
}
setHoveredTopId(id);
};
const scheduleNavPanelClose = () => {
if (hoverCloseTimerRef.current !== null) {
window.clearTimeout(hoverCloseTimerRef.current);
}
hoverCloseTimerRef.current = window.setTimeout(() => {
setHoveredTopId(null);
hoverCloseTimerRef.current = null;
}, 180);
};
const closeNavPanel = () => {
if (hoverCloseTimerRef.current !== null) {
window.clearTimeout(hoverCloseTimerRef.current);
hoverCloseTimerRef.current = null;
}
setHoveredTopId(null);
};
const hasActiveChild = (item: MenuItem): boolean => {
@@ -317,18 +71,21 @@ export function PageSidebarDark({
return item.children?.some(hasActiveChild) ?? false;
};
const initialActiveTopItem = visibleMenuItems.find(hasActiveChild) ?? visibleMenuItems[0];
const [activeTopId, setActiveTopId] = useState(initialActiveTopItem?.id ?? "qa-center");
// lazy init:仅在首次 render 计算一次 active id;之后由下面的 useEffect 跟随 pathname 同步
const [activeTopId, setActiveTopId] = useState<string>(
() => visibleMenuItems.find(hasActiveChild)?.id ?? visibleMenuItems[0]?.id ?? "qa-center",
);
const [hoveredTopId, setHoveredTopId] = useState<string | null>(null);
const hoverCloseTimerRef = useRef<number | null>(null);
useEffect(() => {
const matchedTopItem = visibleMenuItems.find(hasActiveChild);
if (matchedTopItem) {
setActiveTopId(matchedTopItem.id);
}
closeNavPanel();
}, [pathname, visibleMenuItems]);
const activeTopItem = visibleMenuItems.find((item) => item.id === activeTopId) ?? initialActiveTopItem;
const toggleExpanded = (id: string) => {
setExpandedIds((prev) => {
const next = new Set(prev);
@@ -341,10 +98,38 @@ export function PageSidebarDark({
});
};
const findFirstRoutePath = (items?: MenuItem[]): string | null => {
if (!items?.length) {
return null;
}
for (const child of items) {
if (child.path) {
return child.path;
}
const nestedPath = findFirstRoutePath(child.children);
if (nestedPath) {
return nestedPath;
}
}
return null;
};
const handleTopItemClick = (item: MenuItem) => {
setActiveTopId(item.id);
if (!item.children?.length && item.path) {
if (item.children?.length) {
const firstPath = findFirstRoutePath(item.children);
if (firstPath) {
closeNavPanel();
navigate(firstPath);
}
return;
}
if (item.path) {
closeNavPanel();
navigate(item.path);
}
};
@@ -356,6 +141,7 @@ export function PageSidebarDark({
}
if (item.path) {
closeNavPanel();
navigate(item.path);
}
};
@@ -386,35 +172,66 @@ export function PageSidebarDark({
})}
</div>
);
// collapsed 状态:sidebar 主体完全消失(包括占据的宽度),只渲染左下角浮动入口
if (collapsed) {
return (
<button
type="button"
title="展开侧边栏"
aria-label="展开侧边栏"
className="sidebar-collapse-btn sidebar-collapse-btn--floating"
onClick={onToggleCollapsed}
>
<PanelLeftOpen className="sidebar-collapse-btn__icon" aria-hidden="true" />
<span className="sidebar-collapse-btn__label">收起面板</span>
</button>
);
}
return (
<>
<SettingsDialog
open={settingsOpen}
onOpenChange={setSettingsOpen}
defaultSection={settingsDefaultSection}
/>
<NotificationDialog
open={notificationOpen}
onOpenChange={setNotificationOpen}
/>
{/* TODO: 在这里编写深色主题的侧边栏 HTML 结构 */}
<div className="sidebar-contnet">
<div className="sidebar-contents">
<div className="item-nav-content">
{activeTopItem?.children?.length ? renderFlyoutMenu(activeTopItem.children) : null}
<aside className="sidebar-contnet">
<div className="sidebar-contents">
{visibleMenuItems.map((item) => (
<div
key={item.id}
className="item-nav-group"
onMouseEnter={() => handleNavHoverEnter(item.id)}
onMouseLeave={scheduleNavPanelClose}
>
{item.children?.length ? (
<div
className={`item-nav-content ${hoveredTopId === item.id ? "item-nav-content--visible" : ""}`}
>
{renderFlyoutMenu(item.children)}
</div>
) : null}
<div className="item-nav-wrap">
<button
type="button"
className={`item-nav ${item.id === activeTopId ? "item-nav-active" : ""} ${hoveredTopId === item.id ? "item-nav-hovered" : ""}`}
onClick={() => handleTopItemClick(item)}
>
<span className="item-nav__label">{item.label}</span>
</button>
{item.children?.length && hoveredTopId === item.id ? (
<span className="item-nav-hover-bridge" aria-hidden="true" />
) : null}
</div>
</div>
{visibleMenuItems.map((item) => (
<button
type="button"
key={item.id}
className={`item-nav ${item.id === activeTopId ? "item-nav-active" : ""}`}
onClick={() => handleTopItemClick(item)}
>
<span className="item-nav__label">{item.label}</span>
</button>
))}
</div>
))}
{/* 底部"收起面板"按钮:独立样式,mt-auto 推到底 */}
<button
type="button"
title="收起侧边栏"
aria-label="收起侧边栏"
className="sidebar-collapse-btn sidebar-collapse-btn--inline"
onClick={onToggleCollapsed}
>
<PanelLeftClose className="sidebar-collapse-btn__icon" aria-hidden="true" />
<span className="sidebar-collapse-btn__label">收起面板</span>
</button>
</div>
</>
</aside>
);
}
+9 -302
View File
@@ -1,40 +1,14 @@
import { useEffect, useState } from "react";
import type { ElementType } from "react";
import { useNavigate, useLocation } from "react-router-dom";
import {
Bell,
Bot,
Brain,
CalendarClock,
ChevronLeft,
ChevronRight,
ChevronsUpDown,
CircleHelp,
Database,
FileBox,
FileText,
Folder,
FolderOpen,
Gauge,
Grid3X3,
HelpCircle,
History,
Layout,
Library,
List,
ListChecks,
ListTodo,
MessageSquareCode,
MessageSquarePlus,
ShieldCheck,
Network,
NotebookPen,
Plus,
Settings,
Sparkles,
Target,
User,
Users,
Wrench,
} from "lucide-react";
@@ -50,14 +24,12 @@ import { useTheme } from "@/contexts/ThemeContext";
import { getStoredAuth } from "@/core/auth";
import { useI18n } from "@/core/i18n/hooks";
import { useUnreadCount } from "@/core/notifications";
type MenuItem = {
id: string;
label: string;
icon: ElementType;
path?: string;
adminOnly?: boolean;
children?: MenuItem[];
};
import {
defaultExpandedIds,
filterMenuItemsByRole,
sidebarMenuItems,
type MenuItem,
} from "@/core/page-layout/sidebar-menu";
type PageSidebarProps = {
collapsed: boolean;
@@ -66,272 +38,6 @@ type PageSidebarProps = {
type SettingsSection = "account" | "appearance" | "memory" | "tools" | "about";
const menuItems: MenuItem[] = [
{
id: "qa-center",
label: "问答中心",
icon: HelpCircle,
children: [
{ id: "general-qa", label: "通用问答", icon: CircleHelp, path: "/page/workspace/chats/new" },
{ id: "expert-qa", label: "专家问答", icon: Brain, path: "/page/strategy/qa/expert-qa" },
// 「圆桌规划」入口暂时隐藏 —— 功能仍在 /page/roundtable/planning,只是不在侧边栏出现。
// 需要重新开放时取消注释即可。
// {
// id: "qa-roundtable",
// label: "圆桌规划",
// icon: Bot,
// path: "/page/roundtable/planning",
// },
{ id: "qa-zzjc", label: "zzjc支持", icon: Bot },
{ id: "qa-zzxd", label: "zzXD应用", icon: Layout },
{ id: "qa-research", label: "课题研究", icon: FileText, path: "/page/canvas/ai-writing" },
],
},
{
id: "light-app-center",
label: "轻应用",
icon: Grid3X3,
children: [
{
id: "light-app-note-management",
label: "笔记管理",
icon: NotebookPen,
children: [
{
id: "light-app-new-note",
label: "新建笔记",
icon: Plus,
path: "/page/strategy/light-app/new-note",
},
{
id: "light-app-note-list",
label: "笔记列表",
icon: List,
path: "/page/strategy/light-app/notes",
},
{
id: "light-app-note-qa",
label: "笔记问答",
icon: MessageSquareCode,
path: "/page/strategy/light-app/note-qa",
},
],
},
{ id: "light-app-square", label: "应用广场", icon: Layout },
{ id: "light-app-records", label: "运行记录", icon: History },
],
},
{
id: "task-management",
label: "任务管理",
icon: Target,
children: [
{
id: "xk-center-task",
label: "XK中任务",
icon: Folder,
children: [
{
id: "xk-task-jc",
label: "任务JC",
icon: ListTodo,
path: "/page/strategy/task-management/xk-task-jc",
},
{
id: "xk-task-dispatch",
label: "任务调度",
icon: ListTodo,
path: "/page/strategy/task-management/xk-task-dispatch",
},
],
},
{
id: "coh-center-task",
label: "COH中任务",
icon: Folder,
children: [
{
id: "coh-task-3qfx",
label: "3QFX",
icon: ListTodo,
path: "/page/strategy/task-management/plan-task-sentiment",
},
{
id: "coh-task-fx",
label: "任务FX",
icon: ListTodo,
path: "/page/strategy/task-management/plan-task/plan-task-fx",
},
{
id: "coh-task-xdfx",
label: "XDFX",
icon: ListTodo,
path: "/page/strategy/task-management/plan-task/plan-task-action",
},
],
},
{
id: "pg-center-task",
label: "PG中任务",
icon: Folder,
children: [
{
id: "pg-task-pg",
label: "任务PG",
icon: ListTodo,
path: "/page/strategy/task-management/effect-task-pg",
},
{
id: "pg-task-xdpg",
label: "XDPG",
icon: ListTodo,
path: "/page/strategy/task-management/effect-action-pg",
},
{
id: "pg-task-info-pg",
label: "信息PG",
icon: ListTodo,
path: "/page/strategy/task-management/effect-info-pg",
},
],
},
{
id: "ty-center-task",
label: "TY中任务",
icon: Folder,
children: [
{
id: "ty-task-xdty",
label: "XDTY任务",
icon: ListTodo,
path: "/page/strategy/task-management/mnty-task-mechanism",
},
{
id: "ty-task-plan",
label: "方案TY任务",
icon: ListTodo,
path: "/page/strategy/task-management/mnty-task-plan",
},
{
id: "ty-task-xxty",
label: "XXTY任务",
icon: ListTodo,
path: "/page/strategy/task-management/mnty-task-zsty",
},
],
},
],
},
{
id: "comprehensive-management",
label: "综合管理",
icon: Settings,
children: [
{ id: "mgmt-agents", label: "智能体管理", icon: Bot, path: "/page/strategy/agents" },
{ id: "mgmt-skills", label: "技能管理", icon: Sparkles, path: "/page/strategy/skills" },
{ id: "mgmt-memory", label: "记忆管理", icon: Brain, path: "/page/strategy/memory" },
{
id: "mgmt-tasks",
label: "定时任务",
icon: ListChecks,
path: "/page/strategy/scheduled/tasks",
},
{
id: "mgmt-recommended-questions",
label: "推荐问题配置",
icon: MessageSquarePlus,
path: "/page/strategy/settings/recommended-questions",
adminOnly: true,
},
{
id: "mgmt-prompt-prefix",
label: "问答前缀配置",
icon: MessageSquareCode,
path: "/page/strategy/settings/prompt-prefix",
adminOnly: true,
},
{
id: "mgmt-private-square",
label: "私密广场配置",
icon: ShieldCheck,
path: "/page/strategy/settings/private-square",
adminOnly: true,
},
{
id: "mgmt-llm-metrics",
label: "大模型调用统计",
icon: Gauge,
path: "/page/strategy/settings/llm-metrics",
adminOnly: true,
},
{
id: "mgmt-users",
label: "用户管理",
icon: Users,
path: "/page/strategy/admin/users",
adminOnly: true,
},
{
id: "mgmt-admin-scheduled-tasks",
label: "定时任务管理",
icon: CalendarClock,
path: "/page/strategy/admin/scheduled-tasks",
adminOnly: true,
},
{
id: "mgmt-user-activity",
label: "用户活动审计",
icon: History,
path: "/page/strategy/admin/user-activity",
adminOnly: true,
},
],
},
{
id: "resource-management",
label: "资源管理",
icon: Database,
children: [
{
id: "resource-network-xwt",
label: "网络xwt资源管理",
icon: Network,
path: "/page/strategy/resource-management/resource-network-actor",
},
{
id: "resource-knowledge-base",
label: "知识库资源管理",
icon: Library,
path: "/page/strategy/resource-management/resource-knowledge-base",
},
{
id: "resource-evaluation-index",
label: "作战资源管理",
icon: FileBox,
path: "/page/strategy/resource-management/resource-evaluation-index",
},
],
},
];
const defaultExpandedIds = new Set([
"qa-center",
"light-app-center",
"light-app-note-management",
"task-management",
"xk-center-task",
]);
function filterMenuItemsByRole(items: MenuItem[], isAdmin: boolean): MenuItem[] {
return items
.filter((item) => !item.adminOnly || isAdmin)
.map((item) =>
item.children
? { ...item, children: filterMenuItemsByRole(item.children, isAdmin) }
: item,
);
}
export function PageSidebar({
collapsed,
onToggleCollapsed,
@@ -339,10 +45,11 @@ export function PageSidebar({
const { theme } = useTheme();
const isDark = theme === "dark";
const isAdmin = getStoredAuth()?.system_role === "admin";
const visibleMenuItems = filterMenuItemsByRole(menuItems, !!isAdmin);
const visibleMenuItems = filterMenuItemsByRole(sidebarMenuItems, !!isAdmin);
const { t } = useI18n();
const { data: unreadCount = 0 } = useUnreadCount();
const [expandedIds, setExpandedIds] = useState<Set<string>>(defaultExpandedIds);
// defaultExpandedIds 是共享的 ReadonlySet,复制一份给 useState(避免外部突变)
const [expandedIds, setExpandedIds] = useState<Set<string>>(() => new Set(defaultExpandedIds));
const [settingsOpen, setSettingsOpen] = useState(false);
const [settingsDefaultSection, setSettingsDefaultSection] =
useState<SettingsSection>("appearance");