// 排放因子库 + 组织管理
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) => (
)},
{ 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.filter(f => f.standard === 'CHINA').length}条
ISO 14064 通用因子
{factors.filter(f => f.standard === 'ISO').length}条
已停用
{factors.filter(f => f.status === 'disabled').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('category', v)}
options={DF.EMISSION_CATEGORIES.map(c => ({ value: c.code, label: `[范围${c.scope}] ${c.name}` }))}
disabled={readonly} style={{ width: '100%', minWidth: 'auto' }} />
set('value', v)} type="number" disabled={readonly} style={{ width: '100%' }} />
set('unit', v)} disabled={readonly} style={{ width: '100%' }} />
set('standard', v)}
options={[
{ value: 'ISO', label: 'ISO 14064-1:2018' },
{ value: 'CHINA', label: '国家行业指南' }
]} disabled={readonly} style={{ width: '100%', minWidth: 'auto' }} />
set('industry', v)}
options={DF.INDUSTRIES.map(i => ({ value: i.code, label: i.name }))}
disabled={readonly} style={{ width: '100%', minWidth: 'auto' }} />
set('scope', v)}
options={[{ value: 'general', label: '通用因子' }, { value: 'industry', label: '行业专用' }]}
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 && (
)}
);
}
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 (
>}>
);
}
window.FactorLibrary = FactorLibrary;
window.Organization = Organization;