// Shell: header (with org cascader) + sidebar + router const { useState, useEffect, useRef, useMemo, useCallback } = React; const D = window.__CARBON_DATA; // ─── Org context ──────────────────────────────────────────────── const OrgCtx = React.createContext(null); function OrgProvider({ children, orgDepthLimit, industryFocus }) { // Default to group (level 1) const [orgId, setOrgId] = useState('org-001'); // When industry focus changes, jump to that板块 if found useEffect(() => { if (!industryFocus || industryFocus === 'ALL') return; const target = D.ORG_FLAT.find(o => o.level === 2 && o.industry === industryFocus); if (target) setOrgId(target.id); }, [industryFocus]); const value = useMemo(() => ({ orgId, setOrgId, org: D.ORG_FLAT.find(o => o.id === orgId), path: D.getOrgPath(orgId), descendants: D.getDescendants(orgId), orgDepthLimit }), [orgId, orgDepthLimit]); return {children}; } const useOrg = () => React.useContext(OrgCtx); // Filtered tree by depth limit function filterTree(node, maxLevel) { if (node.level > maxLevel) return null; return { ...node, children: (node.children || []) .map(c => filterTree(c, maxLevel)) .filter(Boolean) }; } // ─── Org Switcher (cascading dropdown in header) ──────────────── function OrgSwitcher() { const { org, path, setOrgId, orgDepthLimit } = useOrg(); const [open, setOpen] = useState(false); const [hover, setHover] = useState([]); // hovered node at each column const ref = useRef(null); const limit = orgDepthLimit || 4; useEffect(() => { if (!open) return; const handler = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; document.addEventListener('mousedown', handler); return () => document.removeEventListener('mousedown', handler); }, [open]); // When opening, initialize columns from current path useEffect(() => { if (open) setHover(path.map(p => p.id)); }, [open]); const tree = filterTree(D.ORG_TREE, limit); // build columns based on hover const columns = []; let cur = tree; for (let i = 0; cur && cur.children && cur.children.length; i++) { columns.push(cur.children); const selectedId = hover[i + 1]; cur = cur.children.find(c => c.id === selectedId); if (!selectedId) break; } // also show root as col 0 const allCols = [[tree], ...columns]; const choose = (node, colIdx) => { const newHover = hover.slice(0, colIdx + 1); newHover[colIdx] = node.id; setHover(newHover); if (!node.children || node.children.length === 0 || colIdx === limit - 1) { setOrgId(node.id); setOpen(false); } }; const pathLabel = path.map(p => p.name).join(' / '); return (
setOpen(v => !v)}> 组织: {pathLabel} {open && (
e.stopPropagation()}> {allCols.map((col, idx) => (
{col.map(n => { const isLeaf = !n.children || n.children.length === 0 || idx === limit - 1; const isActive = hover[idx] === n.id; return (
{ if (!isLeaf) { const h = hover.slice(0, idx); h[idx] = n.id; setHover(h); } }} onClick={() => choose(n, idx)}> {n.name} {!isLeaf && }
); })}
))}
)}
); } // ─── Sidebar nav items ────────────────────────────────────────── const NAV_ITEMS = [ { group: '碳排放管理', items: [ { id: 'dashboard', label: '数据看板', icon: 'dashboard' }, { id: 'data-entry', label: '数据填报', icon: 'edit' }, { id: 'data-import', label: '批量导入', icon: 'upload' } ]}, { group: '配置中心', items: [ { id: 'factor-library', label: '排放因子库', icon: 'book' }, { id: 'organization', label: '组织架构', icon: 'tree' } ]}, { group: '报告', items: [ { id: 'report', label: '报告生成', icon: 'doc' } ]} ]; function Sidebar({ collapsed, route, onNavigate }) { return ( ); } // ─── Header ───────────────────────────────────────────────────── function Header({ collapsed, onToggleSidebar }) { return (
组织碳管理系统
张芷涵
); } // ─── Page Header ──────────────────────────────────────────────── function PageHeader({ title, breadcrumb, extra }) { const { path } = useOrg(); return (
{(breadcrumb || []).map((b, i) => ( {b} {i < breadcrumb.length - 1 && /} ))}

{title} 当前:{path[path.length - 1]?.name}

{extra}
); } Object.assign(window, { OrgProvider, useOrg, OrgSwitcher, Sidebar, Header, PageHeader, NAV_ITEMS, filterTree });