feat(web): 车辆租赁合同合同审批类型、法务上传盖章合同;新增数据分析页面;交车任务查看调整
- 租赁合同列表:合同审批类型筛选与列、需求说明;审批通过且未上传盖章件时法务可更多上传 - 新增/续签/转正式/查看等页同步合同审批类型及需求说明 - 新增数据分析:业务部业绩明细、物流业务月度统计、租赁客户氢费台账、租赁车辆收入明细 - 查看交车任务页面更新 Made-with: Cursor
This commit is contained in:
812
web端/数据分析/业务部业绩明细.jsx
Normal file
812
web端/数据分析/业务部业绩明细.jsx
Normal file
@@ -0,0 +1,812 @@
|
||||
// 【重要】必须使用 const Component 作为组件变量名
|
||||
// 数据分析 - 业务部业绩(明细 + 业绩/成本/利润汇总 Tab)
|
||||
// 明细:进入页面默认统计「上一自然月」;跨月后重新进入或刷新页面,默认月份随系统日期更新为上一个月。
|
||||
|
||||
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 Tabs = antd.Tabs;
|
||||
var Space = antd.Space;
|
||||
var message = antd.message;
|
||||
|
||||
var TableSummary = Table.Summary;
|
||||
var SummaryRow = TableSummary.Row;
|
||||
var SummaryCell = TableSummary.Cell;
|
||||
|
||||
function filterOption(input, option) {
|
||||
var label = (option && (option.label || option.children)) || '';
|
||||
return String(label).toLowerCase().indexOf(String(input || '').toLowerCase()) >= 0;
|
||||
}
|
||||
|
||||
/** 表格正文:千分位 + 两位小数;空或 0 显示 -(利润等可为负,仍正常显示) */
|
||||
function fmtCell(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 fmtSum(n) {
|
||||
var x = Number(n);
|
||||
if (isNaN(x)) return '-';
|
||||
return x.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 6 });
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/** 统计月份键 YYYY-MM(上一自然月;每月初「上月」随系统日期变化,刷新/重新进入页面即更新) */
|
||||
function getLastMonthStatKey() {
|
||||
try {
|
||||
if (window.dayjs) return window.dayjs().subtract(1, 'month').format('YYYY-MM');
|
||||
} catch (e1) {}
|
||||
var d = new Date();
|
||||
d.setDate(1);
|
||||
d.setMonth(d.getMonth() - 1);
|
||||
var y = d.getFullYear();
|
||||
var mo = d.getMonth() + 1;
|
||||
return y + '-' + (mo < 10 ? '0' + mo : '' + mo);
|
||||
}
|
||||
|
||||
/** 月份选择器默认值(与 getLastMonthStatKey 同一自然月) */
|
||||
function getLastMonthPickerValue() {
|
||||
try {
|
||||
if (window.dayjs) return window.dayjs().subtract(1, 'month').startOf('month');
|
||||
} catch (e1) {}
|
||||
try {
|
||||
if (window.moment) return window.moment().subtract(1, 'month').startOf('month');
|
||||
} catch (e2) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
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 tableSingleLineStyle =
|
||||
'.biz-dept-perf-table .ant-table-thead th,.biz-dept-perf-table .ant-table-tbody td,.biz-dept-perf-table .ant-table-summary td{white-space:nowrap;}';
|
||||
var tabsBarStyle = '.biz-dept-perf-tabs .ant-tabs-nav{margin-bottom:0;}';
|
||||
|
||||
var monthlyMetricKeys = ['logistics', 'lease', 'sales', 'hydrogen', 'electricity', 'etc', 'other', 'total'];
|
||||
|
||||
function sumMonthlyRows(rows, keys) {
|
||||
var sums = {};
|
||||
keys.forEach(function (k) {
|
||||
sums[k] = (rows || []).reduce(function (acc, row) {
|
||||
var v = row[k];
|
||||
var n = v === null || v === undefined || v === '' ? 0 : Number(v);
|
||||
return acc + (isNaN(n) ? 0 : n);
|
||||
}, 0);
|
||||
});
|
||||
return sums;
|
||||
}
|
||||
|
||||
function monthlyColumnsFor(suffix) {
|
||||
if (suffix === '业绩') {
|
||||
return [
|
||||
{ title: '月份', dataIndex: 'month', key: 'month', width: 72, align: 'center' },
|
||||
{ title: '物流业绩', dataIndex: 'logistics', key: 'logistics', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '租赁业绩', dataIndex: 'lease', key: 'lease', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '销售业绩', dataIndex: 'sales', key: 'sales', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '氢费业绩', dataIndex: 'hydrogen', key: 'hydrogen', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '电费业绩', dataIndex: 'electricity', key: 'electricity', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: 'ETC业绩', dataIndex: 'etc', key: 'etc', width: 110, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '其他', dataIndex: 'other', key: 'other', width: 110, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '合计', dataIndex: 'total', key: 'total', width: 120, align: 'center', render: function (v) { return fmtCell(v); } }
|
||||
];
|
||||
}
|
||||
if (suffix === '成本') {
|
||||
return [
|
||||
{ title: '月份', dataIndex: 'month', key: 'month', width: 72, align: 'center' },
|
||||
{ title: '物流成本', dataIndex: 'logistics', key: 'logistics', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '租赁成本', dataIndex: 'lease', key: 'lease', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '销售成本', dataIndex: 'sales', key: 'sales', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '氢费成本', dataIndex: 'hydrogen', key: 'hydrogen', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '电费成本', dataIndex: 'electricity', key: 'electricity', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: 'ETC成本', dataIndex: 'etc', key: 'etc', width: 110, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '其他', dataIndex: 'other', key: 'other', width: 110, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '合计', dataIndex: 'total', key: 'total', width: 120, align: 'center', render: function (v) { return fmtCell(v); } }
|
||||
];
|
||||
}
|
||||
return [
|
||||
{ title: '月份', dataIndex: 'month', key: 'month', width: 72, align: 'center' },
|
||||
{ title: '物流利润', dataIndex: 'logistics', key: 'logistics', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '租赁利润', dataIndex: 'lease', key: 'lease', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '销售利润', dataIndex: 'sales', key: 'sales', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '氢费利润', dataIndex: 'hydrogen', key: 'hydrogen', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '电费利润', dataIndex: 'electricity', key: 'electricity', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: 'ETC利润', dataIndex: 'etc', key: 'etc', width: 110, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '其他', dataIndex: 'other', key: 'other', width: 110, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '合计', dataIndex: 'total', key: 'total', width: 120, align: 'center', render: function (v) { return fmtCell(v); } }
|
||||
];
|
||||
}
|
||||
|
||||
function rowTotal(r) {
|
||||
var keys = ['logistics', 'lease', 'sales', 'hydrogen', 'electricity', 'etc', 'other'];
|
||||
var s = 0;
|
||||
var any = false;
|
||||
keys.forEach(function (k) {
|
||||
var v = r[k];
|
||||
if (v === null || v === undefined || v === '') return;
|
||||
var n = Number(v);
|
||||
if (isNaN(n)) return;
|
||||
any = true;
|
||||
s += n;
|
||||
});
|
||||
return any ? s : null;
|
||||
}
|
||||
|
||||
function finalizeMonthlyRow(r) {
|
||||
var t = r.total != null && r.total !== '' ? r.total : rowTotal(r);
|
||||
return Object.assign({}, r, { total: t });
|
||||
}
|
||||
|
||||
/** 原型:2026 年按月业绩汇总 */
|
||||
var monthlyPerf2026 = useMemo(function () {
|
||||
var raw = [
|
||||
{ month: 1, logistics: 1005557.94, lease: 4004.73, sales: 1495355.18, hydrogen: null, electricity: 88200.5, etc: 12000, other: 5000, total: null },
|
||||
{ month: 2, logistics: 250000, lease: 180000, sales: null, hydrogen: 3200, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 3, logistics: 180000, lease: 95000, sales: 220000, hydrogen: null, electricity: 45000, etc: 8000, other: 1200, total: null },
|
||||
{ month: 4, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 5, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 6, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 7, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 8, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 9, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 10, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 11, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 12, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null }
|
||||
];
|
||||
return raw.map(function (x) {
|
||||
return finalizeMonthlyRow(Object.assign({ key: 'perf-' + x.month }, x));
|
||||
});
|
||||
}, []);
|
||||
|
||||
/** 原型:2026 年按月成本汇总 */
|
||||
var monthlyCost2026 = useMemo(function () {
|
||||
var raw = [
|
||||
{ month: 1, logistics: 1167787.05, lease: 320000, sales: 980000, hydrogen: 15000, electricity: 72000, etc: 9800, other: 2400, total: null },
|
||||
{ month: 2, logistics: 210000, lease: 165000, sales: null, hydrogen: 2800, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 3, logistics: 155000, lease: 88000, sales: 195000, hydrogen: null, electricity: 38000, etc: 6500, other: 800, total: null },
|
||||
{ month: 4, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 5, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 6, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 7, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 8, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 9, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 10, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 11, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 12, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null }
|
||||
];
|
||||
return raw.map(function (x) {
|
||||
return finalizeMonthlyRow(Object.assign({ key: 'cost-' + x.month }, x));
|
||||
});
|
||||
}, []);
|
||||
|
||||
/** 原型:2026 年按月利润汇总(可为负) */
|
||||
var monthlyProfit2026 = useMemo(function () {
|
||||
var raw = [
|
||||
{ month: 1, logistics: -162229.11, lease: 45000, sales: null, hydrogen: -1200.5, electricity: 6200, etc: null, other: 800, total: null },
|
||||
{ month: 2, logistics: null, lease: 15000, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 3, logistics: null, lease: 7000, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 4, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 5, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 6, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 7, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 8, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 9, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 10, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 11, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null },
|
||||
{ month: 12, logistics: null, lease: null, sales: null, hydrogen: null, electricity: null, etc: null, other: null, total: null }
|
||||
];
|
||||
return raw.map(function (x) {
|
||||
return finalizeMonthlyRow(Object.assign({ key: 'profit-' + x.month }, x));
|
||||
});
|
||||
}, []);
|
||||
|
||||
var deptOptions = useMemo(function () {
|
||||
return [
|
||||
{ value: '华东业务部', label: '华东业务部' },
|
||||
{ value: '华南业务部', label: '华南业务部' },
|
||||
{ value: '华北业务部', label: '华北业务部' },
|
||||
{ value: '西南业务部', label: '西南业务部' }
|
||||
];
|
||||
}, []);
|
||||
|
||||
var salespersonOptions = useMemo(function () {
|
||||
return [
|
||||
{ value: '尚建华', label: '尚建华' },
|
||||
{ value: '刘念念', label: '刘念念' },
|
||||
{ value: '谯云', label: '谯云' },
|
||||
{ value: '董剑煜', label: '董剑煜' }
|
||||
];
|
||||
}, []);
|
||||
|
||||
var lastMonthStatKey = useMemo(function () {
|
||||
return getLastMonthStatKey();
|
||||
}, []);
|
||||
|
||||
var allRows = useMemo(function () {
|
||||
var sm = lastMonthStatKey;
|
||||
return [
|
||||
{
|
||||
key: '1',
|
||||
statMonth: sm,
|
||||
salesperson: '尚建华',
|
||||
dept: '华东业务部',
|
||||
logistics: 208868.38,
|
||||
lease: 36000,
|
||||
sales: null,
|
||||
hydrogen: null,
|
||||
electricity: null,
|
||||
etc: null,
|
||||
other: 55950.23,
|
||||
total: 300818.61
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
statMonth: sm,
|
||||
salesperson: '刘念念',
|
||||
dept: '华南业务部',
|
||||
logistics: 397181.78,
|
||||
lease: 223800,
|
||||
sales: 120000,
|
||||
hydrogen: 4500.12,
|
||||
electricity: 25000,
|
||||
etc: null,
|
||||
other: 224.14,
|
||||
total: 770706.04
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
statMonth: sm,
|
||||
salesperson: '谯云',
|
||||
dept: '华东业务部',
|
||||
logistics: 391153.33,
|
||||
lease: 15000,
|
||||
sales: null,
|
||||
hydrogen: null,
|
||||
electricity: null,
|
||||
etc: 8665.43,
|
||||
other: null,
|
||||
total: 414818.76
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
statMonth: sm,
|
||||
salesperson: '董剑煜',
|
||||
dept: '西南业务部',
|
||||
logistics: 8354.46,
|
||||
lease: null,
|
||||
sales: null,
|
||||
hydrogen: null,
|
||||
electricity: null,
|
||||
etc: null,
|
||||
other: 657.3,
|
||||
total: 9011.76
|
||||
}
|
||||
];
|
||||
}, [lastMonthStatKey]);
|
||||
|
||||
var mainTabState = useState('detail');
|
||||
var mainTab = mainTabState[0];
|
||||
var setMainTab = mainTabState[1];
|
||||
|
||||
var draftState = useState(function () {
|
||||
return {
|
||||
month: getLastMonthPickerValue(),
|
||||
dept: undefined,
|
||||
salesperson: undefined
|
||||
};
|
||||
});
|
||||
var draft = draftState[0];
|
||||
var setDraft = draftState[1];
|
||||
|
||||
var appliedState = useState(function () {
|
||||
return {
|
||||
month: getLastMonthPickerValue(),
|
||||
dept: undefined,
|
||||
salesperson: undefined
|
||||
};
|
||||
});
|
||||
var applied = appliedState[0];
|
||||
var setApplied = appliedState[1];
|
||||
|
||||
function initialYear2026() {
|
||||
try {
|
||||
if (window.dayjs) return window.dayjs('2026-01-01');
|
||||
} catch (e1) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
var summaryYearDraftState = useState(initialYear2026);
|
||||
var summaryYearDraft = summaryYearDraftState[0];
|
||||
var setSummaryYearDraft = summaryYearDraftState[1];
|
||||
|
||||
var summaryYearAppliedState = useState(initialYear2026);
|
||||
var summaryYearApplied = summaryYearAppliedState[0];
|
||||
var setSummaryYearApplied = summaryYearAppliedState[1];
|
||||
|
||||
var filteredRows = useMemo(function () {
|
||||
return (allRows || []).filter(function (r) {
|
||||
if (applied.month && applied.month.format) {
|
||||
var mk = applied.month.format('YYYY-MM');
|
||||
if (r.statMonth !== mk) return false;
|
||||
}
|
||||
if (applied.dept && r.dept !== applied.dept) return false;
|
||||
if (applied.salesperson && r.salesperson !== applied.salesperson) return false;
|
||||
return true;
|
||||
});
|
||||
}, [allRows, applied.month, applied.dept, applied.salesperson]);
|
||||
|
||||
var metricKeys = useMemo(function () {
|
||||
return ['logistics', 'lease', 'sales', 'hydrogen', 'electricity', 'etc', 'other', 'total'];
|
||||
}, []);
|
||||
|
||||
var columnSums = useMemo(function () {
|
||||
var sums = {};
|
||||
metricKeys.forEach(function (k) {
|
||||
sums[k] = filteredRows.reduce(function (acc, row) {
|
||||
var v = row[k];
|
||||
var n = v === null || v === undefined || v === '' ? 0 : Number(v);
|
||||
return acc + (isNaN(n) ? 0 : n);
|
||||
}, 0);
|
||||
});
|
||||
return sums;
|
||||
}, [filteredRows, metricKeys]);
|
||||
|
||||
var bizReportTableTitleStyle = {
|
||||
textAlign: 'center',
|
||||
marginBottom: 16,
|
||||
fontSize: 16,
|
||||
fontWeight: 600,
|
||||
color: 'rgba(0,0,0,0.88)'
|
||||
};
|
||||
|
||||
/** 与「查询」后已选月份一致:x年x月业绩明细表 */
|
||||
var detailTableTitle = useMemo(function () {
|
||||
if (applied.month && applied.month.format) {
|
||||
var y = applied.month.format('YYYY');
|
||||
var m = applied.month.format('M');
|
||||
return y + '年' + m + '月业绩明细表';
|
||||
}
|
||||
return '业绩明细表';
|
||||
}, [applied.month]);
|
||||
|
||||
/** 与汇总 Tab「查询」后已选年份一致 */
|
||||
var perfSumTableTitle = useMemo(function () {
|
||||
if (summaryYearApplied && summaryYearApplied.format) {
|
||||
return summaryYearApplied.format('YYYY') + '年业绩汇总';
|
||||
}
|
||||
return '业绩汇总';
|
||||
}, [summaryYearApplied]);
|
||||
|
||||
var costSumTableTitle = useMemo(function () {
|
||||
if (summaryYearApplied && summaryYearApplied.format) {
|
||||
return summaryYearApplied.format('YYYY') + '年成本汇总';
|
||||
}
|
||||
return '成本汇总';
|
||||
}, [summaryYearApplied]);
|
||||
|
||||
var profitSumTableTitle = useMemo(function () {
|
||||
if (summaryYearApplied && summaryYearApplied.format) {
|
||||
return summaryYearApplied.format('YYYY') + '年利润汇总';
|
||||
}
|
||||
return '利润汇总';
|
||||
}, [summaryYearApplied]);
|
||||
|
||||
var monthlyDataSource = useCallback(function (rows) {
|
||||
var y = summaryYearApplied && summaryYearApplied.format ? summaryYearApplied.format('YYYY') : null;
|
||||
if (!y) return [];
|
||||
if (y !== '2026') return [];
|
||||
return rows;
|
||||
}, [summaryYearApplied]);
|
||||
|
||||
var perfMonthlyRows = useMemo(function () { return monthlyDataSource(monthlyPerf2026); }, [monthlyDataSource, monthlyPerf2026]);
|
||||
var costMonthlyRows = useMemo(function () { return monthlyDataSource(monthlyCost2026); }, [monthlyDataSource, monthlyCost2026]);
|
||||
var profitMonthlyRows = useMemo(function () { return monthlyDataSource(monthlyProfit2026); }, [monthlyDataSource, monthlyProfit2026]);
|
||||
|
||||
var perfMonthlySums = useMemo(function () { return sumMonthlyRows(perfMonthlyRows, monthlyMetricKeys); }, [perfMonthlyRows]);
|
||||
var costMonthlySums = useMemo(function () { return sumMonthlyRows(costMonthlyRows, monthlyMetricKeys); }, [costMonthlyRows]);
|
||||
var profitMonthlySums = useMemo(function () { return sumMonthlyRows(profitMonthlyRows, monthlyMetricKeys); }, [profitMonthlyRows]);
|
||||
|
||||
var handleQuery = useCallback(function () {
|
||||
setApplied(Object.assign({}, draft));
|
||||
}, [draft]);
|
||||
|
||||
var handleReset = useCallback(function () {
|
||||
var def = { month: getLastMonthPickerValue(), dept: undefined, salesperson: undefined };
|
||||
setDraft(def);
|
||||
setApplied(def);
|
||||
}, []);
|
||||
|
||||
var handleSummaryQuery = useCallback(function () {
|
||||
setSummaryYearApplied(summaryYearDraft);
|
||||
}, [summaryYearDraft]);
|
||||
|
||||
var handleSummaryReset = useCallback(function () {
|
||||
var y0 = initialYear2026();
|
||||
setSummaryYearDraft(y0);
|
||||
setSummaryYearApplied(y0);
|
||||
}, []);
|
||||
|
||||
var handleExportDetail = useCallback(function () {
|
||||
var rows = filteredRows;
|
||||
if (!rows || rows.length === 0) {
|
||||
message.warning('当前无数据可导出');
|
||||
return;
|
||||
}
|
||||
var headers = [
|
||||
'业务员',
|
||||
'物流业绩',
|
||||
'租赁业绩',
|
||||
'销售业绩',
|
||||
'氢费业绩',
|
||||
'电费业绩',
|
||||
'ETC业绩',
|
||||
'其他',
|
||||
'合计'
|
||||
];
|
||||
var body = [headers].concat(
|
||||
rows.map(function (r) {
|
||||
return [
|
||||
r.salesperson,
|
||||
fmtCell(r.logistics),
|
||||
fmtCell(r.lease),
|
||||
fmtCell(r.sales),
|
||||
fmtCell(r.hydrogen),
|
||||
fmtCell(r.electricity),
|
||||
fmtCell(r.etc),
|
||||
fmtCell(r.other),
|
||||
fmtCell(r.total)
|
||||
];
|
||||
})
|
||||
);
|
||||
body.push(['总计'].concat(metricKeys.map(function (k) { return fmtSum(columnSums[k] || 0); })));
|
||||
downloadCsv('业绩明细_' + new Date().getTime() + '.csv', body);
|
||||
message.success('已导出 ' + rows.length + ' 条记录');
|
||||
}, [filteredRows, metricKeys, columnSums]);
|
||||
|
||||
var exportMonthly = useCallback(function (rows, sums, headerTitles, filePrefix) {
|
||||
if (!rows || rows.length === 0) {
|
||||
message.warning('当前无数据可导出,请先选择年份并查询');
|
||||
return;
|
||||
}
|
||||
var body = [headerTitles].concat(
|
||||
rows.map(function (r) {
|
||||
return [
|
||||
String(r.month),
|
||||
fmtCell(r.logistics),
|
||||
fmtCell(r.lease),
|
||||
fmtCell(r.sales),
|
||||
fmtCell(r.hydrogen),
|
||||
fmtCell(r.electricity),
|
||||
fmtCell(r.etc),
|
||||
fmtCell(r.other),
|
||||
fmtCell(r.total)
|
||||
];
|
||||
})
|
||||
);
|
||||
body.push(['总计'].concat(monthlyMetricKeys.map(function (k) { return fmtSum(sums[k] || 0); })));
|
||||
downloadCsv(filePrefix + '_' + new Date().getTime() + '.csv', body);
|
||||
message.success('已导出 ' + rows.length + ' 个月度数据');
|
||||
}, []);
|
||||
|
||||
var handleExportPerfMonthly = useCallback(function () {
|
||||
exportMonthly(
|
||||
perfMonthlyRows,
|
||||
perfMonthlySums,
|
||||
['月份', '物流业绩', '租赁业绩', '销售业绩', '氢费业绩', '电费业绩', 'ETC业绩', '其他', '合计'],
|
||||
'业绩汇总'
|
||||
);
|
||||
}, [exportMonthly, perfMonthlyRows, perfMonthlySums]);
|
||||
|
||||
var handleExportCostMonthly = useCallback(function () {
|
||||
exportMonthly(
|
||||
costMonthlyRows,
|
||||
costMonthlySums,
|
||||
['月份', '物流成本', '租赁成本', '销售成本', '氢费成本', '电费成本', 'ETC成本', '其他', '合计'],
|
||||
'成本汇总'
|
||||
);
|
||||
}, [exportMonthly, costMonthlyRows, costMonthlySums]);
|
||||
|
||||
var handleExportProfitMonthly = useCallback(function () {
|
||||
exportMonthly(
|
||||
profitMonthlyRows,
|
||||
profitMonthlySums,
|
||||
['月份', '物流利润', '租赁利润', '销售利润', '氢费利润', '电费利润', 'ETC利润', '其他', '合计'],
|
||||
'利润汇总'
|
||||
);
|
||||
}, [exportMonthly, profitMonthlyRows, profitMonthlySums]);
|
||||
|
||||
var columns = useMemo(function () {
|
||||
return [
|
||||
{ title: '业务员', dataIndex: 'salesperson', key: 'salesperson', width: 110, align: 'center' },
|
||||
{ title: '物流业绩', dataIndex: 'logistics', key: 'logistics', width: 130, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '租赁业绩', dataIndex: 'lease', key: 'lease', width: 130, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '销售业绩', dataIndex: 'sales', key: 'sales', width: 130, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '氢费业绩', dataIndex: 'hydrogen', key: 'hydrogen', width: 130, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '电费业绩', dataIndex: 'electricity', key: 'electricity', width: 140, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: 'ETC业绩', dataIndex: 'etc', key: 'etc', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '其他', dataIndex: 'other', key: 'other', width: 120, align: 'center', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '合计', dataIndex: 'total', key: 'total', width: 130, align: 'center', render: function (v) { return fmtCell(v); } }
|
||||
];
|
||||
}, []);
|
||||
|
||||
var tableSummary = useCallback(function () {
|
||||
return React.createElement(
|
||||
TableSummary,
|
||||
null,
|
||||
React.createElement(
|
||||
SummaryRow,
|
||||
null,
|
||||
React.createElement(SummaryCell, { index: 0, align: 'center' }, '总计'),
|
||||
metricKeys.map(function (k, idx) {
|
||||
return React.createElement(
|
||||
SummaryCell,
|
||||
{ key: k, index: idx + 1, align: 'center' },
|
||||
fmtSum(columnSums[k] || 0)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
}, [metricKeys, columnSums]);
|
||||
|
||||
function renderMonthlySummary(sums) {
|
||||
return function () {
|
||||
return React.createElement(
|
||||
TableSummary,
|
||||
null,
|
||||
React.createElement(
|
||||
SummaryRow,
|
||||
null,
|
||||
React.createElement(SummaryCell, { index: 0, align: 'center' }, '总计'),
|
||||
monthlyMetricKeys.map(function (k, idx) {
|
||||
return React.createElement(
|
||||
SummaryCell,
|
||||
{ key: k, index: idx + 1, align: 'center' },
|
||||
fmtSum(sums[k] || 0)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
var columnsPerfMonthly = useMemo(function () { return monthlyColumnsFor('业绩'); }, []);
|
||||
var columnsCostMonthly = useMemo(function () { return monthlyColumnsFor('成本'); }, []);
|
||||
var columnsProfitMonthly = useMemo(function () { return monthlyColumnsFor('利润'); }, []);
|
||||
|
||||
var renderYearFilterCard = useCallback(function () {
|
||||
return 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: summaryYearDraft,
|
||||
onChange: function (v) { setSummaryYearDraft(v); }
|
||||
})
|
||||
)
|
||||
),
|
||||
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, { size: 8, wrap: true },
|
||||
React.createElement(Button, { onClick: handleSummaryReset }, '重置'),
|
||||
React.createElement(Button, { type: 'primary', onClick: handleSummaryQuery }, '查询')
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}, [summaryYearDraft, handleSummaryReset, handleSummaryQuery]);
|
||||
|
||||
var tabItems = [
|
||||
{
|
||||
key: 'detail',
|
||||
label: '业绩明细',
|
||||
children: React.createElement(React.Fragment, null,
|
||||
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: 'month',
|
||||
style: filterControlStyle,
|
||||
placeholder: '选择器选择月份',
|
||||
format: 'YYYY-MM',
|
||||
value: draft.month,
|
||||
onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { month: 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,
|
||||
allowClear: true,
|
||||
options: deptOptions,
|
||||
value: draft.dept,
|
||||
onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { dept: v }); }); },
|
||||
showSearch: true,
|
||||
filterOption: filterOption
|
||||
})
|
||||
)
|
||||
),
|
||||
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,
|
||||
allowClear: true,
|
||||
options: salespersonOptions,
|
||||
value: draft.salesperson,
|
||||
onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { salesperson: v }); }); },
|
||||
showSearch: true,
|
||||
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, { size: 8, wrap: true },
|
||||
React.createElement(Button, { onClick: handleReset }, '重置'),
|
||||
React.createElement(Button, { type: 'primary', onClick: handleQuery }, '查询')
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
React.createElement(Card, {
|
||||
extra: React.createElement(Button, { onClick: handleExportDetail }, '导出')
|
||||
},
|
||||
React.createElement('div', { style: bizReportTableTitleStyle }, detailTableTitle),
|
||||
React.createElement('style', null, tableSingleLineStyle),
|
||||
React.createElement('div', { className: 'biz-dept-perf-table' },
|
||||
React.createElement(Table, {
|
||||
rowKey: 'key',
|
||||
columns: columns,
|
||||
dataSource: filteredRows,
|
||||
pagination: false,
|
||||
size: 'small',
|
||||
summary: tableSummary,
|
||||
scroll: { x: 1200 }
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'perfSum',
|
||||
label: '业绩汇总',
|
||||
children: React.createElement(React.Fragment, null,
|
||||
renderYearFilterCard(),
|
||||
React.createElement(Card, {
|
||||
extra: React.createElement(Button, { onClick: handleExportPerfMonthly }, '导出')
|
||||
},
|
||||
React.createElement('div', { style: bizReportTableTitleStyle }, perfSumTableTitle),
|
||||
React.createElement('style', null, tableSingleLineStyle),
|
||||
React.createElement('div', { className: 'biz-dept-perf-table' },
|
||||
React.createElement(Table, {
|
||||
rowKey: 'key',
|
||||
columns: columnsPerfMonthly,
|
||||
dataSource: perfMonthlyRows,
|
||||
pagination: false,
|
||||
size: 'small',
|
||||
summary: renderMonthlySummary(perfMonthlySums),
|
||||
scroll: { x: 1180 }
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'costSum',
|
||||
label: '成本汇总',
|
||||
children: React.createElement(React.Fragment, null,
|
||||
renderYearFilterCard(),
|
||||
React.createElement(Card, {
|
||||
extra: React.createElement(Button, { onClick: handleExportCostMonthly }, '导出')
|
||||
},
|
||||
React.createElement('div', { style: bizReportTableTitleStyle }, costSumTableTitle),
|
||||
React.createElement('style', null, tableSingleLineStyle),
|
||||
React.createElement('div', { className: 'biz-dept-perf-table' },
|
||||
React.createElement(Table, {
|
||||
rowKey: 'key',
|
||||
columns: columnsCostMonthly,
|
||||
dataSource: costMonthlyRows,
|
||||
pagination: false,
|
||||
size: 'small',
|
||||
summary: renderMonthlySummary(costMonthlySums),
|
||||
scroll: { x: 1180 }
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'profitSum',
|
||||
label: '利润汇总',
|
||||
children: React.createElement(React.Fragment, null,
|
||||
renderYearFilterCard(),
|
||||
React.createElement(Card, {
|
||||
extra: React.createElement(Button, { onClick: handleExportProfitMonthly }, '导出')
|
||||
},
|
||||
React.createElement('div', { style: bizReportTableTitleStyle }, profitSumTableTitle),
|
||||
React.createElement('style', null, tableSingleLineStyle),
|
||||
React.createElement('div', { className: 'biz-dept-perf-table' },
|
||||
React.createElement(Table, {
|
||||
rowKey: 'key',
|
||||
columns: columnsProfitMonthly,
|
||||
dataSource: profitMonthlyRows,
|
||||
pagination: false,
|
||||
size: 'small',
|
||||
summary: renderMonthlySummary(profitMonthlySums),
|
||||
scroll: { x: 1180 }
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return React.createElement(App, null,
|
||||
React.createElement('div', { style: layoutStyle },
|
||||
React.createElement(Breadcrumb, {
|
||||
style: { marginBottom: 16 },
|
||||
items: [{ title: '数据分析' }, { title: '业务部业绩' }]
|
||||
}),
|
||||
React.createElement(Card, null,
|
||||
React.createElement('style', null, tabsBarStyle),
|
||||
React.createElement(Tabs, {
|
||||
className: 'biz-dept-perf-tabs',
|
||||
activeKey: mainTab,
|
||||
onChange: function (k) { setMainTab(k); },
|
||||
items: tabItems
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
843
web端/数据分析/物流业务月度统计.jsx
Normal file
843
web端/数据分析/物流业务月度统计.jsx
Normal file
@@ -0,0 +1,843 @@
|
||||
// 【重要】必须使用 const Component 作为组件变量名
|
||||
// 数据分析 - 物流业务月度统计:Tab「物流业务人员明细」+「物流盈亏月度汇总」(1–12 月汇总,交互同业务部业绩汇总)
|
||||
// 人员明细:进入页面默认统计「上一自然月」
|
||||
|
||||
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 Tabs = antd.Tabs;
|
||||
var message = antd.message;
|
||||
|
||||
var TableSummary = Table.Summary;
|
||||
var SummaryRow = TableSummary.Row;
|
||||
var SummaryCell = TableSummary.Cell;
|
||||
|
||||
function filterOption(input, option) {
|
||||
var label = (option && (option.label || option.children)) || '';
|
||||
return String(label).toLowerCase().indexOf(String(input || '').toLowerCase()) >= 0;
|
||||
}
|
||||
|
||||
function fmtCell(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 fmtIntCell(n) {
|
||||
if (n === null || n === undefined || n === '') return '—';
|
||||
var x = Number(n);
|
||||
if (isNaN(x)) return '—';
|
||||
if (x === 0) return '—';
|
||||
return String(Math.round(x));
|
||||
}
|
||||
|
||||
/** 盈亏:允许负数、零显示 0.00 */
|
||||
function fmtProfit(n) {
|
||||
if (n === null || n === undefined || n === '') return '—';
|
||||
var x = Number(n);
|
||||
if (isNaN(x)) return '—';
|
||||
return x.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
|
||||
function fmtSum(n) {
|
||||
var x = Number(n);
|
||||
if (isNaN(x)) return '—';
|
||||
return x.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 6 });
|
||||
}
|
||||
|
||||
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 getLastMonthStatKey() {
|
||||
try {
|
||||
if (window.dayjs) return window.dayjs().subtract(1, 'month').format('YYYY-MM');
|
||||
} catch (e1) {}
|
||||
var d = new Date();
|
||||
d.setDate(1);
|
||||
d.setMonth(d.getMonth() - 1);
|
||||
var y = d.getFullYear();
|
||||
var mo = d.getMonth() + 1;
|
||||
return y + '-' + (mo < 10 ? '0' + mo : '' + mo);
|
||||
}
|
||||
|
||||
function getLastMonthPickerValue() {
|
||||
try {
|
||||
if (window.dayjs) return window.dayjs().subtract(1, 'month').startOf('month');
|
||||
} catch (e1) {}
|
||||
try {
|
||||
if (window.moment) return window.moment().subtract(1, 'month').startOf('month');
|
||||
} catch (e2) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
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 tableSingleLineStyle =
|
||||
'.logistics-monthly-stat-table .ant-table-thead th,.logistics-monthly-stat-table .ant-table-tbody td,.logistics-monthly-stat-table .ant-table-summary td{white-space:nowrap;}';
|
||||
var tabsBarStyle = '.logistics-monthly-stat-tabs .ant-tabs-nav{margin-bottom:0;}';
|
||||
|
||||
var bizReportTableTitleStyle = {
|
||||
textAlign: 'center',
|
||||
marginBottom: 16,
|
||||
fontSize: 16,
|
||||
fontWeight: 600,
|
||||
color: 'rgba(0,0,0,0.88)'
|
||||
};
|
||||
|
||||
var profitNegStyle = { background: '#fff1f0', padding: '2px 8px', borderRadius: 4, display: 'inline-block' };
|
||||
|
||||
var numericSumKeys = [
|
||||
'income',
|
||||
'hydrogenFee',
|
||||
'laborCost',
|
||||
'etcFee',
|
||||
'electricityFee',
|
||||
'salary',
|
||||
'vehicleCount',
|
||||
'tireCost',
|
||||
'deprecInsuranceFee',
|
||||
'socialSecurityFee',
|
||||
'trailerFee',
|
||||
'parkingFee',
|
||||
'vehicleTotalCost',
|
||||
'totalCost',
|
||||
'profitLoss'
|
||||
];
|
||||
|
||||
function sumRows(rows, keys) {
|
||||
var sums = {};
|
||||
keys.forEach(function (k) {
|
||||
sums[k] = (rows || []).reduce(function (acc, row) {
|
||||
var v = row[k];
|
||||
var n = v === null || v === undefined || v === '' ? 0 : Number(v);
|
||||
return acc + (isNaN(n) ? 0 : n);
|
||||
}, 0);
|
||||
});
|
||||
return sums;
|
||||
}
|
||||
|
||||
var lastMonthStatKey = useMemo(function () {
|
||||
return getLastMonthStatKey();
|
||||
}, []);
|
||||
|
||||
var rawRowsTemplate = useMemo(function () {
|
||||
var sm = lastMonthStatKey;
|
||||
return [
|
||||
{
|
||||
key: 'lm-1',
|
||||
statMonth: sm,
|
||||
salesperson: '谈云',
|
||||
businessName: '上海虹钦物流有限公司',
|
||||
vehicleModel: '帕立安4.5T',
|
||||
income: 1005557.94,
|
||||
hydrogenFee: 185420.5,
|
||||
laborCost: 128800,
|
||||
etcFee: 45230.12,
|
||||
electricityFee: 62300.8,
|
||||
salary: 96000,
|
||||
vehicleCount: 12,
|
||||
tireCost: 8900,
|
||||
deprecInsuranceFee: 156000,
|
||||
socialSecurityFee: 28400,
|
||||
trailerFee: 12000,
|
||||
parkingFee: 6800,
|
||||
vehicleTotalCost: 420000,
|
||||
totalCost: 918851.42,
|
||||
profitLoss: 86706.52,
|
||||
remark: ''
|
||||
},
|
||||
{
|
||||
key: 'lm-2',
|
||||
statMonth: sm,
|
||||
salesperson: '刘念念',
|
||||
businessName: '杭州绿道城配科技有限公司',
|
||||
vehicleModel: '古道车队',
|
||||
income: 680200,
|
||||
hydrogenFee: 142000,
|
||||
laborCost: 98000,
|
||||
etcFee: 32100,
|
||||
electricityFee: 41000,
|
||||
salary: 72000,
|
||||
vehicleCount: 8,
|
||||
tireCost: 5600,
|
||||
deprecInsuranceFee: 112000,
|
||||
socialSecurityFee: 21000,
|
||||
trailerFee: 8000,
|
||||
parkingFee: 4200,
|
||||
vehicleTotalCost: 298000,
|
||||
totalCost: 832900,
|
||||
profitLoss: -152700,
|
||||
remark: ''
|
||||
},
|
||||
{
|
||||
key: 'lm-3',
|
||||
statMonth: sm,
|
||||
salesperson: '谈云',
|
||||
businessName: '宁波港联氢运物流有限公司',
|
||||
vehicleModel: '飞越49T',
|
||||
income: 892300.5,
|
||||
hydrogenFee: 210000,
|
||||
laborCost: 145000,
|
||||
etcFee: 38900,
|
||||
electricityFee: 55000,
|
||||
salary: 88000,
|
||||
vehicleCount: 10,
|
||||
tireCost: 7200,
|
||||
deprecInsuranceFee: 198000,
|
||||
socialSecurityFee: 25600,
|
||||
trailerFee: 15000,
|
||||
parkingFee: 5500,
|
||||
vehicleTotalCost: 512000,
|
||||
totalCost: 1284200.5,
|
||||
profitLoss: -391900,
|
||||
remark: '淡季线路调整'
|
||||
},
|
||||
{
|
||||
key: 'lm-4',
|
||||
statMonth: sm,
|
||||
salesperson: '谯云',
|
||||
businessName: '嘉兴南湖氢能示范运营',
|
||||
vehicleModel: '帕立安4.5T',
|
||||
income: 325000,
|
||||
hydrogenFee: 52000,
|
||||
laborCost: 38000,
|
||||
etcFee: 12000,
|
||||
electricityFee: 18500,
|
||||
salary: 28000,
|
||||
vehicleCount: 4,
|
||||
tireCost: 2100,
|
||||
deprecInsuranceFee: 48000,
|
||||
socialSecurityFee: 9200,
|
||||
trailerFee: null,
|
||||
parkingFee: 1800,
|
||||
vehicleTotalCost: 125000,
|
||||
totalCost: 334600,
|
||||
profitLoss: -9600,
|
||||
remark: ''
|
||||
},
|
||||
{
|
||||
key: 'lm-5',
|
||||
statMonth: sm,
|
||||
salesperson: '董剑煜',
|
||||
businessName: '温州瓯江冷链专线',
|
||||
vehicleModel: '宇通49T',
|
||||
income: 456780,
|
||||
hydrogenFee: 88000,
|
||||
laborCost: 56000,
|
||||
etcFee: 22000,
|
||||
electricityFee: 31000,
|
||||
salary: 42000,
|
||||
vehicleCount: 5,
|
||||
tireCost: 4500,
|
||||
deprecInsuranceFee: 72000,
|
||||
socialSecurityFee: 11800,
|
||||
trailerFee: 6000,
|
||||
parkingFee: 2400,
|
||||
vehicleTotalCost: 198000,
|
||||
totalCost: 583700,
|
||||
profitLoss: -126920,
|
||||
remark: ''
|
||||
}
|
||||
];
|
||||
}, [lastMonthStatKey]);
|
||||
|
||||
var salespersonOptions = useMemo(function () {
|
||||
var set = {};
|
||||
(rawRowsTemplate || []).forEach(function (r) {
|
||||
if (r.salesperson) set[r.salesperson] = true;
|
||||
});
|
||||
return Object.keys(set).map(function (n) { return { value: n, label: n }; });
|
||||
}, [rawRowsTemplate]);
|
||||
|
||||
var businessNameOptions = useMemo(function () {
|
||||
var set = {};
|
||||
(rawRowsTemplate || []).forEach(function (r) {
|
||||
if (r.businessName) set[r.businessName] = true;
|
||||
});
|
||||
return Object.keys(set).map(function (n) { return { value: n, label: n }; });
|
||||
}, [rawRowsTemplate]);
|
||||
|
||||
/** 物流盈亏月度汇总:按自然月 1–12 列示(原型仅 2026 年有数据) */
|
||||
var monthlyPlMetricKeys = numericSumKeys;
|
||||
|
||||
var monthlyPlSummary2026 = useMemo(function () {
|
||||
function mk(m, p) {
|
||||
var r = Object.assign({ key: 'plm-' + m, month: m }, p);
|
||||
if (r.profitLoss == null && r.income != null && r.totalCost != null) {
|
||||
r.profitLoss = Number(r.income) - Number(r.totalCost);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
function empty(m) {
|
||||
return mk(m, {});
|
||||
}
|
||||
return [
|
||||
mk(1, {
|
||||
income: 3350838.44,
|
||||
hydrogenFee: 537400,
|
||||
laborCost: 418800,
|
||||
etcFee: 148230.12,
|
||||
electricityFee: 228800.8,
|
||||
salary: 386000,
|
||||
vehicleCount: 39,
|
||||
tireCost: 28100,
|
||||
deprecInsuranceFee: 586000,
|
||||
socialSecurityFee: 96000,
|
||||
trailerFee: 53000,
|
||||
parkingFee: 19600,
|
||||
vehicleTotalCost: 1553000,
|
||||
totalCost: 4123731.44,
|
||||
profitLoss: -772893
|
||||
}),
|
||||
mk(2, {
|
||||
income: 2680000,
|
||||
hydrogenFee: 412000,
|
||||
laborCost: 298000,
|
||||
etcFee: 98500,
|
||||
electricityFee: 156000,
|
||||
salary: 265000,
|
||||
vehicleCount: 26,
|
||||
tireCost: 19200,
|
||||
deprecInsuranceFee: 445000,
|
||||
socialSecurityFee: 68400,
|
||||
trailerFee: 41000,
|
||||
parkingFee: 15800,
|
||||
vehicleTotalCost: 1180000,
|
||||
totalCost: 3550900,
|
||||
profitLoss: -870900
|
||||
}),
|
||||
mk(3, {
|
||||
income: 3012000,
|
||||
hydrogenFee: 468000,
|
||||
laborCost: 352000,
|
||||
etcFee: 108000,
|
||||
electricityFee: 172500,
|
||||
salary: 298000,
|
||||
vehicleCount: 31,
|
||||
tireCost: 22100,
|
||||
deprecInsuranceFee: 502000,
|
||||
socialSecurityFee: 75200,
|
||||
trailerFee: 38500,
|
||||
parkingFee: 16900,
|
||||
vehicleTotalCost: 1285000,
|
||||
totalCost: 3488200,
|
||||
profitLoss: -476200
|
||||
}),
|
||||
empty(4),
|
||||
empty(5),
|
||||
empty(6),
|
||||
empty(7),
|
||||
empty(8),
|
||||
empty(9),
|
||||
empty(10),
|
||||
empty(11),
|
||||
empty(12)
|
||||
];
|
||||
}, []);
|
||||
|
||||
function initialYear2026() {
|
||||
try {
|
||||
if (window.dayjs) return window.dayjs('2026-01-01');
|
||||
} catch (e) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
var mainTabState = useState('personDetail');
|
||||
var mainTab = mainTabState[0];
|
||||
var setMainTab = mainTabState[1];
|
||||
|
||||
var summaryYearDraftState = useState(initialYear2026);
|
||||
var summaryYearDraft = summaryYearDraftState[0];
|
||||
var setSummaryYearDraft = summaryYearDraftState[1];
|
||||
|
||||
var summaryYearAppliedState = useState(initialYear2026);
|
||||
var summaryYearApplied = summaryYearAppliedState[0];
|
||||
var setSummaryYearApplied = summaryYearAppliedState[1];
|
||||
|
||||
var draftState = useState(function () {
|
||||
return {
|
||||
month: getLastMonthPickerValue(),
|
||||
salesperson: undefined,
|
||||
businessName: undefined
|
||||
};
|
||||
});
|
||||
var draft = draftState[0];
|
||||
var setDraft = draftState[1];
|
||||
|
||||
var appliedState = useState(function () {
|
||||
return {
|
||||
month: getLastMonthPickerValue(),
|
||||
salesperson: undefined,
|
||||
businessName: undefined
|
||||
};
|
||||
});
|
||||
var applied = appliedState[0];
|
||||
var setApplied = appliedState[1];
|
||||
|
||||
var filteredRows = useMemo(function () {
|
||||
return (rawRowsTemplate || []).filter(function (r) {
|
||||
if (applied.month && applied.month.format) {
|
||||
var mk = applied.month.format('YYYY-MM');
|
||||
if (r.statMonth !== mk) return false;
|
||||
}
|
||||
if (applied.salesperson && r.salesperson !== applied.salesperson) return false;
|
||||
if (applied.businessName && r.businessName !== applied.businessName) return false;
|
||||
return true;
|
||||
});
|
||||
}, [rawRowsTemplate, applied.month, applied.salesperson, applied.businessName]);
|
||||
|
||||
var columnSums = useMemo(function () {
|
||||
return sumRows(filteredRows, numericSumKeys);
|
||||
}, [filteredRows]);
|
||||
|
||||
var reportTitle = useMemo(function () {
|
||||
if (applied.month && applied.month.format) {
|
||||
var y = applied.month.format('YYYY');
|
||||
var m = applied.month.format('M');
|
||||
return y + '年' + m + '月浙江羚牛氢能自运营物流业务盈亏月度汇总表';
|
||||
}
|
||||
return '浙江羚牛氢能自运营物流业务盈亏月度汇总表';
|
||||
}, [applied.month]);
|
||||
|
||||
var plSumTableTitle = useMemo(function () {
|
||||
if (summaryYearApplied && summaryYearApplied.format) {
|
||||
return summaryYearApplied.format('YYYY') + '年物流盈亏月度汇总';
|
||||
}
|
||||
return '物流盈亏月度汇总';
|
||||
}, [summaryYearApplied]);
|
||||
|
||||
var monthlyPlDataSource = useCallback(function (rows) {
|
||||
var y = summaryYearApplied && summaryYearApplied.format ? summaryYearApplied.format('YYYY') : null;
|
||||
if (!y) return [];
|
||||
if (y !== '2026') return [];
|
||||
return rows;
|
||||
}, [summaryYearApplied]);
|
||||
|
||||
var plMonthlyRows = useMemo(function () {
|
||||
return monthlyPlDataSource(monthlyPlSummary2026);
|
||||
}, [monthlyPlDataSource, monthlyPlSummary2026]);
|
||||
|
||||
var plMonthlySums = useMemo(function () {
|
||||
return sumRows(plMonthlyRows, monthlyPlMetricKeys);
|
||||
}, [plMonthlyRows]);
|
||||
|
||||
var handleQuery = useCallback(function () {
|
||||
setApplied(Object.assign({}, draft));
|
||||
}, [draft]);
|
||||
|
||||
var handleReset = useCallback(function () {
|
||||
var def = { month: getLastMonthPickerValue(), salesperson: undefined, businessName: undefined };
|
||||
setDraft(def);
|
||||
setApplied(def);
|
||||
}, []);
|
||||
|
||||
var handleSummaryQuery = useCallback(function () {
|
||||
setSummaryYearApplied(summaryYearDraft);
|
||||
}, [summaryYearDraft]);
|
||||
|
||||
var handleSummaryReset = useCallback(function () {
|
||||
var y0 = initialYear2026();
|
||||
setSummaryYearDraft(y0);
|
||||
setSummaryYearApplied(y0);
|
||||
}, []);
|
||||
|
||||
var handleExport = useCallback(function () {
|
||||
var rows = filteredRows;
|
||||
if (!rows || rows.length === 0) {
|
||||
message.warning('当前无数据可导出');
|
||||
return;
|
||||
}
|
||||
var headers = [
|
||||
'业务员',
|
||||
'业务名称',
|
||||
'系统车型',
|
||||
'收入',
|
||||
'氢费',
|
||||
'人工费用',
|
||||
'ETC',
|
||||
'电费',
|
||||
'薪资',
|
||||
'投入车数',
|
||||
'轮胎',
|
||||
'(折旧、年审、保险)费用',
|
||||
'社保服务费',
|
||||
'挂车费用',
|
||||
'停车费',
|
||||
'车总费用',
|
||||
'总成本',
|
||||
'盈亏',
|
||||
'备注'
|
||||
];
|
||||
var rowLine = function (r) {
|
||||
return [
|
||||
r.salesperson,
|
||||
r.businessName,
|
||||
r.vehicleModel,
|
||||
fmtCell(r.income),
|
||||
fmtCell(r.hydrogenFee),
|
||||
fmtCell(r.laborCost),
|
||||
fmtCell(r.etcFee),
|
||||
fmtCell(r.electricityFee),
|
||||
fmtCell(r.salary),
|
||||
fmtIntCell(r.vehicleCount),
|
||||
fmtCell(r.tireCost),
|
||||
fmtCell(r.deprecInsuranceFee),
|
||||
fmtCell(r.socialSecurityFee),
|
||||
fmtCell(r.trailerFee),
|
||||
fmtCell(r.parkingFee),
|
||||
fmtCell(r.vehicleTotalCost),
|
||||
fmtCell(r.totalCost),
|
||||
fmtProfit(r.profitLoss),
|
||||
r.remark || ''
|
||||
];
|
||||
};
|
||||
var body = [headers].concat(rows.map(rowLine));
|
||||
var sumLine = ['总计', '', ''].concat(
|
||||
numericSumKeys.map(function (k, idx) {
|
||||
if (k === 'vehicleCount') return fmtIntCell(columnSums[k]);
|
||||
if (k === 'profitLoss') return fmtSum(columnSums[k]);
|
||||
return fmtSum(columnSums[k]);
|
||||
})
|
||||
);
|
||||
body.push(sumLine);
|
||||
downloadCsv('物流业务月度统计_' + new Date().getTime() + '.csv', body);
|
||||
message.success('已导出 ' + rows.length + ' 条记录');
|
||||
}, [filteredRows, columnSums]);
|
||||
|
||||
var plMonthlyExportHeaders = [
|
||||
'月份',
|
||||
'收入',
|
||||
'氢费',
|
||||
'人工费用',
|
||||
'ETC',
|
||||
'电费',
|
||||
'薪资',
|
||||
'投入车数',
|
||||
'轮胎',
|
||||
'(折旧、年审、保险)费用',
|
||||
'社保服务费',
|
||||
'挂车费用',
|
||||
'停车费',
|
||||
'车总费用',
|
||||
'总成本',
|
||||
'盈亏月度合计'
|
||||
];
|
||||
|
||||
var handleExportPlMonthly = useCallback(function () {
|
||||
var rows = plMonthlyRows;
|
||||
if (!rows || rows.length === 0) {
|
||||
message.warning('当前无数据可导出,请先选择年份并查询');
|
||||
return;
|
||||
}
|
||||
var rowLine = function (r) {
|
||||
return [
|
||||
String(r.month),
|
||||
fmtCell(r.income),
|
||||
fmtCell(r.hydrogenFee),
|
||||
fmtCell(r.laborCost),
|
||||
fmtCell(r.etcFee),
|
||||
fmtCell(r.electricityFee),
|
||||
fmtCell(r.salary),
|
||||
fmtIntCell(r.vehicleCount),
|
||||
fmtCell(r.tireCost),
|
||||
fmtCell(r.deprecInsuranceFee),
|
||||
fmtCell(r.socialSecurityFee),
|
||||
fmtCell(r.trailerFee),
|
||||
fmtCell(r.parkingFee),
|
||||
fmtCell(r.vehicleTotalCost),
|
||||
fmtCell(r.totalCost),
|
||||
fmtProfit(r.profitLoss)
|
||||
];
|
||||
};
|
||||
var body = [plMonthlyExportHeaders].concat(rows.map(rowLine));
|
||||
body.push(['总计'].concat(
|
||||
monthlyPlMetricKeys.map(function (k) {
|
||||
if (k === 'vehicleCount') return fmtIntCell(plMonthlySums[k]);
|
||||
return fmtSum(plMonthlySums[k] || 0);
|
||||
})
|
||||
));
|
||||
downloadCsv('物流盈亏月度汇总_' + new Date().getTime() + '.csv', body);
|
||||
message.success('已导出 ' + rows.length + ' 个月度数据');
|
||||
}, [plMonthlyRows, plMonthlySums]);
|
||||
|
||||
var columns = useMemo(function () {
|
||||
return [
|
||||
{ title: '业务员', dataIndex: 'salesperson', key: 'salesperson', width: 88, align: 'center', fixed: 'left' },
|
||||
{ title: '业务名称', dataIndex: 'businessName', key: 'businessName', width: 200, ellipsis: true, fixed: 'left' },
|
||||
{ title: '系统车型', dataIndex: 'vehicleModel', key: 'vehicleModel', width: 110, align: 'center' },
|
||||
{ title: '收入', dataIndex: 'income', key: 'income', width: 120, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '氢费', dataIndex: 'hydrogenFee', key: 'hydrogenFee', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '人工费用', dataIndex: 'laborCost', key: 'laborCost', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: 'ETC', dataIndex: 'etcFee', key: 'etcFee', width: 90, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '电费', dataIndex: 'electricityFee', key: 'electricityFee', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '薪资', dataIndex: 'salary', key: 'salary', width: 90, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '投入车数', dataIndex: 'vehicleCount', key: 'vehicleCount', width: 92, align: 'right', render: function (v) { return fmtIntCell(v); } },
|
||||
{ title: '轮胎', dataIndex: 'tireCost', key: 'tireCost', width: 88, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '(折旧、年审、保险)费用', dataIndex: 'deprecInsuranceFee', key: 'deprecInsuranceFee', width: 160, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '社保服务费', dataIndex: 'socialSecurityFee', key: 'socialSecurityFee', width: 110, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '挂车费用', dataIndex: 'trailerFee', key: 'trailerFee', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '停车费', dataIndex: 'parkingFee', key: 'parkingFee', width: 90, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '车总费用', dataIndex: 'vehicleTotalCost', key: 'vehicleTotalCost', width: 110, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '总成本', dataIndex: 'totalCost', key: 'totalCost', width: 120, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{
|
||||
title: '盈亏',
|
||||
dataIndex: 'profitLoss',
|
||||
key: 'profitLoss',
|
||||
width: 120,
|
||||
align: 'right',
|
||||
render: function (v) {
|
||||
var s = fmtProfit(v);
|
||||
var neg = v !== null && v !== undefined && v !== '' && !isNaN(Number(v)) && Number(v) < 0;
|
||||
return neg ? React.createElement('span', { style: profitNegStyle }, s) : s;
|
||||
}
|
||||
},
|
||||
{ title: '备注', dataIndex: 'remark', key: 'remark', width: 140, ellipsis: true }
|
||||
];
|
||||
}, []);
|
||||
|
||||
var tableSummary = useCallback(function () {
|
||||
return React.createElement(
|
||||
TableSummary,
|
||||
null,
|
||||
React.createElement(
|
||||
SummaryRow,
|
||||
null,
|
||||
React.createElement(SummaryCell, { index: 0, align: 'center', colSpan: 3 }, '总计'),
|
||||
numericSumKeys.map(function (k, idx) {
|
||||
var text;
|
||||
if (k === 'vehicleCount') text = fmtIntCell(columnSums[k]);
|
||||
else text = fmtSum(columnSums[k]);
|
||||
var isNegProfit = k === 'profitLoss' && Number(columnSums[k]) < 0;
|
||||
var child = isNegProfit ? React.createElement('span', { style: profitNegStyle }, text) : text;
|
||||
return React.createElement(SummaryCell, { key: k, index: idx + 3, align: 'right' }, child);
|
||||
}),
|
||||
React.createElement(SummaryCell, { index: numericSumKeys.length + 3, align: 'left' }, '')
|
||||
)
|
||||
);
|
||||
}, [columnSums]);
|
||||
|
||||
var columnsPlMonthly = useMemo(function () {
|
||||
return [
|
||||
{ title: '月份', dataIndex: 'month', key: 'month', width: 64, align: 'center' },
|
||||
{ title: '收入', dataIndex: 'income', key: 'income', width: 118, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '氢费', dataIndex: 'hydrogenFee', key: 'hydrogenFee', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '人工费用', dataIndex: 'laborCost', key: 'laborCost', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: 'ETC', dataIndex: 'etcFee', key: 'etcFee', width: 88, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '电费', dataIndex: 'electricityFee', key: 'electricityFee', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '薪资', dataIndex: 'salary', key: 'salary', width: 90, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '投入车数', dataIndex: 'vehicleCount', key: 'vehicleCount', width: 92, align: 'right', render: function (v) { return fmtIntCell(v); } },
|
||||
{ title: '轮胎', dataIndex: 'tireCost', key: 'tireCost', width: 84, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '(折旧、年审、保险)费用', dataIndex: 'deprecInsuranceFee', key: 'deprecInsuranceFee', width: 168, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '社保服务费', dataIndex: 'socialSecurityFee', key: 'socialSecurityFee', width: 110, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '挂车费用', dataIndex: 'trailerFee', key: 'trailerFee', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '停车费', dataIndex: 'parkingFee', key: 'parkingFee', width: 88, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '车总费用', dataIndex: 'vehicleTotalCost', key: 'vehicleTotalCost', width: 110, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '总成本', dataIndex: 'totalCost', key: 'totalCost', width: 118, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{
|
||||
title: '盈亏月度合计',
|
||||
dataIndex: 'profitLoss',
|
||||
key: 'profitLoss',
|
||||
width: 128,
|
||||
align: 'right',
|
||||
render: function (v) {
|
||||
var s = fmtProfit(v);
|
||||
var neg = v !== null && v !== undefined && v !== '' && !isNaN(Number(v)) && Number(v) < 0;
|
||||
return neg ? React.createElement('span', { style: profitNegStyle }, s) : s;
|
||||
}
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
|
||||
var tableSummaryPlMonthly = useCallback(function () {
|
||||
return React.createElement(
|
||||
TableSummary,
|
||||
null,
|
||||
React.createElement(
|
||||
SummaryRow,
|
||||
null,
|
||||
React.createElement(SummaryCell, { index: 0, align: 'center' }, '总计'),
|
||||
monthlyPlMetricKeys.map(function (k, idx) {
|
||||
var text = k === 'vehicleCount' ? fmtIntCell(plMonthlySums[k]) : fmtSum(plMonthlySums[k] || 0);
|
||||
var isNeg = k === 'profitLoss' && Number(plMonthlySums[k]) < 0;
|
||||
var child = isNeg ? React.createElement('span', { style: profitNegStyle }, text) : text;
|
||||
return React.createElement(SummaryCell, { key: k, index: idx + 1, align: 'right' }, child);
|
||||
})
|
||||
)
|
||||
);
|
||||
}, [plMonthlySums]);
|
||||
|
||||
var renderYearFilterCard = useCallback(function () {
|
||||
return 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: summaryYearDraft,
|
||||
onChange: function (v) { setSummaryYearDraft(v); }
|
||||
})
|
||||
)
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 } },
|
||||
React.createElement(Button, { onClick: handleSummaryReset }, '重置'),
|
||||
React.createElement(Button, { type: 'primary', onClick: handleSummaryQuery }, '查询')
|
||||
)
|
||||
);
|
||||
}, [summaryYearDraft, handleSummaryReset, handleSummaryQuery]);
|
||||
|
||||
var tabItems = [
|
||||
{
|
||||
key: 'personDetail',
|
||||
label: '物流业务人员明细',
|
||||
children: React.createElement(React.Fragment, null,
|
||||
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: 'month',
|
||||
style: filterControlStyle,
|
||||
placeholder: '请选择年-月',
|
||||
format: 'YYYY-MM',
|
||||
value: draft.month,
|
||||
onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { month: 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,
|
||||
allowClear: true,
|
||||
options: salespersonOptions,
|
||||
value: draft.salesperson,
|
||||
onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { salesperson: v }); }); },
|
||||
showSearch: true,
|
||||
filterOption: filterOption
|
||||
})
|
||||
)
|
||||
),
|
||||
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,
|
||||
allowClear: true,
|
||||
options: businessNameOptions,
|
||||
value: draft.businessName,
|
||||
onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { businessName: v }); }); },
|
||||
showSearch: true,
|
||||
filterOption: filterOption
|
||||
})
|
||||
)
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 } },
|
||||
React.createElement(Button, { onClick: handleReset }, '重置'),
|
||||
React.createElement(Button, { type: 'primary', onClick: handleQuery }, '查询')
|
||||
)
|
||||
),
|
||||
React.createElement(Card, {
|
||||
extra: React.createElement(Button, { onClick: handleExport }, '导出')
|
||||
},
|
||||
React.createElement('div', { style: bizReportTableTitleStyle }, reportTitle),
|
||||
React.createElement('style', null, tableSingleLineStyle),
|
||||
React.createElement('div', { className: 'logistics-monthly-stat-table' },
|
||||
React.createElement(Table, {
|
||||
rowKey: 'key',
|
||||
columns: columns,
|
||||
dataSource: filteredRows,
|
||||
pagination: false,
|
||||
size: 'small',
|
||||
summary: tableSummary,
|
||||
scroll: { x: 2600 }
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'plMonthly',
|
||||
label: '物流盈亏月度汇总',
|
||||
children: React.createElement(React.Fragment, null,
|
||||
renderYearFilterCard(),
|
||||
React.createElement(Card, {
|
||||
extra: React.createElement(Button, { onClick: handleExportPlMonthly }, '导出')
|
||||
},
|
||||
React.createElement('div', { style: bizReportTableTitleStyle }, plSumTableTitle),
|
||||
React.createElement('style', null, tableSingleLineStyle),
|
||||
React.createElement('div', { className: 'logistics-monthly-stat-table' },
|
||||
React.createElement(Table, {
|
||||
rowKey: 'key',
|
||||
columns: columnsPlMonthly,
|
||||
dataSource: plMonthlyRows,
|
||||
pagination: false,
|
||||
size: 'small',
|
||||
summary: tableSummaryPlMonthly,
|
||||
scroll: { x: 2480 }
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return React.createElement(App, null,
|
||||
React.createElement('div', { style: layoutStyle },
|
||||
React.createElement(Breadcrumb, {
|
||||
style: { marginBottom: 16 },
|
||||
items: [{ title: '数据分析' }, { title: '物流业务月度统计' }]
|
||||
}),
|
||||
React.createElement(Card, null,
|
||||
React.createElement('style', null, tabsBarStyle),
|
||||
React.createElement(Tabs, {
|
||||
className: 'logistics-monthly-stat-tabs',
|
||||
activeKey: mainTab,
|
||||
onChange: function (k) { setMainTab(k); },
|
||||
items: tabItems
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
694
web端/数据分析/租赁客户氢费台账.jsx
Normal file
694
web端/数据分析/租赁客户氢费台账.jsx
Normal file
@@ -0,0 +1,694 @@
|
||||
// 【重要】必须使用 const Component 作为组件变量名
|
||||
// 数据分析 - 租赁客户氢费台账:Tab「租赁客户氢费明细」+「租赁客户氢费总计」(按客户×月汇总金额)
|
||||
|
||||
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 Tabs = antd.Tabs;
|
||||
var message = antd.message;
|
||||
|
||||
var TableSummary = Table.Summary;
|
||||
var SummaryRow = TableSummary.Row;
|
||||
var SummaryCell = TableSummary.Cell;
|
||||
var RangePicker = DatePicker.RangePicker;
|
||||
|
||||
function filterOption(input, option) {
|
||||
var label = (option && (option.label || option.children)) || '';
|
||||
return String(label).toLowerCase().indexOf(String(input || '').toLowerCase()) >= 0;
|
||||
}
|
||||
|
||||
function fmtCell(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 fmtSum(n) {
|
||||
var x = Number(n);
|
||||
if (isNaN(x)) return '—';
|
||||
return x.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 6 });
|
||||
}
|
||||
|
||||
function fmtDateDash(iso) {
|
||||
if (!iso) return '—';
|
||||
try {
|
||||
if (window.dayjs) {
|
||||
var d = window.dayjs(iso);
|
||||
if (!d.isValid()) return '—';
|
||||
return d.format('YYYY-MM-DD');
|
||||
}
|
||||
} catch (e1) {}
|
||||
try {
|
||||
var p = String(iso).split(/[-/]/);
|
||||
if (p.length >= 3) {
|
||||
var y = parseInt(p[0], 10);
|
||||
var m = parseInt(p[1], 10);
|
||||
var day = parseInt(p[2], 10);
|
||||
if (!isNaN(y) && !isNaN(m) && !isNaN(day)) {
|
||||
return y + '-' + (m < 10 ? '0' + m : m) + '-' + (day < 10 ? '0' + day : day);
|
||||
}
|
||||
}
|
||||
} catch (e2) {}
|
||||
return String(iso);
|
||||
}
|
||||
|
||||
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 getLastMonthPickerValue() {
|
||||
try {
|
||||
if (window.dayjs) return window.dayjs().subtract(1, 'month').startOf('month');
|
||||
} catch (e1) {}
|
||||
try {
|
||||
if (window.moment) return window.moment().subtract(1, 'month').startOf('month');
|
||||
} catch (e2) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getStatMonthKey(monthsAgo) {
|
||||
var off = monthsAgo == null ? 1 : monthsAgo;
|
||||
try {
|
||||
if (window.dayjs) return window.dayjs().subtract(off, 'month').format('YYYY-MM');
|
||||
} catch (e1) {}
|
||||
var d = new Date();
|
||||
d.setDate(1);
|
||||
d.setMonth(d.getMonth() - off);
|
||||
var y = d.getFullYear();
|
||||
var mo = d.getMonth() + 1;
|
||||
return y + '-' + (mo < 10 ? '0' + mo : '' + mo);
|
||||
}
|
||||
|
||||
function getLastMonthRange() {
|
||||
try {
|
||||
if (window.dayjs) {
|
||||
var s = window.dayjs().subtract(1, 'month').startOf('month');
|
||||
var e = window.dayjs().subtract(1, 'month').endOf('month');
|
||||
return [s, e];
|
||||
}
|
||||
} catch (e1) {}
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
function rowDayMs(iso) {
|
||||
if (!iso) return NaN;
|
||||
var t = Date.parse(String(iso).length <= 10 ? String(iso) + 'T12:00:00' : iso);
|
||||
return isNaN(t) ? NaN : t;
|
||||
}
|
||||
|
||||
function inRange(iso, range) {
|
||||
if (!range || !range[0] || !range[1]) return true;
|
||||
var t = rowDayMs(iso);
|
||||
if (isNaN(t)) return false;
|
||||
var s = range[0].startOf ? range[0].startOf('day').valueOf() : NaN;
|
||||
var e = range[1].endOf ? range[1].endOf('day').valueOf() : NaN;
|
||||
if (isNaN(s) || isNaN(e)) return true;
|
||||
return t >= s && t <= e;
|
||||
}
|
||||
|
||||
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%' };
|
||||
/** 与「车辆租赁合同」列表表一致:单行不换行;本页含合计行故增加 summary 单元格 */
|
||||
var tableSingleLineStyle =
|
||||
'.contract-list-table .ant-table-thead th,.contract-list-table .ant-table-tbody td,.contract-list-table .ant-table-summary td{white-space:nowrap;}';
|
||||
var listToolbarStyle = { marginBottom: 16, display: 'flex', justifyContent: 'flex-end', alignItems: 'center', flexWrap: 'wrap', gap: 8 };
|
||||
var tabsBarStyle = '.lease-h2-ledger-tabs .ant-tabs-nav{margin-bottom:0;}';
|
||||
|
||||
var bizReportTableTitleStyle = {
|
||||
textAlign: 'center',
|
||||
marginBottom: 16,
|
||||
fontSize: 16,
|
||||
fontWeight: 600,
|
||||
color: 'rgba(0,0,0,0.88)'
|
||||
};
|
||||
|
||||
var boldNumStyle = { fontWeight: 600 };
|
||||
|
||||
/** 原型明细(statMonth / bizDate 随「上一月、上上月」变化,与默认筛选区间一致) */
|
||||
var allDetailRows = useMemo(function () {
|
||||
var m0 = getStatMonthKey(1);
|
||||
var m1 = getStatMonthKey(2);
|
||||
var p0 = m0.split('-');
|
||||
var p1 = m1.split('-');
|
||||
var y0 = parseInt(p0[0], 10);
|
||||
var mo0 = parseInt(p0[1], 10);
|
||||
var y1 = parseInt(p1[0], 10);
|
||||
var mo1 = parseInt(p1[1], 10);
|
||||
function iso(y, mo, day) {
|
||||
return y + '-' + (mo < 10 ? '0' + mo : mo) + '-' + (day < 10 ? '0' + day : day);
|
||||
}
|
||||
return [
|
||||
{
|
||||
key: 'h2-1',
|
||||
statMonth: m0,
|
||||
bizDate: iso(y0, mo0, 1),
|
||||
salesperson: '刘念念',
|
||||
customerName: '嘉兴古道物流有限公司',
|
||||
plateNo: '沪A68122F',
|
||||
quantityKg: 45.6,
|
||||
location: '嘉兴嘉锦亭桥-北综合供能服务站',
|
||||
purchasePrice: 28.5,
|
||||
costAmount: 1299.6,
|
||||
unitPrice: 32,
|
||||
amount: 1459.2
|
||||
},
|
||||
{
|
||||
key: 'h2-2',
|
||||
statMonth: m0,
|
||||
bizDate: iso(y0, mo0, 3),
|
||||
salesperson: '尚建华',
|
||||
customerName: '嘉兴古道物流有限公司',
|
||||
plateNo: '粤AGP3513',
|
||||
quantityKg: 38.2,
|
||||
location: '嘉兴嘉锦亭桥-北综合供能服务站',
|
||||
purchasePrice: 28.5,
|
||||
costAmount: 1088.7,
|
||||
unitPrice: 32,
|
||||
amount: 1222.4
|
||||
},
|
||||
{
|
||||
key: 'h2-3',
|
||||
statMonth: m0,
|
||||
bizDate: iso(y0, mo0, 5),
|
||||
salesperson: '刘念念',
|
||||
customerName: '杭州绿道城配科技有限公司',
|
||||
plateNo: '浙A88888F',
|
||||
quantityKg: 52,
|
||||
location: '杭州钱塘综合能源站',
|
||||
purchasePrice: 29,
|
||||
costAmount: 1508,
|
||||
unitPrice: 33.5,
|
||||
amount: 1742
|
||||
},
|
||||
{
|
||||
key: 'h2-4',
|
||||
statMonth: m0,
|
||||
bizDate: iso(y0, mo0, 12),
|
||||
salesperson: '尚建华',
|
||||
customerName: '宁波港联氢运物流有限公司',
|
||||
plateNo: '浙B12345F',
|
||||
quantityKg: 60.5,
|
||||
location: '宁波北仑氢能示范站',
|
||||
purchasePrice: 27.8,
|
||||
costAmount: 1681.9,
|
||||
unitPrice: 31.2,
|
||||
amount: 1887.6
|
||||
},
|
||||
{
|
||||
key: 'h2-5',
|
||||
statMonth: m1,
|
||||
bizDate: iso(y1, mo1, 2),
|
||||
salesperson: '刘念念',
|
||||
customerName: '嘉兴古道物流有限公司',
|
||||
plateNo: '沪A68122F',
|
||||
quantityKg: 41,
|
||||
location: '嘉兴嘉锦亭桥-北综合供能服务站',
|
||||
purchasePrice: 28.6,
|
||||
costAmount: 1172.6,
|
||||
unitPrice: 32,
|
||||
amount: 1312
|
||||
},
|
||||
{
|
||||
key: 'h2-6',
|
||||
statMonth: m1,
|
||||
bizDate: iso(y1, mo1, 18),
|
||||
salesperson: '刘念念',
|
||||
customerName: '杭州绿道城配科技有限公司',
|
||||
plateNo: '浙A88888F',
|
||||
quantityKg: 48.5,
|
||||
location: '杭州钱塘综合能源站',
|
||||
purchasePrice: 29.1,
|
||||
costAmount: 1411.35,
|
||||
unitPrice: 33.5,
|
||||
amount: 1624.75
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
|
||||
var salespersonOptions = useMemo(function () {
|
||||
var set = {};
|
||||
allDetailRows.forEach(function (r) {
|
||||
if (r.salesperson) set[r.salesperson] = true;
|
||||
});
|
||||
return Object.keys(set).map(function (n) { return { value: n, label: n }; });
|
||||
}, [allDetailRows]);
|
||||
|
||||
var customerOptions = useMemo(function () {
|
||||
var set = {};
|
||||
allDetailRows.forEach(function (r) {
|
||||
if (r.customerName) set[r.customerName] = true;
|
||||
});
|
||||
return Object.keys(set).map(function (n) { return { value: n, label: n }; });
|
||||
}, [allDetailRows]);
|
||||
|
||||
var mainTabState = useState('detail');
|
||||
var mainTab = mainTabState[0];
|
||||
var setMainTab = mainTabState[1];
|
||||
|
||||
var rangeDefault = useMemo(function () { return getLastMonthRange(); }, []);
|
||||
|
||||
var draftDetailState = useState(function () {
|
||||
return {
|
||||
dateRange: rangeDefault[0] && rangeDefault[1] ? [rangeDefault[0], rangeDefault[1]] : null,
|
||||
salesperson: undefined,
|
||||
customerName: undefined
|
||||
};
|
||||
});
|
||||
var draftDetail = draftDetailState[0];
|
||||
var setDraftDetail = draftDetailState[1];
|
||||
|
||||
var appliedDetailState = useState(function () {
|
||||
return {
|
||||
dateRange: rangeDefault[0] && rangeDefault[1] ? [rangeDefault[0], rangeDefault[1]] : null,
|
||||
salesperson: undefined,
|
||||
customerName: undefined
|
||||
};
|
||||
});
|
||||
var appliedDetail = appliedDetailState[0];
|
||||
var setAppliedDetail = appliedDetailState[1];
|
||||
|
||||
var draftTotalState = useState(function () {
|
||||
return {
|
||||
month: getLastMonthPickerValue(),
|
||||
customerName: undefined
|
||||
};
|
||||
});
|
||||
var draftTotal = draftTotalState[0];
|
||||
var setDraftTotal = draftTotalState[1];
|
||||
|
||||
var appliedTotalState = useState(function () {
|
||||
return {
|
||||
month: getLastMonthPickerValue(),
|
||||
customerName: undefined
|
||||
};
|
||||
});
|
||||
var appliedTotal = appliedTotalState[0];
|
||||
var setAppliedTotal = appliedTotalState[1];
|
||||
|
||||
var filteredDetailRows = useMemo(function () {
|
||||
return (allDetailRows || []).filter(function (r) {
|
||||
if (appliedDetail.dateRange && appliedDetail.dateRange[0] && appliedDetail.dateRange[1]) {
|
||||
if (!inRange(r.bizDate, appliedDetail.dateRange)) return false;
|
||||
}
|
||||
if (appliedDetail.salesperson && r.salesperson !== appliedDetail.salesperson) return false;
|
||||
if (appliedDetail.customerName && r.customerName !== appliedDetail.customerName) return false;
|
||||
return true;
|
||||
});
|
||||
}, [allDetailRows, appliedDetail.dateRange, appliedDetail.salesperson, appliedDetail.customerName]);
|
||||
|
||||
var detailSums = useMemo(function () {
|
||||
var q = 0;
|
||||
var c = 0;
|
||||
var a = 0;
|
||||
filteredDetailRows.forEach(function (r) {
|
||||
q += isNaN(Number(r.quantityKg)) ? 0 : Number(r.quantityKg);
|
||||
c += isNaN(Number(r.costAmount)) ? 0 : Number(r.costAmount);
|
||||
a += isNaN(Number(r.amount)) ? 0 : Number(r.amount);
|
||||
});
|
||||
return { quantityKg: q, costAmount: c, amount: a };
|
||||
}, [filteredDetailRows]);
|
||||
|
||||
var totalByCustomerMonthRows = useMemo(function () {
|
||||
var mk = '';
|
||||
if (appliedTotal.month && appliedTotal.month.format) mk = appliedTotal.month.format('YYYY-MM');
|
||||
if (!mk) return [];
|
||||
var map = {};
|
||||
allDetailRows.forEach(function (r) {
|
||||
if (r.statMonth !== mk) return;
|
||||
if (appliedTotal.customerName && r.customerName !== appliedTotal.customerName) return;
|
||||
var k = r.customerName || '';
|
||||
if (!map[k]) {
|
||||
map[k] = { key: 'sum-' + mk + '-' + k, customerName: k, statMonth: mk, quantityKg: 0, amount: 0 };
|
||||
}
|
||||
map[k].quantityKg += isNaN(Number(r.quantityKg)) ? 0 : Number(r.quantityKg);
|
||||
map[k].amount += isNaN(Number(r.amount)) ? 0 : Number(r.amount);
|
||||
});
|
||||
return Object.keys(map).map(function (name) { return map[name]; });
|
||||
}, [allDetailRows, appliedTotal.month, appliedTotal.customerName]);
|
||||
|
||||
var totalTabSums = useMemo(function () {
|
||||
var q = 0;
|
||||
var a = 0;
|
||||
totalByCustomerMonthRows.forEach(function (r) {
|
||||
q += r.quantityKg || 0;
|
||||
a += r.amount || 0;
|
||||
});
|
||||
return { quantityKg: q, amount: a };
|
||||
}, [totalByCustomerMonthRows]);
|
||||
|
||||
var detailTitle = useMemo(function () {
|
||||
return '租赁客户氢费明细表';
|
||||
}, []);
|
||||
|
||||
var totalTitle = useMemo(function () {
|
||||
if (appliedTotal.month && appliedTotal.month.format) {
|
||||
return appliedTotal.month.format('YYYY年M月') + '租赁客户氢费总计';
|
||||
}
|
||||
return '租赁客户氢费总计';
|
||||
}, [appliedTotal.month]);
|
||||
|
||||
var handleQueryDetail = useCallback(function () {
|
||||
setAppliedDetail(Object.assign({}, draftDetail));
|
||||
}, [draftDetail]);
|
||||
|
||||
var handleResetDetail = useCallback(function () {
|
||||
var def = {
|
||||
dateRange: rangeDefault[0] && rangeDefault[1] ? [rangeDefault[0], rangeDefault[1]] : null,
|
||||
salesperson: undefined,
|
||||
customerName: undefined
|
||||
};
|
||||
setDraftDetail(def);
|
||||
setAppliedDetail(def);
|
||||
}, [rangeDefault]);
|
||||
|
||||
var handleQueryTotal = useCallback(function () {
|
||||
setAppliedTotal(Object.assign({}, draftTotal));
|
||||
}, [draftTotal]);
|
||||
|
||||
var handleResetTotal = useCallback(function () {
|
||||
var def = { month: getLastMonthPickerValue(), customerName: undefined };
|
||||
setDraftTotal(def);
|
||||
setAppliedTotal(def);
|
||||
}, []);
|
||||
|
||||
var handleExportDetail = useCallback(function () {
|
||||
var rows = filteredDetailRows;
|
||||
if (!rows || rows.length === 0) {
|
||||
message.warning('当前无数据可导出');
|
||||
return;
|
||||
}
|
||||
var headers = [
|
||||
'业务员',
|
||||
'客户名称',
|
||||
'日期',
|
||||
'车牌号',
|
||||
'加氢数量',
|
||||
'加氢地点',
|
||||
'进价',
|
||||
'成本金额',
|
||||
'单价',
|
||||
'金额'
|
||||
];
|
||||
var line = function (r) {
|
||||
return [
|
||||
r.salesperson,
|
||||
r.customerName,
|
||||
fmtDateDash(r.bizDate),
|
||||
r.plateNo,
|
||||
fmtCell(r.quantityKg),
|
||||
r.location,
|
||||
fmtCell(r.purchasePrice),
|
||||
fmtCell(r.costAmount),
|
||||
fmtCell(r.unitPrice),
|
||||
fmtCell(r.amount)
|
||||
];
|
||||
};
|
||||
var body = [headers].concat(rows.map(line));
|
||||
body.push([
|
||||
'汇总',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
fmtSum(detailSums.quantityKg),
|
||||
'',
|
||||
'',
|
||||
fmtSum(detailSums.costAmount),
|
||||
'',
|
||||
fmtSum(detailSums.amount)
|
||||
]);
|
||||
downloadCsv('租赁客户氢费明细_' + new Date().getTime() + '.csv', body);
|
||||
message.success('已导出 ' + rows.length + ' 条记录');
|
||||
}, [filteredDetailRows, detailSums]);
|
||||
|
||||
var handleExportTotal = useCallback(function () {
|
||||
var rows = totalByCustomerMonthRows;
|
||||
if (!rows || rows.length === 0) {
|
||||
message.warning('当前无数据可导出,请选择统计月份并查询');
|
||||
return;
|
||||
}
|
||||
var headers = ['客户名称', '统计月份', '加氢数量合计', '金额合计'];
|
||||
var body = [headers].concat(
|
||||
rows.map(function (r) {
|
||||
return [r.customerName, r.statMonth, fmtSum(r.quantityKg), fmtSum(r.amount)];
|
||||
})
|
||||
);
|
||||
body.push(['总计', '', fmtSum(totalTabSums.quantityKg), fmtSum(totalTabSums.amount)]);
|
||||
downloadCsv('租赁客户氢费总计_' + new Date().getTime() + '.csv', body);
|
||||
message.success('已导出 ' + rows.length + ' 条记录');
|
||||
}, [totalByCustomerMonthRows, totalTabSums]);
|
||||
|
||||
function renderBoldMoney(v) {
|
||||
var s = fmtCell(v);
|
||||
return React.createElement('span', { style: boldNumStyle }, s);
|
||||
}
|
||||
|
||||
var columnsDetail = useMemo(function () {
|
||||
return [
|
||||
{ title: '业务员', dataIndex: 'salesperson', key: 'salesperson', width: 100, align: 'left' },
|
||||
{ title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 200, ellipsis: true, align: 'left' },
|
||||
{ title: '日期', dataIndex: 'bizDate', key: 'bizDate', width: 118, align: 'left', render: function (_v, r) { return fmtDateDash(r.bizDate); } },
|
||||
{ title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 120, align: 'left' },
|
||||
{ title: '加氢数量', dataIndex: 'quantityKg', key: 'quantityKg', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '加氢地点', dataIndex: 'location', key: 'location', width: 260, ellipsis: true, align: 'left' },
|
||||
{ title: '进价', dataIndex: 'purchasePrice', key: 'purchasePrice', width: 90, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '成本金额', dataIndex: 'costAmount', key: 'costAmount', width: 110, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '单价', dataIndex: 'unitPrice', key: 'unitPrice', width: 90, align: 'right', render: function (v) { return renderBoldMoney(v); } },
|
||||
{ title: '金额', dataIndex: 'amount', key: 'amount', width: 110, align: 'right', render: function (v) { return renderBoldMoney(v); } }
|
||||
];
|
||||
}, []);
|
||||
|
||||
var columnsTotal = useMemo(function () {
|
||||
return [
|
||||
{ title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 240, ellipsis: true, align: 'left' },
|
||||
{ title: '统计月份', dataIndex: 'statMonth', key: 'statMonth', width: 110, align: 'center' },
|
||||
{ title: '加氢数量合计', dataIndex: 'quantityKg', key: 'quantityKg', width: 130, align: 'right', render: function (v) { return fmtSum(v); } },
|
||||
{ title: '金额合计', dataIndex: 'amount', key: 'amount', width: 130, align: 'right', render: function (v) { return React.createElement('span', { style: boldNumStyle }, fmtSum(v)); } }
|
||||
];
|
||||
}, []);
|
||||
|
||||
var detailTableSummary = useCallback(function () {
|
||||
return React.createElement(
|
||||
TableSummary,
|
||||
null,
|
||||
React.createElement(
|
||||
SummaryRow,
|
||||
null,
|
||||
React.createElement(SummaryCell, { index: 0, align: 'center', colSpan: 4 }, '汇总'),
|
||||
React.createElement(SummaryCell, { index: 4, align: 'right' }, fmtSum(detailSums.quantityKg)),
|
||||
React.createElement(SummaryCell, { index: 5, align: 'center' }, '—'),
|
||||
React.createElement(SummaryCell, { index: 6, align: 'right' }, '—'),
|
||||
React.createElement(SummaryCell, { index: 7, align: 'right' }, fmtSum(detailSums.costAmount)),
|
||||
React.createElement(SummaryCell, { index: 8, align: 'center' }, '—'),
|
||||
React.createElement(SummaryCell, { index: 9, align: 'right' },
|
||||
React.createElement('span', { style: boldNumStyle }, fmtSum(detailSums.amount))
|
||||
)
|
||||
)
|
||||
);
|
||||
}, [detailSums]);
|
||||
|
||||
var totalTableSummary = useCallback(function () {
|
||||
return React.createElement(
|
||||
TableSummary,
|
||||
null,
|
||||
React.createElement(
|
||||
SummaryRow,
|
||||
null,
|
||||
React.createElement(SummaryCell, { index: 0, align: 'center', colSpan: 2 }, '总计'),
|
||||
React.createElement(SummaryCell, { index: 2, align: 'right' }, fmtSum(totalTabSums.quantityKg)),
|
||||
React.createElement(SummaryCell, { index: 3, align: 'right' },
|
||||
React.createElement('span', { style: boldNumStyle }, fmtSum(totalTabSums.amount))
|
||||
)
|
||||
)
|
||||
);
|
||||
}, [totalTabSums]);
|
||||
|
||||
var tabItems = [
|
||||
{
|
||||
key: 'detail',
|
||||
label: '租赁客户氢费明细',
|
||||
children: React.createElement(React.Fragment, null,
|
||||
React.createElement(Card, { style: { marginBottom: 16 } },
|
||||
React.createElement(Row, { gutter: [16, 16], align: 'bottom' },
|
||||
React.createElement(Col, { xs: 24, sm: 12, md: 10, lg: 8 },
|
||||
React.createElement('div', { style: filterItemStyle },
|
||||
React.createElement('div', { style: filterLabelStyle }, '日期范围'),
|
||||
React.createElement(RangePicker, {
|
||||
style: filterControlStyle,
|
||||
placeholder: ['开始日期', '结束日期'],
|
||||
format: 'YYYY-MM-DD',
|
||||
value: draftDetail.dateRange,
|
||||
onChange: function (v) { setDraftDetail(function (p) { return Object.assign({}, p, { dateRange: v }); }); }
|
||||
})
|
||||
)
|
||||
),
|
||||
React.createElement(Col, { xs: 24, sm: 12, md: 7, lg: 6 },
|
||||
React.createElement('div', { style: filterItemStyle },
|
||||
React.createElement('div', { style: filterLabelStyle }, '业务员'),
|
||||
React.createElement(Select, {
|
||||
placeholder: '请选择业务员',
|
||||
style: filterControlStyle,
|
||||
allowClear: true,
|
||||
options: salespersonOptions,
|
||||
value: draftDetail.salesperson,
|
||||
onChange: function (v) { setDraftDetail(function (p) { return Object.assign({}, p, { salesperson: v }); }); },
|
||||
showSearch: true,
|
||||
filterOption: filterOption
|
||||
})
|
||||
)
|
||||
),
|
||||
React.createElement(Col, { xs: 24, sm: 12, md: 7, lg: 6 },
|
||||
React.createElement('div', { style: filterItemStyle },
|
||||
React.createElement('div', { style: filterLabelStyle }, '客户名称'),
|
||||
React.createElement(Select, {
|
||||
placeholder: '请选择客户名称',
|
||||
style: filterControlStyle,
|
||||
allowClear: true,
|
||||
options: customerOptions,
|
||||
value: draftDetail.customerName,
|
||||
onChange: function (v) { setDraftDetail(function (p) { return Object.assign({}, p, { customerName: v }); }); },
|
||||
showSearch: true,
|
||||
filterOption: filterOption
|
||||
})
|
||||
)
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 } },
|
||||
React.createElement(Button, { onClick: handleResetDetail }, '重置'),
|
||||
React.createElement(Button, { type: 'primary', onClick: handleQueryDetail }, '查询')
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: listToolbarStyle },
|
||||
React.createElement(Button, { onClick: handleExportDetail }, '导出')
|
||||
),
|
||||
React.createElement(Card, null,
|
||||
React.createElement('div', { style: bizReportTableTitleStyle }, detailTitle),
|
||||
React.createElement(React.Fragment, null,
|
||||
React.createElement('style', null, tableSingleLineStyle),
|
||||
React.createElement('div', { className: 'contract-list-table' },
|
||||
React.createElement(Table, {
|
||||
rowKey: 'key',
|
||||
columns: columnsDetail,
|
||||
dataSource: filteredDetailRows,
|
||||
pagination: false,
|
||||
size: 'small',
|
||||
summary: detailTableSummary,
|
||||
scroll: { x: 1320 }
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'total',
|
||||
label: '租赁客户氢费总计',
|
||||
children: React.createElement(React.Fragment, null,
|
||||
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: 'month',
|
||||
style: filterControlStyle,
|
||||
placeholder: '请选择年-月',
|
||||
format: 'YYYY-MM',
|
||||
value: draftTotal.month,
|
||||
onChange: function (v) { setDraftTotal(function (p) { return Object.assign({}, p, { month: 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,
|
||||
allowClear: true,
|
||||
options: customerOptions,
|
||||
value: draftTotal.customerName,
|
||||
onChange: function (v) { setDraftTotal(function (p) { return Object.assign({}, p, { customerName: v }); }); },
|
||||
showSearch: true,
|
||||
filterOption: filterOption
|
||||
})
|
||||
)
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 } },
|
||||
React.createElement(Button, { onClick: handleResetTotal }, '重置'),
|
||||
React.createElement(Button, { type: 'primary', onClick: handleQueryTotal }, '查询')
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: listToolbarStyle },
|
||||
React.createElement(Button, { onClick: handleExportTotal }, '导出')
|
||||
),
|
||||
React.createElement(Card, null,
|
||||
React.createElement('div', { style: bizReportTableTitleStyle }, totalTitle),
|
||||
React.createElement(React.Fragment, null,
|
||||
React.createElement('style', null, tableSingleLineStyle),
|
||||
React.createElement('div', { className: 'contract-list-table' },
|
||||
React.createElement(Table, {
|
||||
rowKey: 'key',
|
||||
columns: columnsTotal,
|
||||
dataSource: totalByCustomerMonthRows,
|
||||
pagination: false,
|
||||
size: 'small',
|
||||
summary: totalTableSummary,
|
||||
scroll: { x: 720 }
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return React.createElement(App, null,
|
||||
React.createElement('div', { style: layoutStyle },
|
||||
React.createElement(Breadcrumb, {
|
||||
style: { marginBottom: 16 },
|
||||
items: [{ title: '数据分析' }, { title: '租赁客户氢费台账' }]
|
||||
}),
|
||||
React.createElement(Card, null,
|
||||
React.createElement('style', null, tabsBarStyle),
|
||||
React.createElement(Tabs, {
|
||||
className: 'lease-h2-ledger-tabs',
|
||||
activeKey: mainTab,
|
||||
onChange: function (k) { setMainTab(k); },
|
||||
items: tabItems
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
801
web端/数据分析/租赁车辆收入明细.jsx
Normal file
801
web端/数据分析/租赁车辆收入明细.jsx
Normal file
@@ -0,0 +1,801 @@
|
||||
// 【重要】必须使用 const Component 作为组件变量名
|
||||
// 数据分析 - 租赁车辆收入明细(租赁业务明细 Tab + 租赁业务盈亏月度汇总 Tab,汇总交互参考「业务部业绩明细」)
|
||||
|
||||
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 Tabs = antd.Tabs;
|
||||
var message = antd.message;
|
||||
|
||||
var TableSummary = Table.Summary;
|
||||
var SummaryRow = TableSummary.Row;
|
||||
var SummaryCell = TableSummary.Cell;
|
||||
|
||||
function filterOption(input, option) {
|
||||
var label = (option && (option.label || option.children)) || '';
|
||||
return String(label).toLowerCase().indexOf(String(input || '').toLowerCase()) >= 0;
|
||||
}
|
||||
|
||||
function fmtCell(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 fmtSum(n) {
|
||||
var x = Number(n);
|
||||
if (isNaN(x)) return '—';
|
||||
return x.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 6 });
|
||||
}
|
||||
|
||||
function fmtTextSlash(s) {
|
||||
if (s === null || s === undefined || String(s).trim() === '') return '/';
|
||||
return String(s);
|
||||
}
|
||||
|
||||
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 getLastMonthStatKey() {
|
||||
try {
|
||||
if (window.dayjs) return window.dayjs().subtract(1, 'month').format('YYYY-MM');
|
||||
} catch (e1) {}
|
||||
var d = new Date();
|
||||
d.setDate(1);
|
||||
d.setMonth(d.getMonth() - 1);
|
||||
var y = d.getFullYear();
|
||||
var mo = d.getMonth() + 1;
|
||||
return y + '-' + (mo < 10 ? '0' + mo : '' + mo);
|
||||
}
|
||||
|
||||
function getLastMonthPickerValue() {
|
||||
try {
|
||||
if (window.dayjs) return window.dayjs().subtract(1, 'month').startOf('month');
|
||||
} catch (e1) {}
|
||||
try {
|
||||
if (window.moment) return window.moment().subtract(1, 'month').startOf('month');
|
||||
} catch (e2) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
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 tableSingleLineStyle =
|
||||
'.lease-income-detail-table .ant-table-thead th,.lease-income-detail-table .ant-table-tbody td,.lease-income-detail-table .ant-table-summary td{white-space:nowrap;}';
|
||||
var tableMonthlyLineStyle =
|
||||
'.lease-pl-monthly-table .ant-table-thead th,.lease-pl-monthly-table .ant-table-tbody td,.lease-pl-monthly-table .ant-table-summary td{white-space:nowrap;}';
|
||||
var tabsBarStyle = '.lease-income-tabs .ant-tabs-nav{margin-bottom:0;}';
|
||||
|
||||
var bizReportTableTitleStyle = {
|
||||
textAlign: 'center',
|
||||
marginBottom: 16,
|
||||
fontSize: 16,
|
||||
fontWeight: 600,
|
||||
color: 'rgba(0,0,0,0.88)'
|
||||
};
|
||||
|
||||
var outstandingPositiveStyle = { color: '#cf1322' };
|
||||
|
||||
var numericSumKeys = [
|
||||
'deposit',
|
||||
'receivable',
|
||||
'received',
|
||||
'outstanding',
|
||||
'naturalMonthIncome',
|
||||
'hydrogenPrepay',
|
||||
'baseCost',
|
||||
'brokerage',
|
||||
'hydrogenFee',
|
||||
'totalCost'
|
||||
];
|
||||
|
||||
/** 月度盈亏汇总表:各月金额列 + 盈亏(自然月收入 − 总成本,可负) */
|
||||
var monthlyPLKeys = numericSumKeys.concat(['profit']);
|
||||
|
||||
function sumMonthlyRows(rows, keys) {
|
||||
var sums = {};
|
||||
keys.forEach(function (k) {
|
||||
sums[k] = (rows || []).reduce(function (acc, row) {
|
||||
var v = row[k];
|
||||
var n = v === null || v === undefined || v === '' ? 0 : Number(v);
|
||||
return acc + (isNaN(n) ? 0 : n);
|
||||
}, 0);
|
||||
});
|
||||
return sums;
|
||||
}
|
||||
|
||||
function finalizeLeaseMonthlyRow(r) {
|
||||
var inc = r.naturalMonthIncome;
|
||||
var tc = r.totalCost;
|
||||
var profit = null;
|
||||
if (inc != null && inc !== '' && tc != null && tc !== '') {
|
||||
var a = Number(inc);
|
||||
var b = Number(tc);
|
||||
if (!isNaN(a) && !isNaN(b)) profit = a - b;
|
||||
}
|
||||
return Object.assign({}, r, { profit: profit });
|
||||
}
|
||||
|
||||
function sumRows(rows, keys) {
|
||||
var sums = {};
|
||||
keys.forEach(function (k) {
|
||||
sums[k] = (rows || []).reduce(function (acc, row) {
|
||||
var v = row[k];
|
||||
var n = v === null || v === undefined || v === '' ? 0 : Number(v);
|
||||
return acc + (isNaN(n) ? 0 : n);
|
||||
}, 0);
|
||||
});
|
||||
return sums;
|
||||
}
|
||||
|
||||
var lastMonthStatKey = useMemo(function () {
|
||||
return getLastMonthStatKey();
|
||||
}, []);
|
||||
|
||||
var deptOptions = useMemo(function () {
|
||||
return [
|
||||
{ value: '业务二部', label: '业务二部' },
|
||||
{ value: '业务一部', label: '业务一部' }
|
||||
];
|
||||
}, []);
|
||||
|
||||
var rawRowsTemplate = useMemo(function () {
|
||||
var sm = lastMonthStatKey;
|
||||
return [
|
||||
{
|
||||
key: 'lv-1',
|
||||
statMonth: sm,
|
||||
plateNo: '沪A52898F',
|
||||
vehicleType: '18T',
|
||||
salesperson: '刘念忠',
|
||||
nature: '纯租赁',
|
||||
systemModel: '飞驰18T',
|
||||
customerName: '上海虹钦物流有限公司',
|
||||
contractDate: '2025.12.1-2026.11.30',
|
||||
pickupDate: '2025-12-08',
|
||||
deposit: 20000,
|
||||
receivable: 18500,
|
||||
received: 18500,
|
||||
outstanding: 0,
|
||||
naturalMonthIncome: 18500,
|
||||
paymentDate: '2026-02-05',
|
||||
hydrogenPrepay: 5000,
|
||||
paymentMethod: '月付指付',
|
||||
invoiceApplyDate: '2026-02-06',
|
||||
baseCost: 12000,
|
||||
brokerage: 800,
|
||||
hydrogenFee: 3200,
|
||||
totalCost: 16000
|
||||
},
|
||||
{
|
||||
key: 'lv-2',
|
||||
statMonth: sm,
|
||||
plateNo: '粤AGF4535',
|
||||
vehicleType: '4.5T',
|
||||
salesperson: '冉建华',
|
||||
nature: '试用车',
|
||||
systemModel: '现代4.5T',
|
||||
customerName: '杭州绿道城配科技有限公司',
|
||||
contractDate: '2026.1.1-2026.12.31',
|
||||
pickupDate: '',
|
||||
deposit: 4500,
|
||||
receivable: 4200,
|
||||
received: 3000,
|
||||
outstanding: 1200,
|
||||
naturalMonthIncome: 4200,
|
||||
paymentDate: '',
|
||||
hydrogenPrepay: 0,
|
||||
paymentMethod: '季度指付',
|
||||
invoiceApplyDate: '',
|
||||
baseCost: 2800,
|
||||
brokerage: 0,
|
||||
hydrogenFee: 900,
|
||||
totalCost: 3700
|
||||
},
|
||||
{
|
||||
key: 'lv-3',
|
||||
statMonth: sm,
|
||||
plateNo: '浙A88888F',
|
||||
vehicleType: '49T',
|
||||
salesperson: '刘念忠',
|
||||
nature: '纯租赁',
|
||||
systemModel: '苏龙18T',
|
||||
customerName: '宁波港联氢运物流有限公司',
|
||||
contractDate: '2025.11.15-2026.11.14',
|
||||
pickupDate: '2025-11-20',
|
||||
deposit: 50000,
|
||||
receivable: 26800,
|
||||
received: 20000,
|
||||
outstanding: 6800,
|
||||
naturalMonthIncome: 26800,
|
||||
paymentDate: '2026-01-28',
|
||||
hydrogenPrepay: 12000,
|
||||
paymentMethod: '月付指付',
|
||||
invoiceApplyDate: '2026-01-30',
|
||||
baseCost: 18500,
|
||||
brokerage: 1500,
|
||||
hydrogenFee: 6200,
|
||||
totalCost: 26200
|
||||
},
|
||||
{
|
||||
key: 'lv-4',
|
||||
statMonth: sm,
|
||||
plateNo: '苏E66666F',
|
||||
vehicleType: '4.5T',
|
||||
salesperson: '冉建华',
|
||||
nature: '纯租赁',
|
||||
systemModel: '现代4.5T',
|
||||
customerName: '嘉兴南湖氢能示范运营',
|
||||
contractDate: '2026.2.1-2027.1.31',
|
||||
pickupDate: '2026-02-10',
|
||||
deposit: 8000,
|
||||
receivable: 3800,
|
||||
received: 3800,
|
||||
outstanding: 0,
|
||||
naturalMonthIncome: 3800,
|
||||
paymentDate: '2026-02-12',
|
||||
hydrogenPrepay: 2000,
|
||||
paymentMethod: '月付指付',
|
||||
invoiceApplyDate: '2026-02-15',
|
||||
baseCost: 2100,
|
||||
brokerage: 200,
|
||||
hydrogenFee: 650,
|
||||
totalCost: 2950
|
||||
}
|
||||
];
|
||||
}, [lastMonthStatKey]);
|
||||
|
||||
var salespersonOptions = useMemo(function () {
|
||||
var set = {};
|
||||
(rawRowsTemplate || []).forEach(function (r) {
|
||||
if (r.salesperson) set[r.salesperson] = true;
|
||||
});
|
||||
return Object.keys(set).map(function (n) { return { value: n, label: n }; });
|
||||
}, [rawRowsTemplate]);
|
||||
|
||||
var customerOptions = useMemo(function () {
|
||||
var set = {};
|
||||
(rawRowsTemplate || []).forEach(function (r) {
|
||||
if (r.customerName) set[r.customerName] = true;
|
||||
});
|
||||
return Object.keys(set).map(function (n) { return { value: n, label: n }; });
|
||||
}, [rawRowsTemplate]);
|
||||
|
||||
/** 原型:按年 1–12 月租赁业务金额汇总(联调后由接口按年返回) */
|
||||
var monthlyLeasePL2026 = useMemo(function () {
|
||||
var raw = [
|
||||
{ month: 1, deposit: 120000, receivable: 98500, received: 92000, outstanding: 6500, naturalMonthIncome: 92000, hydrogenPrepay: 18000, baseCost: 62000, brokerage: 4200, hydrogenFee: 15000, totalCost: 81200 },
|
||||
{ month: 2, deposit: 45000, receivable: 53200, received: 48000, outstanding: 5200, naturalMonthIncome: 50500, hydrogenPrepay: 8000, baseCost: 31000, brokerage: 800, hydrogenFee: 9800, totalCost: 41600 },
|
||||
{ month: 3, deposit: null, receivable: 66800, received: 66800, outstanding: 0, naturalMonthIncome: 66800, hydrogenPrepay: null, baseCost: 40200, brokerage: 1500, hydrogenFee: 12100, totalCost: 53800 },
|
||||
{ month: 4, deposit: null, receivable: null, received: null, outstanding: null, naturalMonthIncome: null, hydrogenPrepay: null, baseCost: null, brokerage: null, hydrogenFee: null, totalCost: null },
|
||||
{ month: 5, deposit: null, receivable: null, received: null, outstanding: null, naturalMonthIncome: null, hydrogenPrepay: null, baseCost: null, brokerage: null, hydrogenFee: null, totalCost: null },
|
||||
{ month: 6, deposit: null, receivable: null, received: null, outstanding: null, naturalMonthIncome: null, hydrogenPrepay: null, baseCost: null, brokerage: null, hydrogenFee: null, totalCost: null },
|
||||
{ month: 7, deposit: null, receivable: null, received: null, outstanding: null, naturalMonthIncome: null, hydrogenPrepay: null, baseCost: null, brokerage: null, hydrogenFee: null, totalCost: null },
|
||||
{ month: 8, deposit: null, receivable: null, received: null, outstanding: null, naturalMonthIncome: null, hydrogenPrepay: null, baseCost: null, brokerage: null, hydrogenFee: null, totalCost: null },
|
||||
{ month: 9, deposit: null, receivable: null, received: null, outstanding: null, naturalMonthIncome: null, hydrogenPrepay: null, baseCost: null, brokerage: null, hydrogenFee: null, totalCost: null },
|
||||
{ month: 10, deposit: null, receivable: null, received: null, outstanding: null, naturalMonthIncome: null, hydrogenPrepay: null, baseCost: null, brokerage: null, hydrogenFee: null, totalCost: null },
|
||||
{ month: 11, deposit: null, receivable: null, received: null, outstanding: null, naturalMonthIncome: null, hydrogenPrepay: null, baseCost: null, brokerage: null, hydrogenFee: null, totalCost: null },
|
||||
{ month: 12, deposit: null, receivable: null, received: null, outstanding: null, naturalMonthIncome: null, hydrogenPrepay: null, baseCost: null, brokerage: null, hydrogenFee: null, totalCost: null }
|
||||
];
|
||||
return raw.map(function (x) {
|
||||
return finalizeLeaseMonthlyRow(Object.assign({ key: 'lease-pl-' + x.month }, x));
|
||||
});
|
||||
}, []);
|
||||
|
||||
var mainTabState = useState('detail');
|
||||
var mainTab = mainTabState[0];
|
||||
var setMainTab = mainTabState[1];
|
||||
|
||||
function initialYear2026() {
|
||||
try {
|
||||
if (window.dayjs) return window.dayjs('2026-01-01');
|
||||
} catch (e1) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
var summaryYearDraftState = useState(initialYear2026);
|
||||
var summaryYearDraft = summaryYearDraftState[0];
|
||||
var setSummaryYearDraft = summaryYearDraftState[1];
|
||||
|
||||
var summaryYearAppliedState = useState(initialYear2026);
|
||||
var summaryYearApplied = summaryYearAppliedState[0];
|
||||
var setSummaryYearApplied = summaryYearAppliedState[1];
|
||||
|
||||
var draftState = useState(function () {
|
||||
return {
|
||||
month: getLastMonthPickerValue(),
|
||||
salesperson: undefined,
|
||||
customerName: undefined,
|
||||
dept: '业务二部'
|
||||
};
|
||||
});
|
||||
var draft = draftState[0];
|
||||
var setDraft = draftState[1];
|
||||
|
||||
var appliedState = useState(function () {
|
||||
return {
|
||||
month: getLastMonthPickerValue(),
|
||||
salesperson: undefined,
|
||||
customerName: undefined,
|
||||
dept: '业务二部'
|
||||
};
|
||||
});
|
||||
var applied = appliedState[0];
|
||||
var setApplied = appliedState[1];
|
||||
|
||||
var filteredRows = useMemo(function () {
|
||||
return (rawRowsTemplate || []).filter(function (r) {
|
||||
if (applied.month && applied.month.format) {
|
||||
var mk = applied.month.format('YYYY-MM');
|
||||
if (r.statMonth !== mk) return false;
|
||||
}
|
||||
if (applied.salesperson && r.salesperson !== applied.salesperson) return false;
|
||||
if (applied.customerName && r.customerName !== applied.customerName) return false;
|
||||
return true;
|
||||
});
|
||||
}, [rawRowsTemplate, applied.month, applied.salesperson, applied.customerName]);
|
||||
|
||||
var columnSums = useMemo(function () {
|
||||
return sumRows(filteredRows, numericSumKeys);
|
||||
}, [filteredRows]);
|
||||
|
||||
var reportTitle = useMemo(function () {
|
||||
var dept = applied.dept || '业务二部';
|
||||
if (applied.month && applied.month.format) {
|
||||
var y = applied.month.format('YYYY');
|
||||
return y + '年浙江羚牛氢能租赁车辆收入明细表(' + dept + ')';
|
||||
}
|
||||
return '浙江羚牛氢能租赁车辆收入明细表(' + dept + ')';
|
||||
}, [applied.month, applied.dept]);
|
||||
|
||||
var plSumTableTitle = useMemo(function () {
|
||||
if (summaryYearApplied && summaryYearApplied.format) {
|
||||
return '租赁业务' + summaryYearApplied.format('YYYY') + '年盈亏月度汇总';
|
||||
}
|
||||
return '租赁业务盈亏月度汇总';
|
||||
}, [summaryYearApplied]);
|
||||
|
||||
var monthlyDataSource = useCallback(function (rows) {
|
||||
var y = summaryYearApplied && summaryYearApplied.format ? summaryYearApplied.format('YYYY') : null;
|
||||
if (!y) return [];
|
||||
if (y !== '2026') return [];
|
||||
return rows;
|
||||
}, [summaryYearApplied]);
|
||||
|
||||
var plMonthlyRows = useMemo(function () { return monthlyDataSource(monthlyLeasePL2026); }, [monthlyDataSource, monthlyLeasePL2026]);
|
||||
var plMonthlySums = useMemo(function () { return sumMonthlyRows(plMonthlyRows, monthlyPLKeys); }, [plMonthlyRows]);
|
||||
|
||||
var handleQuery = useCallback(function () {
|
||||
setApplied(Object.assign({}, draft));
|
||||
}, [draft]);
|
||||
|
||||
var handleReset = useCallback(function () {
|
||||
var def = {
|
||||
month: getLastMonthPickerValue(),
|
||||
salesperson: undefined,
|
||||
customerName: undefined,
|
||||
dept: '业务二部'
|
||||
};
|
||||
setDraft(def);
|
||||
setApplied(def);
|
||||
}, []);
|
||||
|
||||
var handleSummaryQuery = useCallback(function () {
|
||||
setSummaryYearApplied(summaryYearDraft);
|
||||
}, [summaryYearDraft]);
|
||||
|
||||
var handleSummaryReset = useCallback(function () {
|
||||
var y0 = initialYear2026();
|
||||
setSummaryYearDraft(y0);
|
||||
setSummaryYearApplied(y0);
|
||||
}, []);
|
||||
|
||||
var handleExport = useCallback(function () {
|
||||
var rows = filteredRows;
|
||||
if (!rows || rows.length === 0) {
|
||||
message.warning('当前无数据可导出');
|
||||
return;
|
||||
}
|
||||
var headers = [
|
||||
'车牌号码',
|
||||
'车型',
|
||||
'业务员',
|
||||
'性质',
|
||||
'系统车型',
|
||||
'客户名称',
|
||||
'合同日期',
|
||||
'提车日期',
|
||||
'押金',
|
||||
'应收',
|
||||
'实收',
|
||||
'未收',
|
||||
'自然月收入',
|
||||
'付款日期',
|
||||
'氢费预充值',
|
||||
'付款方式',
|
||||
'申请开票日期',
|
||||
'成本',
|
||||
'居间费',
|
||||
'氢费',
|
||||
'总成本'
|
||||
];
|
||||
var line = function (r) {
|
||||
return [
|
||||
r.plateNo,
|
||||
r.vehicleType,
|
||||
r.salesperson,
|
||||
r.nature,
|
||||
r.systemModel,
|
||||
r.customerName,
|
||||
fmtTextSlash(r.contractDate),
|
||||
fmtTextSlash(r.pickupDate),
|
||||
fmtCell(r.deposit),
|
||||
fmtCell(r.receivable),
|
||||
fmtCell(r.received),
|
||||
fmtCell(r.outstanding),
|
||||
fmtCell(r.naturalMonthIncome),
|
||||
fmtTextSlash(r.paymentDate),
|
||||
fmtCell(r.hydrogenPrepay),
|
||||
fmtTextSlash(r.paymentMethod),
|
||||
fmtTextSlash(r.invoiceApplyDate),
|
||||
fmtCell(r.baseCost),
|
||||
fmtCell(r.brokerage),
|
||||
fmtCell(r.hydrogenFee),
|
||||
fmtCell(r.totalCost)
|
||||
];
|
||||
};
|
||||
var body = [headers].concat(rows.map(line));
|
||||
body.push([
|
||||
'汇总', '', '', '', '', '', '', '',
|
||||
fmtSum(columnSums.deposit),
|
||||
fmtSum(columnSums.receivable),
|
||||
fmtSum(columnSums.received),
|
||||
fmtSum(columnSums.outstanding),
|
||||
fmtSum(columnSums.naturalMonthIncome),
|
||||
'',
|
||||
fmtSum(columnSums.hydrogenPrepay),
|
||||
'', '',
|
||||
fmtSum(columnSums.baseCost),
|
||||
fmtSum(columnSums.brokerage),
|
||||
fmtSum(columnSums.hydrogenFee),
|
||||
fmtSum(columnSums.totalCost)
|
||||
]);
|
||||
downloadCsv('租赁车辆收入明细_' + new Date().getTime() + '.csv', body);
|
||||
message.success('已导出 ' + rows.length + ' 条记录');
|
||||
}, [filteredRows, columnSums]);
|
||||
|
||||
var handleExportPLMonthly = useCallback(function () {
|
||||
var rows = plMonthlyRows;
|
||||
if (!rows || rows.length === 0) {
|
||||
message.warning('当前无数据可导出,请先选择年份并查询');
|
||||
return;
|
||||
}
|
||||
var headers = [
|
||||
'月份',
|
||||
'押金',
|
||||
'应收',
|
||||
'实收',
|
||||
'未收',
|
||||
'自然月收入',
|
||||
'氢费预充值',
|
||||
'成本',
|
||||
'居间费',
|
||||
'氢费',
|
||||
'总成本',
|
||||
'盈亏'
|
||||
];
|
||||
var body = [headers].concat(
|
||||
rows.map(function (r) {
|
||||
return [
|
||||
String(r.month),
|
||||
fmtCell(r.deposit),
|
||||
fmtCell(r.receivable),
|
||||
fmtCell(r.received),
|
||||
fmtCell(r.outstanding),
|
||||
fmtCell(r.naturalMonthIncome),
|
||||
fmtCell(r.hydrogenPrepay),
|
||||
fmtCell(r.baseCost),
|
||||
fmtCell(r.brokerage),
|
||||
fmtCell(r.hydrogenFee),
|
||||
fmtCell(r.totalCost),
|
||||
fmtCell(r.profit)
|
||||
];
|
||||
})
|
||||
);
|
||||
body.push(['总计'].concat(monthlyPLKeys.map(function (k) { return fmtSum(plMonthlySums[k] || 0); })));
|
||||
downloadCsv('租赁业务盈亏月度汇总_' + new Date().getTime() + '.csv', body);
|
||||
message.success('已导出 ' + rows.length + ' 个月度数据');
|
||||
}, [plMonthlyRows, plMonthlySums]);
|
||||
|
||||
function renderOutstanding(v) {
|
||||
var s = fmtCell(v);
|
||||
if (v !== null && v !== undefined && v !== '' && !isNaN(Number(v)) && Number(v) > 0) {
|
||||
return React.createElement('span', { style: outstandingPositiveStyle }, s);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
var columns = useMemo(function () {
|
||||
return [
|
||||
{ title: '车牌号码', dataIndex: 'plateNo', key: 'plateNo', width: 110, fixed: 'left', align: 'center' },
|
||||
{ title: '车型', dataIndex: 'vehicleType', key: 'vehicleType', width: 72, align: 'center' },
|
||||
{ title: '业务员', dataIndex: 'salesperson', key: 'salesperson', width: 88, align: 'center' },
|
||||
{ title: '性质', dataIndex: 'nature', key: 'nature', width: 88, align: 'center' },
|
||||
{ title: '系统车型', dataIndex: 'systemModel', key: 'systemModel', width: 100, align: 'center' },
|
||||
{ title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 200, ellipsis: true },
|
||||
{ title: '合同日期', dataIndex: 'contractDate', key: 'contractDate', width: 168, render: function (v) { return fmtTextSlash(v); } },
|
||||
{ title: '提车日期', dataIndex: 'pickupDate', key: 'pickupDate', width: 110, render: function (v) { return fmtTextSlash(v); } },
|
||||
{ title: '押金', dataIndex: 'deposit', key: 'deposit', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '应收', dataIndex: 'receivable', key: 'receivable', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '实收', dataIndex: 'received', key: 'received', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '未收', dataIndex: 'outstanding', key: 'outstanding', width: 100, align: 'right', render: function (v) { return renderOutstanding(v); } },
|
||||
{ title: '自然月收入', dataIndex: 'naturalMonthIncome', key: 'naturalMonthIncome', width: 118, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '付款日期', dataIndex: 'paymentDate', key: 'paymentDate', width: 110, render: function (v) { return fmtTextSlash(v); } },
|
||||
{ title: '氢费预充值', dataIndex: 'hydrogenPrepay', key: 'hydrogenPrepay', width: 110, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '付款方式', dataIndex: 'paymentMethod', key: 'paymentMethod', width: 100, render: function (v) { return fmtTextSlash(v); } },
|
||||
{ title: '申请开票日期', dataIndex: 'invoiceApplyDate', key: 'invoiceApplyDate', width: 120, render: function (v) { return fmtTextSlash(v); } },
|
||||
{ title: '成本', dataIndex: 'baseCost', key: 'baseCost', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '居间费', dataIndex: 'brokerage', key: 'brokerage', width: 90, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '氢费', dataIndex: 'hydrogenFee', key: 'hydrogenFee', width: 90, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '总成本', dataIndex: 'totalCost', key: 'totalCost', width: 110, align: 'right', render: function (v) { return fmtCell(v); } }
|
||||
];
|
||||
}, []);
|
||||
|
||||
var columnsPLMonthly = useMemo(function () {
|
||||
return [
|
||||
{ title: '月份', dataIndex: 'month', key: 'month', width: 72, align: 'center' },
|
||||
{ title: '押金', dataIndex: 'deposit', key: 'deposit', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '应收', dataIndex: 'receivable', key: 'receivable', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '实收', dataIndex: 'received', key: 'received', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '未收', dataIndex: 'outstanding', key: 'outstanding', width: 100, align: 'right', render: function (v) { return renderOutstanding(v); } },
|
||||
{ title: '自然月收入', dataIndex: 'naturalMonthIncome', key: 'naturalMonthIncome', width: 118, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '氢费预充值', dataIndex: 'hydrogenPrepay', key: 'hydrogenPrepay', width: 110, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '成本', dataIndex: 'baseCost', key: 'baseCost', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '居间费', dataIndex: 'brokerage', key: 'brokerage', width: 90, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '氢费', dataIndex: 'hydrogenFee', key: 'hydrogenFee', width: 90, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '总成本', dataIndex: 'totalCost', key: 'totalCost', width: 100, align: 'right', render: function (v) { return fmtCell(v); } },
|
||||
{ title: '盈亏', dataIndex: 'profit', key: 'profit', width: 100, align: 'right', render: function (v) { return fmtCell(v); } }
|
||||
];
|
||||
}, []);
|
||||
|
||||
function renderPLMonthlySummary(sums) {
|
||||
return function () {
|
||||
return React.createElement(
|
||||
TableSummary,
|
||||
null,
|
||||
React.createElement(
|
||||
SummaryRow,
|
||||
null,
|
||||
React.createElement(SummaryCell, { index: 0, align: 'center' }, '总计'),
|
||||
monthlyPLKeys.map(function (k, idx) {
|
||||
var cell = fmtSum(sums[k] || 0);
|
||||
if (k === 'outstanding' && Number(sums.outstanding) > 0) {
|
||||
cell = React.createElement('span', { style: outstandingPositiveStyle }, fmtSum(sums.outstanding || 0));
|
||||
}
|
||||
return React.createElement(
|
||||
SummaryCell,
|
||||
{ key: k, index: idx + 1, align: 'right' },
|
||||
cell
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
var renderYearFilterCard = useCallback(function () {
|
||||
return 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: summaryYearDraft,
|
||||
onChange: function (v) { setSummaryYearDraft(v); }
|
||||
})
|
||||
)
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 } },
|
||||
React.createElement(Button, { onClick: handleSummaryReset }, '重置'),
|
||||
React.createElement(Button, { type: 'primary', onClick: handleSummaryQuery }, '查询')
|
||||
)
|
||||
);
|
||||
}, [summaryYearDraft, handleSummaryReset, handleSummaryQuery]);
|
||||
|
||||
var tableSummary = useCallback(function () {
|
||||
return React.createElement(
|
||||
TableSummary,
|
||||
null,
|
||||
React.createElement(
|
||||
SummaryRow,
|
||||
null,
|
||||
React.createElement(SummaryCell, { index: 0, align: 'center', colSpan: 8 }, '汇总'),
|
||||
React.createElement(SummaryCell, { index: 8, align: 'right' }, fmtSum(columnSums.deposit)),
|
||||
React.createElement(SummaryCell, { index: 9, align: 'right' }, fmtSum(columnSums.receivable)),
|
||||
React.createElement(SummaryCell, { index: 10, align: 'right' }, fmtSum(columnSums.received)),
|
||||
React.createElement(SummaryCell, { index: 11, align: 'right' },
|
||||
Number(columnSums.outstanding) > 0
|
||||
? React.createElement('span', { style: outstandingPositiveStyle }, fmtSum(columnSums.outstanding))
|
||||
: fmtSum(columnSums.outstanding)
|
||||
),
|
||||
React.createElement(SummaryCell, { index: 12, align: 'right' }, fmtSum(columnSums.naturalMonthIncome)),
|
||||
React.createElement(SummaryCell, { index: 13, align: 'center' }, '—'),
|
||||
React.createElement(SummaryCell, { index: 14, align: 'right' }, fmtSum(columnSums.hydrogenPrepay)),
|
||||
React.createElement(SummaryCell, { index: 15, align: 'center' }, '—'),
|
||||
React.createElement(SummaryCell, { index: 16, align: 'center' }, '—'),
|
||||
React.createElement(SummaryCell, { index: 17, align: 'right' }, fmtSum(columnSums.baseCost)),
|
||||
React.createElement(SummaryCell, { index: 18, align: 'right' }, fmtSum(columnSums.brokerage)),
|
||||
React.createElement(SummaryCell, { index: 19, align: 'right' }, fmtSum(columnSums.hydrogenFee)),
|
||||
React.createElement(SummaryCell, { index: 20, align: 'right' }, fmtSum(columnSums.totalCost))
|
||||
)
|
||||
);
|
||||
}, [columnSums]);
|
||||
|
||||
var tabItems = [
|
||||
{
|
||||
key: 'detail',
|
||||
label: '租赁业务明细',
|
||||
children: React.createElement(React.Fragment, null,
|
||||
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: 'month',
|
||||
style: filterControlStyle,
|
||||
placeholder: '请选择年-月',
|
||||
format: 'YYYY-MM',
|
||||
value: draft.month,
|
||||
onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { month: 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,
|
||||
options: deptOptions,
|
||||
value: draft.dept,
|
||||
onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { dept: 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,
|
||||
allowClear: true,
|
||||
options: salespersonOptions,
|
||||
value: draft.salesperson,
|
||||
onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { salesperson: v }); }); },
|
||||
showSearch: true,
|
||||
filterOption: filterOption
|
||||
})
|
||||
)
|
||||
),
|
||||
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,
|
||||
allowClear: true,
|
||||
options: customerOptions,
|
||||
value: draft.customerName,
|
||||
onChange: function (v) { setDraft(function (p) { return Object.assign({}, p, { customerName: v }); }); },
|
||||
showSearch: true,
|
||||
filterOption: filterOption
|
||||
})
|
||||
)
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 16 } },
|
||||
React.createElement(Button, { onClick: handleReset }, '重置'),
|
||||
React.createElement(Button, { type: 'primary', onClick: handleQuery }, '查询')
|
||||
)
|
||||
),
|
||||
React.createElement(Card, {
|
||||
extra: React.createElement(Button, { onClick: handleExport }, '导出')
|
||||
},
|
||||
React.createElement('div', { style: bizReportTableTitleStyle }, reportTitle),
|
||||
React.createElement('style', null, tableSingleLineStyle),
|
||||
React.createElement('div', { className: 'lease-income-detail-table' },
|
||||
React.createElement(Table, {
|
||||
rowKey: 'key',
|
||||
columns: columns,
|
||||
dataSource: filteredRows,
|
||||
pagination: false,
|
||||
size: 'small',
|
||||
summary: tableSummary,
|
||||
scroll: { x: 2680 }
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
{
|
||||
key: 'monthlyPL',
|
||||
label: '租赁业务盈亏月度汇总',
|
||||
children: React.createElement(React.Fragment, null,
|
||||
renderYearFilterCard(),
|
||||
React.createElement(Card, {
|
||||
extra: React.createElement(Button, { onClick: handleExportPLMonthly }, '导出')
|
||||
},
|
||||
React.createElement('div', { style: bizReportTableTitleStyle }, plSumTableTitle),
|
||||
React.createElement('style', null, tableMonthlyLineStyle),
|
||||
React.createElement('div', { className: 'lease-pl-monthly-table' },
|
||||
React.createElement(Table, {
|
||||
rowKey: 'key',
|
||||
columns: columnsPLMonthly,
|
||||
dataSource: plMonthlyRows,
|
||||
pagination: false,
|
||||
size: 'small',
|
||||
summary: renderPLMonthlySummary(plMonthlySums),
|
||||
scroll: { x: 1320 }
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return React.createElement(App, null,
|
||||
React.createElement('div', { style: layoutStyle },
|
||||
React.createElement(Breadcrumb, {
|
||||
style: { marginBottom: 16 },
|
||||
items: [{ title: '数据分析' }, { title: '租赁车辆收入明细' }]
|
||||
}),
|
||||
React.createElement(Card, null,
|
||||
React.createElement('style', null, tabsBarStyle),
|
||||
React.createElement(Tabs, {
|
||||
className: 'lease-income-tabs',
|
||||
activeKey: mainTab,
|
||||
onChange: function (k) { setMainTab(k); },
|
||||
items: tabItems
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user