// 顶层 App — 把 design 的 3 视图(DR/告警/总览)和 broadcast tab 串起来。 const { useState, useEffect } = React; const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "view": "roster", "density": "regular", "theme": "dark", "aiOn": true, "accent": "blue" }/*EDITMODE-END*/; const ACCENT_HUES = { blue: { h: 220, h2: 145 }, purple: { h: 285, h2: 195 }, green: { h: 165, h2: 220 }, amber: { h: 65, h2: 200 }, }; function App() { const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); // 顶层 tab:监控看板 / 群发消息 const [activeTab, setActiveTab] = useState(() => localStorage.getItem("cm.activeTab") || "dashboard" ); useEffect(() => { localStorage.setItem("cm.activeTab", activeTab); }, [activeTab]); // 时钟(用于 TopBar 实时同步显示) const [now, setNow] = useState(new Date()); useEffect(() => { const id = setInterval(() => setNow(new Date()), 30000); return () => clearInterval(id); }, []); // 主题 useEffect(() => { document.documentElement.setAttribute("data-theme", t.theme || "dark"); }, [t.theme]); // 强调色 useEffect(() => { const a = ACCENT_HUES[t.accent] || ACCENT_HUES.blue; document.documentElement.style.setProperty("--accent", `oklch(0.78 0.14 ${a.h})`); document.documentElement.style.setProperty("--accent-2", `oklch(0.82 0.14 ${a.h2})`); }, [t.accent]); // dashboard 内部状态 const [drFilter, setDrFilter] = useState("all"); const [timeRange, setTimeRange] = useState("today"); const [statusFilter, setStatusFilter] = useState("all"); const [openDr, setOpenDr] = useState(null); const [openGroup, setOpenGroup] = useState(null); // broadcast modal 状态 const [broadcastOpen, setBroadcastOpen] = useState(false); const [broadcastPrefill, setBroadcastPrefill] = useState(null); const openBroadcastWizard = (prefill = null) => { setBroadcastPrefill(prefill); setBroadcastOpen(true); }; const broadcasts = (window.AppData && window.AppData.BROADCASTS) || []; const groups = (window.AppData && window.AppData.GROUPS) || []; return (
{activeTab === "dashboard" && ( <> setTweak("view", v)} drFilter={drFilter} setDrFilter={setDrFilter} timeRange={timeRange} setTimeRange={setTimeRange} statusFilter={statusFilter} setStatusFilter={setStatusFilter} />
{t.view === "roster" && ( )} {t.view === "alerts" && ( )} {t.view === "overview" && ( )}
)} {activeTab === "broadcast" && (
openBroadcastWizard()} />
)}
{/* DR 详情抽屉 */} setOpenDr(null)} title={openDr?.name} subtitle={openDr ? `DR · ${openDr.activeGroups} 活跃 / ${openDr.totalGroups} 总群` : ""} > {openDr && ( { setOpenDr(null); setOpenGroup(g); }} /> )} {/* 群对话抽屉 */} setOpenGroup(null)} title={openGroup?.name} subtitle={ openGroup ? `${(MOCK.DRS.find(d => d.id === openGroup.drId) || {}).name || "—"} 负责` : "" } > {openGroup && } {/* 群发向导(modal) */} setBroadcastOpen(false)} groups={groups} prefillGroup={broadcastPrefill} /> {/* Tweaks 面板 */} setTweak("view", v)} /> setTweak("density", v)} /> setTweak("theme", v)} /> setTweak("accent", v)} /> setTweak("aiOn", v)} />
); } // Error boundary —— 不再黑屏,把异常信息直接画在页面上方便定位。 class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { err: null, info: null }; } static getDerivedStateFromError(err) { return { err }; } componentDidCatch(err, info) { this.setState({ info }); console.error("[ErrorBoundary]", err, info); } render() { if (!this.state.err) return this.props.children; return (
页面渲染出错
{String(this.state.err && this.state.err.message)}
stack
{this.state.err && this.state.err.stack}
{this.state.info && (
{this.state.info.componentStack}
)}
MOCK.DRS: {window.MOCK?.DRS?.length ?? "?"} · MOCK.GROUPS: {window.MOCK?.GROUPS?.length ?? "?"} · AppData.GROUPS: {window.AppData?.GROUPS?.length ?? "?"}
); } } ReactDOM.createRoot(document.getElementById("root")).render( );