feat(web): 同步交车任务、租赁合同、提车应收款等调整

- 更新多处页面交互与需求说明弹窗文案
- 新增 ETC 管理相关原型页面与需求说明目录内容

Made-with: Cursor
This commit is contained in:
王冕
2026-03-12 16:07:38 +08:00
parent 30e3d9f156
commit 6e149d9373
26 changed files with 3144 additions and 366 deletions

View File

@@ -14,8 +14,10 @@ const Component = function () {
var Input = antd.Input;
var Space = antd.Space;
var Popover = antd.Popover;
var Modal = antd.Modal;
var message = antd.message;
var requirementModalVisible = useState(false);
var filterContractCode = useState(undefined);
var filterProjectName = useState(undefined);
var filterCustomerName = useState(undefined);
@@ -25,6 +27,15 @@ const Component = function () {
var filterExpanded = useState(false);
var expandedRowKeysState = useState([]);
var deliveryPopoverOpen = useState(null);
var costEditsState = useState({});
var costEdits = costEditsState[0];
var setCostEdits = costEditsState[1];
var editingCostState = useState(null); // { key, field }
var editingCost = editingCostState[0];
var setEditingCost = editingCostState[1];
var editingCostValueState = useState('');
var editingCostValue = editingCostValueState[0];
var setEditingCostValue = editingCostValueState[1];
var expandedRowKeys = expandedRowKeysState[0];
var setExpandedRowKeys = expandedRowKeysState[1];
@@ -40,9 +51,9 @@ const Component = function () {
businessDept: '业务1部',
businessPerson: '张经理',
children: [
{ period: 1, billStartDate: '2025-01-01', billEndDate: '2025-01-31', deliveryCount: 3, deliveryVehicles: [{ brand: '东风', model: 'DFH1180', plateNo: '浙A12345' }, { brand: '福田', model: 'BJ1180', plateNo: '浙A23456' }, { brand: '重汽', model: 'ZZ1187', plateNo: '浙A34567' }], receivableTotal: 45800.00, actualTotal: 45500.00, discountTotal: 300.00, arrivalAmount: 45500.00, isInvoiced: '已开票', invoiceAmount: 45500.00 },
{ period: 2, billStartDate: '2025-02-01', billEndDate: '2025-02-28', deliveryCount: 2, deliveryVehicles: [{ brand: '陕汽', model: 'SX1313', plateNo: '浙A45678' }, { brand: '解放', model: 'J6P', plateNo: '浙A56789' }], receivableTotal: 45800.00, actualTotal: 45800.00, discountTotal: 0.00, arrivalAmount: 45800.00, isInvoiced: '已开票', invoiceAmount: 45800.00 },
{ period: 3, billStartDate: '2025-03-01', billEndDate: '2025-03-31', deliveryCount: 2, deliveryVehicles: [{ brand: '江淮', model: '格尔发K5', plateNo: '浙A67890' }, { brand: '东风', model: 'DFH1250', plateNo: '浙A11111' }], receivableTotal: 45800.00, actualTotal: 45000.00, discountTotal: 800.00, arrivalAmount: 42000.00, isInvoiced: '部分开票', invoiceAmount: 42000.00 }
{ period: 1, status: '已提交', billStartDate: '2025-01-01', billEndDate: '2025-01-31', deliveryCount: 3, deliveryVehicles: [{ brand: '东风', model: 'DFH1180', plateNo: '浙A12345' }, { brand: '福田', model: 'BJ1180', plateNo: '浙A23456' }, { brand: '重汽', model: 'ZZ1187', plateNo: '浙A34567' }], receivableTotal: 45800.00, actualTotal: 45500.00, discountTotal: 300.00, hydrogenCost: 1200.00, otherCost: 300.00 },
{ period: 2, status: '待提交', billStartDate: '2025-02-01', billEndDate: '2025-02-28', deliveryCount: 2, deliveryVehicles: [{ brand: '陕汽', model: 'SX1313', plateNo: '浙A45678' }, { brand: '解放', model: 'J6P', plateNo: '浙A56789' }], receivableTotal: 45800.00, actualTotal: 45800.00, discountTotal: 0.00, hydrogenCost: 900.00, otherCost: 180.00 },
{ period: 3, status: '待提交', billStartDate: '2025-03-01', billEndDate: '2025-03-31', deliveryCount: 2, deliveryVehicles: [{ brand: '江淮', model: '格尔发K5', plateNo: '浙A67890' }, { brand: '东风', model: 'DFH1250', plateNo: '浙A11111' }], receivableTotal: 45800.00, actualTotal: 45000.00, discountTotal: 800.00, hydrogenCost: 650.00, otherCost: 220.00 }
]
},
{
@@ -55,8 +66,8 @@ const Component = function () {
businessDept: '业务2部',
businessPerson: '李专员',
children: [
{ period: 1, billStartDate: '2025-02-01', billEndDate: '2025-02-28', deliveryCount: 2, deliveryVehicles: [{ brand: '江淮', model: '格尔发K5', plateNo: '沪B11111' }, { brand: '东风', model: 'DFH1250', plateNo: '沪B22222' }], receivableTotal: 33400.00, actualTotal: 33400.00, discountTotal: 0.00, arrivalAmount: 33400.00, isInvoiced: '已开票', invoiceAmount: 33400.00 },
{ period: 2, billStartDate: '2025-03-01', billEndDate: '2025-03-31', deliveryCount: 1, deliveryVehicles: [{ brand: '解放', model: 'JH6', plateNo: '沪B33333' }], receivableTotal: 33400.00, actualTotal: 33400.00, discountTotal: 0.00, arrivalAmount: 0.00, isInvoiced: '未开票', invoiceAmount: 0.00 }
{ period: 1, status: '已提交', billStartDate: '2025-02-01', billEndDate: '2025-02-28', deliveryCount: 2, deliveryVehicles: [{ brand: '江淮', model: '格尔发K5', plateNo: '沪B11111' }, { brand: '东风', model: 'DFH1250', plateNo: '沪B22222' }], receivableTotal: 33400.00, actualTotal: 33400.00, discountTotal: 0.00, hydrogenCost: 780.00, otherCost: 160.00 },
{ period: 2, status: '待提交', billStartDate: '2025-03-01', billEndDate: '2025-03-31', deliveryCount: 1, deliveryVehicles: [{ brand: '解放', model: 'JH6', plateNo: '沪B33333' }], receivableTotal: 33400.00, actualTotal: 33400.00, discountTotal: 0.00, hydrogenCost: 0.00, otherCost: 0.00 }
]
},
{
@@ -69,7 +80,7 @@ const Component = function () {
businessDept: '业务3部',
businessPerson: '王专员',
children: [
{ period: 1, billStartDate: '2025-02-10', billEndDate: '2025-03-09', deliveryCount: 1, deliveryVehicles: [{ brand: '福田', model: '欧曼EST', plateNo: '浙C33333' }], receivableTotal: 41200.00, actualTotal: 41200.00, discountTotal: 0.00, arrivalAmount: 41200.00, isInvoiced: '已开票', invoiceAmount: 41200.00 }
{ period: 1, status: '已提交', billStartDate: '2025-02-10', billEndDate: '2025-03-09', deliveryCount: 1, deliveryVehicles: [{ brand: '福田', model: '欧曼EST', plateNo: '浙C33333' }], receivableTotal: 41200.00, actualTotal: 41200.00, discountTotal: 0.00, hydrogenCost: 500.00, otherCost: 120.00 }
]
}
];
@@ -125,6 +136,92 @@ const Component = function () {
return (isNaN(n) ? '0.00' : n.toFixed(2)) + '元';
}
function pad4(n) {
var s = String(n == null ? '' : n);
return ('0000' + s).slice(-4);
}
function calcDays(startStr, endStr) {
if (!startStr || !endStr) return 0;
var start = new Date(startStr + 'T00:00:00');
var end = new Date(endStr + 'T00:00:00');
var ms = end.getTime() - start.getTime();
if (!isFinite(ms)) return 0;
var days = Math.floor(ms / 86400000) + 1;
return days < 0 ? 0 : days;
}
var modelCostPerDayMap = useMemo(function () {
return {
'DFH1180': 120,
'BJ1180': 95,
'ZZ1187': 130,
'SX1313': 110,
'J6P': 125,
'格尔发K5': 90,
'DFH1250': 140,
'JH6': 118,
'欧曼EST': 150
};
}, []);
function calcVehicleCost(record) {
var days = calcDays(record.billStartDate, record.billEndDate);
var vehicles = record.deliveryVehicles || [];
var sum = 0;
for (var i = 0; i < vehicles.length; i++) {
var m = vehicles[i] && vehicles[i].model;
var costPerDay = modelCostPerDayMap[m] || 0;
sum += costPerDay * days;
}
return sum;
}
function getCostKey(record) {
var p = record._parentRecord;
var code = (p && p.contractCode) || '';
var period = record.period != null ? record.period : '';
return code + '-ZD' + pad4(period);
}
function renderEditableCost(field, record) {
var key = getCostKey(record);
var map = costEdits[key] || {};
var v = map[field];
if (v === undefined) v = record[field];
var isEditing = editingCost && editingCost.key === key && editingCost.field === field;
if (isEditing) {
return React.createElement(Input, {
autoFocus: true,
value: editingCostValue,
onChange: function (e) { setEditingCostValue(e.target.value); },
onBlur: function () {
var raw = (editingCostValue || '').trim();
var num = raw === '' ? 0 : parseFloat(raw);
var next = isNaN(num) ? 0 : Math.round(num * 100) / 100;
setCostEdits(function (prev) {
var p = Object.assign({}, prev || {});
var row = Object.assign({}, p[key] || {});
row[field] = next;
p[key] = row;
return p;
});
setEditingCost(null);
setEditingCostValue('');
},
onPressEnter: function () { if (document && document.activeElement) document.activeElement.blur(); },
suffix: '元'
});
}
return React.createElement('span', {
style: { cursor: 'pointer' },
onClick: function () {
setEditingCost({ key: key, field: field });
setEditingCostValue((v == null || v === '') ? '' : String(Number(v).toFixed(2)));
}
}, fmtMoney(v));
}
function goView(record, parent) {
message.info('查看账单详情(原型)');
}
@@ -132,6 +229,15 @@ const Component = function () {
message.info('收费明细(原型)');
}
function renderSubActions(record, parentRecord) {
var status = record && record.status;
var isSubmitted = status === '已提交';
return React.createElement(Space, { size: 'small' },
React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goView(record, parentRecord); } }, '查看'),
isSubmitted ? null : React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goChargeDetail(record, parentRecord); } }, '收费明细')
);
}
// 子表提车数量气泡:列表显示 品牌、型号、车牌号
function renderDeliveryPopover(record) {
var vehicles = record.deliveryVehicles || [];
@@ -189,17 +295,18 @@ const Component = function () {
];
var subColumns = [
{ title: '账单编', dataIndex: 'billNo', key: 'billNo', width: 200, ellipsis: true, render: function (v, record) { var p = record._parentRecord; var code = (p && p.contractCode) || ''; var period = record.period != null ? record.period : ''; var suffix = period !== '' ? ('0000' + period).slice(-4) : ''; return code + suffix || '—'; } },
{ title: '账单编', dataIndex: 'billNo', key: 'billNo', width: 220, ellipsis: true, render: function (v, record) { var p = record._parentRecord; var code = (p && p.contractCode) || ''; var period = record.period != null ? record.period : ''; return code ? (code + 'ZD' + pad4(period)) : '—'; } },
{ title: '账单期数', dataIndex: 'period', key: 'period', width: 90, align: 'center', render: function (v) { return v != null ? v : '—'; } },
{ title: '状态', dataIndex: 'status', key: 'status', width: 90, render: function (v) { return v || '—'; } },
{ title: '账单开始日期', dataIndex: 'billStartDate', key: 'billStartDate', width: 120, render: function (v) { return v || '—'; } },
{ title: '账单结束日期', dataIndex: 'billEndDate', key: 'billEndDate', width: 120, render: function (v) { return v || '—'; } },
{ title: '提车数量', key: 'deliveryCount', width: 88, align: 'center', render: function (_, record) { return renderDeliveryPopover(record); } },
{ title: '应收款总额', dataIndex: 'receivableTotal', key: 'receivableTotal', width: 110, align: 'right', render: function (v) { return fmtMoney(v); } },
{ title: '实收款总额', dataIndex: 'actualTotal', key: 'actualTotal', width: 110, align: 'right', render: function (v) { return fmtMoney(v); } },
{ title: '减免总金额', dataIndex: 'discountTotal', key: 'discountTotal', width: 100, align: 'right', render: function (v) { return fmtMoney(v); } },
{ title: '实际到账金额', dataIndex: 'arrivalAmount', key: 'arrivalAmount', width: 118, align: 'right', render: function (v) { return fmtMoney(v); } },
{ title: '是否已开票', dataIndex: 'isInvoiced', key: 'isInvoiced', width: 96, render: function (v) { return v === '已开票' ? '已开票' : v === '部分开票' ? '部分开票' : (v === '未开票' ? '未开票' : (v || '—')); } },
{ title: '开票金额', dataIndex: 'invoiceAmount', key: 'invoiceAmount', width: 100, align: 'right', render: function (v) { return fmtMoney(v); } },
{ title: '车辆成本', key: 'vehicleCost', width: 110, align: 'right', render: function (_, record) { return fmtMoney(calcVehicleCost(record)); } },
{ title: '氢费成本', dataIndex: 'hydrogenCost', key: 'hydrogenCost', width: 110, align: 'right', render: function (_, record) { return renderEditableCost('hydrogenCost', record); } },
{ title: '其他成本', dataIndex: 'otherCost', key: 'otherCost', width: 110, align: 'right', render: function (_, record) { return renderEditableCost('otherCost', record); } },
{
title: '操作',
key: 'action',
@@ -207,10 +314,7 @@ const Component = function () {
fixed: 'right',
render: function (_, record, rowIndex, extra) {
var parentRecord = (extra && extra._parentRecord) || record._parentRecord;
return React.createElement(Space, { size: 'small' },
React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goView(record, parentRecord); } }, '查看'),
React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goChargeDetail(record, parentRecord); } }, '收费明细')
);
return renderSubActions(record, parentRecord);
}
}
];
@@ -225,7 +329,7 @@ const Component = function () {
draggable: false,
onDragStart: function (e) { e.preventDefault(); }
},
React.createElement(Table, {
React.createElement(Table, {
rowKey: function (r) { return (record.contractCode || '') + '-' + (r.period != null ? r.period : r._rowIndex); },
columns: subColumns.map(function (col) {
if (col.key !== 'action') return col;
@@ -235,10 +339,7 @@ const Component = function () {
width: col.width,
fixed: col.fixed,
render: function (val, row) {
return React.createElement(Space, { size: 'small' },
React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goView(row, record); } }, '查看'),
React.createElement(Button, { type: 'link', size: 'small', onClick: function () { goChargeDetail(row, record); } }, '收费明细')
);
return renderSubActions(row, record);
}
};
}),
@@ -246,19 +347,22 @@ const Component = function () {
pagination: false,
size: 'small',
bordered: true,
scroll: { x: 1300 }
scroll: { x: 1450 }
})
);
};
var requirementContent = '租赁账单2026年3月10日版本\n一个「数字化资产ONEOS运管平台」中的「租赁账单」模块\n#面包屑:业务管理-租赁账单;\n1.筛选:\n#支持合同编码/项目名称/客户名称/业务部门/业务负责人等筛选方式;\n1.1.合同编码:选择器,支持输入框内手动输入下拉模糊匹配对应项;\n1.2.项目名称:选择器,支持输入框内手动输入下拉模糊匹配对应项;\n1.3.客户名称:选择器,支持输入框内手动输入下拉模糊匹配对应项;\n1.4.业务部门:选择器,支持选择所有业务部门;\n1.5.业务负责人:选择器,支持选择所有业务负责人;\n1.6.交车任务编码:输入框,支持模糊搜索;\n1.7.右侧为重置、查询按钮;\n\n2.租赁账单列表:\n列表展示方式为嵌套子表格分为主表和子表\n2.1.主表:显示以下字段:合同编码、合同类型、项目名称、客户名称、合同生效日期、业务部门、业务负责人;\n 2.1.1.合同编码:显示车辆租赁合同编码;\n 2.1.2.合同类型:显示该租赁合同对应合同类型,显示该合同为试用合同还是正式合同;\n 2.1.3.项目名称:显示该租赁合同对应项目名称;\n 2.1.4.客户名称:显示该租赁合同对应客户名称;\n 2.1.5.合同生效日期显示该租赁合同生效日期格式为YYYY-MM-DD\n 2.1.6.交车任务编码:显示交车任务编码;\n 2.1.7.业务部门:显示该租赁合同对应业务部门名称;\n 2.1.8.业务负责人:显示该租赁合同对应业务负责人;\n 2.1.9.主表右下方为分页符,支持分页和选择单页显示数据条数;\n\n2.2.子表:显示以下字段:账单编号、账单期数、账单开始日期、账单结束日期、应收款总额、实收款总额、减免总金额、实际到账金额、是否已开票、开票金额、操作;\n 2.2.1.账单编号:[合同编码][账单编号]组成主要用于后期与用友YS系统打通时获取财务收款及发票相关数据\n 前缀为合同编码后缀为账单编号规则为ZD+4位编号为该合同下第x份账单例如ZD0001为该合同下第1份账单依次类推\n 例如JXZL20260216YW101235AZD0001即为JXZL20260216YW101235A合同下第1份账单\n 2.2.2.账单期数:显示该笔账单对应期数;\n 2.2.3.处理状态:已提交、待提交、已结清;\n 2.2.3.1.已提交:业务人员已通过点击收费明细,对收费项进行过维护并经过二次确认后提交;\n 2.2.3.2.待提交:业务人员未完成收费明细填报;\n 2.2.3.3.已结清:(等对接财务系统后,到账金额等于实收金额总额后算作已结清);\n 2.2.3.账单开始日期显示该笔账单开始日期格式为YYYY-MM-DD\n 2.2.4.账单结束日期显示该笔账单结束日期格式为YYYY-MM-DD\n 2.2.5.提车数量显示提车数量格式为xx辆点击弹出气泡卡片卡片内为列表显示品牌、型号、车牌号\n 2.2.6.应收款总额显示该笔账单应收款总额格式为xx.xx元计算方式「所有车辆月租金总和」+「所有车辆服务费总和」;\n 2.2.7.实收款总额显示该笔账单实收款总额格式为xx.xx元计算方式「所有车辆月租金总和」+「所有车辆服务费总和」-「减免总金额」;\n 2.2.8.减免总金额显示该笔账单减免总金额格式为xx.xx元计算方式「所有减免金额总和」\n 2.2.9.车辆成本:根据型号成本表中对应型号车辆成本,租赁订单包含车辆型号*实际账单天数计算得出;\n 2.2.10.氢费成本显示格式为xx.xx元点击变为输入框后缀为元支持2位小数失焦后保存\n 2.2.11.其他成本显示格式为xx.xx元点击变为输入框后缀为元支持2位小数失焦后保存\n\n 2.2.12.操作:查看、收费明细;\n 2.2.12.1.查看:点击跳转租赁账单-查看页;\n 2.2.12.2.收费明细:提交后收费明细将不可修改;\n\n';
return React.createElement('div', { style: layoutStyle },
React.createElement('div', { style: { marginBottom: 16 } },
React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } },
React.createElement(Breadcrumb, {
items: [
{ title: '业务管理' },
{ title: '租赁账单' }
{ title: '业务管理' },
{ title: '租赁账单' }
]
})
}),
React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { requirementModalVisible[1](true); } }, '查看需求说明')
),
React.createElement(Card, { title: '筛选', style: cardStyle },
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '16px 24px', alignItems: 'start' } },
@@ -357,6 +461,16 @@ const Component = function () {
bordered: true,
scroll: { x: 958 }
})
),
React.createElement(Modal, {
title: '需求说明',
open: requirementModalVisible[0],
onCancel: function () { requirementModalVisible[1](false); },
width: 720,
footer: React.createElement(Button, { onClick: function () { requirementModalVisible[1](false); } }, '关闭'),
bodyStyle: { maxHeight: '70vh', overflow: 'auto' }
}, React.createElement('div', { style: { padding: '8px 0' } },
React.createElement('div', { style: { whiteSpace: 'pre-wrap', fontSize: 13, lineHeight: 1.6 } }, requirementContent))
)
);
};