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>
1865 lines
94 KiB
JavaScript
1865 lines
94 KiB
JavaScript
// 【重要】必须使用 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;
|