feat(web): 车辆管理查看/列表字段与展示;提车应收款查看总额;财务需求说明等
- 车辆管理-查看:运营公司/车辆来源/租赁公司、车辆与保险状态、出库/证照枚举对齐列表;移除多余概览项与租赁标签旁按钮 - 车辆管理:运营公司/来源/租赁公司列溢出与表格布局;筛选扩展 - 提车应收款-查看:应收/实收总额含氢费预充值,气泡明细与编辑页一致 - 新增数据分析-业务部汇总台账;工作台/提车应收款需求说明等调整 Made-with: Cursor
This commit is contained in:
56
web端/工作台.jsx
56
web端/工作台.jsx
@@ -115,7 +115,7 @@ const Component = function () {
|
|||||||
var todoMoreStatus = todoMoreStatusState[0];
|
var todoMoreStatus = todoMoreStatusState[0];
|
||||||
var setTodoMoreStatus = todoMoreStatusState[1];
|
var setTodoMoreStatus = todoMoreStatusState[1];
|
||||||
|
|
||||||
var todoBoardFilterState = useState('pending');
|
var todoBoardFilterState = useState('all');
|
||||||
var todoBoardFilter = todoBoardFilterState[0];
|
var todoBoardFilter = todoBoardFilterState[0];
|
||||||
var setTodoBoardFilter = todoBoardFilterState[1];
|
var setTodoBoardFilter = todoBoardFilterState[1];
|
||||||
|
|
||||||
@@ -282,34 +282,35 @@ const Component = function () {
|
|||||||
setOverdueReturnModalOpen(true);
|
setOverdueReturnModalOpen(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 待办任务表(原型:任务类型、任务名称、状态、生成时间、操作)
|
// 待办任务表(原型:任务类型、任务名称、状态、任务时间、操作)
|
||||||
var dashboardTodoRows = useMemo(function () {
|
var dashboardTodoRows = useMemo(function () {
|
||||||
return [
|
return [
|
||||||
{ id: '1', taskType: '交车', taskName: '交车任务 · 粤A12345 待确认交车单', genDate: '2026-02-27', path: 'web端/运维管理/车辆业务/交车管理.jsx', status: 'pending' },
|
{ id: '1', taskType: '交车', taskName: '顺通运输租赁苏龙18T(交车数:5)', genDate: '2026-03-26 20:08', path: 'web端/运维管理/车辆业务/交车管理.jsx', status: 'pending' },
|
||||||
{ id: '2', taskType: '调拨', taskName: '调拨任务 · 调拨单 DB-2026-009 待接收', genDate: '2026-02-26', path: 'web端/运维管理/车辆业务/调拨管理.jsx', status: 'overdue' },
|
{ id: '2', taskType: '异动', taskName: '异动任务-魏山(异动车数:9)', genDate: '2026-03-27 12:26', path: 'web端/运维管理/车辆业务/异动管理-结束异动.jsx', status: 'overdue' },
|
||||||
{ id: '3', taskType: '异动', taskName: '异动任务 · 异动单待结束登记', genDate: '2026-02-26', path: 'web端/运维管理/车辆业务/异动管理-结束异动.jsx', status: 'pending' },
|
{ id: '3', taskType: '年审', taskName: '年审任务(沪A62261F)', genDate: '2026-03-27 16:32', path: 'web端/运维管理/车辆业务/异动管理.jsx', status: 'pending' },
|
||||||
{ id: '4', taskType: '年审', taskName: '年审任务 · 粤B11111 年审材料待上传', genDate: '2026-02-25', path: 'web端/运维管理/车辆业务/异动管理.jsx', status: 'overdue' },
|
{ id: '4', taskType: '租赁账单', taskName: '御盛合-租赁苏龙18T(账单编号xxxxxxx)', genDate: '2026-03-28 00:00', path: 'web端/业务管理/租赁账单.jsx', status: 'pending' },
|
||||||
{ id: '5', taskType: '保险', taskName: '商业险到期 · 粤C22334 续保跟进', genDate: '2026-02-24', path: 'web端/车辆管理.jsx', status: 'pending' },
|
{ id: '5', taskType: '还车应结款', taskName: '云通-租赁帕力安18T(粤AGP3649)', genDate: '2026-03-28 11:53', path: 'web端/财务管理/提车应收款.jsx', status: 'pending' }
|
||||||
{ id: '6', taskType: '租赁账单', taskName: '租赁账单生成 · 项目「华南物流」2月账单', genDate: '2026-02-24', path: 'web端/业务管理/租赁账单.jsx', status: 'pending' },
|
|
||||||
{ id: '7', taskType: '审批中心', taskName: '提车应收款 · TK-2026-018 待提交', genDate: '2026-02-23', path: 'web端/财务管理/提车应收款.jsx', status: 'done' },
|
|
||||||
{ id: '8', taskType: '审批中心', taskName: '租赁合同审核 · HT-2025-088 法务附件', genDate: '2026-02-22', path: 'web端/车辆租赁合同/车辆租赁合同.jsx', status: 'done' }
|
|
||||||
];
|
];
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
var todoSummary = useMemo(function () {
|
var todoSummary = useMemo(function () {
|
||||||
var p = 0, o = 0, d = 0;
|
var p = 0, o = 0;
|
||||||
dashboardTodoRows.forEach(function (r) {
|
dashboardTodoRows.forEach(function (r) {
|
||||||
if (r.status === 'pending') p++;
|
if (r.status === 'pending') p++;
|
||||||
else if (r.status === 'overdue') o++;
|
else if (r.status === 'overdue') o++;
|
||||||
else if (r.status === 'done') d++;
|
|
||||||
});
|
});
|
||||||
return { pending: p, overdue: o, done: d };
|
return { pending: p, overdue: o };
|
||||||
}, [dashboardTodoRows]);
|
}, [dashboardTodoRows]);
|
||||||
|
|
||||||
var dashboardTodoBoardRows = useMemo(function () {
|
var dashboardTodoBoardRows = useMemo(function () {
|
||||||
return dashboardTodoRows.filter(function (r) {
|
var rows = dashboardTodoRows.filter(function (r) {
|
||||||
|
if (todoBoardFilter === 'all') return true;
|
||||||
return r.status === todoBoardFilter;
|
return r.status === todoBoardFilter;
|
||||||
});
|
});
|
||||||
|
rows.sort(function (a, b) {
|
||||||
|
return String(b.genDate || '').localeCompare(String(a.genDate || ''));
|
||||||
|
});
|
||||||
|
return rows;
|
||||||
}, [dashboardTodoRows, todoBoardFilter]);
|
}, [dashboardTodoRows, todoBoardFilter]);
|
||||||
|
|
||||||
var dashboardTodoBoardPreviewRows = useMemo(function () {
|
var dashboardTodoBoardPreviewRows = useMemo(function () {
|
||||||
@@ -319,7 +320,7 @@ const Component = function () {
|
|||||||
// 全部待办弹窗:任务类型下拉展示业务全量类型(与示意数据并集,联调可改为接口枚举)
|
// 全部待办弹窗:任务类型下拉展示业务全量类型(与示意数据并集,联调可改为接口枚举)
|
||||||
var todoMoreTaskTypeFilterOptions = useMemo(function () {
|
var todoMoreTaskTypeFilterOptions = useMemo(function () {
|
||||||
var catalog = [
|
var catalog = [
|
||||||
'交车', '调拨', '异动', '年审', '保险', '租赁账单', '审批中心',
|
'交车', '调拨', '异动', '年审', '保险', '租赁账单', '审批中心', '还车应结款',
|
||||||
'还车', '备车', '提车应收', '替换车', '违章', '事故', '充电', 'ETC', '能源账户', '氢费', '电费'
|
'还车', '备车', '提车应收', '替换车', '违章', '事故', '充电', 'ETC', '能源账户', '氢费', '电费'
|
||||||
];
|
];
|
||||||
var seen = {};
|
var seen = {};
|
||||||
@@ -1039,11 +1040,12 @@ const Component = function () {
|
|||||||
render: renderTodoStatusCell
|
render: renderTodoStatusCell
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '生成时间',
|
title: '任务时间',
|
||||||
dataIndex: 'genDate',
|
dataIndex: 'genDate',
|
||||||
key: 'genDate',
|
key: 'genDate',
|
||||||
width: 120,
|
width: 160,
|
||||||
showSorterTooltip: false,
|
showSorterTooltip: false,
|
||||||
|
defaultSortOrder: 'descend',
|
||||||
sorter: function (a, b) {
|
sorter: function (a, b) {
|
||||||
return String(a.genDate || '').localeCompare(String(b.genDate || ''));
|
return String(a.genDate || '').localeCompare(String(b.genDate || ''));
|
||||||
}
|
}
|
||||||
@@ -1084,11 +1086,12 @@ const Component = function () {
|
|||||||
render: renderTodoStatusCell
|
render: renderTodoStatusCell
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '生成时间',
|
title: '任务时间',
|
||||||
dataIndex: 'genDate',
|
dataIndex: 'genDate',
|
||||||
key: 'genDate',
|
key: 'genDate',
|
||||||
width: 120,
|
width: 160,
|
||||||
showSorterTooltip: false,
|
showSorterTooltip: false,
|
||||||
|
defaultSortOrder: 'descend',
|
||||||
sorter: function (a, b) {
|
sorter: function (a, b) {
|
||||||
return String(a.genDate || '').localeCompare(String(b.genDate || ''));
|
return String(a.genDate || '').localeCompare(String(b.genDate || ''));
|
||||||
}
|
}
|
||||||
@@ -1119,13 +1122,17 @@ const Component = function () {
|
|||||||
}, [pushUrgeNotice]);
|
}, [pushUrgeNotice]);
|
||||||
|
|
||||||
var todoMoreFilteredRows = useMemo(function () {
|
var todoMoreFilteredRows = useMemo(function () {
|
||||||
return dashboardTodoRows.filter(function (r) {
|
var rows = dashboardTodoRows.filter(function (r) {
|
||||||
if (todoMoreTaskType && r.taskType !== todoMoreTaskType) return false;
|
if (todoMoreTaskType && r.taskType !== todoMoreTaskType) return false;
|
||||||
if (todoMoreStatus && r.status !== todoMoreStatus) return false;
|
if (todoMoreStatus && r.status !== todoMoreStatus) return false;
|
||||||
if (todoMoreDateStart && r.genDate < todoMoreDateStart) return false;
|
if (todoMoreDateStart && r.genDate < todoMoreDateStart) return false;
|
||||||
if (todoMoreDateEnd && r.genDate > todoMoreDateEnd) return false;
|
if (todoMoreDateEnd && r.genDate > todoMoreDateEnd) return false;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
rows.sort(function (a, b) {
|
||||||
|
return String(b.genDate || '').localeCompare(String(a.genDate || ''));
|
||||||
|
});
|
||||||
|
return rows;
|
||||||
}, [dashboardTodoRows, todoMoreTaskType, todoMoreStatus, todoMoreDateStart, todoMoreDateEnd]);
|
}, [dashboardTodoRows, todoMoreTaskType, todoMoreStatus, todoMoreDateStart, todoMoreDateEnd]);
|
||||||
|
|
||||||
var openTodoMoreModal = useCallback(function () {
|
var openTodoMoreModal = useCallback(function () {
|
||||||
@@ -1474,6 +1481,9 @@ const Component = function () {
|
|||||||
'.workbench-todo-table .ant-table-column-sorter{color:#98a1b0!important}' +
|
'.workbench-todo-table .ant-table-column-sorter{color:#98a1b0!important}' +
|
||||||
'.workbench-todo-table .ant-table-column-sorter-up.active,.workbench-todo-table .ant-table-column-sorter-down.active{color:#707d8f!important}' +
|
'.workbench-todo-table .ant-table-column-sorter-up.active,.workbench-todo-table .ant-table-column-sorter-down.active{color:#707d8f!important}' +
|
||||||
'.workbench-todo-table.ant-table-small .ant-table-thead>tr>th{padding:10px 12px!important}' +
|
'.workbench-todo-table.ant-table-small .ant-table-thead>tr>th{padding:10px 12px!important}' +
|
||||||
|
'.workbench-todo-table .ant-table-thead>tr>th.ant-table-column-sort{background:#f7f8fa!important}' +
|
||||||
|
'.workbench-todo-table .ant-table-tbody>tr>td.ant-table-column-sort{background:#fff!important}' +
|
||||||
|
'.workbench-todo-table .ant-table-tbody>tr.ant-table-row:hover>td.ant-table-column-sort{background:#fafafa!important}' +
|
||||||
'.workbench-one-screen .workbench-quick-card.ant-card{display:flex;flex-direction:column;min-height:0;height:100%}' +
|
'.workbench-one-screen .workbench-quick-card.ant-card{display:flex;flex-direction:column;min-height:0;height:100%}' +
|
||||||
'.workbench-one-screen .workbench-quick-card .ant-card-body{padding:0;flex:1;min-height:0;display:flex;flex-direction:column}' +
|
'.workbench-one-screen .workbench-quick-card .ant-card-body{padding:0;flex:1;min-height:0;display:flex;flex-direction:column}' +
|
||||||
'.workbench-quick-scroll{flex:1;min-height:0;overflow:auto;-webkit-overflow-scrolling:touch}' +
|
'.workbench-quick-scroll{flex:1;min-height:0;overflow:auto;-webkit-overflow-scrolling:touch}' +
|
||||||
@@ -1585,9 +1595,9 @@ const Component = function () {
|
|||||||
title: React.createElement(Space, { size: 8, wrap: true, align: 'center' },
|
title: React.createElement(Space, { size: 8, wrap: true, align: 'center' },
|
||||||
React.createElement('span', { className: 'workbench-dash-pair-head-title' }, '待办任务'),
|
React.createElement('span', { className: 'workbench-dash-pair-head-title' }, '待办任务'),
|
||||||
React.createElement(Badge, { count: todoSummary.pending + todoSummary.overdue, style: { backgroundColor: accentBlue } }),
|
React.createElement(Badge, { count: todoSummary.pending + todoSummary.overdue, style: { backgroundColor: accentBlue } }),
|
||||||
|
renderTodoStatChip('all', '全部', dashboardTodoRows.length, 'rgba(0,0,0,0.04)', '#595959'),
|
||||||
renderTodoStatChip('pending', '待处理', todoSummary.pending, 'rgba(22,119,255,0.06)', accentBlue),
|
renderTodoStatChip('pending', '待处理', todoSummary.pending, 'rgba(22,119,255,0.06)', accentBlue),
|
||||||
renderTodoStatChip('overdue', '已超时', todoSummary.overdue, 'rgba(245,34,45,0.06)', '#f5222d'),
|
renderTodoStatChip('overdue', '已超时', todoSummary.overdue, 'rgba(245,34,45,0.06)', '#f5222d')
|
||||||
renderTodoStatChip('done', '已完成', todoSummary.done, 'rgba(82,196,26,0.08)', '#52c41a')
|
|
||||||
),
|
),
|
||||||
bordered: false,
|
bordered: false,
|
||||||
style: Object.assign({}, cardStyle, { flex: 1, width: '100%', minHeight: 0, display: 'flex', flexDirection: 'column' }),
|
style: Object.assign({}, cardStyle, { flex: 1, width: '100%', minHeight: 0, display: 'flex', flexDirection: 'column' }),
|
||||||
@@ -1731,7 +1741,7 @@ const Component = function () {
|
|||||||
value: todoMoreStatus,
|
value: todoMoreStatus,
|
||||||
onChange: setTodoMoreStatus
|
onChange: setTodoMoreStatus
|
||||||
}),
|
}),
|
||||||
React.createElement(Text, { type: 'secondary', style: { fontSize: 12 } }, '生成时间'),
|
React.createElement(Text, { type: 'secondary', style: { fontSize: 12 } }, '任务时间'),
|
||||||
React.createElement(DatePicker.RangePicker, {
|
React.createElement(DatePicker.RangePicker, {
|
||||||
style: { width: 140 },
|
style: { width: 140 },
|
||||||
format: 'YYYY-MM-DD',
|
format: 'YYYY-MM-DD',
|
||||||
|
|||||||
553
web端/数据分析/业务部汇总台账.jsx
Normal file
553
web端/数据分析/业务部汇总台账.jsx
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 汇总行:对 1–12 月逐字段求和 */
|
||||||
|
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 }
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -43,6 +43,12 @@ const Component = function () {
|
|||||||
];
|
];
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 与提车收款单一致:存在氢费预付款时参与总额;应收/实收金额取值与编辑页 hydrogen 字段一致
|
||||||
|
var hasHydrogenPrepay = true;
|
||||||
|
var hydrogen = useMemo(function () {
|
||||||
|
return { receivable: '3580.00', actual: '3500.00', discount: '80.00', discountRemark: '预付款批量减免' };
|
||||||
|
}, []);
|
||||||
|
|
||||||
var totals = useMemo(function () {
|
var totals = useMemo(function () {
|
||||||
var receivableRent = 0, actualRent = 0, receivableDeposit = 0, receivableService = 0, actualService = 0, discountTotal = 0;
|
var receivableRent = 0, actualRent = 0, receivableDeposit = 0, receivableService = 0, actualService = 0, discountTotal = 0;
|
||||||
vehicles.forEach(function (v) {
|
vehicles.forEach(function (v) {
|
||||||
@@ -63,41 +69,63 @@ const Component = function () {
|
|||||||
};
|
};
|
||||||
}, [vehicles]);
|
}, [vehicles]);
|
||||||
|
|
||||||
var receivableTotal = (parseFloat(totals.receivableRent) + parseFloat(totals.receivableDeposit) + parseFloat(totals.receivableService)).toFixed(2);
|
var hydrogenReceivable = hasHydrogenPrepay ? (parseFloat(hydrogen.receivable) || 0) : 0;
|
||||||
var actualTotal = (parseFloat(totals.actualRent) + parseFloat(totals.receivableDeposit) + parseFloat(totals.actualService) - parseFloat(totals.discountTotal)).toFixed(2);
|
var hydrogenActual = hasHydrogenPrepay ? (parseFloat(hydrogen.actual) || 0) : 0;
|
||||||
|
|
||||||
var receivablePopoverContent = React.createElement('div', { style: { padding: 8, minWidth: 220 } },
|
var receivableTotal = useMemo(function () {
|
||||||
React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13 } },
|
var base = parseFloat(totals.receivableRent) + parseFloat(totals.receivableDeposit) + parseFloat(totals.receivableService);
|
||||||
React.createElement('thead', null,
|
return (base + hydrogenReceivable).toFixed(2);
|
||||||
React.createElement('tr', null,
|
}, [totals, hydrogenReceivable]);
|
||||||
React.createElement('th', { style: { textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '项目'),
|
|
||||||
React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '金额')
|
|
||||||
)
|
|
||||||
),
|
|
||||||
React.createElement('tbody', null,
|
|
||||||
React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收车辆月租金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableRent + ' 元')),
|
|
||||||
React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收车辆保证金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableDeposit + ' 元')),
|
|
||||||
React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收服务费'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableService + ' 元'))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
var actualPopoverContent = React.createElement('div', { style: { padding: 8, minWidth: 220 } },
|
var actualTotal = useMemo(function () {
|
||||||
React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13 } },
|
var base = parseFloat(totals.actualRent) + parseFloat(totals.receivableDeposit) + parseFloat(totals.actualService) - parseFloat(totals.discountTotal);
|
||||||
React.createElement('thead', null,
|
return (base + hydrogenActual).toFixed(2);
|
||||||
React.createElement('tr', null,
|
}, [totals, hydrogenActual]);
|
||||||
React.createElement('th', { style: { textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '项目'),
|
|
||||||
React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '金额')
|
var receivablePopoverContent = useMemo(function () {
|
||||||
)
|
var rows = [
|
||||||
),
|
React.createElement('tr', { key: 'rent' }, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收车辆月租金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableRent + ' 元')),
|
||||||
React.createElement('tbody', null,
|
React.createElement('tr', { key: 'deposit' }, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收车辆保证金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableDeposit + ' 元')),
|
||||||
React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计实收车辆月租金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.actualRent + ' 元')),
|
React.createElement('tr', { key: 'service' }, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收服务费'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableService + ' 元'))
|
||||||
React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收车辆保证金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableDeposit + ' 元')),
|
];
|
||||||
React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计实收服务费'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.actualService + ' 元')),
|
if (hasHydrogenPrepay) {
|
||||||
React.createElement('tr', null, React.createElement('td', { style: { padding: '6px 8px' } }, '总计减免金额'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.discountTotal + ' 元'))
|
rows.push(React.createElement('tr', { key: 'hydrogen' }, React.createElement('td', { style: { padding: '6px 8px' } }, '氢费预充值应收金额'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, (hydrogen.receivable || '0.00') + ' 元')));
|
||||||
|
}
|
||||||
|
return React.createElement('div', { style: { padding: 8, minWidth: 220 } },
|
||||||
|
React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13 } },
|
||||||
|
React.createElement('thead', null,
|
||||||
|
React.createElement('tr', null,
|
||||||
|
React.createElement('th', { style: { textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '项目'),
|
||||||
|
React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '金额')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
React.createElement('tbody', null, rows)
|
||||||
)
|
)
|
||||||
)
|
);
|
||||||
);
|
}, [totals, hasHydrogenPrepay, hydrogen.receivable]);
|
||||||
|
|
||||||
|
var actualPopoverContent = useMemo(function () {
|
||||||
|
var rows = [
|
||||||
|
React.createElement('tr', { key: 'rent' }, React.createElement('td', { style: { padding: '6px 8px' } }, '总计实收车辆月租金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.actualRent + ' 元')),
|
||||||
|
React.createElement('tr', { key: 'deposit' }, React.createElement('td', { style: { padding: '6px 8px' } }, '总计应收车辆保证金'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.receivableDeposit + ' 元')),
|
||||||
|
React.createElement('tr', { key: 'service' }, React.createElement('td', { style: { padding: '6px 8px' } }, '总计实收服务费'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.actualService + ' 元')),
|
||||||
|
React.createElement('tr', { key: 'discount' }, React.createElement('td', { style: { padding: '6px 8px' } }, '总计减免金额'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, totals.discountTotal + ' 元'))
|
||||||
|
];
|
||||||
|
if (hasHydrogenPrepay) {
|
||||||
|
rows.push(React.createElement('tr', { key: 'hydrogen' }, React.createElement('td', { style: { padding: '6px 8px' } }, '氢费预充值实收金额'), React.createElement('td', { style: { textAlign: 'right', padding: '6px 8px' } }, (hydrogen.actual || '0.00') + ' 元')));
|
||||||
|
}
|
||||||
|
return React.createElement('div', { style: { padding: 8, minWidth: 220 } },
|
||||||
|
React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse', fontSize: 13 } },
|
||||||
|
React.createElement('thead', null,
|
||||||
|
React.createElement('tr', null,
|
||||||
|
React.createElement('th', { style: { textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '项目'),
|
||||||
|
React.createElement('th', { style: { textAlign: 'right', padding: '6px 8px', borderBottom: '1px solid #f0f0f0' } }, '金额')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
React.createElement('tbody', null, rows)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, [totals, hasHydrogenPrepay, hydrogen.actual]);
|
||||||
|
|
||||||
// 审批情况:竖向步骤条
|
// 审批情况:竖向步骤条
|
||||||
var approvalSteps = useMemo(function () {
|
var approvalSteps = useMemo(function () {
|
||||||
@@ -231,10 +259,10 @@ const Component = function () {
|
|||||||
)
|
)
|
||||||
),
|
),
|
||||||
React.createElement('div', { style: { marginTop: 16, display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: 16, maxWidth: 800 } },
|
React.createElement('div', { style: { marginTop: 16, display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: 16, maxWidth: 800 } },
|
||||||
React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '氢费预付款应收金额'), React.createElement('div', { style: valueStyle }, '3580.00 元')),
|
React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '氢费预付款应收金额'), React.createElement('div', { style: valueStyle }, (hydrogen.receivable || '0.00') + ' 元')),
|
||||||
React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '氢费预付款实收金额'), React.createElement('div', { style: valueStyle }, '3500.00 元')),
|
React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '氢费预付款实收金额'), React.createElement('div', { style: valueStyle }, (hydrogen.actual || '0.00') + ' 元')),
|
||||||
React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '减免金额'), React.createElement('div', { style: valueStyle }, '80.00 元')),
|
React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '减免金额'), React.createElement('div', { style: valueStyle }, (hydrogen.discount || '0.00') + ' 元')),
|
||||||
React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '减免金额备注'), React.createElement('div', { style: valueStyle }, '预付款批量减免'))
|
React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '减免金额备注'), React.createElement('div', { style: valueStyle }, hydrogen.discountRemark || '—'))
|
||||||
),
|
),
|
||||||
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px 24px', marginTop: 16 } },
|
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px 24px', marginTop: 16 } },
|
||||||
React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '开票方式'), React.createElement('div', { style: valueStyle }, '先开票后付款')),
|
React.createElement('div', { style: formItemStyle }, React.createElement('div', { style: labelStyle }, '开票方式'), React.createElement('div', { style: valueStyle }, '先开票后付款')),
|
||||||
|
|||||||
@@ -25,30 +25,27 @@ const Component = function () {
|
|||||||
vin: 'LA9HE60A0NBAF4031',
|
vin: 'LA9HE60A0NBAF4031',
|
||||||
vehicleNo: '22FHD0007',
|
vehicleNo: '22FHD0007',
|
||||||
owner: '浙江羚牛氢能科技有限公司',
|
owner: '浙江羚牛氢能科技有限公司',
|
||||||
|
operateCompany: '羚牛运营(嘉兴)',
|
||||||
|
vehicleSource: '自有',
|
||||||
|
leaseCompany: '上海迅杰物流有限公司',
|
||||||
contractNo: 'LNZLHTSH2023071301',
|
contractNo: 'LNZLHTSH2023071301',
|
||||||
outStatus: '租赁交车',
|
// 与「车辆管理」列表一致:车辆状态、出库状态、证照状态、保险状态枚举见 web端/车辆管理.jsx 注释
|
||||||
|
vehicleStatus: '已交车',
|
||||||
|
outStatus: '无',
|
||||||
licenseStatus: '正常',
|
licenseStatus: '正常',
|
||||||
|
insuranceStatus: '正常',
|
||||||
location: '浙江省嘉兴市平湖市XXXXXXXXXXX街道XXXXXXXXXXX号XXXXXXXx',
|
location: '浙江省嘉兴市平湖市XXXXXXXXXXX街道XXXXXXXXXXX号XXXXXXXx',
|
||||||
bodyColor: '白色',
|
bodyColor: '白色',
|
||||||
purchaseDate: '2026-09-09',
|
purchaseDate: '2026-09-09',
|
||||||
warehouseStatus: '已交付车-租赁服务',
|
|
||||||
customerName: '上海迅杰物流有限公司',
|
customerName: '上海迅杰物流有限公司',
|
||||||
prepStatus: '正常',
|
|
||||||
scrapStatus: '无',
|
|
||||||
gpsLastTime: '2026-09-09 10:50',
|
gpsLastTime: '2026-09-09 10:50',
|
||||||
resourceCategory: 'XXXXXXXXXX',
|
|
||||||
manufactureYear: '2025',
|
manufactureYear: '2025',
|
||||||
parkingPlace: '-',
|
parkingPlace: '-',
|
||||||
bizDept: '业务三部',
|
bizDept: '业务三部',
|
||||||
lastPrepTime: '2026-09-09',
|
|
||||||
assetRating: 'XXXXXXXXXX',
|
|
||||||
ratingTime: '2027-09-09',
|
ratingTime: '2027-09-09',
|
||||||
forceScrapDate: '2026-09-09',
|
forceScrapDate: '2026-09-09',
|
||||||
nextInspectionTime: '2026-09-09',
|
nextInspectionTime: '2026-09-09',
|
||||||
bizManager: '金可鹏',
|
bizManager: '金可鹏'
|
||||||
maintainStatus: '正常',
|
|
||||||
preemptStatus: '无',
|
|
||||||
transferStatus: '无'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 型号参数 tab 数据
|
// 型号参数 tab 数据
|
||||||
@@ -1278,37 +1275,32 @@ const Component = function () {
|
|||||||
React.createElement('div', { style: styles.cardBody },
|
React.createElement('div', { style: styles.cardBody },
|
||||||
React.createElement('div', { style: styles.overviewHeader },
|
React.createElement('div', { style: styles.overviewHeader },
|
||||||
React.createElement('span', { style: styles.plateNo }, overview.plateNo),
|
React.createElement('span', { style: styles.plateNo }, overview.plateNo),
|
||||||
React.createElement(Tag, { color: 'blue' }, overview.plateTag),
|
React.createElement(Tag, { color: 'blue' }, overview.plateTag)
|
||||||
React.createElement(Button, { type: 'link', size: 'small', style: { padding: 0 } }, '1')
|
|
||||||
),
|
),
|
||||||
React.createElement('div', { style: styles.overviewGrid },
|
React.createElement('div', { style: styles.overviewGrid },
|
||||||
React.createElement(InfoRow, { label: '车架号', value: overview.vin }),
|
React.createElement(InfoRow, { label: '车架号', value: overview.vin }),
|
||||||
React.createElement(InfoRow, { label: '车辆编号', value: overview.vehicleNo }),
|
React.createElement(InfoRow, { label: '车辆编号', value: overview.vehicleNo }),
|
||||||
React.createElement(InfoRow, { label: '登记所有权', value: overview.owner }),
|
React.createElement(InfoRow, { label: '登记所有权', value: overview.owner }),
|
||||||
|
React.createElement(InfoRow, { label: '运营公司', value: overview.operateCompany }),
|
||||||
|
React.createElement(InfoRow, { label: '车辆来源', value: overview.vehicleSource }),
|
||||||
|
React.createElement(InfoRow, { label: '租赁公司', value: overview.leaseCompany }),
|
||||||
React.createElement(InfoRow, { label: '合同编号', value: overview.contractNo }),
|
React.createElement(InfoRow, { label: '合同编号', value: overview.contractNo }),
|
||||||
|
React.createElement(InfoRow, { label: '车辆状态', value: overview.vehicleStatus }),
|
||||||
React.createElement(InfoRow, { label: '出库状态', value: overview.outStatus }),
|
React.createElement(InfoRow, { label: '出库状态', value: overview.outStatus }),
|
||||||
React.createElement(InfoRow, { label: '证照状态', value: overview.licenseStatus }),
|
React.createElement(InfoRow, { label: '证照状态', value: overview.licenseStatus }),
|
||||||
|
React.createElement(InfoRow, { label: '保险状态', value: overview.insuranceStatus }),
|
||||||
React.createElement(InfoRow, { label: '车辆当前位置', value: overview.location, ellipsisWithTooltip: true }),
|
React.createElement(InfoRow, { label: '车辆当前位置', value: overview.location, ellipsisWithTooltip: true }),
|
||||||
React.createElement(InfoRow, { label: '车身颜色', value: overview.bodyColor }),
|
React.createElement(InfoRow, { label: '车身颜色', value: overview.bodyColor }),
|
||||||
React.createElement(InfoRow, { label: '采购入库日期', value: overview.purchaseDate }),
|
React.createElement(InfoRow, { label: '采购入库日期', value: overview.purchaseDate }),
|
||||||
React.createElement(InfoRow, { label: '库位状态', value: overview.warehouseStatus }),
|
|
||||||
React.createElement(InfoRow, { label: '客户名称', value: overview.customerName }),
|
React.createElement(InfoRow, { label: '客户名称', value: overview.customerName }),
|
||||||
React.createElement(InfoRow, { label: '整备状态', value: overview.prepStatus }),
|
|
||||||
React.createElement(InfoRow, { label: '报废状态', value: overview.scrapStatus }),
|
|
||||||
React.createElement(InfoRow, { label: 'GPS最后上传', value: overview.gpsLastTime }),
|
React.createElement(InfoRow, { label: 'GPS最后上传', value: overview.gpsLastTime }),
|
||||||
React.createElement(InfoRow, { label: '资源分类', value: overview.resourceCategory }),
|
|
||||||
React.createElement(InfoRow, { label: '出厂年份', value: overview.manufactureYear }),
|
React.createElement(InfoRow, { label: '出厂年份', value: overview.manufactureYear }),
|
||||||
React.createElement(InfoRow, { label: '停车位置', value: overview.parkingPlace }),
|
React.createElement(InfoRow, { label: '停车位置', value: overview.parkingPlace }),
|
||||||
React.createElement(InfoRow, { label: '业务部门', value: overview.bizDept }),
|
React.createElement(InfoRow, { label: '业务部门', value: overview.bizDept }),
|
||||||
React.createElement(InfoRow, { label: '上次整备时间', value: overview.lastPrepTime }),
|
|
||||||
React.createElement(InfoRow, { label: '资产评级', value: overview.assetRating }),
|
|
||||||
React.createElement(InfoRow, { label: '等评时间', value: overview.ratingTime }),
|
React.createElement(InfoRow, { label: '等评时间', value: overview.ratingTime }),
|
||||||
React.createElement(InfoRow, { label: '强制报废期', value: overview.forceScrapDate }),
|
React.createElement(InfoRow, { label: '强制报废期', value: overview.forceScrapDate }),
|
||||||
React.createElement(InfoRow, { label: '下次年检时间', value: overview.nextInspectionTime }),
|
React.createElement(InfoRow, { label: '下次年检时间', value: overview.nextInspectionTime }),
|
||||||
React.createElement(InfoRow, { label: '业务负责人', value: overview.bizManager }),
|
React.createElement(InfoRow, { label: '业务负责人', value: overview.bizManager })
|
||||||
React.createElement(InfoRow, { label: '维修状态', value: overview.maintainStatus }),
|
|
||||||
React.createElement(InfoRow, { label: '预占状态', value: overview.preemptStatus }),
|
|
||||||
React.createElement(InfoRow, { label: '过户状态', value: overview.transferStatus })
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
2.提车应收款信息:
|
2.提车应收款信息:
|
||||||
#上方显示应收款总额、实收款总额;
|
#上方显示应收款总额、实收款总额;
|
||||||
2.1.应收款总额:格式为:xx.xx元,重点色,计算方式为:「选中车辆应收车辆月租金总和」+「选中车辆应收车辆保证金总和」+「选中车辆应收服务费总和」;
|
2.1.应收款总额:格式为:xx.xx元,重点色,计算方式为:「选中车辆应收车辆月租金总和」+「选中车辆应收车辆保证金总和」+「选中车辆应收服务费总和」+「氢费预充值应收金额」;
|
||||||
点击应收款总额金额,弹出气泡卡片,气泡卡片列表展示:项目、金额;
|
点击应收款总额金额,弹出气泡卡片,气泡卡片列表展示:项目、金额;
|
||||||
2.1.1.项目:包括总计应收车辆月租金、总计应收车辆保证金、总计应收服务费三项;
|
2.1.1.项目:包括总计应收车辆月租金、总计应收车辆保证金、总计应收服务费三项;
|
||||||
2.1.2.金额:显示该项目对应应收金额;
|
2.1.2.金额:显示该项目对应应收金额;
|
||||||
|
|||||||
Reference in New Issue
Block a user