Files
ele-HeiXiu/src/components/PageDispatcher.tsx
YoungestSongMo 7982b69219 refactor: CSS 拆分 + auth 持久化提取 + 目录整理
- globals.css 拆分为 4 文件:tokens.css(颜色)/ transitions.css(过渡)/ base.css(重置)/ globals.css(入口)
- 提取 auth-persistence.ts 到 services/,解除 AppProvider→pages/login/hooks 反向依赖
- use-auth-state.ts 精简为纯 React Hook,持久化逻辑委托给 services/auth-persistence.ts
- components/common/ → components/ui/(目录名语义更清晰)
- TypeScript + Vite 构建通过

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 14:20:07 +08:00

155 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================================
// PageDispatcher — 页面事件调度器
//
// 监听 EVENTS.OPEN_PAGE根据 modalType 渲染对应容器:
// - modal → antd Modal居中模态有遮罩阻断交互
// - drawer → antd Drawer侧滑抽屉
// - floating → FloatingPanel非模态浮动面板可拖拽不阻断交互
//
// 支持多面板共存floating 类型可同时打开多个)
// ============================================================
import {useState, useEffect, useCallback, useRef, type ComponentType} from 'react';
import {Modal, Drawer} from 'antd';
import {on, off, EVENTS} from '@/utils/event-bus';
import {FloatingPanel} from '@/components/ui/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';
import {WechatWorkPage} from '@/pages/wechat-work/WechatWorkPage';
// ---------- 类型 ----------
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<string, ComponentType> = {
'/compliance-assets': ComplianceAssetsPage,
'/account': AccountPage,
'/vip': VipPage,
'/recharge': RechargePage,
'/billing': BillingPage,
'/orders': OrdersPage,
'/projects': ProjectsPage,
'/quota': QuotaPage,
'/wechat-work': WechatWorkPage,
};
// ---------- 组件 ----------
export function PageDispatcher() {
// 单实例modal / drawer—— 同一时间只有一个
const [singleton, setSingleton] = useState<PageEventPayload | null>(null);
// 浮动面板列表(支持多个同时打开)
const [floatingPanels, setFloatingPanels] = useState<ActivePanel[]>([]);
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 SingletonPage = singleton ? PAGE_MAP[singleton.target] : null;
return (
<>
{/* ======== Modal ======== */}
{singleton?.modalType === 'modal' && SingletonPage && (
<Modal
open
centered
title={singleton.label}
onCancel={closeSingleton}
footer={null}
destroyOnHidden={true}
mask={{
closable: true,
}}
width={640}
>
<SingletonPage />
</Modal>
)}
{/* ======== Drawer ======== */}
{singleton?.modalType === 'drawer' && SingletonPage && (
<Drawer
open
title={singleton.label}
onClose={closeSingleton}
destroyOnHidden={true}
size={480}
>
<SingletonPage />
</Drawer>
)}
{/* ======== Floating Panels ======== */}
{floatingPanels.map((panel) => {
const PageContent = PAGE_MAP[panel.target];
if (!PageContent) return null;
return (
<FloatingPanel
key={panel.id}
open
title={panel.label}
onClose={() => closeFloating(panel.id)}
>
<PageContent />
</FloatingPanel>
);
})}
</>
);
}