diff --git a/src/components/PageDispatcher.tsx b/src/components/PageDispatcher.tsx index a7be853..f6ae8a7 100644 --- a/src/components/PageDispatcher.tsx +++ b/src/components/PageDispatcher.tsx @@ -9,18 +9,19 @@ // 支持多面板共存(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'; +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/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'; +import {WechatWorkPage} from '@/pages/wechat-work/WechatWorkPage'; // ---------- 类型 ---------- @@ -40,7 +41,7 @@ interface ActivePanel { // ---------- target → 页面组件映射 ---------- -const PAGE_MAP: Record ReactNode> = { +const PAGE_MAP: Record = { '/compliance-assets': ComplianceAssetsPage, '/account': AccountPage, '/vip': VipPage, @@ -49,6 +50,7 @@ const PAGE_MAP: Record ReactNode> = { '/orders': OrdersPage, '/projects': ProjectsPage, '/quota': QuotaPage, + '/wechat-work': WechatWorkPage, }; // ---------- 组件 ---------- @@ -64,7 +66,7 @@ export function PageDispatcher() { // 监听 OPEN_PAGE 事件 useEffect(() => { const handler = (payload: unknown) => { - const { target, modalType, label } = payload as PageEventPayload; + const {target, modalType, label} = payload as PageEventPayload; if (!target || !PAGE_MAP[target]) { console.warn(`[PageDispatcher] 未找到页面组件: ${target}`); @@ -74,10 +76,10 @@ export function PageDispatcher() { if (modalType === 'floating') { // 浮动面板:追加到列表 const id = `fp-${nextIdRef.current++}`; - setFloatingPanels((prev) => [...prev, { id, target, label }]); + setFloatingPanels((prev) => [...prev, {id, target, label}]); } else { // modal / drawer:单实例,覆盖之前的 - setSingleton({ target, modalType, label }); + setSingleton({target, modalType, label}); } }; @@ -95,36 +97,39 @@ export function PageDispatcher() { setFloatingPanels((prev) => prev.filter((p) => p.id !== id)); }, []); - // 渲染单实例内容 - const singletonContent = singleton && PAGE_MAP[singleton.target] ? PAGE_MAP[singleton.target]() : null; + // 单实例对应的页面组件 + const SingletonPage = singleton ? PAGE_MAP[singleton.target] : null; return ( <> {/* ======== Modal ======== */} - {singleton?.modalType === 'modal' && ( + {singleton?.modalType === 'modal' && SingletonPage && ( - {singletonContent} + )} {/* ======== Drawer ======== */} - {singleton?.modalType === 'drawer' && ( + {singleton?.modalType === 'drawer' && SingletonPage && ( - {singletonContent} + )} diff --git a/src/components/navbar/nav-config.ts b/src/components/navbar/nav-config.ts index c1ffa89..854fcc5 100644 --- a/src/components/navbar/nav-config.ts +++ b/src/components/navbar/nav-config.ts @@ -97,8 +97,9 @@ const personalRight: NavItemConfig[] = [ key: 'wechat-work', label: '企业微信', iconSrc: wechatWorkSvg, - action: 'external', - target: 'https://work.weixin.qq.com/', + action: 'page', + target: '/wechat-work', + modalType: 'modal', position: 'right', }, { diff --git a/src/pages/wechat-work/WechatWorkPage.tsx b/src/pages/wechat-work/WechatWorkPage.tsx new file mode 100644 index 0000000..78bfbed --- /dev/null +++ b/src/pages/wechat-work/WechatWorkPage.tsx @@ -0,0 +1,98 @@ +// ============================================================ +// WechatWorkPage — 企业微信二维码 Modal 内容 +// +// 点击导航栏"企业微信"后以 Modal 形式展示二维码。 +// 数据来源:GET /api/v1/creatives(Banner API) +// +// 识别逻辑:Banner 中 category === 'promotion' 的条目, +// 其 image_url 为企业微信二维码图片 +// ============================================================ + +import { useEffect, useState, useCallback } from 'react'; +import { Spin, Empty } from 'antd'; +import { WechatOutlined } from '@ant-design/icons'; +import { fetchBanners, type Banner } from '@/services/modules'; + +export function WechatWorkPage() { + const [banner, setBanner] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const load = useCallback(async () => { + setLoading(true); + setError(null); + try { + const list = await fetchBanners(); + // category === 'promotion' → 企业微信二维码 + const promotion = list.find( + (item) => item.is_active && item.category === 'promotion', + ); + setBanner(promotion ?? null); + } catch (err) { + setError((err as Error).message || '加载失败'); + } finally { + setLoading(false); + } + }, []); + useEffect(() => { + void load(); + }, [load]); + + // 加载态 + if (loading) { + return ( +
+ +
+ ); + } + + // 无数据 / 错误 + if (error || !banner || !banner.image_url) { + return ( +
+ } + description={error || '暂未获取到企业微信二维码,请联系管理员'} + /> +
+ ); + } + + return ( +
+ {/* 二维码图片 */} +
+ 企业微信二维码 +
+ + {/* 标题(如果 Banner 有标题则显示) */} + {banner.title && ( +

+ {banner.title} +

+ )} + + {/* 提示文字 */} +

+ 请使用微信扫描二维码添加企业微信 +

+
+ ); +}