/* === Shared UI === */ /* global React */ const { useState: useStateUI, useEffect: useEffectUI, useRef: useRefUI } = React; // Icon helper - simple inline SVGs const Icons = { pool: , mgr: , warn: , setting: , operation: , dashboard: , refresh: , bell: , user: , close: , download: , plus: , check: , arrow: , empty: , // Tree icons for Data Dictionary home: , folder: , folderOpen: , file: , // Operation sub-menu icons batchIcon: , ruleIcon: , dataIcon: , probeIcon: , dictIcon: , }; // ---- Toast system ---- const ToastContext = React.createContext({ push: () => {} }); function ToastProvider({ children }) { const [toasts, setToasts] = useStateUI([]); const push = (msg, type = "success") => { const id = Date.now() + Math.random(); setToasts((ts) => [...ts, { id, msg, type }]); setTimeout(() => { setToasts((ts) => ts.filter((t) => t.id !== id)); }, 3000); }; return ( {children}
{toasts.map((t) =>
{t.type === "success" ? "✓" : t.type === "warn" ? "!" : t.type === "error" ? "×" : "i"} {t.msg}
)}
); } function useToast() {return React.useContext(ToastContext);} // ---- Drawer ---- function Drawer({ open, onClose, title, sub, children, footer }) { useEffectUI(() => { if (!open) return; const onKey = (e) => {if (e.key === "Escape") onClose();}; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [open, onClose]); if (!open) return null; return (
{title}
{sub &&
{sub}
}
{children}
{footer &&
{footer}
}
); } // ---- Confirm Dialog ---- function ConfirmModal({ open, onClose, title, message, onConfirm }) { if (!open) return null; return (
onClose(false)}>
e.stopPropagation()}>

{title}

⚠️

{message}

); } // ---- Sidebar ---- function Sidebar({ route, onNav }) { const groups = [ { id: "pool", label: "产品池", icon: Icons.pool, parentRoute: "pool-list", items: [ { id: "pool-detail", label: "产品详情" }, { id: "pool-compare", label: "产品对比" }] }, { id: "mgr", label: "管理人分析", icon: Icons.mgr, parentRoute: "mgr-overview", items: [ { id: "mgr-strategy", label: "策略配置能力" }, { id: "mgr-tactical", label: "战术配置能力" }, { id: "mgr-selection", label: "选基能力" }] }, { id: "warn", label: "预警和售后", icon: Icons.warn, parentRoute: "warn-overview", items: [ { id: "warn-risk", label: "风险事件预警" }, { id: "warn-sop", label: "售后 SOP" }] }, { id: "operation", label: "运营管理", icon: Icons.operation, parentRoute: "operation-overview", items: [ { id: "operation-user", label: "用户权限" }, { id: "operation-batch", label: "批量任务" }, { id: "operation-rule", label: "规则策略" }, { id: "operation-product", label: "产品维护" }, { id: "operation-data", label: "数据接入" }, { id: "operation-indicator", label: "指标计算" }, { id: "operation-probe", label: "数据探查" }, { id: "operation-dict", label: "数据字典" }] }, { id: "dashboard", label: "视图看板", icon: Icons.dashboard, leaf: true, route: "dashboard" }, ]; const [open, setOpen] = useStateUI({ pool: true, mgr: route.startsWith("mgr"), warn: route.startsWith("warn"), operation: route.startsWith("operation") }); return ( ); } // ---- Topbar ---- function Topbar({ title, crumb, onSearchOpen, onNav, drawerOpen, openDrawer }) { const [q, setQ] = useStateUI(""); const [focused, setFocused] = useStateUI(false); const wrapRef = useRefUI(null); useEffectUI(() => { const onDown = (e) => { if (wrapRef.current && !wrapRef.current.contains(e.target)) setFocused(false); }; document.addEventListener("mousedown", onDown); return () => document.removeEventListener("mousedown", onDown); }, []); const results = (() => { if (!q || q.length < 1) return null; const ql = q.toLowerCase(); const prods = window.IRDATA.products.filter((p) => p.name.toLowerCase().includes(ql) || p.code.toLowerCase().includes(ql) || p.id.toLowerCase().includes(ql) ).slice(0, 8); const mgrs = window.IRDATA.managers.filter((m) => m.name.includes(q) || m.type.includes(q) ); return { prods, mgrs }; })(); return (
首页/{crumb}
{title} 实时
投研看板
setQ(e.target.value)} onFocus={() => setFocused(true)} /> {focused && results && results.prods.length + results.mgrs.length > 0 &&
{results.prods.length > 0 &&
产品 · {results.prods.length}
{results.prods.map((p) =>
{ openDrawer({ kind: "product", id: p.id }); setQ("");setFocused(false); }}>
{p.name}
{p.code} · {p.manager}
{(p.annualReturn * 100).toFixed(2)}%
)}
} {results.mgrs.length > 0 &&
管理人 · {results.mgrs.length}
{results.mgrs.map((m) =>
{ onNav("mgr-strategy", { mgrId: m.id }); setQ("");setFocused(false); }}>
{m.name}
{m.type} · {m.productCount} 产品
)}
}
} {focused && q && results && results.prods.length === 0 && results.mgrs.length === 0 &&
未找到匹配
}
数据截至 2026-05-20
更新于 2026-05-21 09:30
张三
); } // ---- Product detail drawer ---- function ProductDetailDrawer({ id, onClose, onNav }) { if (!id) return null; const p = window.IRDATA.products.find((x) => x.id === id); if (!p) return null; const cat = window.IRDATA.CATEGORIES.find((c) => c.id === p.catId); return ( }>
核心指标
最新净值
{p.nav.toFixed(4)}
截至日期
{p.latestDate}
年化收益
{(p.annualReturn * 100).toFixed(2)}%
夏普比率
{p.sharpe.toFixed(2)}
最大回撤
{(p.maxDrawdown * 100).toFixed(2)}%
同类回撤
{(p.peerMaxDrawdown * 100).toFixed(2)}%
当前回撤
{(p.currentDrawdown * 100).toFixed(2)}%
恢复天数
{p.recoveryDays} 日
年化波动
{(p.vol * 100).toFixed(2)}%
卡玛比率
{p.calmar.toFixed(2)}
净值曲线
系统结论
{p.conclusion}
主要风险
{p.risk}
标签
{cat.name} {p.labels.map((l, i) => {l})}
); } // ---- Generic info drawer (for chart point clicks etc) ---- function InfoDrawer({ payload, onClose }) { if (!payload) return null; return (
明细
{payload.kvs && payload.kvs.map((kv, i) =>
{kv.k}
{kv.v}
)}
{payload.body &&
说明
{payload.body}
}
); } Object.assign(window, { Icons, ToastProvider, useToast, Drawer, ConfirmModal, Sidebar, Topbar, ProductDetailDrawer, InfoDrawer });