feat(web): 车辆管理查看/列表字段与展示;提车应收款查看总额;财务需求说明等

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

Made-with: Cursor
This commit is contained in:
王冕
2026-03-30 13:50:44 +08:00
parent 2cb451cc93
commit 623fb62fde
6 changed files with 724 additions and 110 deletions

View File

@@ -0,0 +1,553 @@
// 【重要】必须使用 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 }
})
)
)
);
};