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