// 排放因子库 + 组织管理 const { useState, useMemo } = React; const DF = window.__CARBON_DATA; // ========================================================= // 1. 排放因子库 // ========================================================= function FactorLibrary() { const toast = useToast(); const confirm = useConfirm(); const [factors, setFactors] = useState(() => DF.FACTORS.slice()); const [filters, setFilters] = useState({ industry: '', category: '', standard: '', status: '', keyword: '' }); const [page, setPage] = useState(1); const pageSize = 10; const [modal, setModal] = useState(null); // {mode, data} const [historyOf, setHistoryOf] = useState(null); const [importOpen, setImportOpen] = useState(false); const filtered = useMemo(() => { return factors.filter(f => { if (filters.industry && f.industry !== filters.industry) return false; if (filters.category && f.category !== filters.category) return false; if (filters.standard && f.standard !== filters.standard) return false; if (filters.status && f.status !== filters.status) return false; if (filters.keyword) { const k = filters.keyword.toLowerCase(); if (!f.name.toLowerCase().includes(k) && !f.source.toLowerCase().includes(k) && !f.id.toLowerCase().includes(k)) return false; } return true; }); }, [factors, filters]); const total = filtered.length; const pageData = filtered.slice((page - 1) * pageSize, page * pageSize); const handleSubmit = (data) => { if (modal.mode === 'add') { const id = 'F' + String(factors.length + 100).padStart(3, '0'); setFactors(prev => [{ ...data, id, status: 'enabled' }, ...prev]); toast.push('因子已添加'); } else { setFactors(prev => prev.map(f => f.id === data.id ? { ...f, ...data } : f)); toast.push('因子已更新'); } setModal(null); }; const toggleStatus = (row) => { setFactors(prev => prev.map(f => f.id === row.id ? { ...f, status: f.status === 'enabled' ? 'disabled' : 'enabled' } : f)); toast.push(row.status === 'enabled' ? '因子已停用' : '因子已启用'); }; const handleDelete = async (row) => { const ok = await confirm({ title: '删除因子', message: <>确定删除排放因子 {row.name}?删除前请确保无填报数据引用。 }); if (ok) { setFactors(prev => prev.filter(f => f.id !== row.id)); toast.push('已删除'); } }; const columns = [ { key: 'id', title: '因子编码', width: 80 }, { key: 'name', title: '因子名称', minWidth: 220, render: (r) => (
{r.name}
{r.source}
)}, { key: 'category', title: '排放类别', width: 110, render: (r) => DF.EMISSION_CATEGORIES.find(c => c.code === r.category)?.name }, { key: 'industry', title: '适用行业', width: 110, render: (r) => ( {DF.INDUSTRIES.find(i => i.code === r.industry)?.name} )}, { key: 'value', title: '因子值', width: 100, align: 'right', render: (r) => {r.value} }, { key: 'unit', title: '单位', width: 130, render: (r) => {r.unit} }, { key: 'standard', title: '核算标准', width: 90, align: 'center', render: (r) => {r.standard === 'CHINA' ? '国家指南' : 'ISO 14064'} }, { key: 'version', title: '版本', width: 90, render: (r) => {r.version} }, { key: 'effectiveDate', title: '生效日期', width: 100, render: (r) => {r.effectiveDate} }, { key: 'status', title: '状态', width: 80, align: 'center', render: (r) => }, { key: 'actions', title: '操作', width: 220, render: (r) => (
)} ]; return ( <> } />
因子总数
{factors.length}
国家行业指南因子
{factors.filter(f => f.standard === 'CHINA').length}
ISO 14064 通用因子
{factors.filter(f => f.standard === 'ISO').length}
已停用
{factors.filter(f => f.status === 'disabled').length}
setFilters({ ...filters, category: v })} placeholder="全部类别" options={DF.EMISSION_CATEGORIES.map(c => ({ value: c.code, label: c.name }))} style={{ width: 150 }} /> setFilters({ ...filters, status: v })} placeholder="全部状态" options={[{ value: 'enabled', label: '启用' }, { value: 'disabled', label: '停用' }]} style={{ width: 120 }} /> setFilters({ ...filters, keyword: v })} placeholder="搜索因子名称/编码..." style={{ width: 220 }} />
共 {filtered.length} 条
{modal && setModal(null)} onSubmit={handleSubmit} />} {historyOf && setHistoryOf(null)} />} {importOpen && setImportOpen(false)} onDone={() => { setImportOpen(false); toast.push('成功导入 8 条因子'); }} />} ); } function FactorForm({ mode, initialData, onClose, onSubmit }) { const [data, setData] = useState(initialData); const [errors, setErrors] = useState({}); const readonly = mode === 'view'; const set = (k, v) => { setData(d => ({ ...d, [k]: v })); setErrors(e => ({ ...e, [k]: undefined })); }; const submit = () => { const errs = {}; if (!data.name) errs.name = '请输入因子名称'; if (!data.value || isNaN(Number(data.value)) || Number(data.value) <= 0) errs.value = '请输入有效因子值'; if (!data.unit) errs.unit = '请输入单位'; if (!data.source) errs.source = '请填写数据来源'; if (Object.keys(errs).length) { setErrors(errs); return; } onSubmit({ ...data, value: Number(data.value) }); }; const title = mode === 'add' ? '新增排放因子' : mode === 'edit' ? '编辑排放因子' : '查看排放因子'; return ( 关闭 : <>}>
set('name', v)} disabled={readonly} style={{ width: '100%' }} /> set('value', v)} type="number" disabled={readonly} style={{ width: '100%' }} /> set('unit', v)} disabled={readonly} style={{ width: '100%' }} /> set('industry', v)} options={DF.INDUSTRIES.map(i => ({ value: i.code, label: i.name }))} disabled={readonly} style={{ width: '100%', minWidth: 'auto' }} /> set('version', v)} disabled={readonly} style={{ width: '100%' }} /> set('effectiveDate', v)} type="date" disabled={readonly} style={{ width: '100%' }} /> set('source', v)} disabled={readonly} placeholder="例:《中国发电企业温室气体排放核算方法与报告指南》" style={{ width: '100%' }} />
{readonly && (
因子编码:{data.id}
)}
); } function FactorHistory({ factor, onClose }) { // Simulated history const versions = [ { version: factor.version, value: factor.value, effectiveDate: factor.effectiveDate, source: factor.source, status: '当前生效', updateBy: '陈彦霖', updateDate: factor.effectiveDate + ' 09:24' }, { version: 'v2023.1', value: +(factor.value * 1.06).toFixed(4), effectiveDate: '2023-04-01', source: '生态环境部 2023 年度', status: '已停用', updateBy: '陈彦霖', updateDate: '2023-04-01 10:15' }, { version: 'v2022.1', value: +(factor.value * 1.12).toFixed(4), effectiveDate: '2022-04-01', source: '生态环境部 2022 年度', status: '已停用', updateBy: '李铭哲', updateDate: '2022-04-15 14:08' }, { version: 'v2021.1', value: +(factor.value * 1.18).toFixed(4), effectiveDate: '2021-04-01', source: '生态环境部 2021 年度', status: '已停用', updateBy: '李铭哲', updateDate: '2021-05-08 11:36' } ]; return ( 关闭}>
同一因子可有多版本。系统在数据填报与计算时,自动根据报告期匹配生效版本。
{r.version} }, { key: 'value', title: '因子值', width: 110, align: 'right' }, { key: 'effectiveDate', title: '生效日期', width: 110 }, { key: 'source', title: '数据来源' }, { key: 'status', title: '状态', width: 90, align: 'center', render: (r) => {r.status} }, { key: 'updateBy', title: '操作人', width: 90 }, { key: 'updateDate', title: '更新时间', width: 150 } ]} data={versions} rowKey="version" /> ); } function FactorImport({ onClose, onDone }) { return (
支持 .xlsx / .csv 格式。模板字段:因子名称、排放类别、因子值、单位、数据来源、生效日期、版本号、适用行业、核算标准。
点击或拖拽因子库文件到此处
); } // ========================================================= // 2. 组织管理 // ========================================================= function Organization() { const toast = useToast(); const { orgId, setOrgId } = useOrg(); const confirm = useConfirm(); const [tree, setTree] = useState(DF.ORG_TREE); const [selectedId, setSelectedId] = useState(orgId); const [modal, setModal] = useState(null); const flat = useMemo(() => { const out = []; const walk = (n, parent) => { out.push({ ...n, parentId: parent?.id || null, parentName: parent?.name || null }); (n.children || []).forEach(c => walk(c, n)); }; walk(tree, null); return out; }, [tree]); const selected = flat.find(o => o.id === selectedId) || flat[0]; const children = flat.filter(o => o.parentId === selected.id); // Aggregate emissions for selected const agg = DF.aggregate(selected.id); const handleAddChild = () => { setModal({ mode: 'add', data: { name: '', code: 'ZL-', level: (selected.level || 0) + 1, industry: selected.industry, address: '', employees: 0, standard: selected.standard, parentId: selected.id }}); }; const handleSubmit = (data) => { // Helper to immutably update tree const updateNode = (node) => { if (modal.mode === 'edit' && node.id === data.id) { return { ...node, ...data, children: node.children || [] }; } if (modal.mode === 'add' && node.id === data.parentId) { const newChild = { ...data, id: 'org-new-' + Date.now(), children: [] }; return { ...node, children: [...(node.children || []), newChild] }; } return { ...node, children: (node.children || []).map(updateNode) }; }; setTree(updateNode(tree)); toast.push(modal.mode === 'add' ? '新增组织成功' : '编辑组织成功'); setModal(null); }; const handleDelete = async (node) => { const ok = await confirm({ title: '删除组织', message: <>确定删除 {node.name}?删除组织将同时移除其下级组织和所有关联填报数据。 }); if (!ok) return; const removeNode = (n) => ({ ...n, children: (n.children || []).filter(c => c.id !== node.id).map(removeNode) }); setTree(removeNode(tree)); toast.push('已删除'); if (selectedId === node.id) setSelectedId(tree.id); }; return ( <> 新增子组织} />
展开全部}>
setSelectedId(n.id)} />
共 {flat.length} 个组织节点 · {flat.filter(o => o.level === 4).length} 个末级单元
{selected.level > 1 && }
}>
{[ ['组织编码', selected.code], ['组织层级', `${selected.level} 级(${['集团', '板块', '子公司', '厂区'][selected.level - 1]})`], ['所属行业', DF.INDUSTRIES.find(i => i.code === selected.industry)?.name], ['上级组织', selected.parentName || '—'], ['核算标准', selected.standard], ['员工总数', `${fmt(selected.employees, 0)} 人`], ['注册地址', selected.address], ['末级单元数', flat.filter(o => o.level === 4 && DF.getDescendants(selected.id).includes(o.id)).length + ' 个'] ].map(([k, v], i) => (
{k} {v}
))}
本组织(含下属)年度排放
{fmt(agg.total, 0)}tCO₂e
范围一
{fmt(agg.byScope[1], 0)}tCO₂e
范围二 + 三
{fmt((agg.byScope[2] || 0) + (agg.byScope[3] || 0), 0)}tCO₂e
{children.length === 0 ? (
该组织为末级单元,无下级
{selected.level < 4 && }
) : (
( setSelectedId(r.id)} style={{ cursor: 'pointer' }}>{r.name} )}, { key: 'code', title: '编码', width: 130 }, { key: 'industry', title: '行业', width: 110, render: (r) => DF.INDUSTRIES.find(i => i.code === r.industry)?.name }, { key: 'employees', title: '员工', width: 80, align: 'right' }, { key: 'emission', title: '年度排放 (tCO₂e)', width: 170, align: 'right', render: (r) => { const a = DF.aggregate(r.id); return {fmt(a.total, 0)}; }}, { key: 'actions', title: '操作', width: 180, render: (r) => (
)} ]} data={children} rowKey="id" /> )} {modal && setModal(null)} onSubmit={handleSubmit} />} ); } function OrgForm({ mode, initialData, onClose, onSubmit }) { const [data, setData] = useState(initialData); const [errors, setErrors] = useState({}); const set = (k, v) => { setData(d => ({ ...d, [k]: v })); setErrors(e => ({ ...e, [k]: undefined })); }; const submit = () => { const errs = {}; if (!data.name) errs.name = '请输入组织名称'; if (!data.code) errs.code = '请输入组织编码'; else if (!/^[A-Za-z0-9-]+$/.test(data.code)) errs.code = '编码仅允许字母数字和短横线'; if (Object.keys(errs).length) { setErrors(errs); return; } onSubmit({ ...data, employees: Number(data.employees) || 0 }); }; const levelName = ['集团', '板块', '子公司', '厂区'][data.level - 1] || '组织'; return ( }>
set('name', v)} style={{ width: '100%' }} /> set('code', v)} style={{ width: '100%' }} /> set('standard', v)} style={{ width: '100%' }} /> set('employees', v)} type="number" style={{ width: '100%' }} />
set('address', v)} style={{ width: '100%' }} />
); } window.FactorLibrary = FactorLibrary; window.Organization = Organization;