516 lines
15 KiB
JavaScript
516 lines
15 KiB
JavaScript
// 【重要】必须使用 const Component 作为组件变量名
|
||
// 台账数据 - 车辆保险台账(保险分摊明细)
|
||
// 原型:客户名称 + 结算周期筛选、导出;日成本与分摊成本按业务公式计算(联调可替换为接口)
|
||
|
||
const Component = function () {
|
||
var useState = React.useState;
|
||
var useMemo = React.useMemo;
|
||
var useCallback = React.useCallback;
|
||
|
||
var antd = window.antd;
|
||
var App = antd.App;
|
||
var Breadcrumb = antd.Breadcrumb;
|
||
var Card = antd.Card;
|
||
var Button = antd.Button;
|
||
var Table = antd.Table;
|
||
var Select = antd.Select;
|
||
var DatePicker = antd.DatePicker;
|
||
var Row = antd.Row;
|
||
var Col = antd.Col;
|
||
var Space = antd.Space;
|
||
var message = antd.message;
|
||
|
||
var CUSTOMER_OPTIONS = [
|
||
{ value: '浙江羚牛氢能科技有限公司', label: '浙江羚牛氢能科技有限公司' },
|
||
{ value: '杭州绿运物流有限公司', label: '杭州绿运物流有限公司' },
|
||
{ value: '宁波港城新能源车队', label: '宁波港城新能源车队' },
|
||
{ value: '绍兴氢能示范运营中心', label: '绍兴氢能示范运营中心' }
|
||
];
|
||
|
||
var PROJECT_BY_CUSTOMER = {
|
||
'浙江羚牛氢能科技有限公司': ['氢能重卡租赁一期', '园区通勤包车'],
|
||
'杭州绿运物流有限公司': ['城配氢能车辆项目', '冷链专线'],
|
||
'宁波港城新能源车队': ['港口短驳氢能车', '堆场转运'],
|
||
'绍兴氢能示范运营中心': ['示范线路运营', '加氢站接驳']
|
||
};
|
||
|
||
var PLATE_PREFIX = ['浙A', '浙B', '浙D', '浙G'];
|
||
|
||
function filterOption(input, option) {
|
||
var label = (option && (option.label || option.children)) || '';
|
||
return String(label).toLowerCase().indexOf(String(input || '').toLowerCase()) >= 0;
|
||
}
|
||
|
||
function numOrZero(v) {
|
||
if (v === null || v === undefined || v === '') return 0;
|
||
var n = Number(v);
|
||
return isNaN(n) ? 0 : n;
|
||
}
|
||
|
||
function fmtMoney(n, digits) {
|
||
if (n === null || n === undefined || n === '') return '-';
|
||
var x = Number(n);
|
||
if (isNaN(x)) return '-';
|
||
var d = digits === undefined ? 2 : digits;
|
||
return x.toLocaleString('zh-CN', { minimumFractionDigits: d, maximumFractionDigits: d });
|
||
}
|
||
|
||
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 initialSettlementMonth() {
|
||
try {
|
||
if (window.dayjs) return window.dayjs('2026-05-01');
|
||
} catch (e1) {}
|
||
return null;
|
||
}
|
||
|
||
function settlementYm(d) {
|
||
if (!d || !window.dayjs) return '';
|
||
try {
|
||
return window.dayjs(d).format('YYYY-MM');
|
||
} catch (e2) {
|
||
return '';
|
||
}
|
||
}
|
||
|
||
/** 日成本 = 缴费金额 / 生效天数 */
|
||
function dailyCost(payment, effectiveDays) {
|
||
var pay = numOrZero(payment);
|
||
var days = numOrZero(effectiveDays);
|
||
if (days <= 0) return null;
|
||
return Math.round((pay / days) * 10000) / 10000;
|
||
}
|
||
|
||
/** 保险分摊成本 = Σ(各险日成本 × 分摊天数) */
|
||
function calcApportionCost(row) {
|
||
var d = numOrZero(row.apportionDays);
|
||
if (d <= 0) return null;
|
||
var sum = 0;
|
||
var has = false;
|
||
['compulsoryDaily', 'commercialDaily', 'excessDaily', 'cargoDaily'].forEach(function (k) {
|
||
var v = row[k];
|
||
if (v !== null && v !== undefined && !isNaN(Number(v))) {
|
||
sum += Number(v) * d;
|
||
has = true;
|
||
}
|
||
});
|
||
if (!has) return null;
|
||
return Math.round(sum * 100) / 100;
|
||
}
|
||
|
||
function buildMockRows(ym) {
|
||
var rows = [];
|
||
var seed = 0;
|
||
CUSTOMER_OPTIONS.forEach(function (cust, ci) {
|
||
var projects = PROJECT_BY_CUSTOMER[cust.value] || ['默认项目'];
|
||
projects.forEach(function (proj, pi) {
|
||
seed += 1;
|
||
var plate = PLATE_PREFIX[ci % PLATE_PREFIX.length] + String(10000 + seed).slice(-5) + 'F';
|
||
var effDays = 365;
|
||
var compulsoryPay = 950 + seed * 3;
|
||
var commercialPay = 4200 + seed * 17;
|
||
var excessPay = seed % 3 === 0 ? 1800 + seed * 5 : 0;
|
||
var cargoPay = seed % 2 === 0 ? 600 + seed * 2 : 0;
|
||
var apportionDays = 18 + ((seed + (ym ? ym.length : 0)) % 13);
|
||
|
||
var row = {
|
||
key: 'r' + seed,
|
||
seq: seed,
|
||
settlementCycle: ym || '2026-05',
|
||
customerName: cust.value,
|
||
projectName: proj,
|
||
plateNo: plate,
|
||
compulsoryDaily: dailyCost(compulsoryPay, effDays),
|
||
commercialDaily: dailyCost(commercialPay, effDays),
|
||
excessDaily: excessPay > 0 ? dailyCost(excessPay, effDays) : null,
|
||
cargoDaily: cargoPay > 0 ? dailyCost(cargoPay, effDays) : null,
|
||
apportionDays: apportionDays
|
||
};
|
||
row.apportionCost = calcApportionCost(row);
|
||
rows.push(row);
|
||
});
|
||
});
|
||
return rows;
|
||
}
|
||
|
||
|
||
var layoutStyle = {
|
||
padding: '16px 24px 24px',
|
||
minHeight: '100vh',
|
||
background: 'linear-gradient(165deg, #eef4ff 0%, #f5f7fa 42%, #f0f2f5 100%)'
|
||
};
|
||
var filterLabelStyle = { marginBottom: 6, fontSize: 13, color: 'rgba(0,0,0,0.55)', fontWeight: 500 };
|
||
var filterItemStyle = { marginBottom: 12 };
|
||
var filterControlStyle = { width: '100%' };
|
||
var filterActionsColStyle = { flex: '0 0 auto', marginLeft: 'auto' };
|
||
|
||
var filterCardStyle = {
|
||
marginBottom: 20,
|
||
borderRadius: 16,
|
||
boxShadow: '0 4px 20px -4px rgba(16,24,40,0.03), 0 0 0 1px rgba(16,24,40,0.06)',
|
||
border: 'none',
|
||
background: '#ffffff'
|
||
};
|
||
|
||
var tableCardStyle = {
|
||
borderRadius: 16,
|
||
boxShadow: '0 10px 32px -4px rgba(16,24,40,0.06), 0 0 0 1px rgba(16,24,40,0.04)',
|
||
border: 'none',
|
||
background: '#ffffff',
|
||
overflow: 'hidden'
|
||
};
|
||
|
||
var ledgerTableStyle =
|
||
'.ins-ledger-table-wrap{border-radius:12px;overflow:hidden;box-shadow:0 4px 24px -6px rgba(15,23,42,0.05),0 0 0 1px rgba(22,119,255,0.1)}' +
|
||
'.ins-ledger-table .ant-table-thead>tr>th{white-space:nowrap;color:#1e293b!important;font-weight:600!important;font-size:13px!important;' +
|
||
'background:#e8f4fc!important;border-bottom:1px solid #bae6fd!important;border-inline-end:1px solid #dbeafe!important;padding:0 8px!important;height:38px!important}' +
|
||
'.ins-ledger-table .ant-table-tbody>tr:not(.ant-table-measure-row)>td{white-space:nowrap;font-variant-numeric:tabular-nums;color:#334155;border-bottom:1px solid #f1f5f9!important;border-inline-end:1px solid #f8fafc!important;padding:0 8px!important;height:38px!important}' +
|
||
'.ins-ledger-table .ant-table-tbody>tr.ins-row-data:hover>td{background:#f0f9ff!important}' +
|
||
'.ins-ledger-table .ant-table-summary>tr>td{font-weight:700;background:#f8fafc!important;color:#0f172a!important;border-top:2px solid #cbd5e1!important;padding:0 8px!important;height:38px!important}';
|
||
|
||
var customerDraftState = useState(undefined);
|
||
var customerDraft = customerDraftState[0];
|
||
var setCustomerDraft = customerDraftState[1];
|
||
|
||
var monthDraftState = useState(initialSettlementMonth);
|
||
var monthDraft = monthDraftState[0];
|
||
var setMonthDraft = monthDraftState[1];
|
||
|
||
var customerAppliedState = useState(undefined);
|
||
var customerApplied = customerAppliedState[0];
|
||
var setCustomerApplied = customerAppliedState[1];
|
||
|
||
var monthAppliedState = useState(initialSettlementMonth);
|
||
var monthApplied = monthAppliedState[0];
|
||
var setMonthApplied = monthAppliedState[1];
|
||
|
||
var appliedYm = useMemo(function () {
|
||
return settlementYm(monthApplied) || '2026-05';
|
||
}, [monthApplied]);
|
||
|
||
var allRows = useMemo(function () {
|
||
return buildMockRows(appliedYm);
|
||
}, [appliedYm]);
|
||
|
||
var dataSource = useMemo(function () {
|
||
var list = allRows.filter(function (r) {
|
||
if (customerApplied && r.customerName !== customerApplied) return false;
|
||
return true;
|
||
});
|
||
return list.map(function (r, idx) {
|
||
return Object.assign({}, r, { seq: idx + 1 });
|
||
});
|
||
}, [allRows, customerApplied]);
|
||
|
||
var totalApportionCost = useMemo(function () {
|
||
return dataSource.reduce(function (acc, r) {
|
||
return acc + numOrZero(r.apportionCost);
|
||
}, 0);
|
||
}, [dataSource]);
|
||
|
||
var customerDisplayLabel = customerApplied || '默认全量数据';
|
||
var cycleDisplayLabel = appliedYm || '-';
|
||
|
||
var handleQuery = useCallback(function () {
|
||
setCustomerApplied(customerDraft);
|
||
setMonthApplied(monthDraft);
|
||
message.success('查询成功');
|
||
}, [customerDraft, monthDraft]);
|
||
|
||
var handleReset = useCallback(function () {
|
||
setCustomerDraft(undefined);
|
||
setMonthDraft(initialSettlementMonth());
|
||
setCustomerApplied(undefined);
|
||
setMonthApplied(initialSettlementMonth());
|
||
}, []);
|
||
|
||
var handleExport = useCallback(function () {
|
||
var headers = [
|
||
'序号',
|
||
'结算周期',
|
||
'客户名称',
|
||
'项目名称',
|
||
'车牌号',
|
||
'交强险日成本',
|
||
'商业险日成本',
|
||
'超赔险日成本',
|
||
'货物险日成本',
|
||
'分摊天数',
|
||
'保险分摊成本'
|
||
];
|
||
var body = dataSource.map(function (r) {
|
||
return [
|
||
r.seq,
|
||
r.settlementCycle,
|
||
r.customerName,
|
||
r.projectName,
|
||
r.plateNo,
|
||
r.compulsoryDaily,
|
||
r.commercialDaily,
|
||
r.excessDaily,
|
||
r.cargoDaily,
|
||
r.apportionDays,
|
||
r.apportionCost
|
||
];
|
||
});
|
||
body.push(['合计', '', '', '', '', '', '', '', '', '', totalApportionCost]);
|
||
downloadCsv('车辆保险台账_' + cycleDisplayLabel + '_' + new Date().getTime() + '.csv', [headers].concat(body));
|
||
message.success('已导出 CSV');
|
||
}, [dataSource, cycleDisplayLabel, totalApportionCost]);
|
||
|
||
var columns = useMemo(function () {
|
||
return [
|
||
{
|
||
title: '序号',
|
||
dataIndex: 'seq',
|
||
key: 'seq',
|
||
width: 64,
|
||
align: 'center',
|
||
fixed: 'left'
|
||
},
|
||
{
|
||
title: '结算周期',
|
||
dataIndex: 'settlementCycle',
|
||
key: 'settlementCycle',
|
||
width: 100,
|
||
align: 'center'
|
||
},
|
||
{
|
||
title: '客户名称',
|
||
dataIndex: 'customerName',
|
||
key: 'customerName',
|
||
width: 200,
|
||
align: 'center',
|
||
ellipsis: true
|
||
},
|
||
{
|
||
title: '项目名称',
|
||
dataIndex: 'projectName',
|
||
key: 'projectName',
|
||
width: 160,
|
||
align: 'center',
|
||
ellipsis: true
|
||
},
|
||
{
|
||
title: '车牌号',
|
||
dataIndex: 'plateNo',
|
||
key: 'plateNo',
|
||
width: 110,
|
||
align: 'center'
|
||
},
|
||
{
|
||
title: '交强险日成本',
|
||
dataIndex: 'compulsoryDaily',
|
||
key: 'compulsoryDaily',
|
||
width: 130,
|
||
align: 'right',
|
||
render: function (v) { return fmtMoney(v, 4); }
|
||
},
|
||
{
|
||
title: '商业险日成本',
|
||
dataIndex: 'commercialDaily',
|
||
key: 'commercialDaily',
|
||
width: 130,
|
||
align: 'right',
|
||
render: function (v) { return fmtMoney(v, 4); }
|
||
},
|
||
{
|
||
title: '超赔险日成本',
|
||
dataIndex: 'excessDaily',
|
||
key: 'excessDaily',
|
||
width: 130,
|
||
align: 'right',
|
||
render: function (v) { return fmtMoney(v, 4); }
|
||
},
|
||
{
|
||
title: '货物险日成本',
|
||
dataIndex: 'cargoDaily',
|
||
key: 'cargoDaily',
|
||
width: 130,
|
||
align: 'right',
|
||
render: function (v) { return fmtMoney(v, 4); }
|
||
},
|
||
{
|
||
title: '分摊天数',
|
||
dataIndex: 'apportionDays',
|
||
key: 'apportionDays',
|
||
width: 120,
|
||
align: 'center'
|
||
},
|
||
{
|
||
title: '保险分摊成本',
|
||
dataIndex: 'apportionCost',
|
||
key: 'apportionCost',
|
||
width: 140,
|
||
align: 'right',
|
||
fixed: 'right',
|
||
render: function (v) { return fmtMoney(v, 2); }
|
||
}
|
||
];
|
||
}, []);
|
||
|
||
var tableSummary = useCallback(function () {
|
||
return React.createElement(
|
||
Table.Summary,
|
||
null,
|
||
React.createElement(
|
||
Table.Summary.Row,
|
||
null,
|
||
React.createElement(Table.Summary.Cell, { index: 0, align: 'center', colSpan: 1 }, '合计'),
|
||
React.createElement(Table.Summary.Cell, { index: 1, colSpan: 9 }),
|
||
React.createElement(Table.Summary.Cell, { index: 10, align: 'right' }, fmtMoney(totalApportionCost, 2))
|
||
)
|
||
);
|
||
}, [totalApportionCost]);
|
||
|
||
return React.createElement(
|
||
App,
|
||
null,
|
||
React.createElement('style', null, ledgerTableStyle),
|
||
React.createElement(
|
||
'div',
|
||
{ style: layoutStyle },
|
||
React.createElement(Breadcrumb, {
|
||
style: { marginBottom: 12 },
|
||
items: [{ title: '台账数据' }, { title: '保险分摊明细' }]
|
||
}),
|
||
React.createElement(
|
||
Card,
|
||
{ style: filterCardStyle, bodyStyle: { paddingBottom: 4 } },
|
||
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(Select, {
|
||
allowClear: true,
|
||
showSearch: true,
|
||
placeholder: '默认全量数据',
|
||
style: filterControlStyle,
|
||
value: customerDraft,
|
||
onChange: function (v) { setCustomerDraft(v); },
|
||
options: CUSTOMER_OPTIONS,
|
||
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(DatePicker, {
|
||
picker: 'month',
|
||
style: filterControlStyle,
|
||
placeholder: '请选择结算周期',
|
||
format: 'YYYY-MM',
|
||
value: monthDraft,
|
||
onChange: function (v) { setMonthDraft(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,
|
||
{ wrap: true },
|
||
React.createElement(Button, { onClick: handleReset }, '重置'),
|
||
React.createElement(Button, { type: 'primary', onClick: handleQuery }, '查询')
|
||
)
|
||
)
|
||
)
|
||
)
|
||
),
|
||
React.createElement(
|
||
Card,
|
||
{ style: tableCardStyle, bodyStyle: { padding: '20px 20px 24px' } },
|
||
React.createElement(
|
||
'div',
|
||
{ style: { position: 'relative', marginBottom: 8, minHeight: 36 } },
|
||
React.createElement(
|
||
'div',
|
||
{
|
||
style: {
|
||
textAlign: 'center',
|
||
fontSize: 18,
|
||
fontWeight: 700,
|
||
color: 'rgba(15,23,42,0.92)',
|
||
letterSpacing: '0.02em',
|
||
padding: '0 88px'
|
||
}
|
||
},
|
||
'车辆保险台账'
|
||
),
|
||
React.createElement(
|
||
'div',
|
||
{ style: { position: 'absolute', right: 0, top: '50%', transform: 'translateY(-50%)' } },
|
||
React.createElement(Button, { onClick: handleExport }, '导出')
|
||
)
|
||
),
|
||
React.createElement(
|
||
'div',
|
||
{
|
||
style: {
|
||
textAlign: 'center',
|
||
marginBottom: 16,
|
||
fontSize: 13,
|
||
color: 'rgba(15,23,42,0.55)',
|
||
fontWeight: 500
|
||
}
|
||
},
|
||
'结算周期:',
|
||
cycleDisplayLabel,
|
||
'\u00A0\u00A0\u00A0\u00A0客户:',
|
||
customerDisplayLabel
|
||
),
|
||
React.createElement(
|
||
'div',
|
||
{ className: 'ins-ledger-table-wrap' },
|
||
React.createElement(Table, {
|
||
className: 'ins-ledger-table',
|
||
size: 'small',
|
||
bordered: true,
|
||
rowKey: 'key',
|
||
columns: columns,
|
||
dataSource: dataSource,
|
||
pagination: false,
|
||
rowClassName: function () { return 'ins-row-data'; },
|
||
scroll: { x: 'max-content', y: 'calc(100vh - 320px)' },
|
||
sticky: true,
|
||
summary: tableSummary
|
||
})
|
||
)
|
||
)
|
||
)
|
||
);
|
||
};
|