diff --git a/package-lock.json b/package-lock.json
index 000d706..24c6f1b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,20 +1,19 @@
{
"name": "ele-heixiu",
- "version": "0.0.12",
+ "version": "0.0.13",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ele-heixiu",
- "version": "0.0.12",
+ "version": "0.0.13",
"dependencies": {
"@react-buddy/ide-toolbox": "^2.5.0",
"antd": "^6.4.3",
"axios": "^1.16.1",
"electron-updater": "^6.8.3",
"react": "^19.2.7",
- "react-dom": "^19.2.7",
- "react-router-dom": "^7.16.0"
+ "react-dom": "^19.2.7"
},
"devDependencies": {
"@playwright/test": "^1.60.0",
@@ -3942,19 +3941,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/cookie": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
- "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
"node_modules/core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
@@ -7104,44 +7090,6 @@
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"license": "MIT"
},
- "node_modules/react-router": {
- "version": "7.16.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.16.0.tgz",
- "integrity": "sha512-wArC8lVyJb3+jM9OpDyW6hLCizACWkvQR/sSGqSs+o5uEXEtGlqdZ4v8hENR3Jad6i+LRkK93q/+bQAcvl6V1A==",
- "license": "MIT",
- "dependencies": {
- "cookie": "^1.0.1",
- "set-cookie-parser": "^2.6.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "react": ">=18",
- "react-dom": ">=18"
- },
- "peerDependenciesMeta": {
- "react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/react-router-dom": {
- "version": "7.16.0",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.16.0.tgz",
- "integrity": "sha512-kMUAbimWB5FVbF4Bce4bJsiKJWLIUHq/mEG8+CFDnCSgltptBiG5nguducmsJeGKytlCvQud9Qhzpn49iduTlA==",
- "license": "MIT",
- "dependencies": {
- "react-router": "7.16.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "react": ">=18",
- "react-dom": ">=18"
- }
- },
"node_modules/read-binary-file-arch": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz",
@@ -7541,12 +7489,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/set-cookie-parser": {
- "version": "2.7.2",
- "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
- "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
- "license": "MIT"
- },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
diff --git a/package.json b/package.json
index 216dfd6..b8adc24 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "ele-heixiu",
"private": true,
- "version": "0.0.12",
+ "version": "0.0.13",
"description": "船长·HeiXiu — 桌面效率工作台",
"author": "HeiXiu 杨烨",
"type": "module",
@@ -26,13 +26,12 @@
"test:e2e": "playwright test.json"
},
"dependencies": {
+ "@react-buddy/ide-toolbox": "^2.5.0",
"antd": "^6.4.3",
"axios": "^1.16.1",
"electron-updater": "^6.8.3",
"react": "^19.2.7",
- "react-dom": "^19.2.7",
- "react-router-dom": "^7.16.0",
- "@react-buddy/ide-toolbox": "^2.5.0"
+ "react-dom": "^19.2.7"
},
"build": {
"appId": "com.heixiu.electron",
diff --git a/src/App.tsx b/src/App.tsx
index 1d591ad..990ec0e 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,24 +1,25 @@
import { useState, useEffect, useCallback } from 'react';
-import { HashRouter } from 'react-router-dom';
import { useAppContext } from './contexts/app-context';
import { LoginPage } from './pages/login';
import { SettingsPage } from './pages/settings';
import { on, off, EVENTS } from './utils/event-bus';
-import { AppRoutes } from './router';
+import { Layout } from '@/components/Layout';
+import { HomePage } from '@/pages/home/HomePage';
+import { PageDispatcher } from '@/components/PageDispatcher';
/**
* 根组件 — 主窗口
*
* 层级:
- * HashRouter
- * ├── LoginPage(Modal 弹层,事件总线驱动)
- * ├── SettingsPage(Modal 居中叠加,事件总线驱动 — 对应 QT 原版设置窗口)
- * ├── AppRoutes(页面路由)
+ * Layout(Banner + NavBar)
+ * └── HomePage(主页面,始终挂载)
+ * LoginPage(Modal 弹层,事件总线驱动)
+ * SettingsPage(Modal 居中叠加,事件总线驱动 — 对应 QT 原版设置窗口)
*
* 设计原则(对应 QT 原版多窗口模型):
- * - 叠加层(设置、公告等)→ Modal/Drawer + 事件总线,主页面不卸载
- * - 页面跳转 → React Router,需要跨页保持的状态放 Provider 或 localStorage
+ * - 叠加层(设置、公告、页面等)→ Modal/Drawer + 事件总线,主页面不卸载
+ * - 不再使用 React Router,所有导航由事件总线驱动
*/
function App() {
const { isLoggedIn } = useAppContext();
@@ -59,16 +60,20 @@ function App() {
}, []);
return (
-
+ <>
+
+
+
+
{/* 登录 Modal — 可关闭,关闭后主页可操作 */}
{/* 设置 Modal — 事件驱动,居中叠加,主页面始终挂载(对应 QT 原版独立设置窗口) */}
setSettingsOpen(false)} />
- {/* 页面路由 */}
-
-
+ {/* 页面调度器 — 监听 OPEN_PAGE 事件,根据 modalType 渲染 Modal/Drawer/FloatingPanel */}
+
+ >
);
}
diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx
index 363440b..d9069e9 100644
--- a/src/components/Layout.tsx
+++ b/src/components/Layout.tsx
@@ -1,26 +1,24 @@
// ============================================================
-// Layout — 页面布局壳(Banner轮播 + NavBar + 页面内容 Outlet)
-// BannerCarousel 仅在首页显示(路由为 "/" 或 "")
+// Layout — 页面布局壳(Banner轮播 + NavBar + 页面内容)
+// BannerCarousel 始终渲染(当前仅有首页,后续多面板切换时 Banner 始终保持)
// ============================================================
-import { useLocation, Outlet } from 'react-router-dom';
+import type { ReactNode } from 'react';
import { theme as antTheme } from 'antd';
import { NavBar } from './navbar';
import { BannerCarousel } from '@/pages/home/components/BannerCarousel';
-export function Layout() {
+export function Layout({ children }: { children: ReactNode }) {
const { token } = antTheme.useToken();
- const location = useLocation();
- const isHomePage = location.pathname === '/' || location.pathname === '';
return (
- {isHomePage && }
+
-
+ {children}
);
}
diff --git a/src/components/PageDispatcher.tsx b/src/components/PageDispatcher.tsx
new file mode 100644
index 0000000..a7be853
--- /dev/null
+++ b/src/components/PageDispatcher.tsx
@@ -0,0 +1,149 @@
+// ============================================================
+// PageDispatcher — 页面事件调度器
+//
+// 监听 EVENTS.OPEN_PAGE,根据 modalType 渲染对应容器:
+// - modal → antd Modal(居中模态,有遮罩阻断交互)
+// - drawer → antd Drawer(侧滑抽屉)
+// - floating → FloatingPanel(非模态浮动面板,可拖拽,不阻断交互)
+//
+// 支持多面板共存(floating 类型可同时打开多个)
+// ============================================================
+
+import { useState, useEffect, useCallback, useRef, type ReactNode } from 'react';
+import { Modal, Drawer } from 'antd';
+import { on, off, EVENTS } from '@/utils/event-bus';
+import { FloatingPanel } from '@/components/common/FloatingPanel';
+import { ComplianceAssetsPage } from '@/pages/compliance-assets/ComplianceAssetsPage';
+import { AccountPage } from '@/pages/account/AccountPage';
+import { VipPage } from '@/pages/vip/VipPage';
+import { RechargePage } from '@/pages/recharge/RechargePage';
+import { BillingPage } from '@/pages/billing/BillingPage';
+import { OrdersPage } from '@/pages/orders/OrdersPage';
+import { ProjectsPage } from '@/pages/projects/ProjectsPage';
+import { QuotaPage } from '@/pages/quota/QuotaPage';
+
+// ---------- 类型 ----------
+
+type ModalType = 'modal' | 'drawer' | 'floating';
+
+interface PageEventPayload {
+ target: string;
+ modalType: ModalType;
+ label: string;
+}
+
+interface ActivePanel {
+ id: string;
+ target: string;
+ label: string;
+}
+
+// ---------- target → 页面组件映射 ----------
+
+const PAGE_MAP: Record ReactNode> = {
+ '/compliance-assets': ComplianceAssetsPage,
+ '/account': AccountPage,
+ '/vip': VipPage,
+ '/recharge': RechargePage,
+ '/billing': BillingPage,
+ '/orders': OrdersPage,
+ '/projects': ProjectsPage,
+ '/quota': QuotaPage,
+};
+
+// ---------- 组件 ----------
+
+export function PageDispatcher() {
+ // 单实例(modal / drawer)—— 同一时间只有一个
+ const [singleton, setSingleton] = useState(null);
+
+ // 浮动面板列表(支持多个同时打开)
+ const [floatingPanels, setFloatingPanels] = useState([]);
+ const nextIdRef = useRef(0);
+
+ // 监听 OPEN_PAGE 事件
+ useEffect(() => {
+ const handler = (payload: unknown) => {
+ const { target, modalType, label } = payload as PageEventPayload;
+
+ if (!target || !PAGE_MAP[target]) {
+ console.warn(`[PageDispatcher] 未找到页面组件: ${target}`);
+ return;
+ }
+
+ if (modalType === 'floating') {
+ // 浮动面板:追加到列表
+ const id = `fp-${nextIdRef.current++}`;
+ setFloatingPanels((prev) => [...prev, { id, target, label }]);
+ } else {
+ // modal / drawer:单实例,覆盖之前的
+ setSingleton({ target, modalType, label });
+ }
+ };
+
+ on(EVENTS.OPEN_PAGE, handler);
+ return () => off(EVENTS.OPEN_PAGE, handler);
+ }, []);
+
+ // 关闭单实例
+ const closeSingleton = useCallback(() => {
+ setSingleton(null);
+ }, []);
+
+ // 关闭指定浮动面板
+ const closeFloating = useCallback((id: string) => {
+ setFloatingPanels((prev) => prev.filter((p) => p.id !== id));
+ }, []);
+
+ // 渲染单实例内容
+ const singletonContent = singleton && PAGE_MAP[singleton.target] ? PAGE_MAP[singleton.target]() : null;
+
+ return (
+ <>
+ {/* ======== Modal ======== */}
+ {singleton?.modalType === 'modal' && (
+
+ {singletonContent}
+
+ )}
+
+ {/* ======== Drawer ======== */}
+ {singleton?.modalType === 'drawer' && (
+
+ {singletonContent}
+
+ )}
+
+ {/* ======== Floating Panels ======== */}
+ {floatingPanels.map((panel) => {
+ const PageContent = PAGE_MAP[panel.target];
+ if (!PageContent) return null;
+
+ return (
+ closeFloating(panel.id)}
+ >
+
+
+ );
+ })}
+ >
+ );
+}
diff --git a/src/components/common/FloatingPanel.tsx b/src/components/common/FloatingPanel.tsx
new file mode 100644
index 0000000..aa19c00
--- /dev/null
+++ b/src/components/common/FloatingPanel.tsx
@@ -0,0 +1,215 @@
+// ============================================================
+// FloatingPanel — 非模态可拖拽浮动面板
+//
+// 特性:
+// - 标题栏拖拽移动,无遮罩,不阻断主页面交互
+// - 初始居中定位,支持暗色主题
+// - 边界检测(不拖出视口)
+// - 多个面板可同时打开(点击置顶)
+// - 关闭按钮 + 最小尺寸约束
+//
+// 对应 QT 原版"非模态窗口"的 Web 映射
+// ============================================================
+
+import { useState, useRef, useCallback, useEffect, type ReactNode } from 'react';
+import { useTheme } from '@/hooks/use-theme';
+
+// ---------- 类型 ----------
+
+export interface FloatingPanelProps {
+ open: boolean;
+ onClose: () => void;
+ title: string;
+ children: ReactNode;
+ /** 初始宽度(px),默认 480 */
+ width?: number;
+ /** 初始高度(px),默认 360 */
+ height?: number;
+}
+
+// ---------- 全局 z-index 管理 ----------
+
+let globalZIndex = 1000;
+function nextZIndex(): number {
+ globalZIndex += 1;
+ return globalZIndex;
+}
+
+// ---------- 组件 ----------
+
+export function FloatingPanel({ open, onClose, title, children, width = 480, height = 360 }: FloatingPanelProps) {
+ const { isDark } = useTheme();
+
+ // 位置与尺寸
+ const [position, setPosition] = useState<{ x: number; y: number } | null>(null);
+ const [zIndex, setZIndex] = useState(() => nextZIndex());
+ const panelRef = useRef(null);
+
+ // 拖拽状态
+ const draggingRef = useRef(false);
+ const dragStartRef = useRef({ x: 0, y: 0, panelX: 0, panelY: 0 });
+
+ // 初始居中(首次 open 时计算)
+ useEffect(() => {
+ if (open && position === null) {
+ const x = Math.max(0, (window.innerWidth - width) / 2);
+ const y = Math.max(0, (window.innerHeight - height) / 2);
+ setPosition({ x, y });
+ }
+ }, [open, position, width, height]);
+
+ // 拖拽启动
+ const handleMouseDown = useCallback(
+ (e: React.MouseEvent) => {
+ // 仅标题栏区域可拖拽
+ if ((e.target as HTMLElement).closest('.fp-close-btn')) return;
+ e.preventDefault();
+ draggingRef.current = true;
+ dragStartRef.current = {
+ x: e.clientX,
+ y: e.clientY,
+ panelX: position?.x ?? 0,
+ panelY: position?.y ?? 0,
+ };
+ },
+ [position],
+ );
+
+ // 全局 mousemove / mouseup
+ useEffect(() => {
+ const handleMouseMove = (e: MouseEvent) => {
+ if (!draggingRef.current) return;
+ const ds = dragStartRef.current;
+ const newX = ds.panelX + (e.clientX - ds.x);
+ const newY = ds.panelY + (e.clientY - ds.y);
+
+ // 边界约束:保留标题栏至少 40px 可见,确保用户能拖回
+ const minVisible = 40;
+ const maxX = window.innerWidth - minVisible;
+ const maxY = window.innerHeight - minVisible;
+
+ setPosition({
+ x: Math.max(-width + minVisible, Math.min(maxX, newX)),
+ y: Math.max(0, Math.min(maxY, newY)),
+ });
+ };
+
+ const handleMouseUp = () => {
+ draggingRef.current = false;
+ };
+
+ document.addEventListener('mousemove', handleMouseMove);
+ document.addEventListener('mouseup', handleMouseUp);
+ return () => {
+ document.removeEventListener('mousemove', handleMouseMove);
+ document.removeEventListener('mouseup', handleMouseUp);
+ };
+ }, [width]);
+
+ // 点击面板 → 置顶
+ const bringToFront = useCallback(() => {
+ setZIndex(nextZIndex());
+ }, []);
+
+ if (!open || !position) return null;
+
+ return (
+
+ {/* 标题栏 — 拖拽手柄 */}
+
+
+ {title}
+
+
+
+
+
+ {/* 内容区 */}
+
+ {children}
+
+
+ );
+}
diff --git a/src/components/navbar/NavBar.tsx b/src/components/navbar/NavBar.tsx
index 889299c..bf03a5b 100644
--- a/src/components/navbar/NavBar.tsx
+++ b/src/components/navbar/NavBar.tsx
@@ -8,7 +8,6 @@
// ============================================================
import {useState, useCallback, useMemo} from 'react';
-import {useNavigate} from 'react-router-dom';
import {App} from 'antd';
import {useAppContext} from '@/contexts/app-context';
@@ -25,7 +24,6 @@ export function NavBar() {
const {platform, edition, isLoggedIn, logout} = useAppContext();
const {isDark} = useTheme();
const {message} = App.useApp();
- const navigate = useNavigate();
const [drawerOpen, setDrawerOpen] = useState(false);
@@ -81,6 +79,8 @@ export function NavBar() {
logout();
} else if (item.key === 'settings') {
emit(EVENTS.OPEN_SETTINGS);
+ } else if (item.key === 'auth') {
+ emit(EVENTS.SHOW_LOGIN);
}
break;
@@ -90,12 +90,13 @@ export function NavBar() {
}
break;
- case 'navigate':
- case 'spa':
- if (item.key === 'auth') {
- emit(EVENTS.SHOW_LOGIN);
- } else if (item.target) {
- navigate(item.target);
+ case 'page':
+ if (item.target) {
+ emit(EVENTS.OPEN_PAGE, {
+ target: item.target,
+ modalType: item.modalType ?? 'floating',
+ label: item.label,
+ });
}
break;
}
diff --git a/src/components/navbar/nav-config.ts b/src/components/navbar/nav-config.ts
index 4217f60..c1ffa89 100644
--- a/src/components/navbar/nav-config.ts
+++ b/src/components/navbar/nav-config.ts
@@ -28,8 +28,9 @@ const personalLeft: NavItemConfig[] = [
key: 'compliance-assets',
label: '合规素材库',
iconSrc: complianceAssetsSvg,
- action: 'navigate',
+ action: 'page',
target: '/compliance-assets',
+ modalType: 'floating',
position: 'left',
},
];
@@ -47,8 +48,9 @@ const personalRight: NavItemConfig[] = [
key: 'account',
label: '查帐户',
iconSrc: accountSvg,
- action: 'navigate',
+ action: 'page',
target: '/account',
+ modalType: 'floating',
position: 'right',
requireLogin: true,
},
@@ -56,16 +58,18 @@ const personalRight: NavItemConfig[] = [
key: 'vip-activate',
label: '开会员/激活',
iconSrc: membershipSvg,
- action: 'navigate',
+ action: 'page',
target: '/vip',
+ modalType: 'modal',
position: 'right',
},
{
key: 'recharge-points',
label: '充值积分',
iconSrc: rechargeSvg,
- action: 'navigate',
+ action: 'page',
target: '/recharge',
+ modalType: 'modal',
position: 'right',
requireLogin: true,
},
@@ -73,8 +77,9 @@ const personalRight: NavItemConfig[] = [
key: 'billing-history',
label: '消费记录',
iconSrc: billingSvg,
- action: 'navigate',
+ action: 'page',
target: '/billing',
+ modalType: 'floating',
position: 'right',
requireLogin: true,
},
@@ -82,8 +87,9 @@ const personalRight: NavItemConfig[] = [
key: 'order-details',
label: '订单明细',
iconSrc: orderDetailsSvg,
- action: 'navigate',
+ action: 'page',
target: '/orders',
+ modalType: 'floating',
position: 'right',
requireLogin: true,
},
@@ -106,8 +112,7 @@ const personalRight: NavItemConfig[] = [
key: 'auth',
label: '登录/注册',
iconSrc: accountSvg,
- action: 'navigate',
- target: '/login',
+ action: 'button',
position: 'right',
requireGuest: true,
},
@@ -131,16 +136,18 @@ const enterpriseLeft: NavItemConfig[] = [
key: 'compliance-assets',
label: '合规素材库',
iconSrc: complianceAssetsSvg,
- action: 'navigate',
+ action: 'page',
target: '/compliance-assets',
+ modalType: 'floating',
position: 'left',
},
{
key: 'project-management',
label: '项目管理',
iconSrc: projectManagementSvg,
- action: 'navigate',
+ action: 'page',
target: '/projects',
+ modalType: 'floating',
position: 'left',
},
];
@@ -158,8 +165,9 @@ const enterpriseRight: NavItemConfig[] = [
key: 'quota-check',
label: '查配额',
iconSrc: accountSvg,
- action: 'navigate',
+ action: 'page',
target: '/quota',
+ modalType: 'floating',
position: 'right',
requireLogin: false,
},
@@ -174,8 +182,7 @@ const enterpriseRight: NavItemConfig[] = [
key: 'auth',
label: '登录/注册',
iconSrc: accountSvg,
- action: 'navigate',
- target: '/login',
+ action: 'button',
position: 'right',
requireGuest: true,
},
diff --git a/src/components/navbar/types.ts b/src/components/navbar/types.ts
index b2beec0..cc884ee 100644
--- a/src/components/navbar/types.ts
+++ b/src/components/navbar/types.ts
@@ -6,10 +6,9 @@ import type { Edition } from '@shared/types';
/** 导航项的行为类型 */
export type NavAction =
- | 'navigate' // 新开窗口
- | 'spa' // 单页应用内导航(当前窗口)
+ | 'page' // 打开页面 Modal(携带 target 路径标识符,由事件总线分发)
| 'drawer' // 侧拉抽屉(公告等)
- | 'button' // 纯按钮行为(退出登录)
+ | 'button' // 纯按钮行为(退出登录等)
| 'external'; // 外部链接
/** 单个导航项配置 */
@@ -24,6 +23,8 @@ export interface NavItemConfig {
action: NavAction;
/** 点击回调 / 路由路径 / 外部 URL(取决于 action) */
target?: string;
+ /** 窗口类型(仅 action='page' 时生效):modal 居中模态 | drawer 侧滑 | floating 非模态浮动面板 */
+ modalType?: 'modal' | 'drawer' | 'floating';
/** 仅在指定版本下显示 */
edition?: Edition | Edition[];
/** 仅已登录时显示 */
diff --git a/src/pages/account/AccountPage.tsx b/src/pages/account/AccountPage.tsx
new file mode 100644
index 0000000..5a012c5
--- /dev/null
+++ b/src/pages/account/AccountPage.tsx
@@ -0,0 +1,11 @@
+// ============================================================
+// AccountPage — 查帐户(占位组件,后续实现具体功能)
+// ============================================================
+
+export function AccountPage() {
+ return (
+
+ );
+}
diff --git a/src/pages/billing/BillingPage.tsx b/src/pages/billing/BillingPage.tsx
new file mode 100644
index 0000000..d491b00
--- /dev/null
+++ b/src/pages/billing/BillingPage.tsx
@@ -0,0 +1,11 @@
+// ============================================================
+// BillingPage — 消费记录(占位组件,后续实现具体功能)
+// ============================================================
+
+export function BillingPage() {
+ return (
+
+ );
+}
diff --git a/src/pages/compliance-assets/ComplianceAssetsPage.tsx b/src/pages/compliance-assets/ComplianceAssetsPage.tsx
new file mode 100644
index 0000000..8503865
--- /dev/null
+++ b/src/pages/compliance-assets/ComplianceAssetsPage.tsx
@@ -0,0 +1,11 @@
+// ============================================================
+// ComplianceAssetsPage — 合规素材库(占位组件,后续实现具体功能)
+// ============================================================
+
+export function ComplianceAssetsPage() {
+ return (
+
+ );
+}
diff --git a/src/pages/home/HomeContent.tsx b/src/pages/home/HomeContent.tsx
index 10ebb48..3487b2f 100644
--- a/src/pages/home/HomeContent.tsx
+++ b/src/pages/home/HomeContent.tsx
@@ -51,18 +51,18 @@ export function HomeContent() {
}, []);
const handleSetSelectedModel = useCallback(
- (model: ModelsListResponseBody | null) => {
- setSelectedModel(model);
- setSelectedTask(null);
- },
- [],
+ (model: ModelsListResponseBody | null) => {
+ setSelectedModel(model);
+ setSelectedTask(null);
+ },
+ [],
);
const handleSetSelectedTask = useCallback(
- (task: TaskInfoItemResponseBody | null) => {
- setSelectedTask(task);
- },
- [],
+ (task: TaskInfoItemResponseBody | null) => {
+ setSelectedTask(task);
+ },
+ [],
);
const contextValue = useMemo(() => ({
@@ -104,11 +104,14 @@ export function HomeContent() {
// 用 ref 追踪最新列宽,避免拖拽 mousemove 闭包中读到过期值
const leftWidthRef = useRef(leftWidth);
- leftWidthRef.current = leftWidth;
const rightWidthRef = useRef(rightWidth);
- rightWidthRef.current = rightWidth;
const draggingRef = useRef(dragging);
- draggingRef.current = dragging;
+
+ useEffect(() => {
+ leftWidthRef.current = leftWidth;
+ rightWidthRef.current = rightWidth;
+ draggingRef.current = dragging;
+ }, [leftWidth, rightWidth, dragging]);
const handleDividerMouseDown = (side: 'left' | 'right') => (e: React.MouseEvent) => {
e.preventDefault();
@@ -199,10 +202,10 @@ export function HomeContent() {
flexShrink: 0,
cursor: 'col-resize',
background: isActive
- ? token.colorPrimary
- : isHovered
- ? `${token.colorPrimary}60`
- : token.colorBorderSecondary,
+ ? token.colorPrimary
+ : isHovered
+ ? `${token.colorPrimary}60`
+ : token.colorBorderSecondary,
position: 'relative' as const,
zIndex: 10,
display: 'flex',
@@ -216,107 +219,107 @@ export function HomeContent() {
if (!isLoggedIn) {
return (
-
-
-
- 请先登录后再使用首页功能
-
- }
- image={Empty.PRESENTED_IMAGE_SIMPLE}
- />
-
-
+
+
+
+ 请先登录后再使用首页功能
+
+ }
+ image={Empty.PRESENTED_IMAGE_SIMPLE}
+ />
+
+
);
}
// ======== 三列布局(可拖拽) ========
return (
-
-
- {/* 左列 */}
- {!isLeftCollapsed && (
- <>
-
-
-
+
+
+ {/* 左列 */}
+ {!isLeftCollapsed && (
+ <>
+
+
+
- {/* 左分隔条 */}
-
setHoveredDivider('left')}
- onMouseLeave={() => setHoveredDivider(null)}
- >
- {/* 拖拽手柄竖线 */}
-
-
- >
- )}
+ {/* 左分隔条 */}
+
setHoveredDivider('left')}
+ onMouseLeave={() => setHoveredDivider(null)}
+ >
+ {/* 拖拽手柄竖线 */}
+
+
+ >
+ )}
- {/* 中列 */}
-
-
-
+ {/* 中列 */}
+
+
+
- {/* 右分隔条 */}
-
setHoveredDivider('right')}
- onMouseLeave={() => setHoveredDivider(null)}
- >
- {/* 拖拽手柄竖线 */}
-
-
+ {/* 右分隔条 */}
+
setHoveredDivider('right')}
+ onMouseLeave={() => setHoveredDivider(null)}
+ >
+ {/* 拖拽手柄竖线 */}
+
+
- {/* 右列 */}
-
-
-
-
-
+ {/* 右列 */}
+
+
+
+
+
);
}
diff --git a/src/pages/orders/OrdersPage.tsx b/src/pages/orders/OrdersPage.tsx
new file mode 100644
index 0000000..57277ba
--- /dev/null
+++ b/src/pages/orders/OrdersPage.tsx
@@ -0,0 +1,11 @@
+// ============================================================
+// OrdersPage — 订单明细(占位组件,后续实现具体功能)
+// ============================================================
+
+export function OrdersPage() {
+ return (
+
+ );
+}
diff --git a/src/pages/projects/ProjectsPage.tsx b/src/pages/projects/ProjectsPage.tsx
new file mode 100644
index 0000000..61d55fa
--- /dev/null
+++ b/src/pages/projects/ProjectsPage.tsx
@@ -0,0 +1,11 @@
+// ============================================================
+// ProjectsPage — 项目管理(占位组件,后续实现具体功能)
+// ============================================================
+
+export function ProjectsPage() {
+ return (
+
+ );
+}
diff --git a/src/pages/quota/QuotaPage.tsx b/src/pages/quota/QuotaPage.tsx
new file mode 100644
index 0000000..62e5e3c
--- /dev/null
+++ b/src/pages/quota/QuotaPage.tsx
@@ -0,0 +1,11 @@
+// ============================================================
+// QuotaPage — 查配额(占位组件,后续实现具体功能)
+// ============================================================
+
+export function QuotaPage() {
+ return (
+
+ );
+}
diff --git a/src/pages/recharge/RechargePage.tsx b/src/pages/recharge/RechargePage.tsx
new file mode 100644
index 0000000..d6c8907
--- /dev/null
+++ b/src/pages/recharge/RechargePage.tsx
@@ -0,0 +1,11 @@
+// ============================================================
+// RechargePage — 充值积分(占位组件,后续实现具体功能)
+// ============================================================
+
+export function RechargePage() {
+ return (
+
+ );
+}
diff --git a/src/pages/settings/SettingsPage.tsx b/src/pages/settings/SettingsPage.tsx
index 768da72..9653bc0 100644
--- a/src/pages/settings/SettingsPage.tsx
+++ b/src/pages/settings/SettingsPage.tsx
@@ -46,10 +46,10 @@ export function SettingsPage({ open, onClose }: SettingsPageProps) {
>
);
diff --git a/src/pages/settings/blocks/SystemInfoSetting.tsx b/src/pages/settings/blocks/SystemInfoSetting.tsx
index bbed3b3..66d747a 100644
--- a/src/pages/settings/blocks/SystemInfoSetting.tsx
+++ b/src/pages/settings/blocks/SystemInfoSetting.tsx
@@ -46,14 +46,14 @@ export function SystemInfoSetting() {
];
return (
-
+
{infoItems.map((item) => (
{item.icon}
-
+
{item.label}:
{item.value}
diff --git a/src/pages/vip/VipPage.tsx b/src/pages/vip/VipPage.tsx
new file mode 100644
index 0000000..65a9e47
--- /dev/null
+++ b/src/pages/vip/VipPage.tsx
@@ -0,0 +1,11 @@
+// ============================================================
+// VipPage — 开会员/激活(占位组件,后续实现具体功能)
+// ============================================================
+
+export function VipPage() {
+ return (
+
+ );
+}
diff --git a/src/router/index.tsx b/src/router/index.tsx
deleted file mode 100644
index eb8ce1c..0000000
--- a/src/router/index.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-// ============================================================
-// AppRoutes — 应用路由配置(由 App.tsx 在 HashRouter 内使用)
-//
-// 核心设计:
-// - Settings 等叠加层通过事件总线驱动 Drawer/Modal 展示,不走路由
-// - 首页始终保持挂载,不会因导航而卸载
-// ============================================================
-
-import { Routes, Route } from 'react-router-dom';
-import { Layout } from '@/components/Layout';
-import { HomePage } from '@/pages/home/HomePage';
-
-export function AppRoutes() {
- return (
-
- }>
- } />
-
-
- );
-}
diff --git a/src/utils/event-bus.ts b/src/utils/event-bus.ts
index 9a5f12c..553921d 100644
--- a/src/utils/event-bus.ts
+++ b/src/utils/event-bus.ts
@@ -73,4 +73,6 @@ export const EVENTS = {
TASK_SUBMITTED: 'task:submitted',
/** 打开设置 Drawer(导航栏"设置"按钮点击) */
OPEN_SETTINGS: 'settings:open',
+ /** 打开页面 Modal(导航栏点击页面导航项触发,携带 target 路径标识符) */
+ OPEN_PAGE: 'page:open',
} as const;