feat(web): 车辆租赁合同合同审批类型、法务上传盖章合同;新增数据分析页面;交车任务查看调整

- 租赁合同列表:合同审批类型筛选与列、需求说明;审批通过且未上传盖章件时法务可更多上传
- 新增/续签/转正式/查看等页同步合同审批类型及需求说明
- 新增数据分析:业务部业绩明细、物流业务月度统计、租赁客户氢费台账、租赁车辆收入明细
- 查看交车任务页面更新

Made-with: Cursor
This commit is contained in:
王冕
2026-03-25 15:50:31 +08:00
parent 826a774495
commit e1ac94bacf
13 changed files with 3654 additions and 96 deletions

View File

@@ -1,15 +1,59 @@
// 【重要】必须使用 const Component 作为组件变量名 - Axhub 产品原型
// 数字化资产ONEOS运管平台 - 查看交车任务模块(只读,布局参照新增交车任务
// 数字化资产ONEOS运管平台 - 查看交车任务模块(只读表单 + 车辆列表还车操作
const Component = function () {
var useState = React.useState;
var useCallback = React.useCallback;
const Component = function() {
var antd = window.antd;
var Input = antd.Input;
var Button = antd.Button;
var Modal = antd.Modal;
var Drawer = antd.Drawer;
var DatePicker = antd.DatePicker;
var RangePicker = DatePicker.RangePicker;
var Cascader = antd.Cascader;
var Space = antd.Space;
var Checkbox = antd.Checkbox;
var message = antd.message;
var reqModalOpen = React.useState(false);
var reqModalOpen = useState(false);
/** 交车单是否已点击「车辆到达」(到达后不可撤销还车) */
var vehicleArrivedPair = useState(false);
var vehicleArrived = vehicleArrivedPair[0];
var setVehicleArrived = vehicleArrivedPair[1];
var returnRegionOptions = [
{ value: '浙江省', label: '浙江省', children: [{ value: '杭州市', label: '杭州市' }, { value: '嘉兴市', label: '嘉兴市' }, { value: '宁波市', label: '宁波市' }] },
{ value: '上海市', label: '上海市', children: [{ value: '上海市', label: '上海市' }] },
{ value: '江苏省', label: '江苏省', children: [{ value: '南京市', label: '南京市' }, { value: '苏州市', label: '苏州市' }] },
{ value: '广东省', label: '广东省', children: [{ value: '广州市', label: '广州市' }, { value: '深圳市', label: '深圳市' }] },
{ value: '北京市', label: '北京市', children: [{ value: '北京市', label: '北京市' }] }
];
var initialVehicles = [
{ seq: 1, brand: '东风', model: '氢燃料电池重卡 H31', plateNo: '浙A10001', vin: 'LFV2BJCH8K3123456', monthRent: '12800', serviceFee: '800', deposit: '30000', remark: '首车', deliverySuccess: true, returnTaskCreated: false, returnTimeStart: null, returnTimeEnd: null, returnProvince: null, returnCity: null },
{ seq: 2, brand: '福田', model: '智蓝氢能轻卡', plateNo: '', vin: 'LZYTBACR2M1234567', monthRent: '8500', serviceFee: '500', deposit: '20000', remark: '待上牌', deliverySuccess: false, returnTaskCreated: false, returnTimeStart: null, returnTimeEnd: null, returnProvince: null, returnCity: null },
{ seq: 3, brand: '重汽', model: '豪沃氢能牵引车', plateNo: '浙F20001', vin: 'ZZ4257N386FZ12345', monthRent: '15000', serviceFee: '1000', deposit: '35000', remark: '', deliverySuccess: true, returnTaskCreated: true, returnTimeStart: '2026-04-10', returnTimeEnd: '2026-04-12', returnProvince: '浙江省', returnCity: '嘉兴市' }
];
var vehiclesPair = useState(initialVehicles);
var vehicles = vehiclesPair[0];
var setVehicles = vehiclesPair[1];
var drawerOpenPair = useState(false);
var drawerOpen = drawerOpenPair[0];
var setDrawerOpen = drawerOpenPair[1];
var drawerRowIndexPair = useState(-1);
var drawerRowIndex = drawerRowIndexPair[0];
var setDrawerRowIndex = drawerRowIndexPair[1];
var drawerRangePair = useState(null);
var drawerRange = drawerRangePair[0];
var setDrawerRange = drawerRangePair[1];
var drawerRegionPair = useState([]);
var drawerRegion = drawerRegionPair[0];
var setDrawerRegion = drawerRegionPair[1];
// Mock当前查看的交车任务详情从列表跳转带入或根据 id 拉取)
var task = {
projectName: '嘉兴某某物流氢能运输项目',
contractCode: 'JXZL20260216YW101235A',
@@ -17,21 +61,109 @@ const Component = function() {
deliveryRegion: '浙江省 / 嘉兴市',
deliveryLocation: '浙江省嘉兴市南湖区科技大道1号',
planDeliveryDisplay: '2026-03-01至2026-03-05',
billingStartDate: '2026-03-06',
vehicles: [
{ seq: 1, brand: '东风', model: '氢燃料电池重卡 H31', plateNo: '浙A10001', vin: 'LFV2BJCH8K3123456', monthRent: '12800', serviceFee: '800', deposit: '30000', remark: '首车' },
{ seq: 2, brand: '福田', model: '智蓝氢能轻卡', plateNo: '', vin: 'LZYTBACR2M1234567', monthRent: '8500', serviceFee: '500', deposit: '20000', remark: '待上牌' },
{ seq: 3, brand: '重汽', model: '豪沃氢能牵引车', plateNo: '浙F20001', vin: 'ZZ4257N386FZ12345', monthRent: '15000', serviceFee: '1000', deposit: '35000', remark: '' }
]
billingStartDate: '2026-03-06'
};
var vehicleList = task.vehicles || [];
function toYmd(d) {
if (d == null) return '';
if (typeof d === 'string') return d.slice(0, 10);
if (typeof d.format === 'function') return d.format('YYYY-MM-DD');
return '';
}
function formatReturnTimeDisplay(row) {
if (!row.returnTaskCreated || !row.returnTimeStart) return '-';
var s = row.returnTimeStart;
var e = row.returnTimeEnd || row.returnTimeStart;
if (s === e) return s;
return s + '至' + e;
}
function formatReturnAreaDisplay(row) {
if (!row.returnProvince || !row.returnCity) return '-';
return row.returnProvince + '-' + row.returnCity;
}
function returnStatusText(row) {
return row.returnTaskCreated ? '已还车' : '未还车';
}
var openReturnDrawer = useCallback(function (index) {
setDrawerRowIndex(index);
setDrawerRange(null);
setDrawerRegion([]);
setDrawerOpen(true);
}, [setDrawerOpen, setDrawerRowIndex, setDrawerRange, setDrawerRegion]);
var closeReturnDrawer = useCallback(function () {
setDrawerOpen(false);
setDrawerRowIndex(-1);
setDrawerRange(null);
setDrawerRegion([]);
}, [setDrawerOpen, setDrawerRowIndex, setDrawerRange, setDrawerRegion]);
var submitReturnDrawer = useCallback(function () {
if (drawerRowIndex < 0) return;
var dates = drawerRange;
if (!dates || !dates[0] || !dates[1]) {
message.warning('请选择还车时间');
return;
}
if (!drawerRegion || drawerRegion.length < 2) {
message.warning('请选择还车区域(省-市)');
return;
}
var startStr = toYmd(dates[0]);
var endStr = toYmd(dates[1]);
var prov = drawerRegion[0];
var city = drawerRegion[1];
setVehicles(function (prev) {
var next = prev.slice();
var r = Object.assign({}, next[drawerRowIndex]);
r.returnTaskCreated = true;
r.returnTimeStart = startStr;
r.returnTimeEnd = endStr;
r.returnProvince = prov;
r.returnCity = city;
next[drawerRowIndex] = r;
return next;
});
message.success('还车任务已创建');
closeReturnDrawer();
}, [drawerRowIndex, drawerRange, drawerRegion, setVehicles, closeReturnDrawer]);
var revokeReturn = useCallback(function (index) {
if (vehicleArrived) {
message.warning('交车单已确认车辆到达,无法撤销还车');
return;
}
Modal.confirm({
title: '确认撤销还车?',
content: '撤销后将清除已填写的还车时间与还车区域,需重新发起还车。',
okText: '确定',
cancelText: '取消',
onOk: function () {
setVehicles(function (prev) {
var next = prev.slice();
var r = Object.assign({}, next[index]);
r.returnTaskCreated = false;
r.returnTimeStart = null;
r.returnTimeEnd = null;
r.returnProvince = null;
r.returnCity = null;
next[index] = r;
return next;
});
message.success('已撤销还车');
}
});
}, [vehicleArrived, setVehicles]);
var styles = {
page: { padding: '16px 24px 48px', backgroundColor: '#f5f5f5', minHeight: '100vh', fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', fontSize: 14 },
breadcrumb: { marginBottom: 16, color: '#666' },
breadcrumbSep: { margin: '0 8px', color: '#999' },
card: { backgroundColor: '#fff', borderRadius: 8, marginBottom: 16, boxShadow: '0 1px 2px rgba(0,0,0,0.05)', overflow: 'hidden' },
card: { backgroundColor: '#fff', borderRadius: 8, marginBottom: 16, boxShadow: '0 1px 2px rgba(0,0,0,0.05)', overflow: 'visible' },
cardHeader: { padding: '16px 20px', borderBottom: '1px solid #f0f0f0' },
cardTitle: { fontSize: 16, fontWeight: 600, color: '#333' },
cardBody: { padding: '20px 24px' },
@@ -39,14 +171,35 @@ const Component = function() {
formCol: { flex: '0 0 33.33%', minWidth: 200, paddingRight: 16, marginBottom: 8 },
label: { display: 'block', marginBottom: 6, color: '#333' },
inputDisabled: { width: '100%', padding: '6px 10px', border: '1px solid #d9d9d9', borderRadius: 4, backgroundColor: '#f5f5f5', color: '#666', fontSize: 13 },
footer: { position: 'fixed', bottom: 0, left: 0, right: 0, padding: '12px 24px', backgroundColor: '#fff', borderTop: '1px solid #e8e8e8', display: 'flex', gap: 12, justifyContent: 'flex-start', zIndex: 99 },
tableWrap: { marginTop: 16, overflowX: 'auto' },
table: { width: '100%', borderCollapse: 'collapse', fontSize: 13 },
th: { padding: '10px 8px', textAlign: 'left', borderBottom: '1px solid #e8e8e8', backgroundColor: '#fafafa', fontWeight: 600 },
td: { padding: '8px', borderBottom: '1px solid #f0f0f0', verticalAlign: 'middle' }
footer: { position: 'fixed', bottom: 0, left: 0, right: 0, padding: '12px 24px', backgroundColor: '#fff', borderTop: '1px solid #e8e8e8', display: 'flex', gap: 12, justifyContent: 'flex-start', alignItems: 'center', zIndex: 99 },
tableWrap: { marginTop: 16, width: '100%', maxWidth: '100%', overflowX: 'auto', WebkitOverflowScrolling: 'touch' },
/** 随列内容撑开总宽,窄屏由外层横向滚动 */
table: { width: 'max-content', maxWidth: 'none', borderCollapse: 'collapse', fontSize: 13, tableLayout: 'auto' },
th: { padding: '10px 8px', textAlign: 'left', borderBottom: '1px solid #e8e8e8', backgroundColor: '#fafafa', fontWeight: 600, whiteSpace: 'nowrap' },
td: { padding: '8px', borderBottom: '1px solid #f0f0f0', verticalAlign: 'middle' },
/** 表体单元格内输入框:按内容宽度,避免被 100% 拉满整表 */
inputInTable: { boxSizing: 'border-box', minWidth: 72, maxWidth: 280, width: 'auto', padding: '6px 10px', border: '1px solid #d9d9d9', borderRadius: 4, backgroundColor: '#f5f5f5', color: '#666', fontSize: 13 },
thStickyRight: {
position: 'sticky',
right: 0,
zIndex: 4,
backgroundColor: '#fafafa',
boxShadow: '-6px 0 8px -4px rgba(0,0,0,0.12)',
borderLeft: '1px solid #e8e8e8'
},
tdStickyRight: {
position: 'sticky',
right: 0,
zIndex: 3,
backgroundColor: '#fff',
boxShadow: '-6px 0 8px -4px rgba(0,0,0,0.1)',
borderLeft: '1px solid #f0f0f0'
},
drawerFieldLabel: { marginBottom: 6, fontSize: 14, color: 'rgba(0,0,0,0.65)' },
protoHint: { fontSize: 12, color: '#999', marginTop: 8 }
};
var FormItemReadOnly = function(props) {
var FormItemReadOnly = function (props) {
return React.createElement('div', { style: styles.formCol },
React.createElement('label', { style: styles.label }, props.label),
React.createElement(Input, { value: props.value != null ? props.value : '', disabled: true, style: styles.inputDisabled })
@@ -76,38 +229,122 @@ const Component = function() {
React.createElement('th', { style: styles.th }, '车辆月租金'),
React.createElement('th', { style: styles.th }, '服务费'),
React.createElement('th', { style: styles.th }, '保证金'),
React.createElement('th', { style: styles.th }, '备注')
React.createElement('th', { style: styles.th }, '备注'),
React.createElement('th', { style: styles.th }, '还车时间'),
React.createElement('th', { style: styles.th }, '还车区域'),
React.createElement('th', { style: styles.th }, '还车状态'),
React.createElement('th', { style: Object.assign({}, styles.th, styles.thStickyRight, { width: 140, minWidth: 140 }) }, '操作')
)
);
var renderOperationCell = function (row, i) {
if (!row.deliverySuccess) {
return React.createElement('span', { style: { color: '#999' } }, '—');
}
if (!row.returnTaskCreated) {
return React.createElement(Button, { type: 'link', size: 'small', style: { padding: 0 }, onClick: function () { openReturnDrawer(i); } }, '还车');
}
return React.createElement(Button, {
type: 'link',
size: 'small',
style: { padding: 0 },
disabled: vehicleArrived,
onClick: function () { revokeReturn(i); }
}, '撤销还车');
};
var tableBody = React.createElement('tbody', null,
vehicleList.length === 0
? React.createElement('tr', null, React.createElement('td', { colSpan: 9, style: Object.assign({}, styles.td, { textAlign: 'center', color: '#999' }) }, '暂无车辆'))
: vehicleList.map(function(row, i) {
return React.createElement('tr', { key: i },
React.createElement('td', { style: styles.td }, row.seq != null ? row.seq : '—'),
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.brand, disabled: true, style: styles.inputDisabled })),
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.model, disabled: true, style: styles.inputDisabled })),
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.plateNo || '-', disabled: true, style: styles.inputDisabled })),
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.vin, disabled: true, style: styles.inputDisabled })),
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.monthRent + '元', disabled: true, style: styles.inputDisabled })),
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.serviceFee + '元', disabled: true, style: styles.inputDisabled })),
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.deposit + '元', disabled: true, style: styles.inputDisabled })),
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.remark || '-', disabled: true, style: styles.inputDisabled }))
);
})
vehicles.length === 0
? React.createElement('tr', null, React.createElement('td', { colSpan: 13, style: Object.assign({}, styles.td, { textAlign: 'center', color: '#999' }) }, '暂无车辆'))
: vehicles.map(function (row, i) {
return React.createElement('tr', { key: row.vin || i },
React.createElement('td', { style: styles.td }, row.seq != null ? row.seq : '—'),
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.brand, disabled: true, style: styles.inputInTable })),
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.model, disabled: true, style: styles.inputInTable })),
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.plateNo || '-', disabled: true, style: styles.inputInTable })),
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.vin, disabled: true, style: styles.inputInTable })),
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.monthRent + '元', disabled: true, style: styles.inputInTable })),
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.serviceFee + '元', disabled: true, style: styles.inputInTable })),
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.deposit + '元', disabled: true, style: styles.inputInTable })),
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: row.remark || '-', disabled: true, style: styles.inputInTable })),
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: formatReturnTimeDisplay(row), disabled: true, style: styles.inputInTable })),
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: formatReturnAreaDisplay(row), disabled: true, style: styles.inputInTable })),
React.createElement('td', { style: styles.td }, React.createElement(Input, { value: returnStatusText(row), disabled: true, style: styles.inputInTable })),
React.createElement('td', { style: Object.assign({}, styles.td, styles.tdStickyRight, { minWidth: 140 }) }, renderOperationCell(row, i))
);
})
);
var tableEl = React.createElement('div', { style: styles.tableWrap },
React.createElement('table', { style: styles.table }, tableHeader, tableBody)
);
var handleBack = function() {
var handleBack = function () {
if (window.history && window.history.back) window.history.back();
else antd.message.info('返回');
else message.info('返回');
};
var reqSpecText = '查看交车任务\n一个「数字化资产ONEOS运管平台」中的「交车任务」「查看」模块。\n\n1.面包屑:业务管理-交车任务-查看交车任务\n\n2.表单(只读):项目名称、合同编码、客户名称、交车区域、交车地点、预计交车日期、开始计费日期等,根据交车任务数据反显。\n\n3.车辆列表(只读):\n3.1.序号:与租赁合同车辆序号对应;\n3.2.品牌、型号、车牌号、车辆识别代码、车辆月租金、服务费、保证金、备注。\n\n4.底部为返回按钮。';
var drawerTitle = '还车';
if (drawerRowIndex >= 0 && vehicles[drawerRowIndex]) {
var dr = vehicles[drawerRowIndex];
drawerTitle = '还车(序号 ' + dr.seq + (dr.plateNo ? ' · ' + dr.plateNo : '') + '';
}
var returnDrawer = React.createElement(Drawer, {
title: drawerTitle,
placement: 'right',
width: 420,
open: drawerOpen,
onClose: closeReturnDrawer,
destroyOnClose: true,
footer: React.createElement('div', { style: { textAlign: 'right' } },
React.createElement(Space, null,
React.createElement(Button, { onClick: closeReturnDrawer }, '取消'),
React.createElement(Button, { type: 'primary', onClick: submitReturnDrawer }, '提交')
)
)
},
React.createElement('div', { style: { paddingBottom: 8 } },
React.createElement('div', { style: styles.drawerFieldLabel }, '还车时间'),
React.createElement(RangePicker, {
style: { width: '100%' },
format: 'YYYY-MM-DD',
placeholder: ['开始日期', '结束日期(单日请选同一天)'],
value: drawerRange,
onChange: function (dates) { setDrawerRange(dates && dates.length === 2 ? dates : null); }
}),
React.createElement('div', { style: { marginTop: 4, fontSize: 12, color: '#999' } }, '单日还车请将开始、结束选为同一天;跨天则为开始至结束时间段。')
),
React.createElement('div', { style: { marginTop: 20 } },
React.createElement('div', { style: styles.drawerFieldLabel }, '还车区域'),
React.createElement(Cascader, {
style: { width: '100%' },
options: returnRegionOptions,
value: drawerRegion && drawerRegion.length ? drawerRegion : undefined,
onChange: function (v) { setDrawerRegion(v || []); },
placeholder: '请选择省 / 市',
showSearch: true
})
)
);
var reqSpecText =
'查看交车任务\n' +
'一个「数字化资产ONEOS运管平台」中的「交车任务」「查看」模块。\n\n' +
'1.面包屑:业务管理-交车任务-查看交车任务\n\n' +
'2.表单(只读):项目名称、合同编码、客户名称、交车区域、交车地点、预计交车日期、开始计费日期等,根据交车任务数据反显。\n\n' +
'3.车辆列表:\n' +
'3.1.序号、品牌、型号、车牌号、车辆识别代码、车辆月租金、服务费、保证金、备注(只读);\n' +
'3.2.还车时间:展示操作「还车」提交时填写的还车时间;单日为 YYYY-MM-DD时间段为 YYYY-MM-DD至YYYY-MM-DD未提交还车前显示为「-」;\n' +
'3.3.还车区域:展示提交还车时选择的省-市(格式:省-市);未提交前显示为「-」;\n' +
'3.4.还车状态:已还车(已创建还车任务)/ 未还车;\n' +
'3.5.操作列:\n' +
' · 仅「交车成功」的车辆显示操作;未交车成功的行操作列为「—」;\n' +
' · 未创建还车任务时显示「还车」:点击后右侧抽屉打开,含「还车时间」(日期区间选择器,单输入框双日历,单日将起止选同一天)、「还车区域」(省-市二级级联),底部「提交」「取消」;提交校验通过后创建还车任务并关闭抽屉;\n' +
' · 已创建还车任务后隐藏「还车」,显示「撤销还车」;点击「撤销还车」须二次确认(确认弹窗说明将清除还车时间与区域);确认后撤销还车任务;交车单已点击「车辆到达」后「撤销还车」按钮置灰不可点;\n' +
'3.6.表格布局:表格总宽度随列内容撑开;容器内超出宽度时支持横向滚动;「操作」列固定在可视区域右侧(横向滚动时保持可见)。\n\n' +
'4.原型演示:列表下方提供「模拟交车单已点击车辆到达」勾选,用于联调前验证「撤销还车」置灰逻辑;正式对接后由交车单状态接口驱动,可移除该勾选。\n\n' +
'5.页面底部为「返回」按钮。';
return React.createElement('div', { style: styles.page },
React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } },
@@ -117,22 +354,26 @@ const Component = function() {
React.createElement('span', null, '交车任务'),
React.createElement('span', { style: styles.breadcrumbSep }, ' / '),
React.createElement('span', { style: { color: '#1890ff' } }, '查看交车任务')),
React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function() { reqModalOpen[1](true); } }, '查看需求说明')),
React.createElement(Button, { type: 'link', style: { padding: 0 }, onClick: function () { reqModalOpen[1](true); } }, '查看需求说明')),
React.createElement('div', { style: styles.card },
React.createElement('div', { style: styles.cardHeader }, React.createElement('span', { style: styles.cardTitle }, '交车任务')),
React.createElement('div', { style: styles.cardBody },
formRow1,
formRow2,
tableEl)),
tableEl,
React.createElement('div', { style: styles.protoHint },
React.createElement(Checkbox, { checked: vehicleArrived, onChange: function (e) { setVehicleArrived(e.target.checked); } }),
' 原型演示:模拟交车单已点击「车辆到达」(勾选后「撤销还车」置灰,对接接口后由交车单状态驱动)'))),
React.createElement('div', { style: { height: 60 } }),
React.createElement('div', { style: styles.footer },
React.createElement(Button, { onClick: handleBack }, '返回')),
returnDrawer,
React.createElement(Modal, {
title: '需求说明',
open: reqModalOpen[0],
onCancel: function() { reqModalOpen[1](false); },
onCancel: function () { reqModalOpen[1](false); },
width: 560,
footer: React.createElement(Button, { onClick: function() { reqModalOpen[1](false); } }, '关闭'),
footer: React.createElement(Button, { onClick: function () { reqModalOpen[1](false); } }, '关闭'),
bodyStyle: { maxHeight: '70vh', overflow: 'auto' }
}, React.createElement('div', { style: { padding: '8px 0', whiteSpace: 'pre-wrap', fontSize: 13, lineHeight: 1.6 } }, reqSpecText))
);