重构客户端菜单与设置入口
This commit is contained in:
@@ -187,6 +187,12 @@ function createWindow() {
|
||||
minHeight: DEFAULT_WINDOW_MIN_HEIGHT,
|
||||
title: APP_DISPLAY_NAME,
|
||||
icon: getIconPath(),
|
||||
...(process.platform === "darwin"
|
||||
? {
|
||||
titleBarStyle: "hiddenInset" as const,
|
||||
trafficLightPosition: { x: 18, y: 16 },
|
||||
}
|
||||
: {}),
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, "..", "preload", "index.js"),
|
||||
contextIsolation: true,
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
notification,
|
||||
message,
|
||||
} from "antd";
|
||||
import { ArrowLeftOutlined, ReloadOutlined } from "@ant-design/icons";
|
||||
import { MenuFoldOutlined, MenuUnfoldOutlined } from "@ant-design/icons";
|
||||
import {
|
||||
setupService,
|
||||
authService,
|
||||
@@ -29,7 +29,11 @@ import {
|
||||
logout,
|
||||
isLoggedIn as checkIsLoggedIn,
|
||||
} from "./services/core/auth";
|
||||
import { AUTH_KEYS, normalizeAgentEngine } from "@shared/constants";
|
||||
import {
|
||||
APP_DISPLAY_NAME,
|
||||
AUTH_KEYS,
|
||||
normalizeAgentEngine,
|
||||
} from "@shared/constants";
|
||||
import type { QuickInitConfig } from "@shared/types/quickInit";
|
||||
import type { UpdateState } from "@shared/types/updateTypes";
|
||||
import { t, getCurrentLang } from "./services/core/i18n";
|
||||
@@ -38,12 +42,6 @@ import LoginPage from "./components/setup/LoginPage";
|
||||
import SetupDependencies from "./components/setup/SetupDependencies";
|
||||
import ClientPage from "./components/pages/ClientPage";
|
||||
import OverviewPage from "./components/pages/OverviewPage";
|
||||
import SettingsPage from "./components/pages/SettingsPage";
|
||||
import DependenciesPage from "./components/pages/DependenciesPage";
|
||||
import AboutPage from "./components/pages/AboutPage";
|
||||
import LogViewer from "./components/pages/LogViewer";
|
||||
import PermissionsPage from "./components/pages/PermissionsPage";
|
||||
import SessionsPage from "./components/pages/SessionsPage";
|
||||
import AiChatPage from "./components/pages/AiChatPage";
|
||||
import AutomationPage from "./components/pages/AutomationPage";
|
||||
import KnowledgePage from "./components/pages/KnowledgePage";
|
||||
@@ -53,15 +51,15 @@ import McpMarketplacePage from "./components/pages/McpMarketplacePage";
|
||||
import WorkflowMarketplacePage from "./components/pages/WorkflowMarketplacePage";
|
||||
import DataFetchPage from "./components/pages/DataFetchPage";
|
||||
import DocumentEditorPage from "./components/pages/DocumentEditorPage";
|
||||
import MCPSettings from "./components/settings/MCPSettings";
|
||||
import AppSidebar, {
|
||||
NavKey,
|
||||
LegacyTabKey,
|
||||
} from "./components/layout/AppSidebar";
|
||||
import type { WebviewHeaderActions } from "./components/pages/SessionsPage";
|
||||
import ToolIntegrationPage, {
|
||||
ToolIntegrationTab,
|
||||
} from "./components/pages/ToolIntegrationPage";
|
||||
import SystemSettingsPage, {
|
||||
SystemSettingsTab,
|
||||
} from "./components/pages/SystemSettingsPage";
|
||||
import AppSidebar, { NavKey } from "./components/layout/AppSidebar";
|
||||
import type { AiAgentLaunchContext } from "./services/core/aiChat";
|
||||
import { createLogger } from "./services/utils/rendererLog";
|
||||
import styles from "./styles/components/App.module.css";
|
||||
import { lightTheme, darkTheme } from "./styles/theme";
|
||||
import { FEATURES } from "@shared/featureFlags";
|
||||
|
||||
@@ -105,9 +103,7 @@ export function useI18nLang(): I18nContextValue {
|
||||
// Tab 类型定义(对齐 Tauri 客户端)
|
||||
type TabKey =
|
||||
| "overview"
|
||||
| "client"
|
||||
| "sessions"
|
||||
| "legacySessions"
|
||||
| "automation"
|
||||
| "knowledge"
|
||||
| "document"
|
||||
@@ -116,12 +112,8 @@ type TabKey =
|
||||
| "agents"
|
||||
| "mcpLibrary"
|
||||
| "workflow"
|
||||
| "mcp"
|
||||
| "settings"
|
||||
| "dependencies"
|
||||
| "permissions"
|
||||
| "logs"
|
||||
| "about";
|
||||
| "toolIntegration"
|
||||
| "systemSettings";
|
||||
|
||||
/** macOS/Linux 无 download-progress 时,用本地模拟进度避免长期显示 0%。 */
|
||||
const UPDATE_SIMULATED_PROGRESS_CAP = 90;
|
||||
@@ -340,11 +332,14 @@ function App() {
|
||||
// ============================================
|
||||
const [activeTab, setActiveTab] = useState<TabKey>("overview");
|
||||
const [activeNavKey, setActiveNavKey] = useState<NavKey>("overview");
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||
const [toolIntegrationTab, setToolIntegrationTab] =
|
||||
useState<ToolIntegrationTab>("mcp");
|
||||
const [systemSettingsTab, setSystemSettingsTab] =
|
||||
useState<SystemSettingsTab>("settings");
|
||||
const [sessionsAutoOpen, setSessionsAutoOpen] = useState(false);
|
||||
const [aiAgentLaunchContext, setAiAgentLaunchContext] =
|
||||
useState<AiAgentLaunchContext | null>(null);
|
||||
const [webviewActions, setWebviewActions] =
|
||||
useState<WebviewHeaderActions | null>(null);
|
||||
const [username, setUsername] = useState<string>("");
|
||||
const [userEmail, setUserEmail] = useState<string>("");
|
||||
const [onlineStatus, setOnlineStatus] = useState<boolean | null>(null);
|
||||
@@ -903,7 +898,9 @@ function App() {
|
||||
// 监听设置菜单
|
||||
const handleSettings = () => {
|
||||
console.log("[App] Received menu:settings event");
|
||||
setActiveTab("settings");
|
||||
setSystemSettingsTab("settings");
|
||||
setActiveTab("systemSettings");
|
||||
setActiveNavKey("systemSettings");
|
||||
};
|
||||
window.electronAPI.on("menu:settings", handleSettings);
|
||||
cleanupHandlers.push(() =>
|
||||
@@ -913,7 +910,9 @@ function App() {
|
||||
// 监听依赖管理菜单
|
||||
const handleDependencies = () => {
|
||||
console.log("[App] Received menu:dependencies event");
|
||||
setActiveTab("dependencies");
|
||||
setToolIntegrationTab("dependencies");
|
||||
setActiveTab("toolIntegration");
|
||||
setActiveNavKey("toolIntegration");
|
||||
};
|
||||
window.electronAPI.on("menu:dependencies", handleDependencies);
|
||||
cleanupHandlers.push(() =>
|
||||
@@ -923,7 +922,9 @@ function App() {
|
||||
// 监听 MCP 设置菜单
|
||||
const handleMcpSettings = () => {
|
||||
console.log("[App] Received menu:mcp-settings event");
|
||||
setActiveTab("settings");
|
||||
setToolIntegrationTab("mcp");
|
||||
setActiveTab("toolIntegration");
|
||||
setActiveNavKey("toolIntegration");
|
||||
};
|
||||
window.electronAPI.on("menu:mcp-settings", handleMcpSettings);
|
||||
cleanupHandlers.push(() =>
|
||||
@@ -1099,36 +1100,19 @@ function App() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === "toolIntegration" || key === "systemSettings") {
|
||||
setActiveTab("settings");
|
||||
if (key === "toolIntegration") {
|
||||
setActiveTab("toolIntegration");
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === "systemSettings") {
|
||||
setActiveTab("systemSettings");
|
||||
return;
|
||||
}
|
||||
|
||||
setActiveTab("overview");
|
||||
}, []);
|
||||
|
||||
const handleLegacySelect = useCallback((key: LegacyTabKey) => {
|
||||
if (key === "sessions") {
|
||||
setActiveTab("legacySessions");
|
||||
setActiveNavKey("aiChat");
|
||||
return;
|
||||
}
|
||||
|
||||
setActiveTab(key);
|
||||
const navByLegacyTab: Partial<Record<LegacyTabKey, NavKey>> = {
|
||||
client: "overview",
|
||||
sessions: "aiChat",
|
||||
automation: "automation",
|
||||
mcp: "systemSettings",
|
||||
settings: "systemSettings",
|
||||
dependencies: "systemSettings",
|
||||
permissions: "systemSettings",
|
||||
logs: "systemSettings",
|
||||
about: "systemSettings",
|
||||
};
|
||||
setActiveNavKey(navByLegacyTab[key] || "systemSettings");
|
||||
}, []);
|
||||
|
||||
const handleDownloadUpdate = useCallback(async () => {
|
||||
if (updateState.canAutoUpdate === false) {
|
||||
await window.electronAPI?.app?.openReleasesPage?.();
|
||||
@@ -1311,48 +1295,44 @@ function App() {
|
||||
value={{ themeMode, isDarkMode, setThemeMode: handleSetThemeMode }}
|
||||
>
|
||||
<div className="app-container">
|
||||
{webviewActions && (
|
||||
<div className="app-header">
|
||||
<div className={styles.headerWebviewActions}>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<ArrowLeftOutlined />}
|
||||
onClick={webviewActions.onBack}
|
||||
>
|
||||
{t("Claw.App.back")}
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={webviewActions.onReload}
|
||||
>
|
||||
{t("Claw.App.refresh")}
|
||||
</Button>
|
||||
</div>
|
||||
{isMacOS && (
|
||||
<div className="app-window-titlebar">
|
||||
<div className="app-window-title">{APP_DISPLAY_NAME}</div>
|
||||
<button
|
||||
type="button"
|
||||
className="app-window-sidebar-toggle"
|
||||
title={sidebarCollapsed ? "展开主菜单" : "收起主菜单"}
|
||||
aria-label={sidebarCollapsed ? "展开主菜单" : "收起主菜单"}
|
||||
aria-expanded={!sidebarCollapsed}
|
||||
onClick={() => setSidebarCollapsed((current) => !current)}
|
||||
>
|
||||
{sidebarCollapsed ? (
|
||||
<MenuUnfoldOutlined />
|
||||
) : (
|
||||
<MenuFoldOutlined />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 主体部分 */}
|
||||
<div className="app-body">
|
||||
{/* 左侧边栏 (hidden when webview is active) */}
|
||||
{!webviewActions && (
|
||||
<AppSidebar
|
||||
activeNavKey={activeNavKey}
|
||||
username={username}
|
||||
email={userEmail}
|
||||
onlineStatus={onlineStatus}
|
||||
isMacOS={isMacOS}
|
||||
updateNotice={sidebarUpdateNotice}
|
||||
onNavSelect={handleNavSelect}
|
||||
onLegacySelect={handleLegacySelect}
|
||||
onLogout={handleSidebarLogout}
|
||||
/>
|
||||
)}
|
||||
<AppSidebar
|
||||
activeNavKey={activeNavKey}
|
||||
collapsed={sidebarCollapsed}
|
||||
username={username}
|
||||
email={userEmail}
|
||||
onlineStatus={onlineStatus}
|
||||
updateNotice={sidebarUpdateNotice}
|
||||
showToggle={!isMacOS}
|
||||
onNavSelect={handleNavSelect}
|
||||
onToggle={() => setSidebarCollapsed((current) => !current)}
|
||||
onLogout={handleSidebarLogout}
|
||||
/>
|
||||
|
||||
{/* 主内容区:flex 子撑满,便于日志等页占满高度 */}
|
||||
<div
|
||||
className={
|
||||
webviewActions ||
|
||||
activeTab === "overview" ||
|
||||
activeTab === "document" ||
|
||||
activeTab === "dataFetch"
|
||||
@@ -1370,25 +1350,6 @@ function App() {
|
||||
}}
|
||||
>
|
||||
{activeTab === "overview" && <OverviewPage />}
|
||||
{activeTab === "client" && (
|
||||
<ClientPage
|
||||
onNavigate={(tab) => {
|
||||
if (tab === "sessions") setSessionsAutoOpen(true);
|
||||
setActiveTab(tab);
|
||||
setActiveNavKey(
|
||||
tab === "sessions" ? "aiChat" : "overview",
|
||||
);
|
||||
}}
|
||||
services={services}
|
||||
servicesLoading={servicesLoading}
|
||||
startingServices={startingServices}
|
||||
setStartingServices={setStartingServices}
|
||||
onRefreshServices={pollServicesStatus}
|
||||
authRefreshTrigger={authRefreshTrigger}
|
||||
onAuthChange={handleAuthChange}
|
||||
onLoginStarted={handleLoginStarted}
|
||||
/>
|
||||
)}
|
||||
{activeTab === "sessions" && (
|
||||
<AiChatPage
|
||||
autoNew={sessionsAutoOpen}
|
||||
@@ -1396,12 +1357,6 @@ function App() {
|
||||
onAutoNewConsumed={() => setSessionsAutoOpen(false)}
|
||||
/>
|
||||
)}
|
||||
{activeTab === "legacySessions" && (
|
||||
<SessionsPage
|
||||
autoOpen={false}
|
||||
onWebviewChange={setWebviewActions}
|
||||
/>
|
||||
)}
|
||||
{activeTab === "automation" && <AutomationPage />}
|
||||
{activeTab === "knowledge" && <KnowledgePage />}
|
||||
{activeTab === "document" && <DocumentEditorPage />}
|
||||
@@ -1412,12 +1367,52 @@ function App() {
|
||||
)}
|
||||
{activeTab === "mcpLibrary" && <McpMarketplacePage />}
|
||||
{activeTab === "workflow" && <WorkflowMarketplacePage />}
|
||||
{activeTab === "mcp" && <MCPSettings />}
|
||||
{activeTab === "settings" && <SettingsPage />}
|
||||
{activeTab === "dependencies" && <DependenciesPage />}
|
||||
{activeTab === "permissions" && <PermissionsPage />}
|
||||
{activeTab === "logs" && <LogViewer />}
|
||||
{activeTab === "about" && <AboutPage />}
|
||||
{activeTab === "toolIntegration" && (
|
||||
<ToolIntegrationPage
|
||||
activeTab={toolIntegrationTab}
|
||||
clientContent={
|
||||
<ClientPage
|
||||
onNavigate={(tab) => {
|
||||
if (tab === "sessions") {
|
||||
setSessionsAutoOpen(true);
|
||||
setActiveTab("sessions");
|
||||
setActiveNavKey("aiChat");
|
||||
return;
|
||||
}
|
||||
if (
|
||||
tab === "client" ||
|
||||
tab === "dependencies" ||
|
||||
tab === "permissions"
|
||||
) {
|
||||
setToolIntegrationTab(tab);
|
||||
setActiveTab("toolIntegration");
|
||||
setActiveNavKey("toolIntegration");
|
||||
return;
|
||||
}
|
||||
setSystemSettingsTab(tab);
|
||||
setActiveTab("systemSettings");
|
||||
setActiveNavKey("systemSettings");
|
||||
}}
|
||||
services={services}
|
||||
servicesLoading={servicesLoading}
|
||||
startingServices={startingServices}
|
||||
setStartingServices={setStartingServices}
|
||||
onRefreshServices={pollServicesStatus}
|
||||
authRefreshTrigger={authRefreshTrigger}
|
||||
onAuthChange={handleAuthChange}
|
||||
onLoginStarted={handleLoginStarted}
|
||||
/>
|
||||
}
|
||||
isMacOS={isMacOS}
|
||||
onTabChange={setToolIntegrationTab}
|
||||
/>
|
||||
)}
|
||||
{activeTab === "systemSettings" && (
|
||||
<SystemSettingsPage
|
||||
activeTab={systemSettingsTab}
|
||||
onTabChange={setSystemSettingsTab}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { Popover } from "antd";
|
||||
import { Tooltip } from "antd";
|
||||
import {
|
||||
AppstoreOutlined,
|
||||
ApiOutlined,
|
||||
@@ -8,15 +8,12 @@ import {
|
||||
CommentOutlined,
|
||||
DatabaseOutlined,
|
||||
FileTextOutlined,
|
||||
FolderOutlined,
|
||||
InfoCircleOutlined,
|
||||
LogoutOutlined,
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
ProjectOutlined,
|
||||
RobotOutlined,
|
||||
SafetyOutlined,
|
||||
SettingOutlined,
|
||||
TeamOutlined,
|
||||
ThunderboltOutlined,
|
||||
ToolOutlined,
|
||||
} from "@ant-design/icons";
|
||||
@@ -37,17 +34,6 @@ export type NavKey =
|
||||
| "toolIntegration"
|
||||
| "systemSettings";
|
||||
|
||||
export type LegacyTabKey =
|
||||
| "client"
|
||||
| "sessions"
|
||||
| "automation"
|
||||
| "mcp"
|
||||
| "settings"
|
||||
| "dependencies"
|
||||
| "permissions"
|
||||
| "logs"
|
||||
| "about";
|
||||
|
||||
interface NavItem {
|
||||
key: NavKey;
|
||||
label: string;
|
||||
@@ -55,12 +41,6 @@ interface NavItem {
|
||||
badge?: string;
|
||||
}
|
||||
|
||||
interface LegacyItem {
|
||||
key: LegacyTabKey;
|
||||
label: string;
|
||||
icon: React.ReactNode;
|
||||
}
|
||||
|
||||
interface UpdateNotice {
|
||||
tone: "available" | "downloading" | "install";
|
||||
label: string;
|
||||
@@ -69,13 +49,14 @@ interface UpdateNotice {
|
||||
|
||||
interface AppSidebarProps {
|
||||
activeNavKey: NavKey;
|
||||
collapsed: boolean;
|
||||
username: string;
|
||||
email?: string;
|
||||
onlineStatus: boolean | null;
|
||||
isMacOS: boolean;
|
||||
updateNotice?: UpdateNotice;
|
||||
showToggle?: boolean;
|
||||
onNavSelect: (key: NavKey) => void;
|
||||
onLegacySelect: (key: LegacyTabKey) => void;
|
||||
onToggle: () => void;
|
||||
onLogout: () => void;
|
||||
}
|
||||
|
||||
@@ -100,27 +81,6 @@ const settingItems: NavItem[] = [
|
||||
{ key: "systemSettings", label: "系统设置", icon: <SettingOutlined /> },
|
||||
];
|
||||
|
||||
function getLegacyItems(isMacOS: boolean): LegacyItem[] {
|
||||
const items: LegacyItem[] = [
|
||||
{ key: "client", label: "客户端", icon: <AppstoreOutlined /> },
|
||||
{ key: "sessions", label: "会话", icon: <TeamOutlined /> },
|
||||
{ key: "mcp", label: "MCP", icon: <ApiOutlined /> },
|
||||
{ key: "settings", label: "设置", icon: <SettingOutlined /> },
|
||||
{ key: "dependencies", label: "依赖", icon: <FolderOutlined /> },
|
||||
];
|
||||
|
||||
if (isMacOS) {
|
||||
items.push({ key: "permissions", label: "授权", icon: <SafetyOutlined /> });
|
||||
}
|
||||
|
||||
items.push(
|
||||
{ key: "logs", label: "日志", icon: <FileTextOutlined /> },
|
||||
{ key: "about", label: "关于", icon: <InfoCircleOutlined /> },
|
||||
);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
function getInitial(name: string): string {
|
||||
const trimmed = name.trim();
|
||||
if (!trimmed) return "陇";
|
||||
@@ -131,16 +91,17 @@ function renderNavItem(
|
||||
item: NavItem,
|
||||
activeNavKey: NavKey,
|
||||
onNavSelect: (key: NavKey) => void,
|
||||
collapsed: boolean,
|
||||
) {
|
||||
const active = activeNavKey === item.key;
|
||||
const isStatusBadge = item.badge === "运行中";
|
||||
|
||||
return (
|
||||
const button = (
|
||||
<button
|
||||
key={item.key}
|
||||
type="button"
|
||||
className={`${styles.navItem} ${active ? styles.navItemActive : ""}`}
|
||||
onClick={() => onNavSelect(item.key)}
|
||||
aria-label={collapsed ? item.label : undefined}
|
||||
>
|
||||
<span className={styles.navIcon}>{item.icon}</span>
|
||||
<span className={styles.navLabel}>{item.label}</span>
|
||||
@@ -153,22 +114,32 @@ function renderNavItem(
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
key={item.key}
|
||||
title={collapsed ? item.label : undefined}
|
||||
placement="right"
|
||||
>
|
||||
{button}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AppSidebar({
|
||||
activeNavKey,
|
||||
collapsed,
|
||||
username,
|
||||
email,
|
||||
onlineStatus,
|
||||
isMacOS,
|
||||
updateNotice,
|
||||
showToggle = true,
|
||||
onNavSelect,
|
||||
onLegacySelect,
|
||||
onToggle,
|
||||
onLogout,
|
||||
}: AppSidebarProps) {
|
||||
const displayName = username || "陈星宇";
|
||||
const displayEmail = email || "xingyu@feitian.com";
|
||||
const legacyItems = getLegacyItems(isMacOS);
|
||||
const online = onlineStatus !== false;
|
||||
const updateNoticeToneClass = updateNotice
|
||||
? styles[
|
||||
@@ -176,27 +147,20 @@ export default function AppSidebar({
|
||||
]
|
||||
: "";
|
||||
|
||||
const historyMenu = (
|
||||
<div className={styles.historyMenu}>
|
||||
<div className={styles.historyTitle}>历史菜单</div>
|
||||
{legacyItems.map((item) => (
|
||||
<button
|
||||
key={item.key}
|
||||
type="button"
|
||||
className={styles.historyItem}
|
||||
onClick={() => onLegacySelect(item.key)}
|
||||
>
|
||||
<span className={styles.historyIcon}>{item.icon}</span>
|
||||
<span>{item.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<aside className={styles.sidebar}>
|
||||
<div className={styles.brand}>
|
||||
<img className={styles.brandLogo} src="./icon.png" alt="" />
|
||||
<aside
|
||||
className={`${styles.sidebar} ${collapsed ? styles.sidebarCollapsed : ""}`}
|
||||
>
|
||||
<div
|
||||
className={`${styles.brand} ${showToggle ? "" : styles.brandWithoutToggle}`}
|
||||
>
|
||||
<Tooltip title={collapsed ? APP_DISPLAY_NAME : undefined}>
|
||||
<img
|
||||
className={styles.brandLogo}
|
||||
src="./icon.png"
|
||||
alt={APP_DISPLAY_NAME}
|
||||
/>
|
||||
</Tooltip>
|
||||
<div className={styles.brandText}>
|
||||
<div className={styles.brandName}>{APP_DISPLAY_NAME}</div>
|
||||
<div className={styles.brandSubTitle}>DIGITAL EMPLOYEE</div>
|
||||
@@ -211,65 +175,71 @@ export default function AppSidebar({
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<Popover
|
||||
content={historyMenu}
|
||||
trigger="click"
|
||||
placement="rightTop"
|
||||
overlayClassName={styles.historyPopover}
|
||||
>
|
||||
{showToggle && (
|
||||
<button
|
||||
type="button"
|
||||
className={styles.historyTrigger}
|
||||
title="导航目录"
|
||||
className={styles.sidebarToggle}
|
||||
title={collapsed ? "展开主菜单" : "收起主菜单"}
|
||||
aria-label={collapsed ? "展开主菜单" : "收起主菜单"}
|
||||
aria-expanded={!collapsed}
|
||||
onClick={onToggle}
|
||||
>
|
||||
<MenuFoldOutlined />
|
||||
{collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||
</button>
|
||||
</Popover>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<nav className={styles.nav}>
|
||||
<section className={styles.navSection}>
|
||||
<div className={styles.sectionTitle}>工作台</div>
|
||||
{workspaceItems.map((item) =>
|
||||
renderNavItem(item, activeNavKey, onNavSelect),
|
||||
renderNavItem(item, activeNavKey, onNavSelect, collapsed),
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className={styles.navSection}>
|
||||
<div className={styles.sectionTitle}>扩展市场</div>
|
||||
{marketplaceItems.map((item) =>
|
||||
renderNavItem(item, activeNavKey, onNavSelect),
|
||||
renderNavItem(item, activeNavKey, onNavSelect, collapsed),
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className={styles.navSection}>
|
||||
<div className={styles.sectionTitle}>设置</div>
|
||||
{settingItems.map((item) =>
|
||||
renderNavItem(item, activeNavKey, onNavSelect),
|
||||
renderNavItem(item, activeNavKey, onNavSelect, collapsed),
|
||||
)}
|
||||
</section>
|
||||
</nav>
|
||||
|
||||
<div className={styles.footer}>
|
||||
<div className={styles.userCard}>
|
||||
<div className={styles.avatar}>{getInitial(displayName)}</div>
|
||||
<div className={styles.userMeta}>
|
||||
<div className={styles.userName}>{displayName}</div>
|
||||
<div className={styles.userEmail}>{displayEmail}</div>
|
||||
</div>
|
||||
<span
|
||||
className={`${styles.onlineDot} ${online ? styles.onlineDotActive : ""}`}
|
||||
aria-label={online ? "在线" : "离线"}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.logoutButton}
|
||||
onClick={onLogout}
|
||||
<Tooltip
|
||||
title={collapsed ? `${displayName} · ${displayEmail}` : undefined}
|
||||
placement="right"
|
||||
>
|
||||
<LogoutOutlined />
|
||||
<span>退出登录</span>
|
||||
</button>
|
||||
<div className={styles.userCard}>
|
||||
<div className={styles.avatar}>{getInitial(displayName)}</div>
|
||||
<div className={styles.userMeta}>
|
||||
<div className={styles.userName}>{displayName}</div>
|
||||
<div className={styles.userEmail}>{displayEmail}</div>
|
||||
</div>
|
||||
<span
|
||||
className={`${styles.onlineDot} ${online ? styles.onlineDotActive : ""}`}
|
||||
aria-label={online ? "在线" : "离线"}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip title={collapsed ? "退出登录" : undefined} placement="right">
|
||||
<button
|
||||
type="button"
|
||||
className={styles.logoutButton}
|
||||
onClick={onLogout}
|
||||
aria-label={collapsed ? "退出登录" : undefined}
|
||||
>
|
||||
<LogoutOutlined />
|
||||
<span className={styles.logoutLabel}>退出登录</span>
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Tabs } from "antd";
|
||||
import AboutPage from "./AboutPage";
|
||||
import LogViewer from "./LogViewer";
|
||||
import SettingsPage from "./SettingsPage";
|
||||
import styles from "../../styles/components/SystemSettingsPage.module.css";
|
||||
|
||||
export type SystemSettingsTab = "settings" | "logs" | "about";
|
||||
|
||||
interface SystemSettingsPageProps {
|
||||
activeTab: SystemSettingsTab;
|
||||
onTabChange: (tab: SystemSettingsTab) => void;
|
||||
}
|
||||
|
||||
export default function SystemSettingsPage({
|
||||
activeTab,
|
||||
onTabChange,
|
||||
}: SystemSettingsPageProps) {
|
||||
const items = [
|
||||
{
|
||||
key: "settings",
|
||||
label: "设置",
|
||||
children: (
|
||||
<div className={styles.scrollPanel}>
|
||||
<SettingsPage />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "logs",
|
||||
label: "日志",
|
||||
children: (
|
||||
<div className={styles.fullPanel}>
|
||||
<LogViewer />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "about",
|
||||
label: "关于",
|
||||
children: (
|
||||
<div className={styles.scrollPanel}>
|
||||
<AboutPage />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<section className={styles.page}>
|
||||
<h1 className={styles.title}>系统设置</h1>
|
||||
<Tabs
|
||||
className={styles.tabs}
|
||||
activeKey={activeTab}
|
||||
items={items}
|
||||
onChange={(key) => onTabChange(key as SystemSettingsTab)}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { Tabs } from "antd";
|
||||
import DependenciesPage from "./DependenciesPage";
|
||||
import PermissionsPage from "./PermissionsPage";
|
||||
import MCPSettings from "../settings/MCPSettings";
|
||||
import styles from "../../styles/components/ToolIntegrationPage.module.css";
|
||||
|
||||
export type ToolIntegrationTab =
|
||||
| "client"
|
||||
| "mcp"
|
||||
| "dependencies"
|
||||
| "permissions";
|
||||
|
||||
interface ToolIntegrationPageProps {
|
||||
activeTab: ToolIntegrationTab;
|
||||
clientContent: ReactNode;
|
||||
isMacOS: boolean;
|
||||
onTabChange: (tab: ToolIntegrationTab) => void;
|
||||
}
|
||||
|
||||
export default function ToolIntegrationPage({
|
||||
activeTab,
|
||||
clientContent,
|
||||
isMacOS,
|
||||
onTabChange,
|
||||
}: ToolIntegrationPageProps) {
|
||||
const items = [
|
||||
{
|
||||
key: "client",
|
||||
label: "客户端",
|
||||
children: clientContent,
|
||||
},
|
||||
{
|
||||
key: "mcp",
|
||||
label: "MCP",
|
||||
children: <MCPSettings embedded />,
|
||||
},
|
||||
{
|
||||
key: "dependencies",
|
||||
label: "依赖",
|
||||
children: <DependenciesPage />,
|
||||
},
|
||||
...(isMacOS
|
||||
? [
|
||||
{
|
||||
key: "permissions",
|
||||
label: "授权",
|
||||
children: <PermissionsPage />,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
];
|
||||
|
||||
return (
|
||||
<section className={styles.page}>
|
||||
<h1 className={styles.title}>工具集成</h1>
|
||||
<Tabs
|
||||
className={styles.tabs}
|
||||
activeKey={activeTab}
|
||||
items={items}
|
||||
onChange={(key) => onTabChange(key as ToolIntegrationTab)}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -47,9 +47,10 @@ const { Text } = Typography;
|
||||
interface MCPSettingsProps {
|
||||
isOpen?: boolean;
|
||||
onClose?: () => void;
|
||||
embedded?: boolean;
|
||||
}
|
||||
|
||||
function MCPSettings({ isOpen = true }: MCPSettingsProps) {
|
||||
function MCPSettings({ isOpen = true, embedded = false }: MCPSettingsProps) {
|
||||
const [isDarkMode, setIsDarkMode] = useState(
|
||||
document.body.getAttribute("data-theme") === "dark",
|
||||
);
|
||||
@@ -454,7 +455,7 @@ function MCPSettings({ isOpen = true }: MCPSettingsProps) {
|
||||
? currentServers[editingServerId]
|
||||
: undefined;
|
||||
return (
|
||||
<div style={{ padding: 24 }}>
|
||||
<div style={embedded ? undefined : { padding: 24 }}>
|
||||
<MCPServerEditor
|
||||
key={editorMode === "edit" ? editingServerId : "__create__"}
|
||||
mode={editorMode}
|
||||
@@ -471,7 +472,7 @@ function MCPSettings({ isOpen = true }: MCPSettingsProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ padding: 24 }}>
|
||||
<div style={embedded ? undefined : { padding: 24 }}>
|
||||
<Card
|
||||
title={
|
||||
<Space>
|
||||
|
||||
@@ -11,31 +11,31 @@
|
||||
--color-primary-hover: #27272a;
|
||||
--color-primary-active: #3f3f46;
|
||||
--color-success: #16a34a;
|
||||
--color-error: #EF4444;
|
||||
--color-warning: #F59E0B;
|
||||
--color-info: #3B82F6;
|
||||
--color-error: #ef4444;
|
||||
--color-warning: #f59e0b;
|
||||
--color-info: #3b82f6;
|
||||
--color-success-bg: #f0fdf4;
|
||||
--color-error-bg: #fef2f2;
|
||||
--color-warning-bg: #fffbeb;
|
||||
|
||||
--color-bg-layout: #F8F9FA;
|
||||
--color-bg-layout: #f8f9fa;
|
||||
--color-bg-container: #ffffff;
|
||||
--color-bg-elevated: #ffffff;
|
||||
--color-bg-header: #ffffff;
|
||||
--color-bg-sider: #ffffff;
|
||||
--color-bg-section: #ffffff;
|
||||
--color-bg-section-header: #F9FAFB;
|
||||
--color-bg-section-header: #f9fafb;
|
||||
|
||||
--color-border: #E5E7EB;
|
||||
--color-border-secondary: #F3F4F6;
|
||||
--color-border: #e5e7eb;
|
||||
--color-border-secondary: #f3f4f6;
|
||||
|
||||
--color-text: #18181b;
|
||||
--color-text-secondary: #6B7280;
|
||||
--color-text-tertiary: #9CA3AF;
|
||||
--color-text-quaternary: #D1D5DB;
|
||||
--color-text-secondary: #6b7280;
|
||||
--color-text-tertiary: #9ca3af;
|
||||
--color-text-quaternary: #d1d5db;
|
||||
|
||||
--color-icon: #6B7280;
|
||||
--color-divider: #F3F4F6;
|
||||
--color-icon: #6b7280;
|
||||
--color-divider: #f3f4f6;
|
||||
|
||||
--border-radius: 8px;
|
||||
--border-radius-lg: 10px;
|
||||
@@ -48,9 +48,9 @@
|
||||
--color-primary-hover: #e4e4e7;
|
||||
--color-primary-active: #d4d4d8;
|
||||
--color-success: #22c55e;
|
||||
--color-error: #EF4444;
|
||||
--color-warning: #F59E0B;
|
||||
--color-info: #3B82F6;
|
||||
--color-error: #ef4444;
|
||||
--color-warning: #f59e0b;
|
||||
--color-info: #3b82f6;
|
||||
--color-success-bg: #052e16;
|
||||
--color-error-bg: #450a0a;
|
||||
--color-warning-bg: #422006;
|
||||
@@ -104,7 +104,9 @@ body {
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
background: var(--color-bg-layout);
|
||||
transition: background-color 0.2s ease, color 0.2s ease;
|
||||
transition:
|
||||
background-color 0.2s ease,
|
||||
color 0.2s ease;
|
||||
}
|
||||
|
||||
/* ========== 布局 ========== */
|
||||
@@ -116,6 +118,55 @@ body {
|
||||
background: var(--color-bg-layout);
|
||||
}
|
||||
|
||||
.app-window-titlebar {
|
||||
height: 44px;
|
||||
flex: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 18px 0 84px;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
background: var(--color-bg-header);
|
||||
-webkit-app-region: drag;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.app-window-sidebar-toggle {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
flex: none;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #ededeb;
|
||||
color: #555752;
|
||||
font: inherit;
|
||||
font-size: 16px;
|
||||
margin-left: 10px;
|
||||
cursor: pointer;
|
||||
-webkit-app-region: no-drag;
|
||||
transition:
|
||||
background-color 0.16s ease,
|
||||
color 0.16s ease;
|
||||
}
|
||||
|
||||
.app-window-sidebar-toggle:hover {
|
||||
background: #dfdfdb;
|
||||
color: #20211f;
|
||||
}
|
||||
|
||||
.app-window-title {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: var(--color-text);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 22px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.app-header {
|
||||
height: 48px;
|
||||
background: var(--color-bg-header);
|
||||
@@ -151,7 +202,9 @@ body {
|
||||
border-radius: 10px;
|
||||
line-height: 18px;
|
||||
white-space: nowrap;
|
||||
transition: opacity 0.2s, transform 0.15s;
|
||||
transition:
|
||||
opacity 0.2s,
|
||||
transform 0.15s;
|
||||
}
|
||||
|
||||
.app-header-update-tag[role="button"] {
|
||||
@@ -266,7 +319,7 @@ body {
|
||||
}
|
||||
|
||||
.app-sider .ant-menu-light .ant-menu-item:hover {
|
||||
background: #F3F4F6;
|
||||
background: #f3f4f6;
|
||||
color: #18181b;
|
||||
}
|
||||
|
||||
@@ -307,14 +360,14 @@ body {
|
||||
|
||||
/* ========== 卡片 ========== */
|
||||
.ant-card {
|
||||
border: 1px solid #E5E7EB;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.ant-card-head {
|
||||
min-height: auto;
|
||||
padding: 10px 16px;
|
||||
border-bottom: 1px solid #F3F4F6;
|
||||
border-bottom: 1px solid #f3f4f6;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@@ -356,46 +409,46 @@ body {
|
||||
|
||||
.ant-tag-error,
|
||||
.ant-tag-red {
|
||||
background: #FEF2F2;
|
||||
color: #EF4444;
|
||||
border-color: #FEE2E2;
|
||||
background: #fef2f2;
|
||||
color: #ef4444;
|
||||
border-color: #fee2e2;
|
||||
}
|
||||
|
||||
.ant-tag-warning,
|
||||
.ant-tag-orange {
|
||||
background: #FFFBEB;
|
||||
color: #D97706;
|
||||
border-color: #FEF3C7;
|
||||
background: #fffbeb;
|
||||
color: #d97706;
|
||||
border-color: #fef3c7;
|
||||
}
|
||||
|
||||
.ant-tag-processing {
|
||||
background: #F3F4F6;
|
||||
color: #6B7280;
|
||||
border-color: #E5E7EB;
|
||||
background: #f3f4f6;
|
||||
color: #6b7280;
|
||||
border-color: #e5e7eb;
|
||||
}
|
||||
|
||||
.ant-tag-default {
|
||||
background: #F3F4F6;
|
||||
color: #6B7280;
|
||||
border-color: #E5E7EB;
|
||||
background: #f3f4f6;
|
||||
color: #6b7280;
|
||||
border-color: #e5e7eb;
|
||||
}
|
||||
|
||||
.ant-tag-blue {
|
||||
background: #EFF6FF;
|
||||
color: #2563EB;
|
||||
border-color: #DBEAFE;
|
||||
background: #eff6ff;
|
||||
color: #2563eb;
|
||||
border-color: #dbeafe;
|
||||
}
|
||||
|
||||
.ant-tag-purple {
|
||||
background: #F5F3FF;
|
||||
color: #7C3AED;
|
||||
border-color: #EDE9FE;
|
||||
background: #f5f3ff;
|
||||
color: #7c3aed;
|
||||
border-color: #ede9fe;
|
||||
}
|
||||
|
||||
.ant-tag-cyan {
|
||||
background: #ECFEFF;
|
||||
color: #0891B2;
|
||||
border-color: #CFFAFE;
|
||||
background: #ecfeff;
|
||||
color: #0891b2;
|
||||
border-color: #cffafe;
|
||||
}
|
||||
|
||||
/* ========== 输入框 ========== */
|
||||
@@ -403,7 +456,7 @@ body {
|
||||
.ant-input-affix-wrapper,
|
||||
.ant-input-number {
|
||||
border-radius: 8px;
|
||||
border-color: #E5E7EB;
|
||||
border-color: #e5e7eb;
|
||||
}
|
||||
|
||||
.ant-input:focus,
|
||||
@@ -418,18 +471,18 @@ body {
|
||||
.ant-input:hover,
|
||||
.ant-input-affix-wrapper:hover,
|
||||
.ant-input-number:hover {
|
||||
border-color: #D1D5DB;
|
||||
border-color: #d1d5db;
|
||||
}
|
||||
|
||||
.ant-input::placeholder {
|
||||
color: #9CA3AF;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
/* ========== 表单 ========== */
|
||||
.ant-form-item-label > label {
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
color: #6B7280;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.ant-form-item {
|
||||
@@ -451,7 +504,7 @@ body {
|
||||
|
||||
.ant-list-item-meta-description {
|
||||
font-size: 12px;
|
||||
color: #6B7280;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
/* ========== 警告框 - 更柔和 ========== */
|
||||
@@ -468,18 +521,18 @@ body {
|
||||
}
|
||||
|
||||
.ant-alert-warning {
|
||||
background: #FFFBEB;
|
||||
border-color: #FEF3C7;
|
||||
background: #fffbeb;
|
||||
border-color: #fef3c7;
|
||||
}
|
||||
|
||||
.ant-alert-error {
|
||||
background: #FEF2F2;
|
||||
border-color: #FEE2E2;
|
||||
background: #fef2f2;
|
||||
border-color: #fee2e2;
|
||||
}
|
||||
|
||||
.ant-alert-info {
|
||||
background: #F9FAFB;
|
||||
border-color: #E5E7EB;
|
||||
background: #f9fafb;
|
||||
border-color: #e5e7eb;
|
||||
}
|
||||
|
||||
.ant-alert-message {
|
||||
@@ -519,7 +572,7 @@ body {
|
||||
/* ========== 进度条 ========== */
|
||||
.ant-progress-text {
|
||||
font-size: 11px;
|
||||
color: #6B7280;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
/* ========== Tabs ========== */
|
||||
@@ -553,18 +606,18 @@ body {
|
||||
|
||||
.ant-modal-footer {
|
||||
padding: 12px 20px 16px;
|
||||
border-top: 1px solid #F3F4F6;
|
||||
border-top: 1px solid #f3f4f6;
|
||||
}
|
||||
|
||||
/* ========== 徽章 ========== */
|
||||
.ant-badge-status-text {
|
||||
font-size: 12px;
|
||||
color: #6B7280;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
/* ========== Divider ========== */
|
||||
.ant-divider {
|
||||
border-color: #F3F4F6;
|
||||
border-color: #f3f4f6;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
@@ -592,12 +645,12 @@ body {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
background: #F8F9FA;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.app-loading-text {
|
||||
margin-top: 16px;
|
||||
color: #6B7280;
|
||||
color: #6b7280;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@@ -622,7 +675,7 @@ body {
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 12px;
|
||||
color: #6B7280;
|
||||
color: #6b7280;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
@@ -632,7 +685,7 @@ body {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #F3F4F6;
|
||||
border-bottom: 1px solid #f3f4f6;
|
||||
}
|
||||
|
||||
.status-row:last-child {
|
||||
@@ -668,7 +721,7 @@ body {
|
||||
|
||||
.info-label {
|
||||
font-size: 12px;
|
||||
color: #6B7280;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
@@ -688,7 +741,7 @@ body {
|
||||
.section-title {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #6B7280;
|
||||
color: #6b7280;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
margin-bottom: 8px;
|
||||
@@ -705,7 +758,7 @@ body {
|
||||
}
|
||||
|
||||
.message-assistant {
|
||||
background: #F3F4F6;
|
||||
background: #f3f4f6;
|
||||
color: #18181b;
|
||||
padding: 12px 16px;
|
||||
border-radius: 12px;
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
/**
|
||||
* App 组件样式
|
||||
* 包含顶部栏、布局等样式
|
||||
*/
|
||||
|
||||
/* Webview 模式下顶部栏左侧操作区(替代 logo) */
|
||||
.headerWebviewActions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
@@ -7,15 +7,29 @@
|
||||
background: #f1f2ef;
|
||||
border-right: 1px solid #deded9;
|
||||
color: #1f211f;
|
||||
overflow: hidden;
|
||||
transition:
|
||||
width 0.18s ease,
|
||||
min-width 0.18s ease;
|
||||
}
|
||||
|
||||
.sidebarCollapsed {
|
||||
width: 64px;
|
||||
min-width: 64px;
|
||||
}
|
||||
|
||||
.brand {
|
||||
height: 104px;
|
||||
height: 84px;
|
||||
display: grid;
|
||||
grid-template-columns: 38px 1fr 34px;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 12px 14px 20px;
|
||||
padding: 6px 12px 10px 20px;
|
||||
}
|
||||
|
||||
.brandWithoutToggle {
|
||||
grid-template-columns: 38px 1fr;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.brandLogo {
|
||||
@@ -100,9 +114,10 @@
|
||||
color: #d48806;
|
||||
}
|
||||
|
||||
.historyTrigger {
|
||||
.sidebarToggle {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
flex: none;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
display: inline-flex;
|
||||
@@ -116,7 +131,7 @@
|
||||
color 0.16s ease;
|
||||
}
|
||||
|
||||
.historyTrigger:hover {
|
||||
.sidebarToggle:hover {
|
||||
background: #dadad4;
|
||||
color: #252622;
|
||||
}
|
||||
@@ -320,52 +335,94 @@
|
||||
color: #c8342e;
|
||||
}
|
||||
|
||||
.historyPopover :global(.ant-popover-inner) {
|
||||
padding: 8px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 12px 28px rgba(22, 24, 26, 0.12);
|
||||
.sidebarCollapsed .brand {
|
||||
height: 84px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
.historyPopover :global(.ant-popover-arrow) {
|
||||
.sidebarCollapsed .brandLogo {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.sidebarCollapsed .brandText,
|
||||
.sidebarCollapsed .sectionTitle,
|
||||
.sidebarCollapsed .navLabel,
|
||||
.sidebarCollapsed .navBadge,
|
||||
.sidebarCollapsed .userMeta,
|
||||
.sidebarCollapsed .logoutLabel {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.historyMenu {
|
||||
width: 176px;
|
||||
.sidebarCollapsed .nav {
|
||||
padding: 0 10px 16px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.historyTitle {
|
||||
padding: 4px 8px 8px;
|
||||
color: #8e8b84;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
.sidebarCollapsed .navSection {
|
||||
padding-bottom: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.historyItem {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
border: 0;
|
||||
border-radius: 7px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 0 8px;
|
||||
background: transparent;
|
||||
color: #4e4e49;
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.historyItem:hover {
|
||||
background: #f4f4f1;
|
||||
color: #d66c47;
|
||||
}
|
||||
|
||||
.historyIcon {
|
||||
width: 16px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
.sidebarCollapsed .navItem {
|
||||
width: 42px;
|
||||
padding: 0;
|
||||
justify-content: center;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.sidebarCollapsed .navItemActive::after {
|
||||
right: 3px;
|
||||
}
|
||||
|
||||
.sidebarCollapsed .footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 10px 0 12px;
|
||||
}
|
||||
|
||||
.sidebarCollapsed .userCard {
|
||||
position: relative;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
justify-content: center;
|
||||
gap: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.sidebarCollapsed .avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.sidebarCollapsed .onlineDot {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border: 2px solid #f1f2ef;
|
||||
}
|
||||
|
||||
.sidebarCollapsed .logoutButton {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
margin-top: 8px;
|
||||
padding: 0;
|
||||
justify-content: center;
|
||||
gap: 0;
|
||||
background: transparent;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.sidebarCollapsed .logoutButton:hover {
|
||||
background: #f1e3df;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
.page {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0 0 8px;
|
||||
color: var(--color-text);
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tabs :global(.ant-tabs-nav) {
|
||||
flex: none;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tabs :global(.ant-tabs-content-holder),
|
||||
.tabs :global(.ant-tabs-content),
|
||||
.tabs :global(.ant-tabs-tabpane) {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.tabs :global(.ant-tabs-content-holder),
|
||||
.tabs :global(.ant-tabs-content) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tabs :global(.ant-tabs-tabpane-active) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.scrollPanel {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.fullPanel {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
.page {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0 0 8px;
|
||||
color: var(--color-text);
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tabs :global(.ant-tabs-nav) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tabs :global(.ant-tabs-content),
|
||||
.tabs :global(.ant-tabs-tabpane) {
|
||||
min-width: 0;
|
||||
}
|
||||
Reference in New Issue
Block a user