From a2741edf0160baf0a11ed5b7fc99a76a6afa62cf Mon Sep 17 00:00:00 2001 From: YoungestSongMo <2130460579@qq.com> Date: Mon, 8 Jun 2026 17:27:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=20Modal=20=E5=B1=95=E7=A4=BA=E4=BA=8C?= =?UTF-8?q?=E7=BB=B4=E7=A0=81=EF=BC=8C=E4=BF=AE=E5=A4=8D=20PageDispatcher?= =?UTF-8?q?=20hooks=20=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将"企业微信"导航项从 external(跳转浏览器)改为 page(Modal 展示), 二维码图片数据来源于 Banner API(/api/v1/creatives,category=promotion)。 - 新增 WechatWorkPage:调用 fetchBanners,筛选 category==='promotion', 展示 image_url 二维码 + 扫码提示 + 刷新按钮,加载/空态/错误三态覆盖 - nav-config:wechat-work 从 action:'external' 改为 action:'page', modalType:'modal',target:'/wechat-work' - PageDispatcher:注册 /wechat-work 映射,Modal 新增 centered 居中 - 修复 PageDispatcher 致命 bug:PAGE_MAP[...]() 函数调用改为 JSX ,避免子组件 hooks 误跑在父组件链上导致崩溃 - PAGE_MAP 类型从 Record ReactNode> 修正为 Record --- src/components/PageDispatcher.tsx | 57 +++++++------- src/components/navbar/nav-config.ts | 5 +- src/pages/wechat-work/WechatWorkPage.tsx | 98 ++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 28 deletions(-) create mode 100644 src/pages/wechat-work/WechatWorkPage.tsx 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} +

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

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

+
+ ); +}