feat(web): 同步 web 端目录更新至 Gitea

包含加氢站站点信息、运维交车/故障、台账与数据分析等页面新增与改动。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
王冕
2026-06-04 19:57:30 +08:00
parent d29e2a821b
commit d432d51eed
35 changed files with 26963 additions and 1463 deletions

View File

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