Files
ONE-OS/web端/数据分析/业务部汇总台账.jsx
王冕 623fb62fde feat(web): 车辆管理查看/列表字段与展示;提车应收款查看总额;财务需求说明等
- 车辆管理-查看:运营公司/车辆来源/租赁公司、车辆与保险状态、出库/证照枚举对齐列表;移除多余概览项与租赁标签旁按钮
- 车辆管理:运营公司/来源/租赁公司列溢出与表格布局;筛选扩展
- 提车应收款-查看:应收/实收总额含氢费预充值,气泡明细与编辑页一致
- 新增数据分析-业务部汇总台账;工作台/提车应收款需求说明等调整

Made-with: Cursor
2026-03-30 13:50:44 +08:00

554 lines
20 KiB
JavaScript
Raw 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 作为组件变量名
// 数据分析 - 浙江羚牛氢能业务部汇总台账(月度 × 业务 × 业绩/成本/利润)
// 原型:年份 + 业务部筛选、导出;点击「业绩」金额钻取业务员 → 再钻取项目明细(联调可替换为接口)
const Component = function () {
var useState = React.useState;
var useMemo = React.useMemo;
var useCallback = React.useCallback;
var antd = window.antd;
var App = antd.App;
var Breadcrumb = antd.Breadcrumb;
var Card = antd.Card;
var Button = antd.Button;
var Table = antd.Table;
var Select = antd.Select;
var DatePicker = antd.DatePicker;
var Row = antd.Row;
var Col = antd.Col;
var Space = antd.Space;
var Modal = antd.Modal;
var message = antd.message;
/** 业务板块(与示意图一致):自营 / 租赁 / 销售 / 审车 / 代办 / ETC / 其他 */
var CATEGORY_DEFS = [
{ key: 'self', label: '自营业务' },
{ key: 'lease', label: '租赁业务' },
{ key: 'sales', label: '销售' },
{ key: 'inspection', label: '审车' },
{ key: 'agency', label: '代办' },
{ key: 'etc', label: 'ETC' },
{ key: 'other', label: '其他' }
];
var CATEGORY_KEYS = CATEGORY_DEFS.map(function (c) { return c.key; });
function filterOption(input, option) {
var label = (option && (option.label || option.children)) || '';
return String(label).toLowerCase().indexOf(String(input || '').toLowerCase()) >= 0;
}
function fmtMoney(n) {
if (n === null || n === undefined || n === '') return '-';
var x = Number(n);
if (isNaN(x)) return '-';
if (x === 0) return '-';
return x.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
function escapeCsv(v) {
var s = v == null ? '' : String(v);
if (s.indexOf(',') !== -1 || s.indexOf('"') !== -1 || s.indexOf('\n') !== -1 || s.indexOf('\r') !== -1) {
return '"' + s.replace(/"/g, '""') + '"';
}
return s;
}
function downloadCsv(filename, lines) {
var csv = lines.map(function (row) { return row.map(escapeCsv).join(','); }).join('\n');
var blob = new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8' });
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
function initialYear() {
try {
if (window.dayjs) return window.dayjs('2026-01-01');
} catch (e1) {}
return null;
}
function numOrZero(v) {
if (v === null || v === undefined || v === '') return 0;
var n = Number(v);
return isNaN(n) ? 0 : n;
}
/** 汇总行:对 112 月逐字段求和 */
function sumLedgerRows(monthRows) {
var acc = { month: 13, monthLabel: '合计', rowType: 'total', key: 'total' };
CATEGORY_KEYS.forEach(function (k) {
acc[k + 'Perf'] = 0;
acc[k + 'Cost'] = 0;
acc[k + 'Profit'] = 0;
});
(monthRows || []).forEach(function (r) {
CATEGORY_KEYS.forEach(function (k) {
acc[k + 'Perf'] += numOrZero(r[k + 'Perf']);
acc[k + 'Cost'] += numOrZero(r[k + 'Cost']);
acc[k + 'Profit'] += numOrZero(r[k + 'Profit']);
});
});
CATEGORY_KEYS.forEach(function (k) {
if (acc[k + 'Perf'] === 0) acc[k + 'Perf'] = null;
if (acc[k + 'Cost'] === 0) acc[k + 'Cost'] = null;
if (acc[k + 'Profit'] === 0) acc[k + 'Profit'] = null;
});
return acc;
}
/**
* 原型数据2026 年按月1 月部分数值与示意图/样例一致审车业绩、代办业绩、ETC业绩
* 其余月份为演示占位,可联调替换
*/
function buildMockYear2026() {
var rows = [];
var template = [
{
month: 1,
selfPerf: 285000.5, selfCost: 240000, selfProfit: 45000.5,
leasePerf: 188000, leaseCost: 120000, leaseProfit: 68000,
salesPerf: 420000, salesCost: 310000, salesProfit: 110000,
inspectionPerf: 131241.59, inspectionCost: 88000, inspectionProfit: 43241.59,
agencyPerf: 4004.73, agencyCost: 1200, agencyProfit: 2804.73,
etcPerf: 79750.92, etcCost: 45000, etcProfit: 34750.92,
otherPerf: 12000, otherCost: 5000, otherProfit: 7000
},
{
month: 2,
selfPerf: 260000, selfCost: 230000, selfProfit: 30000,
leasePerf: 195000, leaseCost: 125000, leaseProfit: 70000,
salesPerf: null, salesCost: null, salesProfit: null,
inspectionPerf: 98000, inspectionCost: 60000, inspectionProfit: 38000,
agencyPerf: 3200, agencyCost: 1000, agencyProfit: 2200,
etcPerf: 72000, etcCost: 40000, etcProfit: 32000,
otherPerf: null, otherCost: null, otherProfit: null
},
{
month: 3,
selfPerf: 270000, selfCost: 235000, selfProfit: 35000,
leasePerf: 200000, leaseCost: 128000, leaseProfit: 72000,
salesPerf: 380000, salesCost: 290000, salesProfit: 90000,
inspectionPerf: 105000, inspectionCost: 70000, inspectionProfit: 35000,
agencyPerf: 4100, agencyCost: 1100, agencyProfit: 3000,
etcPerf: 81000, etcCost: 43000, etcProfit: 38000,
otherPerf: 8500, otherCost: 3000, otherProfit: 5500
}
];
var i;
for (i = 1; i <= 12; i++) {
var src = template[i - 1];
if (!src) {
src = {
month: i,
selfPerf: null, selfCost: null, selfProfit: null,
leasePerf: null, leaseCost: null, leaseProfit: null,
salesPerf: null, salesCost: null, salesProfit: null,
inspectionPerf: null, inspectionCost: null, inspectionProfit: null,
agencyPerf: null, agencyCost: null, agencyProfit: null,
etcPerf: null, etcCost: null, etcProfit: null,
otherPerf: null, otherCost: null, otherProfit: null
};
}
rows.push({
key: 'm' + i,
month: i,
monthLabel: i + '月',
rowType: 'month',
selfPerf: src.selfPerf, selfCost: src.selfCost, selfProfit: src.selfProfit,
leasePerf: src.leasePerf, leaseCost: src.leaseCost, leaseProfit: src.leaseProfit,
salesPerf: src.salesPerf, salesCost: src.salesCost, salesProfit: src.salesProfit,
inspectionPerf: src.inspectionPerf, inspectionCost: src.inspectionCost, inspectionProfit: src.inspectionProfit,
agencyPerf: src.agencyPerf, agencyCost: src.agencyCost, agencyProfit: src.agencyProfit,
etcPerf: src.etcPerf, etcCost: src.etcCost, etcProfit: src.etcProfit,
otherPerf: src.otherPerf, otherCost: src.otherCost, otherProfit: src.otherProfit
});
}
rows.push(sumLedgerRows(rows));
return rows;
}
/** 业务员钻取:按 月 + 业务 返回演示列表(金额拆分为比例,末行补齐差额) */
function mockSalesmenDrill(month, catKey, cellPerf) {
var base = [
{ key: 's1', name: '尚建华', ratio: 0.42 },
{ key: 's2', name: '刘念念', ratio: 0.35 },
{ key: 's3', name: '谯云', ratio: 0.15 },
{ key: 's4', name: '董剑煜', ratio: 0.08 }
];
var total = numOrZero(cellPerf);
if (total <= 0) total = 100000;
var assigned = 0;
var parts = base.map(function (b, idx) {
if (idx === base.length - 1) {
var last = Math.round((total - assigned) * 100) / 100;
return { key: b.key, salesperson: b.name, amount: last };
}
var amt = Math.round(total * b.ratio * 100) / 100;
assigned += amt;
return { key: b.key, salesperson: b.name, amount: amt };
});
return parts;
}
/** 项目明细钻取 */
function mockProjectRows(salesperson, catKey) {
var catLabel = (CATEGORY_DEFS.find(function (c) { return c.key === catKey; }) || {}).label || catKey;
return [
{ key: 'p1', projectCode: 'PRJ-2026-001', projectName: catLabel + ' · 嘉兴冷链城配项目', plateNo: '沪A62261F', amount: null, bizDate: '2026-01-08', remark: '演示' },
{ key: 'p2', projectCode: 'PRJ-2026-018', projectName: catLabel + ' · 沪浙干线运输', plateNo: '粤AGP3649', amount: null, bizDate: '2026-01-15', remark: '-' },
{ key: 'p3', projectCode: 'PRJ-2026-033', projectName: catLabel + ' · 园区短驳', plateNo: '苏E·D32891', amount: null, bizDate: '2026-01-22', remark: '-' }
].map(function (r, i, arr) {
var share = i === arr.length - 1 ? 1 - arr.slice(0, -1).reduce(function (a) { return a + 0.31; }, 0) : 0.31;
return Object.assign({}, r, { amount: Math.round(88000 * share * 100) / 100 });
});
}
var layoutStyle = { padding: '16px 24px', background: '#f5f5f5', minHeight: '100vh' };
var filterLabelStyle = { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' };
var filterItemStyle = { marginBottom: 12 };
var filterControlStyle = { width: '100%' };
var filterActionsColStyle = { flex: '0 0 auto', marginLeft: 'auto' };
var ledgerTableStyle =
'.biz-ledger-summary-table .ant-table-thead>tr>th{color:rgba(0,0,0,0.75)!important;font-weight:500!important;font-size:12px!important;background:#e6f4ff!important;border-color:rgba(0,0,0,0.06)!important}' +
'.biz-ledger-summary-table .ant-table-thead>tr>th.ant-table-cell{background:#e6f4ff!important}' +
'.biz-ledger-summary-table .ant-table-tbody>tr>td,.biz-ledger-summary-table .ant-table-tbody>tr.ant-table-row{white-space:nowrap}' +
'.biz-ledger-summary-table .ant-table-tbody>tr[data-row-key=\"total\"]>td{font-weight:600;background:#fafafa!important}' +
'.biz-ledger-perf-link{cursor:pointer;color:#1677ff;padding:0;border:none;background:none;font:inherit}' +
'.biz-ledger-perf-link:hover{text-decoration:underline;color:#4096ff}';
var deptOptions = useMemo(function () {
return [
{ value: '业务二部', label: '业务二部' },
{ value: '华东业务部', label: '华东业务部' },
{ value: '华南业务部', label: '华南业务部' },
{ value: '华北业务部', label: '华北业务部' },
{ value: '西南业务部', label: '西南业务部' }
];
}, []);
var yearDraftState = useState(initialYear);
var yearDraft = yearDraftState[0];
var setYearDraft = yearDraftState[1];
var yearAppliedState = useState(initialYear);
var yearApplied = yearAppliedState[0];
var setYearApplied = yearAppliedState[1];
var deptDraftState = useState('业务二部');
var deptDraft = deptDraftState[0];
var setDeptDraft = deptDraftState[1];
var deptAppliedState = useState('业务二部');
var deptApplied = deptAppliedState[0];
var setDeptApplied = deptAppliedState[1];
var dataSource = useMemo(function () {
var y = yearApplied && yearApplied.format ? yearApplied.format('YYYY') : '';
if (y === '2026') return buildMockYear2026();
return [];
}, [yearApplied]);
var tableTitle = useMemo(function () {
var y = yearApplied && yearApplied.format ? yearApplied.format('YYYY') : '—';
return y + '年度浙江羚牛氢能业务部汇总台账';
}, [yearApplied]);
var handleQuery = useCallback(function () {
setYearApplied(yearDraft);
setDeptApplied(deptDraft);
}, [yearDraft, deptDraft]);
var handleReset = useCallback(function () {
var y0 = initialYear();
setYearDraft(y0);
setYearApplied(y0);
setDeptDraft('业务二部');
setDeptApplied('业务二部');
}, []);
var salesModalState = useState({ open: false, month: null, monthLabel: '', catKey: '', catLabel: '', perf: null });
var salesModal = salesModalState[0];
var setSalesModal = salesModalState[1];
var projectModalState = useState({ open: false, salesperson: '', catKey: '', amount: null });
var projectModal = projectModalState[0];
var setProjectModal = projectModalState[1];
var salesModalRows = useMemo(function () {
if (!salesModal.open || salesModal.month == null || !salesModal.catKey) return [];
return mockSalesmenDrill(salesModal.month, salesModal.catKey, salesModal.perf);
}, [salesModal.open, salesModal.month, salesModal.catKey, salesModal.perf]);
var projectModalRows = useMemo(function () {
if (!projectModal.open || !projectModal.salesperson) return [];
return mockProjectRows(projectModal.salesperson, projectModal.catKey || 'self');
}, [projectModal.open, projectModal.salesperson, projectModal.catKey]);
var openPerfDrill = useCallback(function (row, catKey) {
if (row.rowType === 'total') {
message.info('合计行演示不支持钻取,请从各月份「业绩」进入');
return;
}
var v = row[catKey + 'Perf'];
if (v === null || v === undefined || v === '' || numOrZero(v) === 0) {
message.warning('该单元格无业绩数据');
return;
}
var catLabel = (CATEGORY_DEFS.find(function (c) { return c.key === catKey; }) || {}).label || catKey;
setSalesModal({
open: true,
month: row.month,
monthLabel: row.monthLabel,
catKey: catKey,
catLabel: catLabel,
perf: v
});
}, []);
var closeSalesModal = useCallback(function () {
setSalesModal(function (s) { return Object.assign({}, s, { open: false }); });
}, []);
var openProjectDrill = useCallback(function (r) {
setProjectModal({
open: true,
salesperson: r.salesperson,
catKey: salesModal.catKey,
amount: r.amount
});
}, [salesModal.catKey]);
var closeProjectModal = useCallback(function () {
setProjectModal({ open: false, salesperson: '', catKey: '', amount: null });
}, []);
var salesModalColumns = useMemo(function () {
return [
{ title: '业务员', dataIndex: 'salesperson', key: 'salesperson', width: 120 },
{
title: '业绩金额',
dataIndex: 'amount',
key: 'amount',
align: 'right',
render: function (v, r) {
return React.createElement('button', {
type: 'button',
className: 'biz-ledger-perf-link',
onClick: function () { openProjectDrill(r); }
}, fmtMoney(v));
}
},
{ title: '说明', key: 'hint', width: 200, render: function () { return React.createElement('span', { style: { color: 'rgba(0,0,0,0.45)', fontSize: 12 } }, '点击金额查看项目明细'); } }
];
}, [openProjectDrill]);
var projectModalColumns = useMemo(function () {
return [
{ title: '项目编号', dataIndex: 'projectCode', key: 'projectCode', width: 130 },
{ title: '项目名称', dataIndex: 'projectName', key: 'projectName', ellipsis: true },
{ title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 110 },
{ title: '业绩金额', dataIndex: 'amount', key: 'amount', align: 'right', render: function (v) { return fmtMoney(v); } },
{ title: '业务日期', dataIndex: 'bizDate', key: 'bizDate', width: 110 },
{ title: '备注', dataIndex: 'remark', key: 'remark', width: 80 }
];
}, []);
var handleExport = useCallback(function () {
if (!dataSource || dataSource.length === 0) {
message.warning('当前无数据可导出,请先查询');
return;
}
var y = yearApplied && yearApplied.format ? yearApplied.format('YYYY') : 'ledger';
var headers = ['月份'];
CATEGORY_DEFS.forEach(function (c) {
headers.push(c.label + '-业绩', c.label + '-成本', c.label + '-利润');
});
var body = [headers];
dataSource.forEach(function (r) {
var line = [r.monthLabel];
CATEGORY_KEYS.forEach(function (k) {
line.push(fmtMoney(r[k + 'Perf']), fmtMoney(r[k + 'Cost']), fmtMoney(r[k + 'Profit']));
});
body.push(line);
});
body.push(['业务部', deptApplied]);
downloadCsv('业务部汇总台账_' + y + '_' + new Date().getTime() + '.csv', body);
message.success('已导出');
}, [dataSource, yearApplied, deptApplied]);
var ledgerColumns = useMemo(function () {
var cols = [
{
title: '月份',
dataIndex: 'monthLabel',
key: 'monthLabel',
fixed: 'left',
width: 72,
align: 'center',
render: function (t, r) {
if (r.rowType === 'total') return React.createElement('span', { style: { fontWeight: 600 } }, t);
return t;
}
}
];
CATEGORY_DEFS.forEach(function (cat) {
var ck = cat.key;
cols.push({
title: cat.label,
key: 'grp-' + ck,
children: [
{
title: '业绩',
dataIndex: ck + 'Perf',
key: ck + 'Perf',
width: 108,
align: 'right',
render: function (v, row) {
if (row.rowType === 'total' || v === null || v === undefined || v === '' || numOrZero(v) === 0) {
return fmtMoney(v);
}
return React.createElement('button', {
type: 'button',
className: 'biz-ledger-perf-link',
onClick: function () { openPerfDrill(row, ck); }
}, fmtMoney(v));
}
},
{
title: '成本',
dataIndex: ck + 'Cost',
key: ck + 'Cost',
width: 108,
align: 'right',
render: function (v) { return fmtMoney(v); }
},
{
title: '利润',
dataIndex: ck + 'Profit',
key: ck + 'Profit',
width: 108,
align: 'right',
render: function (v) { return fmtMoney(v); }
}
]
});
});
return cols;
}, [openPerfDrill]);
return React.createElement(App, null,
React.createElement('style', null, ledgerTableStyle),
React.createElement('div', { style: layoutStyle },
React.createElement(Breadcrumb, { style: { marginBottom: 12 }, items: [
{ title: '数据分析' },
{ title: '业务部汇总台账' }
] }),
React.createElement(Card, { style: { marginBottom: 16 } },
React.createElement(Row, { gutter: [16, 16], align: 'bottom' },
React.createElement(Col, { xs: 24, sm: 12, md: 8, lg: 6 },
React.createElement('div', { style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '年份选择'),
React.createElement(DatePicker, {
picker: 'year',
style: filterControlStyle,
placeholder: '请选择年份',
format: 'YYYY',
value: yearDraft,
onChange: function (v) { setYearDraft(v); }
})
)
),
React.createElement(Col, { xs: 24, sm: 12, md: 8, lg: 6 },
React.createElement('div', { style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '业务部'),
React.createElement(Select, {
placeholder: '请选择业务部',
style: filterControlStyle,
value: deptDraft,
onChange: function (v) { setDeptDraft(v); },
options: deptOptions,
showSearch: true,
allowClear: false,
filterOption: filterOption
})
)
),
React.createElement(Col, { xs: 24, sm: 12, md: 8, lg: 6, style: filterActionsColStyle },
React.createElement('div', { style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '\u00a0'),
React.createElement(Space, { wrap: true },
React.createElement(Button, { onClick: handleReset }, '重置'),
React.createElement(Button, { type: 'primary', onClick: handleQuery }, '查询'),
React.createElement(Button, { onClick: handleExport }, '导出')
)
)
)
)
),
React.createElement(Card, null,
React.createElement('div', { style: { textAlign: 'center', marginBottom: 16, fontSize: 16, fontWeight: 600, color: 'rgba(0,0,0,0.88)' } }, tableTitle),
React.createElement('div', { style: { textAlign: 'center', marginBottom: 8, fontSize: 12, color: 'rgba(0,0,0,0.45)' } },
'业务部:', deptApplied, ' (原型:仅 2026 年有演示数据;筛选业务部不影响数值,联调后按接口过滤)'
),
React.createElement(Table, {
className: 'biz-ledger-summary-table',
size: 'small',
bordered: true,
rowKey: 'key',
columns: ledgerColumns,
dataSource: dataSource,
pagination: false,
scroll: { x: 2600, y: 520 },
sticky: true
})
),
React.createElement(Modal, {
title: '业务员业绩 — ' + (salesModal.monthLabel || '') + ' / ' + (salesModal.catLabel || ''),
open: salesModal.open,
width: 720,
onCancel: closeSalesModal,
footer: React.createElement(Button, { onClick: closeSalesModal }, '关闭'),
destroyOnClose: true
},
React.createElement('p', { style: { marginBottom: 12, color: 'rgba(0,0,0,0.55)', fontSize: 12 } },
'从总表钻取:本业务线下各业务员业绩构成。点击「业绩金额」继续查看项目明细。'
),
React.createElement(Table, {
size: 'small',
rowKey: 'key',
columns: salesModalColumns,
dataSource: salesModalRows,
pagination: false
})
),
React.createElement(Modal, {
title: '项目明细 — ' + (projectModal.salesperson || '') + ' · ' + ((CATEGORY_DEFS.find(function (c) { return c.key === projectModal.catKey; }) || {}).label || ''),
open: projectModal.open,
width: 900,
onCancel: closeProjectModal,
footer: React.createElement(Button, { onClick: closeProjectModal }, '关闭'),
destroyOnClose: true
},
React.createElement('p', { style: { marginBottom: 12, color: 'rgba(0,0,0,0.55)', fontSize: 12 } },
'二级钻取:该项目业务员名下具体项目/车辆维度业绩(演示数据)。'
),
React.createElement(Table, {
size: 'small',
rowKey: 'key',
columns: projectModalColumns,
dataSource: projectModalRows,
pagination: false,
scroll: { x: 800 }
})
)
)
);
};