Files
Cyrene_v2/frontend/web/src/admin/modules/channels/ChannelsPage.tsx
T
Aska 2d7a377d74
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(channels): 适配器面板重构 P1——子侧边栏+4页+每配置专属页(design/17)
- Channels.tsx 改为 re-export 到 ./channels/ChannelsPage(入口不变)
- 新增 channels/ 模块:子侧边栏(桌面rail+移动端抽屉复用mu-drawer-in) + 子路由
  #/admin/channels(<page>|detail/<adapter_id>),刷新/返回可还原
- 4页:①适配器活动总览 ②添加/新建 ③活动适配器列表(每账号可点进专属页)
  ④适配器模块总日志(adminChannelLogOverview总览)
- 每配置一个页面(绑adapter_id, 不同配置不共用):实时日志+功能操作+身份测试
- 迁移保留原消息日志弹窗/身份测试/配置表单; 全部文件<500行
- 验收: tsc+vite build 96模块全绿
- 注: 另8个channels/*文件于5a0c1e2一并入库(该提交信息为后端修复,历史遗留混提交)
2026-09-07 18:01:11 +08:00

79 lines
3.1 KiB
TypeScript

import { useState } from "react";
import { Icon } from "../../../components/Icon";
import { AdaptersDrawer, AdaptersRail } from "./AdaptersNav";
import { PLATFORM_LABEL } from "../channelFields";
import { platformOf } from "./channelUtils";
import { useAdapterNav, type AdapterNav } from "./useAdapterNav";
import { OverviewPage } from "./OverviewPage";
import { CreatePage } from "./CreatePage";
import { ListPage } from "./ListPage";
import { GlobalLogPage } from "./GlobalLogPage";
import { AdapterDetailPage } from "./AdapterDetailPage";
/** Title for the mobile sub-nav bar; also echoed by the rail when in a detail page. */
function pageTitle(nav: AdapterNav): string {
if (nav.kind === "detail") {
const platform = platformOf(nav.adapter);
return `${PLATFORM_LABEL[platform] ?? platform} · ${nav.adapter}`;
}
switch (nav.page) {
case "create": return "新建适配器";
case "list": return "活动适配器";
case "log": return "模块总日志";
default: return "活动总览";
}
}
function renderPage(nav: AdapterNav, navigate: (n: AdapterNav) => void): React.ReactNode {
if (nav.kind === "detail") {
return <AdapterDetailPage key={nav.adapter} adapter={nav.adapter} onBack={() => navigate({ kind: "page", page: "list" })} />;
}
switch (nav.page) {
case "create": return <CreatePage />;
case "list": return <ListPage navigate={navigate} />;
case "log": return <GlobalLogPage />;
default: return <OverviewPage navigate={navigate} />;
}
}
/** Adapter module shell: sub-sidebar + 4 pages, sub-routed by the location hash
* (design/17 §1). Desktop keeps the rail sticky at the top; mobile (<768px)
* collapses it into a drawer opened from a hamburger in the page title bar. */
export function ChannelsModule(): React.ReactNode {
const { nav, navigate } = useAdapterNav();
const [drawerOpen, setDrawerOpen] = useState(false);
return (
<div className="flex flex-col gap-4 md:flex-row md:items-start">
<AdaptersRail
nav={nav}
onNavigate={navigate}
className="hidden w-[var(--mu-sidebar-w)] shrink-0 md:sticky md:top-0 md:self-start md:flex"
/>
<div className="min-w-0 flex-1 space-y-4">
<div className="flex items-center gap-2 md:hidden">
<button
onClick={() => setDrawerOpen(true)}
aria-label="打开适配器导航"
title="适配器菜单"
className="inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-[var(--mu-radius)] text-[var(--mu-text-mid)] transition-all duration-200 ease-[var(--mu-spring)] hover:bg-[var(--mu-surface-4)] active:scale-95"
>
<Icon name="menu" size={20} />
</button>
<h1 className="min-w-0 flex-1 truncate whitespace-nowrap text-base font-semibold">{pageTitle(nav)}</h1>
</div>
{renderPage(nav, navigate)}
</div>
<AdaptersDrawer
open={drawerOpen}
nav={nav}
onNavigate={(n) => {
navigate(n);
setDrawerOpen(false);
}}
onClose={() => setDrawerOpen(false)}
/>
</div>
);
}