Files
ONE-OS/ONEOS-web/企业台账搭建方案.jsx
王冕 a27e3b8e43 feat: sync full workspace including web modules, docs, and configurations to Gitea
Optimized the root .gitignore to exclude virtual environments, node modules,
and temp folders to ensure clean and lightweight version tracking.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 18:12:25 +08:00

1865 lines
94 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 【重要】必须使用 const Component 作为组件变量名
// ONEOS-web · 企业业务总台账搭建方案(交互汇报页)
// 双维业务总台账七类业务×近12月业绩/成本/利润)+ 车辆运营明细台账(氢/电/ETC/运维)
const Component = function () {
const { useState, useMemo, useCallback, useEffect } = React;
const antd = window.antd || {};
const { Table, Tag, Modal, Button, Space } = antd;
const C_PRIMARY = '#0f766e';
const C_PRIMARY_L = '#14b8a6';
const C_HERO = '#0c1222';
const C_HERO_ACCENT = '#132337';
const C_PAGE = '#f1f5f9';
const C_CARD = '#ffffff';
const C_TEXT = '#0f172a';
const C_MUTED = '#64748b';
const C_LINE = 'rgba(15, 23, 42, 0.08)';
const C_BLUE = '#2563eb';
const C_AMBER = '#d97706';
const C_VIOLET = '#7c3aed';
const C_ROSE = '#e11d48';
const FONT = '-apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", "Segoe UI", sans-serif';
const AXHUB_PROTO_URL = 'https://axhub.im/ax11/a57ed3c0b68f7f6a/#g=1';
const AXHUB_BIZ_LEDGER_URL = 'https://axhub.im/ax11/7587168366793103/?g=1&id=358ccx&p=%E4%B8%9A%E5%8A%A1%E5%8F%B0%E8%B4%A6';
/** 统计双维:业务总台账(经营结果) + 车辆运营明细台账(发生明细) */
const ledgerDimensions = [
{
id: 'bizTotal',
label: '业务总台账',
color: C_BLUE,
role: '按业务线汇总',
summary: '列表展示七类业务费用总计;每类按月份展示近 12 个月业绩、成本、利润(利润 = 业绩 成本)。',
listItems: ['租赁业务', '自营业务', '氢费业务', '电费业务', 'ETC业务', '销售业务', '其他业务'],
},
{
id: 'vehicleOps',
label: '车辆运营明细台账',
color: C_PRIMARY,
role: '按车辆归集明细',
summary: '记录单车维度的氢费、电费、ETC、运维等发生额归集后写入业务总台账对应业务线避免重复录入。',
listItems: ['车辆氢费明细', '电费明细(特来电)', 'ETC 明细', '运维维修/保养/年审等'],
},
];
/** 业务总台账 · 七类业务口径(列表列与按月汇总规则) */
const bizTotalLedgerTypes = [
{
id: 'lease',
label: '租赁业务',
color: C_BLUE,
period: '按月份 · 近 12 个月',
perf: '租金、服务费',
cost: '车辆成本、居间费',
profit: '业绩 成本',
drill: '业绩可钻取至业务员 → 项目/合同',
writeBack: '租赁账单、收款确认;车辆成本来自运维/财务分摊',
},
{
id: 'self',
label: '自营业务',
color: C_VIOLET,
period: '按月份 · 近 12 个月',
perf: '自营明细表 ·「金额」列汇总',
cost: '自营明细中氢费、ETC 费用、薪资、人工报销、日社保服务费、日轮胎费用、车辆费用',
profit: '业绩 成本',
drill: '业绩可钻取至业务员 → 项目',
writeBack: '自营明细 Excel 导入;氢/ETC 可与车辆明细勾稽',
},
{
id: 'h2',
label: '氢费业务',
color: C_PRIMARY,
period: '按月份 · 近 12 个月',
perf: '氢费营收(客户/业务员分摊)',
cost: '加氢采购成本、站点费用等',
profit: '业绩 成本',
drill: '业绩 → 业务员 → 项目',
writeBack: '车辆运营明细 · 车辆氢费明细归集',
},
{
id: 'apply',
label: '电费业务',
color: C_AMBER,
period: '按月份 · 近 12 个月',
perf: '充电营收(内外部车辆区分)',
cost: '场地、损耗、手续费、内部车成本等',
profit: '业绩 成本',
drill: '业绩 → 业务员 → 项目',
writeBack: '车辆运营明细 · 特来电导入电费明细',
},
{
id: 'etc',
label: 'ETC业务',
color: '#0891b2',
period: '按月份 · 近 12 个月',
perf: '通行等 ETC 营收',
cost: 'ETC 通道/服务成本',
profit: '业绩 成本',
drill: '业绩 → 业务员 → 项目',
writeBack: '车辆运营明细 · ETC 供应商抓取',
},
{
id: 'resale',
label: '销售业务',
color: C_ROSE,
period: '按月份 · 近 12 个月',
perf: '车辆销售合同结算金额',
cost: '销售相关成本',
profit: '业绩 成本',
drill: '业绩 → 业务员 → 项目',
writeBack: '销售合同与出库结算',
},
{
id: 'other',
label: '其他业务',
color: C_MUTED,
period: '按月份 · 近 12 个月',
perf: '其他收入项汇总',
cost: '其他成本项汇总',
profit: '业绩 成本',
drill: '按配置是否可钻取',
writeBack: '杂项业务单据',
},
];
const BIZ_LEDGER_CATS = [
{ key: 'lease', groupTitle: '租赁业务', short: '租赁' },
{ key: 'self', groupTitle: '自营业务', short: '自营' },
{ key: 'h2', groupTitle: '氢费业务', short: '氢费' },
{ key: 'apply', groupTitle: '电费业务', short: '电费' },
{ key: 'etc', groupTitle: 'ETC业务', short: 'ETC' },
{ key: 'resale', groupTitle: '销售业务', short: '销售' },
{ key: 'other', groupTitle: '其他业务', short: '其他' },
];
const BIZ_LEDGER_SOURCE_MAP = {
lease: '业绩:租金、服务费(租赁账单);成本:车辆成本、居间费;利润 = 业绩 成本',
self: '业绩自营明细「金额」列成本氢费、ETC、薪资、人工报销、日社保服务费、日轮胎费、车辆费用利润 = 业绩 成本',
h2: '车辆运营明细 → 车辆氢费明细,按客户/业务员分摊计入氢费业务',
apply: '车辆运营明细 → 特来电电费导入,区分内外部车辆',
etc: '车辆运营明细 → ETC 供应商抓取,按车牌归集',
resale: '销售合同与出库结算',
other: '其他业务单据汇总',
};
const fmtBizMoney = (n) => {
if (n === null || n === undefined || n === '') return '—';
const x = Number(n);
if (Number.isNaN(x) || x === 0) return '—';
return x.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
};
const BIZ_LEDGER_MAIN_ROWS = [
{
key: 'm5', month: 5, monthLabel: '5月', rowType: 'month',
selfPerf: 318520.5, selfCost: 256816.4, selfProfit: 61704.1,
leasePerf: 542180, leaseCost: 328600, leaseProfit: 213580,
resalePerf: 412000, resaleCost: 298000, resaleProfit: 114000,
h2Perf: 128560, h2Cost: 77200, h2Profit: 51360,
applyPerf: 18200, applyCost: 5400, applyProfit: 12800,
etcPerf: 6800, etcCost: 3200, etcProfit: 3600,
otherPerf: 9200, otherCost: 3100, otherProfit: 6100,
},
{
key: 'm6', month: 6, monthLabel: '6月', rowType: 'month',
selfPerf: 295400, selfCost: 241200, selfProfit: 54200,
leasePerf: 518900, leaseCost: 315200, leaseProfit: 203700,
resalePerf: 380500, resaleCost: 275000, resaleProfit: 105500,
h2Perf: 119800, h2Cost: 71800, h2Profit: 48000,
applyPerf: 16800, applyCost: 5100, applyProfit: 11700,
etcPerf: 6200, etcCost: 2900, etcProfit: 3300,
otherPerf: 8600, otherCost: 2800, otherProfit: 5800,
},
{
key: 'hint12', month: 0, monthLabel: '…', rowType: 'month',
selfPerf: null, leasePerf: null, resalePerf: null, h2Perf: null, applyPerf: null, etcPerf: null, otherPerf: null,
},
{
key: 'total', month: 13, monthLabel: '合计', rowType: 'total',
selfPerf: 613920.5, selfCost: 498016.4, selfProfit: 115904.1,
leasePerf: 1061080, leaseCost: 643800, leaseProfit: 417280,
resalePerf: 792500, resaleCost: 573000, resaleProfit: 219500,
h2Perf: 248360, h2Cost: 149000, h2Profit: 99360,
applyPerf: 35000, applyCost: 10500, applyProfit: 24500,
etcPerf: 13000, etcCost: 6100, etcProfit: 6900,
otherPerf: 17800, otherCost: 5900, otherProfit: 11900,
},
];
const BIZ_SALES_BY_CAT = {
h2: [
{ key: 's1', salesperson: '尚建华', amount: 53915.2 },
{ key: 's2', salesperson: '刘念念', amount: 44926 },
{ key: 's3', salesperson: '谯云', amount: 19284 },
{ key: 's4', salesperson: '董剑煜', amount: 10334.8 },
],
lease: [
{ key: 'd1', ym: '2026-05', monthRowSpan: 2, deptName: '业务三部', salesperson: '尚建华', mainPerf: 227715.6, h2PerfCol: 53915.2, elecPerf: 7644, etcPerfCol: 2856, otherAmt: 3864 },
{ key: 'd2', ym: '2026-05', monthRowSpan: 0, deptName: '业务三部', salesperson: '刘念念', mainPerf: 189763, h2PerfCol: 44926, elecPerf: 6370, etcPerfCol: 2380, otherAmt: 3220 },
],
self: [
{ key: 'd3', ym: '2026-05', monthRowSpan: 2, deptName: '业务一部', salesperson: '陈思', mainPerf: 133777.4, h2PerfCol: 25872, elecPerf: 3822, etcPerfCol: 1428, otherAmt: 1932 },
{ key: 'd4', ym: '2026-05', monthRowSpan: 0, deptName: '业务一部', salesperson: '周宁', mainPerf: 111481, h2PerfCol: 21560, elecPerf: 3185, etcPerfCol: 1190, otherAmt: 1610 },
],
};
const BIZ_PROJECT_ROWS = [
{ key: 'p1', projectCode: 'PRJ-2026-001', projectName: '嘉兴冷链城配项目', plateNo: '沪A62261F', amount: 27280, bizDate: '2026-05-08', remark: '—' },
{ key: 'p2', projectCode: 'PRJ-2026-018', projectName: '沪浙干线运输', plateNo: '粤AGP3649', amount: 27280, bizDate: '2026-05-15', remark: '—' },
{ key: 'p3', projectCode: 'PRJ-2026-033', projectName: '园区短驳', plateNo: '苏ED32891', amount: 27280, bizDate: '2026-05-22', remark: '演示' },
];
const sectionNav = [
{ key: 'hero', label: '汇报总览' },
{ key: 'dimensions', label: '双维统计' },
{ key: 'bizTypes', label: '业务总台账' },
{ key: 'mindmap', label: '车辆明细' },
{ key: 'principles', label: '搭建原则' },
{ key: 'domains', label: '明细搭建' },
{ key: 'workflows', label: '环节样表' },
{ key: 'links', label: '勾稽关系' },
{ key: 'drill', label: '钻取路径' },
{ key: 'samples', label: '样表索引' },
{ key: 'summary', label: '决策要点' },
];
const kpiStats = [
{ label: '统计维度', value: '2', unit: '类', color: C_PRIMARY },
{ label: '业务总台账', value: '7', unit: '业务线', color: C_BLUE },
{ label: '按月展示', value: '12', unit: '个月', color: C_AMBER },
{ label: '利润公式', value: '绩−成', unit: '统一', color: C_VIOLET },
];
const renderProtoStatus = (status) => {
if (status === 'done') return <Tag color="success" style={{ margin: 0 }}>原型已实现</Tag>;
if (status === 'partial') return <Tag color="processing" style={{ margin: 0 }}>部分实现</Tag>;
return <Tag color="default" style={{ margin: 0 }}>待建设</Tag>;
};
/** 各环节:对照 Axhub / web 端原型路径与样表 key */
const ledgerWorkflows = {
bizTotal: {
color: C_BLUE,
steps: [
{ key: 'businessLedger', name: '业务总台账列表', status: 'done', protoPath: 'web端/数据分析/业务台账.jsx', sampleKey: 'businessLedger', flow: '七类业务 × 近12月业绩/成本/利润;仅业绩可钻取', link: 'Axhub 业务台账' },
{ key: 'leaseRule', name: '租赁业务口径', status: 'done', protoPath: '租赁账单 + 成本分摊', sampleKey: 'leaseRent', flow: '业绩=租金+服务费;成本=车辆成本+居间费' },
{ key: 'selfRule', name: '自营业务口径', status: 'partial', protoPath: '自营明细导入', sampleKey: 'selfImport', flow: '业绩=金额列;成本=氢费/ETC/薪资/报销/日社保/日轮胎/车辆费' },
],
},
lease: {
color: C_BLUE,
steps: [
{ key: 'leaseProfit', name: '租赁·客户利润样表(参考)', status: 'partial', protoPath: '业务总台账·租赁业务列', sampleKey: 'lease', flow: '汇总层见业务总台账;本样表为客户维度参考' },
{ key: 'leaseRent', name: '收入·租赁业务(租金/服务费)', status: 'partial', protoPath: 'web端/财务管理/租赁账单.jsx', sampleKey: 'leaseRent', flow: '合同账单确认后计入收入' },
{ key: 'leaseH2Rollup', name: '收入·氢费(从车辆氢费取)', status: 'done', protoPath: 'web端/台账数据/车辆氢费明细.jsx', sampleKey: 'vehicleH2Detail', flow: '已对账氢费按客户/合同汇总' },
{ key: 'leaseCost', name: '成本·车辆/居间费', status: 'partial', protoPath: '运维台账+财务', sampleKey: 'leaseCost', flow: '车辆成本、居间费分摊至客户' },
],
},
h2: {
color: C_PRIMARY,
steps: [
{ key: 'h2detail', name: '氢费明细·车辆氢费明细', status: 'done', protoPath: 'web端/台账数据/车辆氢费明细.jsx', sampleKey: 'vehicleH2Detail', flow: '待保存 → 保存 → 未对账 → 完成对账 → 已对账' },
{ key: 'h2customer', name: '氢费明细·按客户归集', status: 'done', protoPath: 'web端/数据分析/租赁客户氢费台账.jsx', sampleKey: 'leaseCustomerH2Sum', flow: '按加氢时间合并到对应客户;明细 Tab + 总计 Tab' },
{ key: 'h2station', name: '加氢站账户·站点汇总', status: 'done', protoPath: 'web端/台账数据/氢费采购端汇总报表.jsx', sampleKey: 'h2PurchaseSummary', flow: '应付/已付/未付/开票/余额;金额可钻取' },
{ key: 'h2stationPay', name: '加氢站·已付/充值钻取', status: 'done', protoPath: '氢费采购端汇总报表·钻取弹窗', sampleKey: 'h2PurchasePay', flow: '付款账单、充值明细、余额变更' },
{ key: 'h2custAcct', name: '客户预充值·账户扣款', status: 'partial', protoPath: '能源账户(待统一入口)', sampleKey: 'h2Account', flow: '客户氢费扣款、充值导入、变更记录' },
],
},
electric: {
color: C_AMBER,
steps: [
{ key: 'elecImport', name: '电费明细·特来电导入', status: 'partial', protoPath: '待建:电费明细页', sampleKey: 'electric', flow: '平台导出 CSV/Excel 后导入' },
{ key: 'elecRevenue', name: '营收·外部车/租赁内部车', status: 'partial', protoPath: '—', sampleKey: 'electricRevenue', flow: '区分非库存外部车与租赁内部车' },
{ key: 'elecCost', name: '成本·场地/损耗/手续费/自营内部车', status: 'partial', protoPath: '—', sampleKey: 'electricCost', flow: '成本分拆计入自营或站点' },
],
},
etc: {
color: '#0891b2',
steps: [
{ key: 'etcFetch', name: 'ETC 明细·供应商抓取', status: 'partial', protoPath: '—', sampleKey: 'etc', flow: 'ETC 供应商网站自动扒取' },
{ key: 'etcAcct', name: '客户 ETC 预充值账户', status: 'partial', protoPath: '—', sampleKey: 'etcAccount', flow: '预充值、实时扣费流水' },
],
},
ops: {
color: C_PRIMARY_L,
steps: [
{ key: 'opsRepair', name: '维修明细', status: 'done', protoPath: 'web端/台账数据/车辆维修明细.jsx', sampleKey: 'opsRepair', flow: '待保存 → 确认提交(提交后锁定)' },
{ key: 'opsMaintain', name: '保养明细', status: 'partial', protoPath: '车辆维修明细·类型=保养', sampleKey: 'opsMaintain', flow: '同维修页,类型区分' },
{ key: 'opsAnnual', name: '年审明细', status: 'partial', protoPath: 'web端/运维管理/车辆业务/年审管理.jsx', sampleKey: 'opsAnnual', flow: '年审办理结果回写' },
{ key: 'opsTire', name: '轮胎磨损明细', status: 'partial', protoPath: '—', sampleKey: 'opsTire', flow: '待建专用表或并入运维其他' },
{ key: 'opsOther', name: '其他运维', status: 'partial', protoPath: '—', sampleKey: 'opsOther', flow: '杂项运维费用' },
],
},
safety: {
color: C_ROSE,
steps: [
{ key: 'safetyClaim', name: '保险赔付', status: 'partial', protoPath: 'web端/安全管理/事故管理.jsx', sampleKey: 'safety', flow: '事故过程、责任、赔付金额' },
],
},
self: {
color: C_VIOLET,
steps: [
{ key: 'selfImport', name: '自营明细导入', status: 'partial', protoPath: '—', sampleKey: 'selfImport', flow: 'Excel 导入自营收支' },
{ key: 'selfDriver', name: '司机·工资/社保', status: 'partial', protoPath: '—', sampleKey: 'selfDriver', flow: '人力成本按月' },
{ key: 'selfVehicle', name: '车辆·损耗/运维/氢电ETC', status: 'partial', protoPath: '车辆氢费明细+运维台账', sampleKey: 'self', flow: '氢电 ETC 从对应车辆明细取' },
],
},
};
/** 车辆运营明细台账分支(为业务总台账提供明细数据源) */
const ledgerBranches = [
{
id: 'h2',
label: '车辆运营·氢费',
color: C_PRIMARY,
tag: '能源',
formula: '明细 → 客户归集 → 账户扣款',
anchor: '加氢时间 · 车牌 · 客户',
listShows: '氢费明细列表、加氢站账户余额、客户预充值账户',
detailShows: '手动录入/导入明细;按加氢时间合并至客户;充值与变更记录',
writeBack: '导入/录入明细;加氢站账户扣款;客户预充值账户扣款',
sampleKey: 'h2',
blocks: [
{
title: '氢费明细',
nodes: [
{ label: '车辆氢费明细', note: '手动记录 / 导入;现有车辆氢费明细功能' },
{ label: '客户归集', note: '自动按加氢时间合并到对应客户' },
],
},
{
title: '加氢站账户',
nodes: [
{ label: '站点扣款', note: '加氢站产生的氢费从账户直接扣' },
{ label: '充值导入', note: '充值记录导入、变更记录' },
],
},
{
title: '客户预充值',
nodes: [
{ label: '客户扣款', note: '客户氢费从客户账户直接扣' },
{ label: '充值导入', note: '充值记录导入、变更记录' },
],
},
],
},
{
id: 'electric',
label: '车辆运营·电费',
color: C_AMBER,
tag: '能源',
formula: '特来电导出 → 导入 → 营收/成本分拆',
anchor: '充电记录 · 车牌 · 内外部车辆',
listShows: '电费明细、营收(外部车/租赁内部车)、成本(场地/损耗/手续费/自营内部车)',
detailShows: '特来电平台导出后导入;区分外部非库存车与内部业务车辆',
writeBack: 'Excel/CSV 导入;归集至业务总台账·电费业务',
sampleKey: 'electric',
blocks: [
{
title: '电费明细',
nodes: [{ label: '明细导入', note: '特来电平台手动导出后导入' }],
},
{
title: '营收',
nodes: [
{ label: '外部车辆', note: '非库存车辆' },
{ label: '租赁业务内部车辆', note: '' },
],
},
{
title: '成本',
nodes: [
{ label: '场地租金', note: '' },
{ label: '损耗费', note: '' },
{ label: '手续费', note: '' },
{ label: '自营业务内部车辆', note: '' },
],
},
],
},
{
id: 'etc',
label: '车辆运营·ETC',
color: '#0891b2',
tag: '通行',
formula: '供应商抓取 + 客户预充值实时扣',
anchor: 'ETC 账户 · 客户 · 车牌',
listShows: 'ETC 明细、客户 ETC 账户余额、扣费流水',
detailShows: '供应商网站自动扒取;客户可预充值;费用实时扣取记录',
writeBack: '爬取/同步明细;账户充值;归集至租赁与自营台账',
sampleKey: 'etc',
blocks: [
{
title: 'ETC 明细',
nodes: [{ label: '自动抓取', note: 'ETC 供应商网站自动扒取' }],
},
{
title: '账户',
nodes: [
{ label: '客户预充值', note: '客户可预充值 ETC 账户' },
{ label: '实时扣费', note: '客户 ETC 费用有记录并实时扣取' },
],
},
],
},
{
id: 'ops',
label: '车辆运营·运维',
color: C_PRIMARY_L,
tag: '成本来源',
formula: '多类明细 → 车辆/自营成本',
anchor: '车牌 · 工单号 · 账期',
listShows: '维修、保养、年审、轮胎磨损、其他运维明细',
detailShows: '按类型分表;关联车辆与费用承担方(租赁/自营)',
writeBack: '维修站派单、年审办理、保养记录入库',
sampleKey: 'ops',
blocks: [
{
title: '明细类型',
nodes: [
{ label: '维修明细', note: '' },
{ label: '保养明细', note: '' },
{ label: '年审明细', note: '' },
{ label: '轮胎磨损明细', note: '' },
{ label: '其他', note: '' },
],
},
],
},
{
id: 'safety',
label: '安全台账',
color: C_ROSE,
tag: '风控',
formula: '事故过程 → 责任 → 赔付',
anchor: '事故单号 · 保单',
listShows: '保险赔付记录、责任认定、赔付金额状态',
detailShows: '事故发生过程、责任划分、保险赔付金额与进度',
writeBack: '事故管理结案、保险理赔结果',
sampleKey: 'safety',
blocks: [
{
title: '保险赔付',
nodes: [
{ label: '事故过程', note: '出现事故时的过程记录' },
{ label: '责任', note: '责任认定' },
{ label: '赔付金额', note: '保险赔付金额' },
],
},
],
},
];
const crossLinks = [
{ from: '车辆氢费明细', to: '业务总台账·氢费业务', rule: '按车牌/客户汇总业绩与成本', color: C_PRIMARY },
{ from: '车辆氢费明细', to: '业务总台账·自营业务成本', rule: '自营明细氢费列勾稽', color: C_PRIMARY },
{ from: '车辆电费明细', to: '业务总台账·电费业务', rule: '特来电导入后归集', color: C_AMBER },
{ from: '车辆 ETC 明细', to: '业务总台账·ETC业务', rule: '供应商抓取归集', color: '#0891b2' },
{ from: '运维各类明细', to: '业务总台账·租赁业务成本', rule: '车辆成本、居间相关分摊', color: C_PRIMARY_L },
{ from: '租赁账单', to: '业务总台账·租赁业务业绩', rule: '租金、服务费计入业绩', color: C_BLUE },
{ from: '自营明细导入', to: '业务总台账·自营业务', rule: '金额列=业绩;多列费用=成本', color: C_VIOLET },
];
const principles = [
{ id: 'two', title: '双维统计', desc: '「业务总台账」看七类业务按月业绩/成本/利润;「车辆运营明细台账」看单车发生额,向上归集,不重复记账。', icon: '维' },
{ id: 'month12', title: '近 12 个月', desc: '业务总台账每类业务按月份展示近 12 个月三列(业绩、成本、利润),并支持年度筛选与合计行。', icon: '月' },
{ id: 'profit', title: '统一利润公式', desc: '各类业务利润均为「业绩 成本」;租赁业绩来自租金+服务费,自营业绩来自自营明细金额列。', icon: '利' },
{ id: 'hub', title: '明细归集不上浮重复', desc: '氢/电/ETC 先在车辆运营明细落账,再汇总进业务总台账对应业务线;账户层只管预充值与扣款轨迹。', icon: '枢' },
];
const drillPaths = [
{
id: 'd0',
title: '业务部汇总:业绩 → 业务员 → 项目',
scene: '业务台账',
steps: ['年份+业务部', '七类业务×近12月', '业绩/成本/利润', '点击业绩', '业务员', '项目明细'],
color: C_BLUE,
},
{
id: 'd1',
title: '租赁利润:总览 → 收入项 → 车辆明细',
scene: '租赁台账',
steps: ['租赁台账汇总', '展开收入(租金/氢/电/ETC', '点击氢费金额', '车辆氢费明细', '加氢订单/导入行'],
color: C_BLUE,
},
{
id: 'd2',
title: '氢费:明细 → 客户 → 账户',
scene: '氢费台账',
steps: ['氢费明细列表', '按加氢时间归集客户', '客户氢费汇总', '预充值账户余额', '充值/扣款变更记录'],
color: C_PRIMARY,
},
{
id: 'd3',
title: '电费:导入 → 营收/成本 → 车辆',
scene: '电费台账',
steps: ['特来电导出导入', '电费明细表', '筛选内部/外部车辆', '单车电费', '回写租赁收入·电费'],
color: C_AMBER,
},
{
id: 'd4',
title: 'ETC抓取 → 账户 → 实时扣费',
scene: 'ETC 台账',
steps: ['供应商自动扒取', 'ETC 明细', '客户 ETC 账户', '预充值记录', '通行扣费流水'],
color: '#0891b2',
},
{
id: 'd5',
title: '运维:类型 → 工单 → 车辆成本',
scene: '运维台账',
steps: ['选择明细类型(维修/年审等)', '工单列表', '费用与承担方', '车辆运维成本', '进入租赁/自营成本'],
color: C_PRIMARY_L,
},
{
id: 'd6',
title: '安全:事故 → 责任 → 赔付',
scene: '安全台账',
steps: ['事故列表', '过程与责任认定', '保险赔付申请', '赔付金额确认', '归档至安全台账'],
color: C_ROSE,
},
{
id: 'd7',
title: '自营明细 → 业务总台账·自营业务',
scene: '自营明细',
steps: ['自营明细导入', '金额列汇总业绩', '成本列汇总', '业务总台账按月', '利润=业绩−成本'],
color: C_VIOLET,
},
];
const sampleTables = {
businessLedger: {
title: '业务总台账列表(七类业务 × 近12月',
protoNote: 'Axhub 业务台账 · web端/数据分析/业务台账.jsx · 租赁/自营口径见「业务总台账」章节',
columns: [],
data: [],
interactive: true,
},
businessLedgerDrill: {
title: '钻取 · 业务员业绩第2层',
protoNote: '氢费/销售/电/ETC业务员+业绩金额;自营/租赁部门宽表含氢电ETC列',
columns: [
{ title: '业务员', dataIndex: 'salesperson', width: 88 },
{ title: '业绩金额', dataIndex: 'amount', width: 96, align: 'right' },
{ title: '说明', key: 'hint', render: () => '点击金额 → 项目' },
],
data: BIZ_SALES_BY_CAT.h2,
},
lease: {
title: '租赁台账 · 客户利润汇总',
protoNote: '导图:收入−成本=利润;氢/电/ETC 从车辆明细归集',
columns: [
{ title: '客户', dataIndex: 'cust', width: 140, ellipsis: true },
{ title: '租金', dataIndex: 'rent', width: 88, align: 'right' },
{ title: '服务费', dataIndex: 'svc', width: 88, align: 'right' },
{ title: '氢费收入', dataIndex: 'h2', width: 88, align: 'right' },
{ title: '电费收入', dataIndex: 'elec', width: 88, align: 'right' },
{ title: 'ETC收入', dataIndex: 'etc', width: 88, align: 'right' },
{ title: '车辆成本', dataIndex: 'vcost', width: 88, align: 'right' },
{ title: '居间费', dataIndex: 'broker', width: 80, align: 'right' },
{ title: '利润', dataIndex: 'profit', width: 88, align: 'right' },
],
data: [{ key: '1', cust: '上海迅杰物流', rent: '380,000', svc: '40,000', h2: '128,560', elec: '18,200', etc: '6,800', vcost: '260,000', broker: '50,000', profit: '263,560' }],
},
leaseRent: {
title: '收入 · 租赁业务(租金/服务费)',
columns: [
{ title: '合同号', dataIndex: 'contract', width: 130, ellipsis: true },
{ title: '客户', dataIndex: 'cust', width: 120, ellipsis: true },
{ title: '账期', dataIndex: 'period', width: 88 },
{ title: '租金(元)', dataIndex: 'rent', width: 96, align: 'right' },
{ title: '服务费(元)', dataIndex: 'svc', width: 96, align: 'right' },
{ title: '状态', dataIndex: 'st', width: 80 },
],
data: [{ key: '1', contract: 'LNZLHTSH2023071301', cust: '上海迅杰物流', period: '2026-05', rent: '35,000', svc: '5,000', st: '已确认' }],
},
leaseCost: {
title: '成本 · 车辆成本 / 居间费',
columns: [
{ title: '成本项', dataIndex: 'item', width: 100 },
{ title: '车牌/客户', dataIndex: 'ref', width: 120, ellipsis: true },
{ title: '金额(元)', dataIndex: 'amt', width: 96, align: 'right' },
{ title: '来源台账', dataIndex: 'src', width: 100 },
],
data: [
{ key: '1', item: '车辆成本', ref: '浙F06900F', amt: '12,500', src: '运维台账' },
{ key: '2', item: '居间费', ref: '上海迅杰', amt: '8,000', src: '财务录入' },
],
},
vehicleH2Detail: {
title: '车辆氢费明细(原型已实现)',
protoNote: 'web端/台账数据/车辆氢费明细.jsx · 待保存/未对账/已对账',
columns: [
{ title: '状态', dataIndex: 'st', width: 72 },
{ title: '加氢时间', dataIndex: 'time', width: 140 },
{ title: '加氢站', dataIndex: 'station', width: 120, ellipsis: true },
{ title: '客户', dataIndex: 'cust', width: 120, ellipsis: true },
{ title: '车牌', dataIndex: 'plate', width: 100 },
{ title: '加氢量', dataIndex: 'kg', width: 72, align: 'right' },
{ title: '加氢单价', dataIndex: 'up', width: 80, align: 'right' },
{ title: '加氢总价', dataIndex: 'amt', width: 88, align: 'right' },
{ title: '承担方式', dataIndex: 'bear', width: 100 },
{ title: '订单号', dataIndex: 'orderNo', width: 110, ellipsis: true },
],
data: [
{ key: '1', st: '未对账', time: '2026-05-28 14:20:08', station: '嘉兴港区站', cust: '上海迅杰物流', plate: '浙F06900F', kg: '12.50', up: '70.00', amt: '875.00', bear: '客户承担', orderNo: 'H2260528001' },
{ key: '2', st: '已对账', time: '2026-05-20 09:15:00', station: '平湖站', cust: '上海迅杰物流', plate: '浙F06900F', kg: '10.20', up: '68.00', amt: '693.60', bear: '客户承担', orderNo: 'H2260520008' },
],
},
leaseCustomerH2Detail: {
title: '租赁客户氢费明细 Tab原型已实现',
protoNote: 'web端/数据分析/租赁客户氢费台账.jsx',
columns: [
{ title: '业务员', dataIndex: 'sales', width: 80 },
{ title: '客户', dataIndex: 'cust', width: 140, ellipsis: true },
{ title: '日期', dataIndex: 'date', width: 96 },
{ title: '车牌', dataIndex: 'plate', width: 100 },
{ title: '加氢数量', dataIndex: 'kg', width: 80, align: 'right' },
{ title: '加氢地点', dataIndex: 'loc', width: 140, ellipsis: true },
{ title: '金额', dataIndex: 'amt', width: 88, align: 'right' },
],
data: [{ key: '1', sales: '张明辉', cust: '上海迅杰物流', date: '2026-05-28', plate: '浙F06900F', kg: '12.50', loc: '嘉兴港区加氢站', amt: '875.00' }],
},
leaseCustomerH2Sum: {
title: '租赁客户氢费总计 Tab按客户×',
columns: [
{ title: '客户', dataIndex: 'cust', width: 180, ellipsis: true },
{ title: '统计月份', dataIndex: 'month', width: 96 },
{ title: '加氢数量合计', dataIndex: 'kg', width: 110, align: 'right' },
{ title: '金额合计', dataIndex: 'amt', width: 110, align: 'right' },
],
data: [{ key: '1', cust: '上海迅杰物流有限公司', month: '2026-05', kg: '1,286.50', amt: '128,560.00' }],
},
h2PurchaseSummary: {
title: '氢费(采购端)汇总报表(原型已实现)',
protoNote: 'web端/台账数据/氢费采购端汇总报表.jsx · 点击金额钻取',
columns: [
{ title: '地区', dataIndex: 'region', width: 80 },
{ title: '加氢站全称', dataIndex: 'station', width: 160, ellipsis: true },
{ title: '结算方式', dataIndex: 'settle', width: 72 },
{ title: '加氢总量', dataIndex: 'kg', width: 88, align: 'right' },
{ title: '应付总金额', dataIndex: 'payable', width: 100, align: 'right' },
{ title: '已付总金额', dataIndex: 'paid', width: 100, align: 'right' },
{ title: '未付总金额', dataIndex: 'unpaid', width: 100, align: 'right' },
{ title: '当前余额', dataIndex: 'bal', width: 96, align: 'right' },
],
data: [{ key: '1', region: '浙江嘉兴', station: '嘉兴港区加氢站', settle: '月结', kg: '8,520', payable: '596,400', paid: '520,000', unpaid: '76,400', bal: '12,600' }],
},
h2PurchasePay: {
title: '钻取 · 已付总金额(账单+付款证明)',
columns: [
{ title: '加氢站', dataIndex: 'station', width: 140, ellipsis: true },
{ title: '账单开始', dataIndex: 'start', width: 96 },
{ title: '账单结束', dataIndex: 'end', width: 96 },
{ title: '应付(元)', dataIndex: 'payable', width: 88, align: 'right' },
{ title: '已付(元)', dataIndex: 'paid', width: 88, align: 'right' },
{ title: '操作', key: 'op', width: 100, render: () => <span style={{ color: C_PRIMARY, fontWeight: 600 }}>付款凭据</span> },
],
data: [{ key: '1', station: '嘉兴港区加氢站', start: '2026-05-01', end: '2026-05-31', payable: '76,400', paid: '76,400' }],
},
h2Account: {
title: '氢费账户 · 加氢站 / 客户预充值',
columns: [
{ title: '账户类型', dataIndex: 'type', width: 100 },
{ title: '主体', dataIndex: 'name', ellipsis: true },
{ title: '余额(元)', dataIndex: 'bal', width: 100, align: 'right' },
{ title: '最近充值', dataIndex: 'topup', width: 100 },
{ title: '操作', key: 'op', width: 120, render: () => <span style={{ color: C_PRIMARY, fontWeight: 600 }}>充值/变更</span> },
],
data: [
{ key: '1', type: '加氢站账户', name: '嘉兴港区加氢站', bal: '12,600.00', topup: '2026-05-20' },
{ key: '2', type: '客户预充值', name: '上海迅杰物流', bal: '80,000.00', topup: '2026-05-15' },
],
},
electric: {
title: '电费明细 · 特来电导入(待建页)',
columns: [
{ title: '充电时间', dataIndex: 'time', width: 140 },
{ title: '车牌', dataIndex: 'plate', width: 100 },
{ title: '车辆属性', dataIndex: 'attr', width: 120 },
{ title: '电量(kWh)', dataIndex: 'kwh', width: 88, align: 'right' },
{ title: '金额(元)', dataIndex: 'amt', width: 96, align: 'right' },
{ title: '导入批次', dataIndex: 'batch', width: 96 },
],
data: [
{ key: '1', time: '2026-05-27 09:10', plate: '浙F06900F', attr: '租赁业务内部车辆', kwh: '86', amt: '172.00', batch: 'TELD-202605' },
{ key: '2', time: '2026-05-26 18:00', plate: '外协-苏E88888', attr: '外部车辆(非库存)', kwh: '40', amt: '80.00', batch: 'TELD-202605' },
],
},
electricRevenue: {
title: '电费台账 · 营收分拆',
columns: [
{ title: '口径', dataIndex: 'type', width: 140 },
{ title: '电量合计', dataIndex: 'kwh', width: 96, align: 'right' },
{ title: '金额(元)', dataIndex: 'amt', width: 96, align: 'right' },
],
data: [
{ key: '1', type: '外部车辆(非库存车辆)', kwh: '1,240', amt: '2,480' },
{ key: '2', type: '租赁业务内部车辆', kwh: '5,680', amt: '11,360' },
],
},
electricCost: {
title: '电费台账 · 成本分拆',
columns: [
{ title: '成本项', dataIndex: 'item', width: 120 },
{ title: '金额(元)', dataIndex: 'amt', width: 96, align: 'right' },
{ title: '备注', dataIndex: 'note', ellipsis: true },
],
data: [
{ key: '1', item: '场地租金', amt: '18,000', note: '充电场地' },
{ key: '2', item: '损耗费', amt: '2,400', note: '' },
{ key: '3', item: '手续费', amt: '800', note: '' },
{ key: '4', item: '自营业务内部车辆', amt: '4,200', note: '内部用电成本' },
],
},
etc: {
title: 'ETC 明细 · 供应商抓取(待建)',
columns: [
{ title: '通行时间', dataIndex: 'time', width: 140 },
{ title: '车牌', dataIndex: 'plate', width: 100 },
{ title: '路段', dataIndex: 'road', ellipsis: true },
{ title: '金额(元)', dataIndex: 'amt', width: 88, align: 'right' },
{ title: '数据来源', dataIndex: 'src', width: 88 },
],
data: [{ key: '1', time: '2026-05-28 08:12', plate: '浙F06900F', road: '沪杭高速·嘉兴段', amt: '68.50', src: '自动扒取' }],
},
etcAccount: {
title: 'ETC · 客户预充值与扣费流水',
columns: [
{ title: '客户', dataIndex: 'cust', width: 140, ellipsis: true },
{ title: '账户余额', dataIndex: 'bal', width: 96, align: 'right' },
{ title: '流水类型', dataIndex: 'type', width: 80 },
{ title: '金额(元)', dataIndex: 'amt', width: 88, align: 'right' },
{ title: '时间', dataIndex: 'time', width: 140 },
],
data: [
{ key: '1', cust: '上海迅杰物流', bal: '5,200', type: '预充值', amt: '+10,000', time: '2026-05-01 10:00' },
{ key: '2', cust: '上海迅杰物流', bal: '5,131.5', type: '扣费', amt: '-68.50', time: '2026-05-28 08:12' },
],
},
opsRepair: {
title: '运维 · 维修明细(原型已实现)',
protoNote: 'web端/台账数据/车辆维修明细.jsx',
columns: [
{ title: '状态', dataIndex: 'st', width: 72 },
{ title: '进修日期', dataIndex: 'date', width: 96 },
{ title: '车牌', dataIndex: 'plate', width: 100 },
{ title: '类型', dataIndex: 'type', width: 56 },
{ title: '项目/材料', dataIndex: 'item', width: 140, ellipsis: true },
{ title: '含税合计', dataIndex: 'amt', width: 88, align: 'right' },
],
data: [{ key: '1', st: '已提交', date: '2026-05-18', plate: '浙F06900F', type: '维修', item: '更换燃料电池冷却泵', amt: '3,200.00' }],
},
opsMaintain: {
title: '运维 · 保养明细',
columns: [
{ title: '进修日期', dataIndex: 'date', width: 96 },
{ title: '车牌', dataIndex: 'plate', width: 100 },
{ title: '保养项目', dataIndex: 'item', ellipsis: true },
{ title: '金额(元)', dataIndex: 'amt', width: 88, align: 'right' },
],
data: [{ key: '1', date: '2026-05-10', plate: '沪BDB9161', item: '首保-机油三滤', amt: '1,280.00' }],
},
opsAnnual: {
title: '运维 · 年审明细',
columns: [
{ title: '年审单号', dataIndex: 'no', width: 120 },
{ title: '车牌', dataIndex: 'plate', width: 100 },
{ title: '办理状态', dataIndex: 'st', width: 88 },
{ title: '费用(元)', dataIndex: 'amt', width: 88, align: 'right' },
{ title: '到期日', dataIndex: 'exp', width: 96 },
],
data: [{ key: '1', no: 'NS202605008', plate: '沪BDB9161', st: '已完成', amt: '680.00', exp: '2027-06-04' }],
},
opsTire: {
title: '运维 · 轮胎磨损明细(待建)',
columns: [
{ title: '车牌', dataIndex: 'plate', width: 100 },
{ title: '轮位', dataIndex: 'pos', width: 72 },
{ title: '磨损描述', dataIndex: 'desc', ellipsis: true },
{ title: '预估费用', dataIndex: 'amt', width: 88, align: 'right' },
],
data: [{ key: '1', plate: '浙F06900F', pos: '右后', desc: '花纹深度不足', amt: '2,400.00' }],
},
opsOther: {
title: '运维 · 其他',
columns: [
{ title: '摘要', dataIndex: 'desc', ellipsis: true },
{ title: '车牌', dataIndex: 'plate', width: 100 },
{ title: '金额(元)', dataIndex: 'amt', width: 88, align: 'right' },
],
data: [{ key: '1', desc: '拖车救援', plate: '粤AGP9827', amt: '800.00' }],
},
safety: {
title: '安全 · 保险赔付',
columns: [
{ title: '事故号', dataIndex: 'acc', width: 120 },
{ title: '车牌', dataIndex: 'plate', width: 100 },
{ title: '过程摘要', dataIndex: 'proc', width: 160, ellipsis: true },
{ title: '责任', dataIndex: 'duty', width: 72 },
{ title: '赔付金额', dataIndex: 'pay', width: 96, align: 'right' },
{ title: '状态', dataIndex: 'st', width: 80 },
],
data: [{ key: '1', acc: 'SG202604012', plate: '粤AGP9827', proc: '追尾,已报保险', duty: '次责', pay: '45,000.00', st: '理赔中' }],
},
selfImport: {
title: '自营 · 明细导入',
columns: [
{ title: '导入批次', dataIndex: 'batch', width: 100 },
{ title: '业务日期', dataIndex: 'date', width: 96 },
{ title: '科目', dataIndex: 'subj', width: 100 },
{ title: '金额(元)', dataIndex: 'amt', width: 96, align: 'right' },
],
data: [{ key: '1', batch: 'ZY-202605', date: '2026-05-31', subj: '运输收入', amt: '120,000.00' }],
},
selfDriver: {
title: '自营 · 司机工资/社保',
columns: [
{ title: '司机', dataIndex: 'driver', width: 88 },
{ title: '月份', dataIndex: 'month', width: 80 },
{ title: '工资(元)', dataIndex: 'wage', width: 96, align: 'right' },
{ title: '社保(元)', dataIndex: 'ins', width: 96, align: 'right' },
],
data: [{ key: '1', driver: '王某某', month: '2026-05', wage: '12,000', ins: '3,200' }],
},
self: {
title: '自营 · 车辆成本汇总(氢/电/ETC 从车辆取)',
columns: [
{ title: '自营项目', dataIndex: 'proj', width: 120 },
{ title: '损耗成本', dataIndex: 'loss', width: 88, align: 'right' },
{ title: '运维成本', dataIndex: 'ops', width: 88, align: 'right' },
{ title: '氢费', dataIndex: 'h2', width: 80, align: 'right' },
{ title: '电费', dataIndex: 'elec', width: 80, align: 'right' },
{ title: 'ETC', dataIndex: 'etc', width: 80, align: 'right' },
],
data: [{ key: '1', proj: '嘉兴自营一队', loss: '12,000', ops: '8,500', h2: '28,600', elec: '6,200', etc: '7,500' }],
},
h2: {
title: '氢费明细(简表)',
columns: [
{ title: '加氢时间', dataIndex: 'time', width: 140 },
{ title: '车牌', dataIndex: 'plate', width: 100 },
{ title: '客户', dataIndex: 'cust', ellipsis: true },
{ title: '加氢量(kg)', dataIndex: 'kg', width: 88, align: 'right' },
{ title: '加氢总价', dataIndex: 'amt', width: 96, align: 'right' },
],
data: [{ key: '1', time: '2026-05-28 14:20', plate: '浙F06900F', cust: '上海迅杰物流', kg: '12.5', amt: '875.00' }],
},
h2Account: {
title: '氢费账户(简表)',
columns: [
{ title: '账户类型', dataIndex: 'type', width: 100 },
{ title: '主体', dataIndex: 'name', ellipsis: true },
{ title: '余额(元)', dataIndex: 'bal', width: 100, align: 'right' },
],
data: [
{ key: '1', type: '加氢站账户', name: '嘉兴港区加氢站', bal: '12,600.00' },
{ key: '2', type: '客户预充值', name: '上海迅杰物流', bal: '80,000.00' },
],
},
ops: {
title: '运维台账 · 分类型一览',
columns: [
{ title: '类型', dataIndex: 'type', width: 88 },
{ title: '单号', dataIndex: 'no', width: 120 },
{ title: '车牌', dataIndex: 'plate', width: 100 },
{ title: '金额(元)', dataIndex: 'amt', width: 96, align: 'right' },
],
data: [
{ key: '1', type: '维修', no: 'WX202605001', plate: '浙F06900F', amt: '3,200.00' },
{ key: '2', type: '年审', no: 'NS202605008', plate: '沪BDB9161', amt: '680.00' },
],
},
};
const sampleCatalog = Object.keys(sampleTables).map((key) => {
const t = sampleTables[key];
const wfBranch = Object.keys(ledgerWorkflows).find((bid) => (ledgerWorkflows[bid]?.steps || []).some((s) => s.sampleKey === key));
const branchLabel = wfBranch === 'bizTotal'
? '业务总台账'
: (ledgerBranches.find((b) => b.id === wfBranch)?.label || '车辆运营明细');
return { key, title: t.title.replace(/.*?/g, '').slice(0, 28), branch: branchLabel, protoNote: t.protoNote };
});
const summaryPoints = [
'台账从两个维度统计:业务总台账(七类业务按月业绩/成本/利润)与车辆运营明细台账(单车氢/电/ETC/运维发生额)。',
'租赁业务:业绩=租金+服务费,成本=车辆成本+居间费;自营业务:业绩=自营明细金额列,成本=氢费/ETC/薪资/人工报销/日社保/日轮胎/车辆费用。',
'业务总台账列表已对照 Axhub「业务台账」与 web 端业务台账.jsx支持业绩钻取车辆明细样表覆盖氢费、运维等已实现模块。',
'氢/电/ETC 先在车辆运营明细落账,再归集至业务总台账对应业务线,避免与租赁/自营口径重复录入。',
];
const [activeSection, setActiveSection] = useState('hero');
const [activeDomain, setActiveDomain] = useState('h2');
const [activeBizType, setActiveBizType] = useState('lease');
const [activeDrill, setActiveDrill] = useState('d0');
const [navOpen, setNavOpen] = useState(false);
const [sampleOpen, setSampleOpen] = useState(false);
const [sampleKey, setSampleKey] = useState('lease');
const [drillStep, setDrillStep] = useState(0);
const [expandedMind, setExpandedMind] = useState(() => ledgerBranches.map((b) => b.id));
const [workflowBranch, setWorkflowBranch] = useState('bizTotal');
const [workflowStepIdx, setWorkflowStepIdx] = useState(0);
const [bizView, setBizView] = useState('main');
const [bizCtx, setBizCtx] = useState({
month: null,
monthLabel: '',
catKey: '',
catLabel: '',
perf: null,
salesperson: '',
amount: null,
deptDisplay: '浙江羚牛氢运(业务三部)',
year: '2026',
});
const workflowSteps = useMemo(
() => ledgerWorkflows[workflowBranch]?.steps || [],
[workflowBranch]
);
const activeWorkflowStep = workflowSteps[workflowStepIdx] || workflowSteps[0];
const activeWorkflowSample = activeWorkflowStep
? (sampleTables[activeWorkflowStep.sampleKey] || sampleTables.vehicleH2Detail)
: sampleTables.lease;
const selectedDomain = useMemo(
() => ledgerBranches.find((d) => d.id === activeDomain) || ledgerBranches[0],
[activeDomain]
);
const selectedBizType = useMemo(
() => bizTotalLedgerTypes.find((t) => t.id === activeBizType) || bizTotalLedgerTypes[0],
[activeBizType]
);
const selectedDrill = useMemo(
() => drillPaths.find((d) => d.id === activeDrill) || drillPaths[0],
[activeDrill]
);
const currentSample = sampleTables[sampleKey] || sampleTables.lease;
const openSample = useCallback((key) => {
setSampleKey(key);
setSampleOpen(true);
}, []);
const selectDomain = useCallback((id) => {
setActiveDomain(id);
setWorkflowBranch(id);
setWorkflowStepIdx(0);
const el = document.getElementById('el-section-domains');
if (el?.scrollIntoView) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, []);
const toggleMindBranch = useCallback((id) => {
setExpandedMind((prev) => (
prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]
));
}, []);
const scrollToSection = useCallback((key) => {
setActiveSection(key);
setNavOpen(false);
const el = document.getElementById(`el-section-${key}`);
if (el?.scrollIntoView) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, []);
const goWorkflows = useCallback((branchId, stepIdx) => {
setWorkflowBranch(branchId || 'bizTotal');
setWorkflowStepIdx(stepIdx == null ? 0 : stepIdx);
scrollToSection('workflows');
}, [scrollToSection]);
useEffect(() => {
const ids = sectionNav.map((s) => s.key);
const onScroll = () => {
let found = 'hero';
ids.forEach((id) => {
const node = document.getElementById(`el-section-${id}`);
if (node) {
const rect = node.getBoundingClientRect();
if (rect.top <= 120) found = id;
}
});
setActiveSection(found);
};
window.addEventListener('scroll', onScroll, { passive: true });
return () => window.removeEventListener('scroll', onScroll);
}, []);
useEffect(() => {
setDrillStep(0);
}, [activeDrill]);
useEffect(() => {
setWorkflowStepIdx(0);
}, [workflowBranch]);
useEffect(() => {
setBizView('main');
setBizCtx({
month: null,
monthLabel: '',
catKey: '',
catLabel: '',
perf: null,
salesperson: '',
amount: null,
deptDisplay: '浙江羚牛氢运(业务三部)',
year: '2026',
});
}, [workflowStepIdx, workflowBranch]);
const openBizPerfDrill = useCallback((row, catKey) => {
if (row.rowType === 'total' || row.month === 0) return;
const v = row[`${catKey}Perf`];
if (!v) return;
const cat = BIZ_LEDGER_CATS.find((c) => c.key === catKey);
setBizCtx((p) => ({
...p,
month: row.month,
monthLabel: row.monthLabel,
catKey,
catLabel: cat?.groupTitle || catKey,
perf: v,
salesperson: '',
amount: null,
}));
setBizView('sales');
}, []);
const openBizProjectDrill = useCallback((salesperson, amount) => {
setBizCtx((p) => ({ ...p, salesperson, amount }));
setBizView('project');
}, []);
const renderBizDrillExplain = () => {
if (bizView === 'main') {
return (
<div className="el-biz-drill-explain">
<div className="el-biz-drill-explain-title">钻取说明 · 汇总层</div>
<p><strong>列表口径</strong>ETC 12 = </p>
<p><strong>如何钻取</strong>/</p>
<p><strong>租赁</strong>=+=+<strong></strong>==/ETC/////</p>
<p><strong>原型对照</strong><a href={AXHUB_BIZ_LEDGER_URL} target="_blank" rel="noopener noreferrer">Axhub · </a></p>
</div>
);
}
if (bizView === 'sales') {
const isSelfLease = bizCtx.catKey === 'self' || bizCtx.catKey === 'lease';
return (
<div className="el-biz-drill-explain">
<div className="el-biz-drill-explain-title">
钻取说明 · 2 {bizCtx.monthLabel} / {bizCtx.catLabel} / 业绩 {fmtBizMoney(bizCtx.perf)}
</div>
{isSelfLease ? (
<p><strong>如何钻取</strong>//ETC/{bizCtx.catKey === 'lease' ? '' : ''}</p>
) : (
<p><strong>如何钻取</strong></p>
)}
<p><strong>数据来源</strong>{BIZ_LEDGER_SOURCE_MAP[bizCtx.catKey] || ''}</p>
</div>
);
}
return (
<div className="el-biz-drill-explain">
<div className="el-biz-drill-explain-title">
钻取说明 · 3 业务员 {bizCtx.salesperson}
</div>
<p><strong>展示内容</strong> {bizCtx.catLabel} /</p>
<p><strong>数据来源</strong>/ + 线//ETC </p>
</div>
);
};
const renderBusinessLedgerDrillDemo = () => {
const perfLink = (v, row, catKey) => {
if (row.rowType === 'total' || !v) return fmtBizMoney(v);
return (
<button type="button" className="el-biz-perf-link" onClick={() => openBizPerfDrill(row, catKey)}>
{fmtBizMoney(v)}
</button>
);
};
const mainColumns = [
{
title: '月份',
dataIndex: 'monthLabel',
width: 72,
fixed: 'left',
align: 'center',
render: (t, r) => (r.rowType === 'total' ? <strong>{t}</strong> : t),
},
...BIZ_LEDGER_CATS.map((cat) => ({
title: cat.groupTitle,
align: 'center',
children: [
{ title: '业绩', dataIndex: `${cat.key}Perf`, width: 92, align: 'right', render: (v, row) => perfLink(v, row, cat.key) },
{ title: '成本', dataIndex: `${cat.key}Cost`, width: 92, align: 'right', render: fmtBizMoney },
{ title: '利润', dataIndex: `${cat.key}Profit`, width: 92, align: 'right', render: fmtBizMoney },
],
})),
];
const salesSimpleColumns = [
{ title: '业务员', dataIndex: 'salesperson', width: 100 },
{
title: '业绩金额',
dataIndex: 'amount',
width: 120,
align: 'right',
render: (v, r) => (
<button type="button" className="el-biz-perf-link" onClick={() => openBizProjectDrill(r.salesperson, v)}>
{fmtBizMoney(v)}
</button>
),
},
{ title: '说明', key: 'hint', render: () => <span style={{ fontSize: 12, color: C_MUTED }}>点击金额 项目明细</span> },
];
const isSelfLease = bizCtx.catKey === 'self' || bizCtx.catKey === 'lease';
const mainTitle = bizCtx.catKey === 'lease' ? '租赁业绩' : '自营业绩';
const selfLeaseColumns = [
{ title: '月份', dataIndex: 'ym', width: 88, align: 'center', onCell: (r) => ({ rowSpan: r.monthRowSpan }) },
{ title: '业务部门', dataIndex: 'deptName', width: 108 },
{ title: '业务人员', dataIndex: 'salesperson', width: 88 },
{
title: mainTitle,
dataIndex: 'mainPerf',
width: 112,
align: 'right',
render: (v, r) => (
v ? (
<button type="button" className="el-biz-perf-link" onClick={() => openBizProjectDrill(r.salesperson, v)}>
{fmtBizMoney(v)}
</button>
) : fmtBizMoney(v)
),
},
{ title: '氢费业绩', dataIndex: 'h2PerfCol', width: 100, align: 'right', render: fmtBizMoney },
{ title: '电费业绩', dataIndex: 'elecPerf', width: 100, align: 'right', render: fmtBizMoney },
{ title: 'ETC业绩', dataIndex: 'etcPerfCol', width: 96, align: 'right', render: fmtBizMoney },
{ title: '其他', dataIndex: 'otherAmt', width: 88, align: 'right', render: fmtBizMoney },
];
const projectColumns = [
{ title: '项目编号', dataIndex: 'projectCode', width: 120 },
{ title: '项目名称', dataIndex: 'projectName', ellipsis: true },
{ title: '车牌号', dataIndex: 'plateNo', width: 100 },
{ title: '业绩金额', dataIndex: 'amount', width: 108, align: 'right', render: fmtBizMoney },
{ title: '业务日期', dataIndex: 'bizDate', width: 100 },
{ title: '备注', dataIndex: 'remark', width: 72 },
];
const salesRows = BIZ_SALES_BY_CAT[bizCtx.catKey] || BIZ_SALES_BY_CAT.h2;
return (
<div className="el-biz-drill-shell">
<div className="el-biz-drill-toolbar">
<div className="el-biz-drill-breadcrumb">
<button type="button" className={`el-biz-crumb${bizView === 'main' ? ' is-active' : ''}`} onClick={() => setBizView('main')}>汇总表</button>
{bizView !== 'main' ? (
<>
<span className="el-biz-crumb-sep"></span>
<button type="button" className={`el-biz-crumb${bizView === 'sales' ? ' is-active' : ''}`} onClick={() => setBizView('sales')}>
{bizCtx.monthLabel} · {bizCtx.catLabel}
</button>
</>
) : null}
{bizView === 'project' ? (
<>
<span className="el-biz-crumb-sep"></span>
<span className="el-biz-crumb is-active">{bizCtx.salesperson} · 项目</span>
</>
) : null}
</div>
{bizView !== 'main' ? (
<Button size="small" onClick={() => setBizView(bizView === 'project' ? 'sales' : 'main')}>返回上一步</Button>
) : null}
</div>
<div className="el-biz-table-title">
{bizView === 'main' ? `${bizCtx.year}年 业务部汇总台账` : null}
{bizView === 'sales' && isSelfLease ? `浙江羚牛氢能业务员${bizCtx.catKey === 'lease' ? '租赁' : '自营'}业务汇总(${bizCtx.year}-${String(bizCtx.month).padStart(2, '0')}` : null}
{bizView === 'sales' && !isSelfLease ? `业务员业绩 — ${bizCtx.monthLabel} / ${bizCtx.catLabel}` : null}
{bizView === 'project' ? `项目明细 — ${bizCtx.salesperson} / ${bizCtx.catLabel}` : null}
</div>
<div className="el-biz-filter-hint">业务部{bizCtx.deptDisplay} · 年份 {bizCtx.year} · 按月近 12 个月演示含省略行</div>
<Table
className="el-biz-ledger-table"
size="small"
bordered
pagination={false}
scroll={{ x: 'max-content' }}
columns={
bizView === 'main' ? mainColumns
: bizView === 'sales' ? (isSelfLease ? selfLeaseColumns : salesSimpleColumns)
: projectColumns
}
dataSource={
bizView === 'main' ? BIZ_LEDGER_MAIN_ROWS
: bizView === 'sales' ? salesRows
: BIZ_PROJECT_ROWS
}
/>
{renderBizDrillExplain()}
</div>
);
};
const css = `
*,*::before,*::after{box-sizing:border-box}
.el-plan{font-family:${FONT};color:${C_TEXT};background:${C_PAGE};min-height:100vh;line-height:1.5;-webkit-font-smoothing:antialiased}
.el-nav{position:sticky;top:0;z-index:100;background:rgba(255,255,255,.94);backdrop-filter:saturate(180%) blur(12px);border-bottom:1px solid ${C_LINE}}
.el-nav-inner{max-width:1240px;margin:0 auto;padding:0 20px;height:56px;display:flex;align-items:center;gap:12px}
.el-brand{display:flex;align-items:center;gap:10px;font-weight:800;font-size:14px;flex-shrink:0}
.el-brand-mark{width:34px;height:34px;border-radius:10px;background:linear-gradient(135deg,${C_PRIMARY},${C_PRIMARY_L});color:#fff;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800}
.el-nav-links{display:flex;gap:4px;flex:1;overflow-x:auto;scrollbar-width:none}
.el-nav-link{padding:7px 12px;border-radius:999px;font-size:12px;font-weight:500;color:${C_MUTED};cursor:pointer;border:none;background:transparent;white-space:nowrap}
.el-nav-link:hover{color:${C_TEXT};background:rgba(15,118,110,.08)}
.el-nav-link.is-active{color:${C_PRIMARY};background:rgba(15,118,110,.12);font-weight:700}
.el-nav-toggle{display:none;margin-left:auto;padding:8px 12px;border-radius:8px;border:1px solid ${C_LINE};background:#fff;cursor:pointer;font-size:12px}
.el-hero{position:relative;background:${C_HERO};color:#fff;padding:56px 20px 64px;overflow:hidden}
.el-hero::before{content:"";position:absolute;inset:0;background:radial-gradient(ellipse 70% 50% at 80% 10%,rgba(20,184,166,.22),transparent 55%);pointer-events:none}
.el-hero-inner{position:relative;max-width:1240px;margin:0 auto}
.el-hero-tag{display:inline-block;padding:4px 10px;border-radius:999px;font-size:11px;font-weight:700;background:rgba(20,184,166,.15);border:1px solid rgba(45,212,191,.35);color:#5eead4;margin-bottom:14px}
.el-hero-title{font-size:clamp(26px,4.5vw,40px);font-weight:800;line-height:1.2;margin:0 0 12px;max-width:820px}
.el-hero-desc{font-size:clamp(14px,2.2vw,16px);color:rgba(255,255,255,.75);max-width:760px;margin:0 0 20px;line-height:1.65}
.el-hero-actions{display:flex;flex-wrap:wrap;gap:10px}
.el-btn{padding:10px 18px;border-radius:10px;font-size:13px;font-weight:700;cursor:pointer;border:none}
.el-btn-primary{background:linear-gradient(135deg,${C_PRIMARY_L},${C_PRIMARY});color:#fff;box-shadow:0 4px 20px rgba(20,184,166,.35)}
.el-btn-ghost{background:rgba(255,255,255,.08);color:#fff;border:1px solid rgba(255,255,255,.2)}
.el-kpi-row{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px;margin-top:-32px;position:relative;z-index:2;padding:0 20px;max-width:1240px;margin-left:auto;margin-right:auto}
.el-kpi{background:${C_CARD};border-radius:14px;padding:18px 16px;border:1px solid ${C_LINE};box-shadow:0 8px 30px rgba(15,23,42,.06)}
.el-kpi-val{font-size:26px;font-weight:800}
.el-kpi-unit{font-size:13px;font-weight:600;opacity:.7;margin-left:2px}
.el-kpi-label{font-size:12px;color:${C_MUTED};margin-top:6px}
.el-main{max-width:1240px;margin:0 auto;padding:40px 20px 72px}
.el-section{margin-bottom:52px;scroll-margin-top:72px}
.el-section-head{margin-bottom:22px}
.el-section-tag{font-size:11px;font-weight:800;letter-spacing:.08em;color:${C_PRIMARY};margin-bottom:6px}
.el-section-title{font-size:clamp(20px,3vw,26px);font-weight:800;margin:0 0 8px}
.el-section-desc{font-size:14px;color:${C_MUTED};margin:0;max-width:720px;line-height:1.6}
.el-root-hub{text-align:center;margin-bottom:24px}
.el-root-pill{display:inline-flex;padding:14px 28px;border-radius:16px;background:linear-gradient(135deg,${C_HERO},${C_HERO_ACCENT});color:#fff;font-size:18px;font-weight:800;box-shadow:0 12px 40px rgba(15,23,42,.2)}
.el-mind-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px}
.el-mind-card{background:#fff;border:1px solid ${C_LINE};border-radius:14px;overflow:hidden;transition:box-shadow .2s}
.el-mind-card.is-active{box-shadow:0 12px 32px rgba(15,23,42,.1)}
.el-mind-card-head{width:100%;text-align:left;padding:14px 16px;border:none;background:#fff;cursor:pointer;display:flex;align-items:center;justify-content:space-between;gap:8px}
.el-mind-card-head h4{margin:0;font-size:15px;font-weight:800}
.el-mind-card-body{padding:0 16px 14px;border-top:1px dashed ${C_LINE}}
.el-mind-block{margin-top:12px}
.el-mind-block-title{font-size:11px;font-weight:800;color:${C_MUTED};text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px}
.el-mind-node{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px;padding:8px 10px;border-radius:8px;background:#f8fafc;margin-bottom:6px;font-size:13px}
.el-mind-node-label{font-weight:600}
.el-mind-node-note{font-size:12px;color:${C_MUTED}}
.el-mind-link{font-size:11px;font-weight:700;color:${C_PRIMARY};cursor:pointer;border:none;background:transparent;padding:0}
.el-principle-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px}
.el-principle-card{background:#fff;border:1px solid ${C_LINE};border-radius:14px;padding:18px}
.el-principle-icon{width:40px;height:40px;border-radius:12px;background:rgba(15,118,110,.1);color:${C_PRIMARY};display:flex;align-items:center;justify-content:center;font-weight:800;margin-bottom:12px}
.el-principle-card h4{margin:0 0 8px;font-size:15px;font-weight:800}
.el-principle-card p{margin:0;font-size:13px;color:${C_MUTED};line-height:1.55}
.el-domain-layout{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.05fr);gap:18px}
.el-domain-tabs{display:flex;flex-direction:column;gap:8px}
.el-domain-tab{width:100%;text-align:left;padding:12px 14px;border-radius:12px;border:1px solid ${C_LINE};background:#fff;cursor:pointer}
.el-domain-tab.is-active{box-shadow:0 8px 22px rgba(15,23,42,.1);transform:translateX(4px)}
.el-domain-tab-name{font-size:14px;font-weight:700}
.el-domain-tab-formula{font-size:11px;color:${C_MUTED};margin-top:4px}
.el-domain-detail{background:#fff;border:1px solid ${C_LINE};border-radius:16px;padding:22px;position:sticky;top:76px}
.el-domain-meta{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:16px 0}
.el-domain-meta dt{font-size:11px;font-weight:700;color:${C_MUTED};margin-bottom:4px}
.el-domain-meta dd{margin:0;font-size:13px;line-height:1.5}
.el-link-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}
.el-link-card{background:#fff;border:1px solid ${C_LINE};border-radius:12px;padding:14px 16px;border-left:4px solid ${C_PRIMARY}}
.el-link-arrow{font-size:12px;color:${C_MUTED};margin:6px 0}
.el-link-rule{font-size:13px;font-weight:600;color:${C_TEXT}}
.el-drill-layout{display:grid;grid-template-columns:minmax(0,.95fr) minmax(0,1.05fr);gap:18px}
.el-drill-item{padding:14px 16px;border-radius:12px;border:1px solid ${C_LINE};background:#fff;cursor:pointer;margin-bottom:8px}
.el-drill-item.is-active{box-shadow:0 8px 24px rgba(15,23,42,.08)}
.el-drill-panel{background:#fff;border:1px solid ${C_LINE};border-radius:16px;padding:22px}
.el-drill-step{padding:8px 14px;border-radius:999px;font-size:12px;font-weight:600;border:1px solid ${C_LINE};background:#f8fafc;color:${C_MUTED};cursor:pointer;margin:0 6px 8px 0;display:inline-block}
.el-drill-step.is-active{color:#fff;border-color:transparent}
.el-sample-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}
.el-sample-card{background:#fff;border:1px solid ${C_LINE};border-radius:14px;padding:16px;cursor:pointer}
.el-sample-card:hover{border-color:rgba(15,118,110,.35);box-shadow:0 8px 22px rgba(15,23,42,.06)}
.el-workflow-layout{display:grid;grid-template-columns:minmax(0,280px) minmax(0,1fr);gap:18px;align-items:start}
.el-workflow-steps{display:flex;flex-direction:column;gap:8px;max-height:520px;overflow-y:auto;padding-right:4px}
.el-workflow-step{width:100%;text-align:left;padding:12px 14px;border-radius:12px;border:1px solid ${C_LINE};background:#fff;cursor:pointer}
.el-workflow-step.is-active{box-shadow:0 8px 22px rgba(15,23,42,.1)}
.el-workflow-step-name{font-size:13px;font-weight:700;margin-bottom:4px}
.el-workflow-step-flow{font-size:11px;color:${C_MUTED};line-height:1.45}
.el-workflow-step-path{font-size:10px;color:${C_PRIMARY};margin-top:6px;word-break:break-all}
.el-workflow-preview{background:#fff;border:1px solid ${C_LINE};border-radius:16px;padding:18px;overflow:hidden}
.el-workflow-preview-head{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:10px;margin-bottom:12px}
.el-workflow-preview-title{margin:0;font-size:16px;font-weight:800}
.el-proto-banner{padding:10px 14px;border-radius:10px;background:#eff6ff;border:1px solid #bfdbfe;font-size:12px;color:#1e40af;margin-bottom:12px;line-height:1.5}
.el-dim-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:16px}
.el-dim-card{background:${C_CARD};border-radius:14px;padding:20px;border:1px solid ${C_LINE};box-shadow:0 1px 3px rgba(15,23,42,.06)}
.el-dim-list{margin:0;padding-left:18px;font-size:13px;line-height:1.7;color:${C_TEXT}}
.el-biz-type-layout{display:grid;grid-template-columns:minmax(140px,200px) 1fr;gap:16px}
@media(max-width:768px){.el-biz-type-layout{grid-template-columns:1fr}}
.el-biz-type-tabs{display:flex;flex-direction:column;gap:6px}
.el-biz-type-tab{text-align:left;padding:10px 12px;border-radius:10px;border:1px solid ${C_LINE};background:#fff;cursor:pointer;font-size:13px;font-weight:600;color:${C_MUTED}}
.el-biz-type-tab.is-active{background:rgba(15,118,110,.08);color:${C_TEXT};font-weight:800}
.el-biz-type-detail{background:${C_CARD};border-radius:14px;padding:20px;border:1px solid ${C_LINE}}
.el-biz-drill-shell{border:1px solid ${C_LINE};border-radius:12px;overflow:hidden;background:#fff}
.el-biz-drill-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 14px;background:#f8fafc;border-bottom:1px solid ${C_LINE};flex-wrap:wrap}
.el-biz-drill-breadcrumb{display:flex;align-items:center;flex-wrap:wrap;gap:4px;font-size:13px}
.el-biz-crumb{border:none;background:transparent;color:${C_MUTED};cursor:pointer;padding:4px 8px;border-radius:6px;font-size:13px;font-weight:600}
.el-biz-crumb:hover{color:${C_BLUE};background:rgba(37,99,235,.08)}
.el-biz-crumb.is-active{color:${C_BLUE};background:rgba(37,99,235,.12)}
.el-biz-crumb-sep{color:#cbd5e1;font-size:12px}
.el-biz-table-title{text-align:center;font-size:16px;font-weight:800;padding:14px 12px 4px;color:${C_TEXT}}
.el-biz-filter-hint{text-align:center;font-size:12px;color:${C_MUTED};margin-bottom:10px}
.el-biz-ledger-table .ant-table-thead>tr>th{background:#f8fafc!important;font-size:12px}
.el-biz-perf-link{border:none;background:transparent;padding:0;color:${C_BLUE};font-weight:700;cursor:pointer;text-decoration:underline;text-underline-offset:2px;font-variant-numeric:tabular-nums}
.el-biz-perf-link:hover{color:#1d4ed8}
.el-biz-drill-explain{margin:0;padding:14px 16px;background:linear-gradient(180deg,#f0fdf4 0%,#f8fafc 100%);border-top:1px solid ${C_LINE};font-size:13px;line-height:1.65;color:#334155}
.el-biz-drill-explain-title{font-size:12px;font-weight:800;color:${C_PRIMARY};letter-spacing:.04em;margin-bottom:8px;text-transform:uppercase}
.el-biz-drill-explain p{margin:0 0 8px}
.el-biz-drill-explain p:last-child{margin-bottom:0}
.el-biz-drill-explain a{color:${C_PRIMARY};font-weight:600}
.el-summary{background:linear-gradient(135deg,${C_HERO},${C_HERO_ACCENT});border-radius:18px;padding:28px;color:#fff}
.el-summary ul{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:12px}
.el-summary li{font-size:14px;line-height:1.6;color:rgba(255,255,255,.9);display:flex;gap:10px}
.el-summary li::before{content:"✓";color:#5eead4;font-weight:800}
.el-footer{text-align:center;padding:24px;font-size:12px;color:${C_MUTED}}
@media(max-width:1024px){
.el-principle-grid,.el-kpi-row,.el-link-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
.el-domain-layout,.el-drill-layout,.el-workflow-layout{grid-template-columns:1fr}
.el-domain-detail{position:static}
.el-workflow-steps{max-height:none}
}
@media(max-width:640px){
.el-nav-toggle{display:block}
.el-nav-links{display:none;position:absolute;top:56px;left:0;right:0;background:#fff;flex-direction:column;padding:12px 16px;border-bottom:1px solid ${C_LINE}}
.el-nav-links.is-open{display:flex}
.el-principle-grid,.el-kpi-row,.el-sample-grid,.el-mind-grid,.el-link-grid{grid-template-columns:1fr}
.el-domain-meta{grid-template-columns:1fr}
}
`;
return (
<div className="el-plan">
<style>{css}</style>
<header className="el-nav">
<div className="el-nav-inner">
<div className="el-brand">
<span className="el-brand-mark">总账</span>
<span>企业台账 · 双维汇报</span>
</div>
<button type="button" className="el-nav-toggle" onClick={() => setNavOpen((v) => !v)}>章节</button>
<nav className={`el-nav-links${navOpen ? ' is-open' : ''}`}>
{sectionNav.map((s) => (
<button
key={s.key}
type="button"
className={`el-nav-link${activeSection === s.key ? ' is-active' : ''}`}
onClick={() => scrollToSection(s.key)}
>
{s.label}
</button>
))}
</nav>
</div>
</header>
<section id="el-section-hero" className="el-hero">
<div className="el-hero-inner">
<span className="el-hero-tag">业务总台账 + 车辆运营明细 · 双维统计</span>
<h1 className="el-hero-title">ONE-OS 企业业务总台账</h1>
<p className="el-hero-desc">
台账从两个维度统计<strong style={{ color: '#99f6e4' }}>业务总台账</strong>
展示七类业务按月业绩/成本/利润
<strong style={{ color: '#99f6e4' }}>车辆运营明细台账</strong>
记录单车氢//ETC/运维发生额并向上归集已对照
{' '}
<a href={AXHUB_BIZ_LEDGER_URL} target="_blank" rel="noopener noreferrer" style={{ color: '#5eead4', fontWeight: 600 }}>
Axhub 业务台账
</a>
{' '}
web 端已实现模块支持业绩钻取演示
</p>
<div className="el-hero-actions">
<button type="button" className="el-btn el-btn-primary" onClick={() => goWorkflows('bizTotal', 0)}>
业务总台账样表
</button>
<button type="button" className="el-btn el-btn-ghost" onClick={() => scrollToSection('dimensions')}>
双维结构说明
</button>
<button type="button" className="el-btn el-btn-ghost" onClick={() => scrollToSection('mindmap')}>
车辆明细域
</button>
<button type="button" className="el-btn el-btn-ghost" onClick={() => scrollToSection('drill')}>
钻取路径演示
</button>
</div>
</div>
</section>
<div className="el-kpi-row">
{kpiStats.map((k) => (
<div key={k.label} className="el-kpi">
<div className="el-kpi-val" style={{ color: k.color }}>
{k.value}
<span className="el-kpi-unit">{k.unit}</span>
</div>
<div className="el-kpi-label">{k.label}</div>
</div>
))}
</div>
<main className="el-main">
<section id="el-section-dimensions" className="el-section">
<div className="el-section-head">
<div className="el-section-tag">TWO DIMENSIONS</div>
<h2 className="el-section-title">台账统计的两个维度</h2>
<p className="el-section-desc">老板看业务总台账掌握各业务线经营结果财务/运营看车辆运营明细掌握单车发生额二者通过归集规则勾稽</p>
</div>
<div className="el-dim-grid">
{ledgerDimensions.map((dim) => (
<div key={dim.id} className="el-dim-card" style={{ borderTop: `4px solid ${dim.color}` }}>
<h3 style={{ margin: '0 0 8px', fontSize: 18, fontWeight: 800, color: dim.color }}>{dim.label}</h3>
<Tag color="processing" style={{ marginBottom: 10 }}>{dim.role}</Tag>
<p style={{ margin: '0 0 12px', fontSize: 13, lineHeight: 1.6, color: C_MUTED }}>{dim.summary}</p>
<ul className="el-dim-list">
{dim.listItems.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
{dim.id === 'bizTotal' ? (
<Button type="primary" size="small" style={{ marginTop: 12, borderRadius: 8 }} onClick={() => goWorkflows('bizTotal', 0)}>
打开业务总台账样表
</Button>
) : (
<Button size="small" style={{ marginTop: 12, borderRadius: 8 }} onClick={() => scrollToSection('mindmap')}>
查看车辆明细域
</Button>
)}
</div>
))}
</div>
</section>
<section id="el-section-bizTypes" className="el-section">
<div className="el-section-head">
<div className="el-section-tag">BUSINESS LEDGER</div>
<h2 className="el-section-title">业务总台账 · 七类业务口径</h2>
<p className="el-section-desc">列表按月份展示近 12 个月业绩成本利润下列为各类业务的取数规则租赁自营已按最新口径固化</p>
</div>
<div className="el-biz-type-layout">
<div className="el-biz-type-tabs">
{bizTotalLedgerTypes.map((t) => (
<button
key={t.id}
type="button"
className={`el-biz-type-tab${activeBizType === t.id ? ' is-active' : ''}`}
style={activeBizType === t.id ? { borderLeft: `4px solid ${t.color}` } : undefined}
onClick={() => setActiveBizType(t.id)}
>
{t.label}
</button>
))}
</div>
<div className="el-biz-type-detail">
<h3 style={{ margin: 0, fontSize: 17, fontWeight: 800, color: selectedBizType.color }}>{selectedBizType.label}</h3>
<p style={{ margin: '8px 0 14px', fontSize: 12, color: C_MUTED }}>{selectedBizType.period}</p>
<dl className="el-domain-meta">
<div><dt>业绩来源</dt><dd>{selectedBizType.perf}</dd></div>
<div><dt>成本来源</dt><dd>{selectedBizType.cost}</dd></div>
<div><dt>利润计算</dt><dd>{selectedBizType.profit}</dd></div>
<div><dt>钻取</dt><dd>{selectedBizType.drill}</dd></div>
<div><dt>数据回写</dt><dd>{selectedBizType.writeBack}</dd></div>
</dl>
<Space wrap style={{ marginTop: 12 }}>
<Button type="primary" style={{ borderRadius: 8 }} onClick={() => goWorkflows('bizTotal', 0)}>
业务总台账列表样表
</Button>
{selectedBizType.id === 'lease' ? (
<Button style={{ borderRadius: 8 }} onClick={() => goWorkflows('lease', 0)}>租赁环节样表</Button>
) : null}
{selectedBizType.id === 'self' ? (
<Button style={{ borderRadius: 8 }} onClick={() => goWorkflows('bizTotal', 2)}>自营明细样表</Button>
) : null}
{['h2', 'apply', 'etc'].includes(selectedBizType.id) ? (
<Button style={{ borderRadius: 8 }} onClick={() => goWorkflows(selectedBizType.id === 'apply' ? 'electric' : selectedBizType.id, 0)}>
车辆明细样表
</Button>
) : null}
</Space>
</div>
</div>
</section>
<section id="el-section-mindmap" className="el-section">
<div className="el-section-head">
<div className="el-section-tag">VEHICLE OPS</div>
<h2 className="el-section-title">车辆运营明细台账</h2>
<p className="el-section-desc">氢费电费ETC运维等按车牌/客户记录发生明细归集后写入业务总台账对应业务线避免重复录入</p>
</div>
<div className="el-root-hub">
<div className="el-root-pill">车辆运营明细台账</div>
</div>
<div className="el-mind-grid">
{ledgerBranches.map((branch) => {
const open = expandedMind.includes(branch.id);
return (
<div key={branch.id} className={`el-mind-card${activeDomain === branch.id ? ' is-active' : ''}`}>
<button
type="button"
className="el-mind-card-head"
style={{ borderLeft: `4px solid ${branch.color}` }}
onClick={() => toggleMindBranch(branch.id)}
>
<h4 style={{ color: branch.color }}>{branch.label}</h4>
<span style={{ fontSize: 12, color: C_MUTED }}>{open ? '收起' : '展开'}</span>
</button>
{open ? (
<div className="el-mind-card-body">
{branch.blocks.map((block) => (
<div key={block.title} className="el-mind-block">
<div className="el-mind-block-title">{block.title}</div>
{block.nodes.map((node) => (
<div key={node.label} className="el-mind-node">
<span className="el-mind-node-label">{node.label}</span>
{node.note ? <span className="el-mind-node-note">{node.note}</span> : null}
{node.linkId ? (
<button type="button" className="el-mind-link" onClick={() => selectDomain(node.linkId)}>
跳转台账
</button>
) : null}
</div>
))}
</div>
))}
<Button
type="link"
size="small"
style={{ padding: 0, fontWeight: 700, color: branch.color }}
onClick={() => selectDomain(branch.id)}
>
查看搭建方案与样表
</Button>
</div>
) : null}
</div>
);
})}
</div>
</section>
<section id="el-section-principles" className="el-section">
<div className="el-section-head">
<div className="el-section-tag">PRINCIPLES</div>
<h2 className="el-section-title">搭建原则提炼自导图</h2>
</div>
<div className="el-principle-grid">
{principles.map((p) => (
<div key={p.id} className="el-principle-card">
<div className="el-principle-icon">{p.icon}</div>
<h4>{p.title}</h4>
<p>{p.desc}</p>
</div>
))}
</div>
</section>
<section id="el-section-domains" className="el-section">
<div className="el-section-head">
<div className="el-section-tag">LEDGERS</div>
<h2 className="el-section-title">车辆明细域显示内容与回写</h2>
</div>
<div className="el-domain-layout">
<div className="el-domain-tabs">
{ledgerBranches.map((d) => (
<button
key={d.id}
type="button"
className={`el-domain-tab${activeDomain === d.id ? ' is-active' : ''}`}
style={activeDomain === d.id ? { borderLeft: `4px solid ${d.color}` } : undefined}
onClick={() => setActiveDomain(d.id)}
>
<div className="el-domain-tab-name">{d.label}</div>
<div className="el-domain-tab-formula">{d.formula}</div>
</button>
))}
</div>
<div className="el-domain-detail">
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', flexWrap: 'wrap', gap: 8 }}>
<h3 style={{ margin: 0, fontSize: 18, fontWeight: 800 }}>{selectedDomain.label}</h3>
<Tag color="processing">{selectedDomain.tag}</Tag>
</div>
<p style={{ margin: '12px 0', fontSize: 13, color: C_MUTED }}>{selectedDomain.formula}</p>
<dl className="el-domain-meta">
<div><dt>建档锚点</dt><dd>{selectedDomain.anchor}</dd></div>
<div><dt>列表展示</dt><dd>{selectedDomain.listShows}</dd></div>
<div><dt>详情结构</dt><dd>{selectedDomain.detailShows}</dd></div>
<div><dt>数据回写</dt><dd>{selectedDomain.writeBack}</dd></div>
</dl>
<div style={{ marginTop: 16, marginBottom: 12, fontSize: 12, fontWeight: 700, color: C_MUTED }}>本域业务环节对照原型</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 16 }}>
{(ledgerWorkflows[selectedDomain.id]?.steps || []).map((step, idx) => (
<div
key={step.key}
style={{
padding: '10px 12px',
borderRadius: 10,
border: `1px solid ${C_LINE}`,
background: '#f8fafc',
fontSize: 12,
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8, marginBottom: 4 }}>
<span style={{ fontWeight: 700, color: C_TEXT }}>{step.name}</span>
{renderProtoStatus(step.status)}
</div>
<div style={{ color: C_MUTED, lineHeight: 1.45 }}>{step.flow}</div>
<div style={{ color: C_PRIMARY, marginTop: 4, wordBreak: 'break-all' }}>{step.protoPath}</div>
</div>
))}
</div>
<Space wrap>
<Button type="primary" style={{ borderRadius: 8, fontWeight: 600 }} onClick={() => goWorkflows(selectedDomain.id, 0)}>
查看环节样表
</Button>
<Button style={{ borderRadius: 8 }} onClick={() => openSample((ledgerWorkflows[selectedDomain.id]?.steps[0] || {}).sampleKey || selectedDomain.sampleKey)}>
弹窗预览
</Button>
</Space>
</div>
</div>
</section>
<section id="el-section-workflows" className="el-section">
<div className="el-section-head">
<div className="el-section-tag">WORKFLOW SAMPLES</div>
<h2 className="el-section-title">各环节台账样表内嵌预览</h2>
<p className="el-section-desc">
先选统计维度业务总台账七类业务汇总或车辆运营明细单车发生额绿色 Tag 为已实现模块
</p>
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginBottom: 16 }}>
<button
type="button"
className={`el-nav-link${workflowBranch === 'bizTotal' ? ' is-active' : ''}`}
style={{ border: `1px solid ${C_LINE}`, background: workflowBranch === 'bizTotal' ? 'rgba(37,99,235,.1)' : '#fff', fontWeight: 700 }}
onClick={() => setWorkflowBranch('bizTotal')}
>
业务总台账
</button>
{ledgerBranches.map((b) => (
<button
key={b.id}
type="button"
className={`el-nav-link${workflowBranch === b.id ? ' is-active' : ''}`}
style={{ border: `1px solid ${C_LINE}`, background: workflowBranch === b.id ? 'rgba(15,118,110,.1)' : '#fff' }}
onClick={() => setWorkflowBranch(b.id)}
>
{b.label}
</button>
))}
</div>
<div className="el-workflow-layout">
<div className="el-workflow-steps">
{workflowSteps.map((step, idx) => (
<button
key={step.key}
type="button"
className={`el-workflow-step${workflowStepIdx === idx ? ' is-active' : ''}`}
style={workflowStepIdx === idx ? { borderLeft: `4px solid ${ledgerWorkflows[workflowBranch].color}` } : undefined}
onClick={() => setWorkflowStepIdx(idx)}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8 }}>
<span className="el-workflow-step-name">{step.name}</span>
{renderProtoStatus(step.status)}
</div>
<div className="el-workflow-step-flow">{step.flow}</div>
<div className="el-workflow-step-path">{step.protoPath}</div>
</button>
))}
</div>
<div className="el-workflow-preview">
<div className="el-workflow-preview-head">
<h3 className="el-workflow-preview-title">{activeWorkflowSample.title}</h3>
<Space>
<Button size="small" onClick={() => openSample(activeWorkflowStep?.sampleKey)}>
全屏弹窗
</Button>
</Space>
</div>
{activeWorkflowSample.protoNote ? (
<div className="el-proto-banner">
{activeWorkflowSample.protoNote}
{activeWorkflowStep?.sampleKey === 'businessLedger' ? (
<span>
{' '}
·
{' '}
<a href={AXHUB_BIZ_LEDGER_URL} target="_blank" rel="noopener noreferrer">打开 Axhub 业务台账原型</a>
</span>
) : null}
</div>
) : activeWorkflowStep?.protoPath ? (
<div className="el-proto-banner">原型路径{activeWorkflowStep.protoPath}</div>
) : null}
{activeWorkflowStep?.sampleKey === 'businessLedger' ? (
renderBusinessLedgerDrillDemo()
) : (
<Table
size="small"
pagination={false}
scroll={{ x: 'max-content' }}
columns={activeWorkflowSample.columns}
dataSource={activeWorkflowSample.data}
/>
)}
</div>
</div>
</section>
<section id="el-section-links" className="el-section">
<div className="el-section-head">
<div className="el-section-tag">LINKAGE</div>
<h2 className="el-section-title">跨台账勾稽关系</h2>
<p className="el-section-desc">车辆运营明细归集至业务总台账对应业务线租赁业绩来自账单自营业绩/成本来自自营明细表</p>
</div>
<div className="el-link-grid">
{crossLinks.map((link) => (
<div key={link.from + link.to} className="el-link-card" style={{ borderLeftColor: link.color }}>
<div style={{ fontSize: 13, fontWeight: 700 }}>{link.from}</div>
<div className="el-link-arrow"> 归集至</div>
<div style={{ fontSize: 13, fontWeight: 700 }}>{link.to}</div>
<div className="el-link-rule" style={{ marginTop: 8, color: C_MUTED, fontWeight: 500 }}>{link.rule}</div>
</div>
))}
</div>
</section>
<section id="el-section-drill" className="el-section">
<div className="el-section-head">
<div className="el-section-tag">DRILL-DOWN</div>
<h2 className="el-section-title">钻取路径业务总账 / 车辆明细</h2>
<p className="el-section-desc">点击步骤序号高亮当前层级演示点哪里看到什么</p>
</div>
<div className="el-drill-layout">
<div>
{drillPaths.map((path) => (
<div
key={path.id}
role="button"
tabIndex={0}
className={`el-drill-item${activeDrill === path.id ? ' is-active' : ''}`}
style={activeDrill === path.id ? { borderLeft: `4px solid ${path.color}` } : undefined}
onClick={() => setActiveDrill(path.id)}
onKeyDown={(e) => { if (e.key === 'Enter') setActiveDrill(path.id); }}
>
<h4 style={{ margin: '0 0 4px', fontSize: 14 }}>{path.title}</h4>
<p style={{ margin: 0, fontSize: 12, color: C_MUTED }}>{path.scene}</p>
</div>
))}
</div>
<div className="el-drill-panel">
<h4 style={{ margin: '0 0 12px' }}>{selectedDrill.title}</h4>
<div style={{ marginBottom: 16 }}>
{selectedDrill.steps.map((step, idx) => (
<span
key={step}
role="button"
tabIndex={0}
className={`el-drill-step${drillStep === idx ? ' is-active' : ''}`}
style={drillStep === idx ? { background: selectedDrill.color } : undefined}
onClick={() => setDrillStep(idx)}
onKeyDown={(e) => { if (e.key === 'Enter') setDrillStep(idx); }}
>
{idx + 1}. {step}
</span>
))}
</div>
<p style={{ fontSize: 13, color: C_MUTED, margin: 0 }}>
当前层级<strong style={{ color: C_TEXT }}>{selectedDrill.steps[drillStep]}</strong>
</p>
</div>
</div>
</section>
<section id="el-section-samples" className="el-section">
<div className="el-section-head">
<div className="el-section-tag">SAMPLES</div>
<h2 className="el-section-title">样表索引24+</h2>
<p className="el-section-desc">快速跳转任意样表环节样表区数据一致</p>
</div>
<div className="el-sample-grid">
{sampleCatalog.map((item) => (
<div
key={item.key}
role="button"
tabIndex={0}
className="el-sample-card"
onClick={() => openSample(item.key)}
onKeyDown={(e) => { if (e.key === 'Enter') openSample(item.key); }}
>
<Tag style={{ marginBottom: 8 }}>{item.branch}</Tag>
<h4 style={{ margin: '0 0 6px', fontSize: 14 }}>{item.title}</h4>
{item.protoNote ? <p style={{ margin: '0 0 6px', fontSize: 11, color: C_PRIMARY }}>{item.protoNote}</p> : null}
<p style={{ margin: 0, fontSize: 12, color: C_MUTED }}>点击查看 </p>
</div>
))}
</div>
</section>
<section id="el-section-summary" className="el-section">
<div className="el-summary">
<h3 style={{ margin: '0 0 16px', fontSize: 18, fontWeight: 800 }}>向老板汇报的要点</h3>
<ul>
{summaryPoints.map((pt) => (
<li key={pt}>{pt}</li>
))}
</ul>
</div>
</section>
</main>
<footer className="el-footer">
双维统计业务总台账七类业务+ 车辆运营明细台账 · 对照 Axhub web 端已实现模块
</footer>
<Modal
title={currentSample.title}
open={sampleOpen}
onCancel={() => setSampleOpen(false)}
footer={<Button onClick={() => setSampleOpen(false)}>关闭</Button>}
width={Math.min(1100, typeof window !== 'undefined' ? window.innerWidth - 32 : 1100)}
centered
destroyOnClose
>
{currentSample.protoNote ? (
<p style={{ fontSize: 13, color: '#1e40af', marginBottom: 12, padding: '8px 12px', background: '#eff6ff', borderRadius: 8 }}>
{currentSample.protoNote}
{sampleKey === 'businessLedger' ? (
<span>
{' '}
·
{' '}
<a href={AXHUB_BIZ_LEDGER_URL} target="_blank" rel="noopener noreferrer">Axhub 业务台账</a>
</span>
) : null}
</p>
) : null}
{sampleKey === 'businessLedger' ? (
renderBusinessLedgerDrillDemo()
) : (
<>
<p style={{ fontSize: 13, color: C_MUTED, marginBottom: 12 }}>
列表层样表汇总数字需可点击钻取至下行明细车辆/客户/账户字段与 Axhub 原型保持一致处已标注路径
</p>
<Table size="small" pagination={false} scroll={{ x: 'max-content' }} columns={currentSample.columns} dataSource={currentSample.data} />
</>
)}
</Modal>
</div>
);
};
export default Component;