运维/财务:完善交车单编辑/查看与还车应结款页面
- 交车单编辑页:布局对齐、检查单合并、照片必填与需求说明 - 新增交车单查看页:只读展示与样例数据 - 还车应结款相关页面与需求说明补齐 Made-with: Cursor
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
.DS_Store
|
||||
**/.DS_Store
|
||||
589
web端/财务管理/还车应结款-查看.jsx
Normal file
589
web端/财务管理/还车应结款-查看.jsx
Normal file
@@ -0,0 +1,589 @@
|
||||
// 【重要】必须使用 const Component 作为组件变量名
|
||||
// 财务管理 - 还车应结款 - 查看(只读)
|
||||
|
||||
const Component = function () {
|
||||
var useMemo = React.useMemo;
|
||||
|
||||
var antd = window.antd;
|
||||
var Breadcrumb = antd.Breadcrumb;
|
||||
var Card = antd.Card;
|
||||
var Table = antd.Table;
|
||||
var Button = antd.Button;
|
||||
var Input = antd.Input;
|
||||
var Tooltip = antd.Tooltip;
|
||||
var Popover = antd.Popover;
|
||||
var message = antd.message;
|
||||
|
||||
var TextArea = Input.TextArea;
|
||||
|
||||
function toFixed2(v) {
|
||||
if (v === null || v === undefined || v === '') return '';
|
||||
var n = typeof v === 'number' ? v : parseFloat(v);
|
||||
return isNaN(n) ? '' : n.toFixed(2);
|
||||
}
|
||||
|
||||
function fmtMoney(v) {
|
||||
var n = typeof v === 'number' ? v : parseFloat(v);
|
||||
if (isNaN(n)) n = 0;
|
||||
return n.toFixed(2);
|
||||
}
|
||||
|
||||
// 页面样式
|
||||
var layoutStyle = { padding: '16px 24px 72px', background: '#f5f5f5', minHeight: '100vh' };
|
||||
var cardStyle = { marginBottom: 16 };
|
||||
var footerStyle = { position: 'fixed', left: 0, right: 0, bottom: 0, background: '#fff', borderTop: '1px solid #f0f0f0', padding: '12px 24px', display: 'flex', justifyContent: 'flex-start', gap: 12, zIndex: 10 };
|
||||
|
||||
// 还车车辆明细(单车示例)
|
||||
var vehicleDetail = useMemo(function () {
|
||||
return [{
|
||||
key: 'v1',
|
||||
plateNo: '粤AGP5621',
|
||||
contractCode: 'LNZLHT20251106001',
|
||||
projectName: '嘉兴腾4.5T租赁',
|
||||
customerName: '嘉兴某某物流有限公司',
|
||||
deliveryTime: '2026-02-01',
|
||||
returnTime: '2026-02-27',
|
||||
fragileInsurance: '是',
|
||||
tireInsurance: '否',
|
||||
maintenanceInsurance: '是'
|
||||
}];
|
||||
}, []);
|
||||
|
||||
// 业务服务组明细(只读样例)
|
||||
var businessServiceRows = useMemo(function () {
|
||||
var items = ['违章处理违约金', '保险上浮', 'ETC-客户未缴费用', 'ETC卡缺损费', 'ETC设备缺损费'];
|
||||
return items.map(function (name, i) {
|
||||
return {
|
||||
key: 'bs-' + i,
|
||||
seq: i + 1,
|
||||
feeItem: name,
|
||||
amount: i === 2 ? '100.00' : '0.00',
|
||||
remark: '',
|
||||
lastUpdateTime: '2026-02-27 10:20',
|
||||
photos: [],
|
||||
attachments: []
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
var billInfo = useMemo(function () {
|
||||
return {
|
||||
receivedRent: '0.00',
|
||||
actualRent: '0.00',
|
||||
shouldRefundRent: '0.00'
|
||||
};
|
||||
}, []);
|
||||
|
||||
var energy = useMemo(function () {
|
||||
return {
|
||||
deliveryHydrogen: '85.00',
|
||||
returnHydrogen: '72.00',
|
||||
hydrogenUnitPrice: '35.00',
|
||||
hydrogenSupplement: '455.00',
|
||||
hydrogenFee: '0.00',
|
||||
electricFee: '0.00',
|
||||
prepayRefund: '0.00',
|
||||
userBalance: '1200.00'
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 运维部明细(只读样例)
|
||||
var tireTreadList = useMemo(function () {
|
||||
var unit = 25; // 元/mm
|
||||
var rows = [
|
||||
{ key: 't1', name: '左前轮', deliveryDepth: '6.80', returnDepth: '6.20' },
|
||||
{ key: 't2', name: '左后轮(内)', deliveryDepth: '6.70', returnDepth: '6.00' },
|
||||
{ key: 't3', name: '左后轮(外)', deliveryDepth: '6.60', returnDepth: '6.10' },
|
||||
{ key: 't4', name: '右前轮', deliveryDepth: '6.90', returnDepth: '6.40' },
|
||||
{ key: 't5', name: '右后轮(内)', deliveryDepth: '6.50', returnDepth: '6.00' },
|
||||
{ key: 't6', name: '右后轮(外)', deliveryDepth: '6.80', returnDepth: '6.30' },
|
||||
{ key: 't7', name: '左中轮', deliveryDepth: '6.70', returnDepth: '6.20' },
|
||||
{ key: 't8', name: '右中轮', deliveryDepth: '6.60', returnDepth: '6.10' },
|
||||
{ key: 't9', name: '左后备位轮', deliveryDepth: '6.90', returnDepth: '6.50' },
|
||||
{ key: 't10', name: '右后备位轮', deliveryDepth: '6.40', returnDepth: '6.00' },
|
||||
{ key: 'spare', name: '备胎', deliveryDepth: '7.00', returnDepth: '7.00' }
|
||||
];
|
||||
return rows.map(function (r) {
|
||||
var d = parseFloat(r.deliveryDepth) || 0;
|
||||
var rr = parseFloat(r.returnDepth) || 0;
|
||||
var diff = Math.max(0, d - rr);
|
||||
var total = diff * unit;
|
||||
return {
|
||||
key: r.key,
|
||||
name: r.name,
|
||||
deliveryDepth: d.toFixed(2),
|
||||
returnDepth: rr.toFixed(2),
|
||||
diff: diff.toFixed(2),
|
||||
unitPrice: unit.toFixed(2),
|
||||
totalAmount: total.toFixed(2)
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
var operationRows = useMemo(function () {
|
||||
var fixed = [
|
||||
{ name: '清洗费', amount: '0.00' },
|
||||
{ name: '未结算保养', amount: '372.50' },
|
||||
{ name: '未结算维修', amount: '0.00' },
|
||||
{ name: '车损费用', amount: '0.00' },
|
||||
{ name: '工具损坏丢失费用', amount: '0.00' },
|
||||
{ name: '证件丢失费用', amount: '0.00' },
|
||||
{ name: '广告损坏丢失费用', amount: '0.00' },
|
||||
{ name: '送车服务费', amount: '0.00' },
|
||||
{ name: '接车服务费', amount: '0.00' },
|
||||
{ name: '轮胎磨损费用', amount: '0.00' }
|
||||
];
|
||||
return fixed.map(function (x, i) {
|
||||
return { key: 'op-' + i, seq: i + 1, feeItem: x.name, amount: x.amount, worryFreeDiscount: '0.00', remark: '', lastUpdateTime: '2026-02-27 10:20' };
|
||||
});
|
||||
}, []);
|
||||
|
||||
// 安全组(只读示例)
|
||||
var violationList = useMemo(function () {
|
||||
return [{
|
||||
key: 'w1',
|
||||
code: 'WZ202602010001',
|
||||
plateNo: '浙F03218F',
|
||||
violationBehavior: '闯红灯',
|
||||
violationTime: '2026-02-01',
|
||||
penaltyAmount: '100.00',
|
||||
paymentStatus: '未缴费',
|
||||
score: '6',
|
||||
handleStatus: '未处理',
|
||||
violationCustomer: '上海馨想事成物流有限公司',
|
||||
violationPhoto: '',
|
||||
remark: ''
|
||||
}];
|
||||
}, []);
|
||||
var accidentList = useMemo(function () {
|
||||
return [{
|
||||
key: 'a1',
|
||||
accidentCode: 'SG202508250001',
|
||||
plateNo: '京A29256F',
|
||||
accidentTime: '2025-08-25',
|
||||
accidentPlace: '北京市大兴区某路段',
|
||||
accidentType: '撞固定物',
|
||||
customerName: '北京海龙运输有限公司',
|
||||
ourClaimAmount: '',
|
||||
theirClaimAmount: '',
|
||||
responsibility: '全责',
|
||||
accidentStatus: '未结案',
|
||||
closeTime: '',
|
||||
otherFee: '',
|
||||
remark: ''
|
||||
}];
|
||||
}, []);
|
||||
|
||||
// 合计/统计(只读)
|
||||
var businessServiceTotal = useMemo(function () {
|
||||
var sum = 0;
|
||||
(businessServiceRows || []).forEach(function (r) { sum += (parseFloat(r.amount) || 0); });
|
||||
return sum.toFixed(2);
|
||||
}, [businessServiceRows]);
|
||||
var operationTotal = useMemo(function () {
|
||||
var sum = 0;
|
||||
(operationRows || []).forEach(function (r) { sum += (parseFloat(r.amount) || 0); });
|
||||
return sum.toFixed(2);
|
||||
}, [operationRows]);
|
||||
var energyTotal = useMemo(function () {
|
||||
var a = parseFloat(energy.hydrogenSupplement) || 0;
|
||||
var b = parseFloat(energy.hydrogenFee) || 0;
|
||||
var c = parseFloat(energy.electricFee) || 0;
|
||||
return (a + b + c).toFixed(2);
|
||||
}, [energy.hydrogenSupplement, energy.hydrogenFee, energy.electricFee]);
|
||||
|
||||
var pendingSettle = useMemo(function () {
|
||||
var bs = parseFloat(businessServiceTotal) || 0;
|
||||
var rentRefund = parseFloat(billInfo.shouldRefundRent) || 0;
|
||||
var op = parseFloat(operationTotal) || 0;
|
||||
var en = (parseFloat(energy.hydrogenSupplement) || 0) + (parseFloat(energy.hydrogenFee) || 0) + (parseFloat(energy.electricFee) || 0) - (parseFloat(energy.prepayRefund) || 0);
|
||||
return (bs + rentRefund + en + op).toFixed(2);
|
||||
}, [businessServiceTotal, billInfo.shouldRefundRent, operationTotal, energy.hydrogenSupplement, energy.hydrogenFee, energy.electricFee, energy.prepayRefund]);
|
||||
|
||||
var depositAmount = '5000.00';
|
||||
var refundTotal = useMemo(function () {
|
||||
var diff = (parseFloat(depositAmount) || 0) - (parseFloat(pendingSettle) || 0);
|
||||
return diff > 0 ? diff.toFixed(2) : '0.00';
|
||||
}, [depositAmount, pendingSettle]);
|
||||
var payTotal = useMemo(function () {
|
||||
var diff = (parseFloat(depositAmount) || 0) - (parseFloat(pendingSettle) || 0);
|
||||
return diff < 0 ? Math.abs(diff).toFixed(2) : '0.00';
|
||||
}, [depositAmount, pendingSettle]);
|
||||
|
||||
function buildBreakdownContent(list) {
|
||||
var headStyle = { padding: '6px 10px', textAlign: 'left', borderBottom: '1px solid #f0f0f0', background: '#fafafa', fontWeight: 600, fontSize: 12 };
|
||||
var tdStyle = { padding: '6px 10px', borderBottom: '1px solid #f0f0f0', fontSize: 12 };
|
||||
return React.createElement('div', { style: { padding: 0, minWidth: 320 } },
|
||||
React.createElement('table', { style: { width: '100%', borderCollapse: 'collapse' } },
|
||||
React.createElement('thead', null,
|
||||
React.createElement('tr', null,
|
||||
React.createElement('th', { style: headStyle }, '费用项'),
|
||||
React.createElement('th', { style: Object.assign({}, headStyle, { textAlign: 'right' }) }, '金额(元)')
|
||||
)
|
||||
),
|
||||
React.createElement('tbody', null,
|
||||
(list || []).map(function (r) {
|
||||
return React.createElement('tr', { key: r.key },
|
||||
React.createElement('td', { style: tdStyle }, r.item),
|
||||
React.createElement('td', { style: Object.assign({}, tdStyle, { fontWeight: r.strong ? 600 : 400, textAlign: 'right' }) }, r.amount)
|
||||
);
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
var settleBreakdown = useMemo(function () {
|
||||
return [
|
||||
{ key: 'bs', item: '业务服务组费用项金额总和', amount: businessServiceTotal || '0.00' },
|
||||
{ key: 'rent', item: '业务服务组-车辆应退租金', amount: (toFixed2(billInfo.shouldRefundRent) || '0.00') },
|
||||
{ key: 'hDiff', item: '能源采购组-氢量差补缴金额', amount: (toFixed2(energy.hydrogenSupplement) || '0.00') },
|
||||
{ key: 'hFee', item: '能源采购组-氢费补缴金额', amount: (toFixed2(energy.hydrogenFee) || '0.00') },
|
||||
{ key: 'eFee', item: '能源采购组-电费补缴金额', amount: (toFixed2(energy.electricFee) || '0.00') },
|
||||
{ key: 'prepay', item: '能源采购组-预付款退费金额(减)', amount: '-' + (toFixed2(energy.prepayRefund) || '0.00') },
|
||||
{ key: 'op', item: '运维部费用项金额总额', amount: operationTotal || '0.00' },
|
||||
{ key: 'total', item: '待结算总额', amount: pendingSettle || '0.00', strong: true }
|
||||
];
|
||||
}, [businessServiceTotal, billInfo.shouldRefundRent, energy.hydrogenSupplement, energy.hydrogenFee, energy.electricFee, energy.prepayRefund, operationTotal, pendingSettle]);
|
||||
|
||||
var refundBreakdown = useMemo(function () {
|
||||
return [
|
||||
{ key: 'd', item: '保证金总额', amount: (toFixed2(depositAmount) || '0.00') },
|
||||
{ key: 's', item: '待结算总额', amount: (pendingSettle || '0.00') },
|
||||
{ key: 'r', item: '应退还总额', amount: (refundTotal || '0.00'), strong: true }
|
||||
];
|
||||
}, [depositAmount, pendingSettle, refundTotal]);
|
||||
|
||||
var payBreakdown = useMemo(function () {
|
||||
return [
|
||||
{ key: 'd', item: '保证金总额', amount: (toFixed2(depositAmount) || '0.00') },
|
||||
{ key: 's', item: '待结算总额', amount: (pendingSettle || '0.00') },
|
||||
{ key: 'p', item: '应补缴总额', amount: (payTotal || '0.00'), strong: true }
|
||||
];
|
||||
}, [depositAmount, pendingSettle, payTotal]);
|
||||
|
||||
var vehicleColumns = useMemo(function () {
|
||||
return [
|
||||
{ title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 110 },
|
||||
{ title: '合同编码', dataIndex: 'contractCode', key: 'contractCode', width: 160, ellipsis: true },
|
||||
{ title: '项目名称', dataIndex: 'projectName', key: 'projectName', width: 140, ellipsis: true },
|
||||
{ title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 140, ellipsis: true },
|
||||
{ title: '交车时间', dataIndex: 'deliveryTime', key: 'deliveryTime', width: 110 },
|
||||
{ title: '还车时间', dataIndex: 'returnTime', key: 'returnTime', width: 110 },
|
||||
{
|
||||
title: '易损保', dataIndex: 'fragileInsurance', key: 'fragileInsurance', width: 110,
|
||||
render: function (v) {
|
||||
return React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 6 } },
|
||||
(v || '-'),
|
||||
React.createElement(Tooltip, { title: '提供刹车片、灯泡、蓄电池、雨刮等易损件租期内免费更换服务(不含轮胎,只限自行到服务站更换)' },
|
||||
React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 16, height: 16, borderRadius: 999, border: '1px solid #d9d9d9', color: '#999', fontSize: 12, lineHeight: '16px', cursor: 'help', userSelect: 'none' } }, 'i')
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '轮胎保', dataIndex: 'tireInsurance', key: 'tireInsurance', width: 110,
|
||||
render: function (v) {
|
||||
return React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 6 } },
|
||||
(v || '-'),
|
||||
React.createElement(Tooltip, { title: '每个租赁年度内提供1次车辆轮胎因自然磨损产生的替换服务' },
|
||||
React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 16, height: 16, borderRadius: 999, border: '1px solid #d9d9d9', color: '#999', fontSize: 12, lineHeight: '16px', cursor: 'help', userSelect: 'none' } }, 'i')
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '养护保', dataIndex: 'maintenanceInsurance', key: 'maintenanceInsurance', width: 110,
|
||||
render: function (v) {
|
||||
return React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 6 } },
|
||||
(v || '-'),
|
||||
React.createElement(Tooltip, { title: '按厂家保养要求提供定期保养服务' },
|
||||
React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 16, height: 16, borderRadius: 999, border: '1px solid #d9d9d9', color: '#999', fontSize: 12, lineHeight: '16px', cursor: 'help', userSelect: 'none' } }, 'i')
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
|
||||
var businessServiceColumns = useMemo(function () {
|
||||
return [
|
||||
{ title: '序号', dataIndex: 'seq', key: 'seq', width: 60 },
|
||||
{ title: '费用项', dataIndex: 'feeItem', key: 'feeItem', width: 200, ellipsis: true },
|
||||
{ title: '金额', dataIndex: 'amount', key: 'amount', width: 140, render: function (v) { return React.createElement(Input, { value: v, addonAfter: '元', disabled: true }); } },
|
||||
{ title: '备注', dataIndex: 'remark', key: 'remark', render: function (v) { return React.createElement(TextArea, { value: v, rows: 1, disabled: true }); } },
|
||||
{ title: '最后更新时间', dataIndex: 'lastUpdateTime', key: 'lastUpdateTime', width: 150, render: function (v) { return React.createElement('span', { style: { fontSize: 12, color: '#666' } }, v || '-'); } },
|
||||
{ title: '照片', key: 'photo', width: 100, render: function () { return React.createElement(Button, { size: 'small', disabled: true }, '上传'); } },
|
||||
{ title: '附件', key: 'attachment', width: 110, render: function () { return React.createElement(Button, { size: 'small', disabled: true }, '上传附件'); } }
|
||||
];
|
||||
}, []);
|
||||
|
||||
var operationColumns = useMemo(function () {
|
||||
return [
|
||||
{ title: '序号', dataIndex: 'seq', key: 'seq', width: 60 },
|
||||
{
|
||||
title: '费用项', dataIndex: 'feeItem', key: 'feeItem', width: 200,
|
||||
render: function (v) {
|
||||
if (v === '轮胎磨损费用') {
|
||||
var popContent = React.createElement('div', { style: { width: 760, maxWidth: '80vw' } },
|
||||
React.createElement('div', { style: { fontWeight: 600, marginBottom: 8 } }, '轮胎磨损费用明细'),
|
||||
React.createElement(Table, {
|
||||
rowKey: 'key',
|
||||
size: 'small',
|
||||
bordered: true,
|
||||
pagination: false,
|
||||
dataSource: tireTreadList,
|
||||
columns: [
|
||||
{ title: '轮胎名称', dataIndex: 'name', key: 'name', width: 120, ellipsis: true },
|
||||
{ title: '交车胎纹深度', dataIndex: 'deliveryDepth', key: 'deliveryDepth', width: 110, render: function (vv) { return (vv || '0.00') + 'mm'; } },
|
||||
{ title: '还车胎纹深度', dataIndex: 'returnDepth', key: 'returnDepth', width: 110, render: function (vv) { return (vv || '0.00') + 'mm'; } },
|
||||
{ title: '胎纹差', dataIndex: 'diff', key: 'diff', width: 80, render: function (vv) { return (vv || '0.00') + 'mm'; } },
|
||||
{ title: '单价', dataIndex: 'unitPrice', key: 'unitPrice', width: 90, render: function (vv) { return (vv || '0.00') + '元/mm'; } },
|
||||
{ title: '总金额', dataIndex: 'totalAmount', key: 'totalAmount', width: 90, render: function (vv) { return (vv || '0.00') + '元'; } }
|
||||
]
|
||||
})
|
||||
);
|
||||
return React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 6 } },
|
||||
(v || '-'),
|
||||
React.createElement(Popover, { content: popContent, trigger: 'hover', placement: 'topLeft' },
|
||||
React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 16, height: 16, borderRadius: 999, border: '1px solid #d9d9d9', color: '#999', fontSize: 12, lineHeight: '16px', cursor: 'help', userSelect: 'none' } }, 'i')
|
||||
)
|
||||
);
|
||||
}
|
||||
return v || '-';
|
||||
}
|
||||
},
|
||||
{ title: '金额', dataIndex: 'amount', key: 'amount', width: 140, render: function (v) { return React.createElement(Input, { value: v, addonAfter: '元', disabled: true }); } },
|
||||
{
|
||||
title: React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 6 } },
|
||||
'无忧包减免',
|
||||
React.createElement(Tooltip, { title: '无忧包减免不会列入运维成本,而是计入业务成本' },
|
||||
React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 16, height: 16, borderRadius: 999, border: '1px solid #d9d9d9', color: '#999', fontSize: 12, lineHeight: '16px', cursor: 'help', userSelect: 'none' } }, 'i')
|
||||
)
|
||||
),
|
||||
dataIndex: 'worryFreeDiscount',
|
||||
key: 'worryFreeDiscount',
|
||||
width: 140,
|
||||
render: function (v) { return React.createElement(Input, { value: v, addonAfter: '元', disabled: true }); }
|
||||
},
|
||||
{ title: '备注', dataIndex: 'remark', key: 'remark', render: function (v) { return React.createElement(TextArea, { value: v, rows: 1, disabled: true }); } },
|
||||
{ title: '最后更新时间', dataIndex: 'lastUpdateTime', key: 'lastUpdateTime', width: 150, render: function (v) { return React.createElement('span', { style: { fontSize: 12, color: '#666' } }, v || '-'); } },
|
||||
{ title: '照片', key: 'photo', width: 100, render: function () { return React.createElement(Button, { size: 'small', disabled: true }, '上传'); } },
|
||||
{ title: '附件', key: 'attachment', width: 110, render: function () { return React.createElement(Button, { size: 'small', disabled: true }, '上传附件'); } }
|
||||
];
|
||||
}, [tireTreadList]);
|
||||
|
||||
function goBack() {
|
||||
message.info('返回还车应结款列表页(原型)');
|
||||
try { if (window.history && window.history.length > 1) window.history.back(); } catch (e) {}
|
||||
}
|
||||
|
||||
return React.createElement('div', { style: layoutStyle },
|
||||
React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } },
|
||||
React.createElement(Breadcrumb, { items: [{ title: '财务管理' }, { title: '还车应结款' }, { title: '查看' }] })
|
||||
),
|
||||
|
||||
React.createElement(Card, { title: '还车车辆明细', style: cardStyle },
|
||||
React.createElement(Table, { rowKey: 'key', columns: vehicleColumns, dataSource: vehicleDetail, pagination: false, bordered: true, size: 'middle', scroll: { x: 980 } })
|
||||
),
|
||||
|
||||
React.createElement(Card, { title: '还车费用明细', style: cardStyle },
|
||||
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 16, marginBottom: 16 } },
|
||||
React.createElement('div', { style: { background: '#fff', border: '1px solid #f0f0f0', borderRadius: 8, padding: '18px 16px', boxShadow: '0 1px 2px rgba(0,0,0,0.04)' } },
|
||||
React.createElement('div', { style: { fontSize: 13, color: '#666', marginBottom: 12 } }, '保证金总额'),
|
||||
React.createElement('div', { style: { fontSize: 22, fontWeight: 600, color: '#333' } }, (depositAmount || '0.00'), React.createElement('span', { style: { fontSize: 14, fontWeight: 500, marginLeft: 4 } }, '元'))
|
||||
),
|
||||
React.createElement('div', { style: { background: '#fff', border: '1px solid #f0f0f0', borderRadius: 8, padding: '18px 16px', boxShadow: '0 1px 2px rgba(0,0,0,0.04)' } },
|
||||
React.createElement('div', { style: { fontSize: 13, color: '#666', marginBottom: 12 } }, '待结算总额'),
|
||||
React.createElement(Popover, { trigger: 'click', placement: 'bottomLeft', content: buildBreakdownContent(settleBreakdown) },
|
||||
React.createElement('div', { style: { fontSize: 22, fontWeight: 600, color: '#fa8c16', cursor: 'pointer' } },
|
||||
(pendingSettle || '0.00'),
|
||||
React.createElement('span', { style: { fontSize: 14, fontWeight: 500, marginLeft: 4, color: '#333' } }, '元')
|
||||
)
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: { background: '#fff', border: '1px solid #f0f0f0', borderRadius: 8, padding: '18px 16px', boxShadow: '0 1px 2px rgba(0,0,0,0.04)' } },
|
||||
React.createElement('div', { style: { fontSize: 13, color: '#666', marginBottom: 12 } }, '应退还总额'),
|
||||
React.createElement(Popover, { trigger: 'click', placement: 'bottomLeft', content: buildBreakdownContent(refundBreakdown) },
|
||||
React.createElement('div', { style: { fontSize: 22, fontWeight: 600, color: '#52c41a', cursor: 'pointer' } },
|
||||
(refundTotal || '0.00'),
|
||||
React.createElement('span', { style: { fontSize: 14, fontWeight: 500, marginLeft: 4, color: '#333' } }, '元')
|
||||
)
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: { background: '#fff', border: '1px solid #f0f0f0', borderRadius: 8, padding: '18px 16px', boxShadow: '0 1px 2px rgba(0,0,0,0.04)' } },
|
||||
React.createElement('div', { style: { fontSize: 13, color: '#666', marginBottom: 12 } }, '应补缴总额'),
|
||||
React.createElement(Popover, { trigger: 'click', placement: 'bottomLeft', content: buildBreakdownContent(payBreakdown) },
|
||||
React.createElement('div', { style: { fontSize: 22, fontWeight: 600, color: '#f5222d', cursor: 'pointer' } },
|
||||
(payTotal || '0.00'),
|
||||
React.createElement('span', { style: { fontSize: 14, fontWeight: 500, marginLeft: 4, color: '#333' } }, '元')
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement('div', { style: { background: '#fff', border: '1px solid #f0f0f0', borderRadius: 8, marginBottom: 12 } },
|
||||
React.createElement('div', { style: { padding: '12px 16px', display: 'flex', alignItems: 'center', gap: 12 } },
|
||||
React.createElement('div', { style: { fontWeight: 600, fontSize: 14 } }, '业务服务组'),
|
||||
React.createElement('div', { style: { flex: 1 } }),
|
||||
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 12, color: '#666', fontSize: 13 } },
|
||||
React.createElement('span', null, '总金额:', businessServiceTotal, ' 元'),
|
||||
React.createElement('span', null, '提交人:业务服务组-张三'),
|
||||
React.createElement('span', null, '状态:已提交')
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: { padding: '12px 16px', borderTop: '1px solid #f0f0f0' } },
|
||||
React.createElement(Table, { rowKey: 'key', columns: businessServiceColumns, dataSource: businessServiceRows, pagination: false, bordered: true, size: 'small' }),
|
||||
React.createElement('div', { style: { marginTop: 12, paddingTop: 12, borderTop: '1px dashed #f0f0f0' } },
|
||||
React.createElement('div', { style: { fontWeight: 600, marginBottom: 10 } }, '车辆租金'),
|
||||
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', gap: 12 } },
|
||||
React.createElement('div', null,
|
||||
React.createElement('div', { style: { fontSize: 12, color: '#666', marginBottom: 6 } }, '本期账单已收租金'),
|
||||
React.createElement(Input, { value: billInfo.receivedRent, addonAfter: '元', disabled: true })
|
||||
),
|
||||
React.createElement('div', null,
|
||||
React.createElement('div', { style: { fontSize: 12, color: '#666', marginBottom: 6 } }, '车辆实际租金'),
|
||||
React.createElement(Input, { value: billInfo.actualRent, addonAfter: '元', disabled: true })
|
||||
),
|
||||
React.createElement('div', null,
|
||||
React.createElement('div', { style: { fontSize: 12, color: '#666', marginBottom: 6 } }, '车辆应退租金'),
|
||||
React.createElement(Input, { value: billInfo.shouldRefundRent, addonAfter: '元', disabled: true })
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement('div', { style: { background: '#fff', border: '1px solid #f0f0f0', borderRadius: 8, marginBottom: 12 } },
|
||||
React.createElement('div', { style: { padding: '12px 16px', display: 'flex', alignItems: 'center', gap: 12 } },
|
||||
React.createElement('div', { style: { fontWeight: 600, fontSize: 14 } }, '能源采购组'),
|
||||
React.createElement('div', { style: { flex: 1 } }),
|
||||
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 12, color: '#666', fontSize: 13 } },
|
||||
React.createElement('span', null, '总金额:', energyTotal, ' 元'),
|
||||
React.createElement('span', null, '提交人:能源采购组-李四'),
|
||||
React.createElement('span', null, '状态:已提交')
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: { padding: '12px 16px', borderTop: '1px solid #f0f0f0' } },
|
||||
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(4, minmax(0, 1fr))', gap: 12, marginBottom: 16 } },
|
||||
React.createElement('div', null,
|
||||
React.createElement('div', { style: { marginBottom: 8, fontWeight: 500 } }, '氢量差补缴金额'),
|
||||
React.createElement(Input, { value: energy.hydrogenSupplement, addonAfter: '元', disabled: true })
|
||||
),
|
||||
React.createElement('div', null,
|
||||
React.createElement('div', { style: { marginBottom: 8, fontWeight: 500 } }, '交车氢量'),
|
||||
React.createElement(Input, { value: (toFixed2(energy.deliveryHydrogen) || '0.00'), addonAfter: 'MPa', disabled: true })
|
||||
),
|
||||
React.createElement('div', null,
|
||||
React.createElement('div', { style: { marginBottom: 8, fontWeight: 500 } }, '还车氢量'),
|
||||
React.createElement(Input, { value: (toFixed2(energy.returnHydrogen) || '0.00'), addonAfter: 'MPa', disabled: true })
|
||||
),
|
||||
React.createElement('div', null,
|
||||
React.createElement('div', { style: { marginBottom: 8, fontWeight: 500 } }, '退还车氢气单价'),
|
||||
React.createElement(Input, { value: (toFixed2(energy.hydrogenUnitPrice) || '0.00'), addonAfter: '元', disabled: true })
|
||||
)
|
||||
),
|
||||
React.createElement('div', null,
|
||||
React.createElement('div', { style: { marginBottom: 8, fontWeight: 500 } }, '能源费补缴金额'),
|
||||
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(4, minmax(0, 1fr))', gap: 12, marginBottom: 12 } },
|
||||
React.createElement('div', null, React.createElement(Input, { value: energy.hydrogenFee, placeholder: '氢费补缴金额', addonAfter: '元', style: { width: '100%' }, disabled: true })),
|
||||
React.createElement('div', null, React.createElement(Input, { value: energy.electricFee, placeholder: '电费补缴金额', addonAfter: '元', style: { width: '100%' }, disabled: true })),
|
||||
React.createElement('div', null),
|
||||
React.createElement('div', null)
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: { gridColumn: '1 / -1', borderTop: '1px solid #f0f0f0', paddingTop: 12, marginTop: 4 } },
|
||||
React.createElement('div', { style: { marginBottom: 8, fontWeight: 500 } }, '预付款退费金额'),
|
||||
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(4, minmax(0, 1fr))', gap: 12 } },
|
||||
React.createElement('div', null,
|
||||
React.createElement(Input, { value: energy.prepayRefund, placeholder: '0.00', addonAfter: '元', style: { width: '100%' }, disabled: true })
|
||||
),
|
||||
React.createElement('div', null),
|
||||
React.createElement('div', null),
|
||||
React.createElement('div', null)
|
||||
),
|
||||
React.createElement('div', { style: { marginTop: 8, fontSize: 12, color: '#666' } }, '项目预充值余额:', energy.userBalance, ' 元')
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement('div', { style: { background: '#fff', border: '1px solid #f0f0f0', borderRadius: 8, marginBottom: 12 } },
|
||||
React.createElement('div', { style: { padding: '12px 16px', display: 'flex', alignItems: 'center', gap: 12 } },
|
||||
React.createElement('div', { style: { fontWeight: 600, fontSize: 14 } }, '运维部'),
|
||||
React.createElement('div', { style: { flex: 1 } }),
|
||||
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 12, color: '#666', fontSize: 13 } },
|
||||
React.createElement('span', null, '总金额:', operationTotal, ' 元'),
|
||||
React.createElement('span', null, '提交人:运维部-王五'),
|
||||
React.createElement('span', null, '状态:已提交')
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: { padding: '12px 16px', borderTop: '1px solid #f0f0f0' } },
|
||||
React.createElement(Table, { rowKey: 'key', columns: operationColumns, dataSource: operationRows, pagination: false, bordered: true, size: 'small' })
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement('div', { style: { background: '#fff', border: '1px solid #f0f0f0', borderRadius: 8, marginBottom: 12 } },
|
||||
React.createElement('div', { style: { padding: '12px 16px', display: 'flex', alignItems: 'center', gap: 12 } },
|
||||
React.createElement('div', { style: { fontWeight: 600, fontSize: 14 } }, '安全组'),
|
||||
React.createElement('div', { style: { flex: 1 } }),
|
||||
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 12, color: '#666', fontSize: 13 } },
|
||||
React.createElement('span', null, '提交人:安全组-赵六'),
|
||||
React.createElement('span', null, '状态:已提交')
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: { padding: '12px 16px', borderTop: '1px solid #f0f0f0' } },
|
||||
React.createElement('div', { style: { marginBottom: 12, fontWeight: 600 } }, '违章清单'),
|
||||
React.createElement(Table, {
|
||||
rowKey: 'key',
|
||||
size: 'small',
|
||||
bordered: true,
|
||||
pagination: false,
|
||||
dataSource: violationList,
|
||||
columns: [
|
||||
{ title: '违章编码', dataIndex: 'code', key: 'code', width: 130, ellipsis: true },
|
||||
{ title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 110 },
|
||||
{ title: '违法行为', dataIndex: 'violationBehavior', key: 'violationBehavior', width: 110, ellipsis: true },
|
||||
{ title: '违法时间', dataIndex: 'violationTime', key: 'violationTime', width: 110 },
|
||||
{ title: '罚款金额', dataIndex: 'penaltyAmount', key: 'penaltyAmount', width: 90, render: function (v) { return (v || '0.00'); } },
|
||||
{ title: '缴费状态', dataIndex: 'paymentStatus', key: 'paymentStatus', width: 90 },
|
||||
{ title: '计分值', dataIndex: 'score', key: 'score', width: 70 },
|
||||
{ title: '是否处理', dataIndex: 'handleStatus', key: 'handleStatus', width: 90 },
|
||||
{ title: '违章客户', dataIndex: 'violationCustomer', key: 'violationCustomer', width: 160, ellipsis: true },
|
||||
{ title: '违章照片', dataIndex: 'violationPhoto', key: 'violationPhoto', width: 90, render: function (v) { return v ? v : ''; } },
|
||||
{ title: '备注', dataIndex: 'remark', key: 'remark', width: 120, ellipsis: true }
|
||||
]
|
||||
}),
|
||||
React.createElement('div', { style: { margin: '16px 0 12px', fontWeight: 600 } }, '事故清单'),
|
||||
React.createElement(Table, {
|
||||
rowKey: 'key',
|
||||
size: 'small',
|
||||
bordered: true,
|
||||
pagination: false,
|
||||
dataSource: accidentList,
|
||||
locale: { emptyText: '暂无事故记录' },
|
||||
columns: [
|
||||
{ title: '事故编码', dataIndex: 'accidentCode', key: 'accidentCode', width: 130, ellipsis: true },
|
||||
{ title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 110 },
|
||||
{ title: '事故时间', dataIndex: 'accidentTime', key: 'accidentTime', width: 110 },
|
||||
{ title: '事故地点', dataIndex: 'accidentPlace', key: 'accidentPlace', width: 140, ellipsis: true },
|
||||
{ title: '事故类型', dataIndex: 'accidentType', key: 'accidentType', width: 110, ellipsis: true },
|
||||
{ title: '客户名称', dataIndex: 'customerName', key: 'customerName', width: 140, ellipsis: true },
|
||||
{ title: '我方定损金额', dataIndex: 'ourClaimAmount', key: 'ourClaimAmount', width: 110, render: function (v) { return v || ''; } },
|
||||
{ title: '对方定损金额', dataIndex: 'theirClaimAmount', key: 'theirClaimAmount', width: 110, render: function (v) { return v || ''; } },
|
||||
{ title: '责任划分', dataIndex: 'responsibility', key: 'responsibility', width: 90 },
|
||||
{ title: '事故状态', dataIndex: 'accidentStatus', key: 'accidentStatus', width: 90 },
|
||||
{ title: '结案时间', dataIndex: 'closeTime', key: 'closeTime', width: 110 },
|
||||
{ title: '其他费用', dataIndex: 'otherFee', key: 'otherFee', width: 90, render: function (v) { return v || ''; } },
|
||||
{ title: '备注', dataIndex: 'remark', key: 'remark', width: 120, ellipsis: true }
|
||||
]
|
||||
})
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement('div', { style: footerStyle },
|
||||
React.createElement(Button, { onClick: goBack }, '返回')
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
1334
web端/财务管理/还车应结款-费用明细.jsx
Normal file
1334
web端/财务管理/还车应结款-费用明细.jsx
Normal file
File diff suppressed because it is too large
Load Diff
2054
web端/财务管理/还车应结款.jsx
Normal file
2054
web端/财务管理/还车应结款.jsx
Normal file
File diff suppressed because it is too large
Load Diff
1352
web端/车辆管理.jsx
1352
web端/车辆管理.jsx
File diff suppressed because one or more lines are too long
494
web端/运维管理/车辆业务/交车管理-交车单-查看.jsx
Normal file
494
web端/运维管理/车辆业务/交车管理-交车单-查看.jsx
Normal file
@@ -0,0 +1,494 @@
|
||||
// 【重要】必须使用 const Component 作为组件变量名
|
||||
// 运维管理 - 车辆业务 - 交车管理 - 交车单 - 查看(只读)
|
||||
|
||||
const Component = function () {
|
||||
var useState = React.useState;
|
||||
var useMemo = React.useMemo;
|
||||
var useRef = React.useRef;
|
||||
|
||||
var antd = window.antd;
|
||||
var Breadcrumb = antd.Breadcrumb;
|
||||
var Card = antd.Card;
|
||||
var Table = antd.Table;
|
||||
var Button = antd.Button;
|
||||
var Input = antd.Input;
|
||||
var Select = antd.Select;
|
||||
var Switch = antd.Switch;
|
||||
var Modal = antd.Modal;
|
||||
var Drawer = antd.Drawer;
|
||||
var message = antd.message;
|
||||
|
||||
// ---------- utils ----------
|
||||
function RequiredLabel(text) {
|
||||
return React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 4 } },
|
||||
React.createElement('span', { style: { color: '#f5222d', fontWeight: 600 } }, '*'),
|
||||
React.createElement('span', null, text)
|
||||
);
|
||||
}
|
||||
|
||||
function toFixed2(v) {
|
||||
if (v === null || v === undefined || v === '') return '';
|
||||
var n = typeof v === 'number' ? v : parseFloat(v);
|
||||
return isNaN(n) ? '' : n.toFixed(2);
|
||||
}
|
||||
|
||||
function isEmpty(v) {
|
||||
return v === null || v === undefined || String(v).trim() === '';
|
||||
}
|
||||
|
||||
function filterOption(input, option) {
|
||||
var label = (option && (option.label || option.children)) || '';
|
||||
return String(label).toLowerCase().indexOf(String(input || '').toLowerCase()) >= 0;
|
||||
}
|
||||
|
||||
function makeThumb(url, onPreview) {
|
||||
return React.createElement('div', {
|
||||
style: {
|
||||
width: 64,
|
||||
height: 64,
|
||||
borderRadius: 4,
|
||||
border: '1px solid #f0f0f0',
|
||||
background: '#fafafa',
|
||||
position: 'relative',
|
||||
overflow: 'hidden'
|
||||
}
|
||||
},
|
||||
React.createElement('img', {
|
||||
src: url,
|
||||
style: { width: '100%', height: '100%', objectFit: 'cover', cursor: 'pointer' },
|
||||
onClick: onPreview
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function ReadonlyUploadBox(props) {
|
||||
var label = props.label;
|
||||
var value = props.value; // array of {name,url}
|
||||
var tip = props.tip;
|
||||
var onPreview = props.onPreview;
|
||||
|
||||
return React.createElement('div', null,
|
||||
React.createElement('div', { style: { fontSize: 12, color: '#666', marginBottom: 6 } }, label),
|
||||
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' } },
|
||||
(value || []).length > 0
|
||||
? (value || []).map(function (f) {
|
||||
return React.createElement('div', { key: f.uid },
|
||||
makeThumb(f.url, function () { onPreview && onPreview(f); })
|
||||
);
|
||||
})
|
||||
: React.createElement('div', { style: { fontSize: 12, color: '#999' } }, '暂无')
|
||||
),
|
||||
tip ? React.createElement('div', { style: { marginTop: 6, fontSize: 12, color: '#999' } }, tip) : null
|
||||
);
|
||||
}
|
||||
|
||||
// ---------- styles ----------
|
||||
var layoutStyle = { padding: '16px 24px 88px', background: '#f5f5f5', minHeight: '100vh' };
|
||||
var cardStyle = { marginBottom: 16 };
|
||||
var footerStyle = { position: 'fixed', left: 0, right: 0, bottom: 0, background: '#fff', borderTop: '1px solid #f0f0f0', padding: '12px 24px', display: 'flex', justifyContent: 'flex-start', gap: 12, zIndex: 10 };
|
||||
|
||||
var styles = {
|
||||
formRow: { display: 'flex', gap: 16, marginBottom: 12, alignItems: 'flex-start' },
|
||||
formItem: { flex: 1, minWidth: 0 },
|
||||
label: { fontSize: 12, color: '#666', marginBottom: 6 }
|
||||
};
|
||||
|
||||
function FormItem(props) {
|
||||
var label = props.label;
|
||||
var fullWidth = !!props.fullWidth;
|
||||
return React.createElement('div', { style: fullWidth ? Object.assign({}, styles.formItem, { flex: '0 0 100%' }) : styles.formItem },
|
||||
React.createElement('div', { style: styles.label }, label),
|
||||
props.children
|
||||
);
|
||||
}
|
||||
|
||||
// ---------- mock data (只读样例) ----------
|
||||
var reserveVehicles = useMemo(function () {
|
||||
return [
|
||||
{ plateNo: '京A12345', vehicleType: '牵引车', brand: '东风', model: 'DFH1180', vin: 'LGHXCAE28M1234567', hasAd: true, hasTailboard: true },
|
||||
{ plateNo: '京C11111', vehicleType: '厢式货车', brand: '福田', model: 'BJ1180', vin: 'LGHXCAE28M7654321', hasAd: false, hasTailboard: false }
|
||||
];
|
||||
}, []);
|
||||
|
||||
var plateOptions = useMemo(function () {
|
||||
return reserveVehicles.map(function (v) { return { value: v.plateNo, label: v.plateNo }; });
|
||||
}, [reserveVehicles]);
|
||||
|
||||
var initialForm = useMemo(function () {
|
||||
return {
|
||||
plateNo: '京A12345',
|
||||
vehicleType: '牵引车',
|
||||
brand: '东风',
|
||||
model: 'DFH1180',
|
||||
vin: 'LGHXCAE28M1234567',
|
||||
|
||||
hasAd: true,
|
||||
adPhoto: [{ uid: 'ad1', name: '广告照片.jpg', url: 'https://dummyimage.com/640x360/eee/666&text=Ad' }],
|
||||
bigWordPhoto: [{ uid: 'bw1', name: '放大字照片.jpg', url: 'https://dummyimage.com/640x360/eee/666&text=BigWord' }],
|
||||
|
||||
hasTailboard: true,
|
||||
|
||||
spareTirePhoto: [{ uid: 'sp1', name: '备胎照片.jpg', url: 'https://dummyimage.com/640x360/eee/666&text=SpareTire' }],
|
||||
spareTireDepth: '6.50',
|
||||
|
||||
trainingRecognized: true,
|
||||
driverLicenses: [
|
||||
{ uid: 'id1', name: '身份证正面.jpg', url: 'https://dummyimage.com/600x400/eee/666&text=ID-Front' },
|
||||
{ uid: 'id2', name: '身份证反面.jpg', url: 'https://dummyimage.com/600x400/eee/666&text=ID-Back' },
|
||||
{ uid: 'dl', name: '驾驶证.jpg', url: 'https://dummyimage.com/600x400/eee/666&text=DriverLicense' },
|
||||
{ uid: 'qc', name: '从业资格证.jpg', url: 'https://dummyimage.com/600x400/eee/666&text=Qualification' }
|
||||
],
|
||||
|
||||
mileageKm: '120',
|
||||
batteryKwh: '86',
|
||||
hydrogenAmount: '35.60',
|
||||
serviceFee: '200.00'
|
||||
};
|
||||
}, []);
|
||||
|
||||
var photos = useMemo(function () {
|
||||
function one(uid, name) {
|
||||
return [{ uid: uid, name: name + '.jpg', url: 'https://dummyimage.com/640x360/eee/666&text=' + encodeURIComponent(name) }];
|
||||
}
|
||||
return {
|
||||
vehicle: {
|
||||
'仪表盘': one('v1', '车辆-仪表盘'),
|
||||
'车辆正面': one('v2', '车辆-正面'),
|
||||
'车辆左前方': one('v3', '车辆-左前方'),
|
||||
'车辆左后方': one('v4', '车辆-左后方'),
|
||||
'车辆右后方': one('v5', '车辆-右后方'),
|
||||
'车辆右前方': one('v6', '车辆-右前方')
|
||||
},
|
||||
chassis: {
|
||||
'正前方底部': one('c1', '底盘-正前方'),
|
||||
'左侧前方底部': one('c2', '底盘-左前'),
|
||||
'左侧后方底部': one('c3', '底盘-左后'),
|
||||
'正后方底部': one('c4', '底盘-正后方'),
|
||||
'右侧后方底部': one('c5', '底盘-右后'),
|
||||
'右侧前方底部': one('c6', '底盘-右前')
|
||||
},
|
||||
tire: {
|
||||
'左前轮': one('t1', '轮胎-左前'),
|
||||
'左后轮(内)': one('t2', '轮胎-左后内'),
|
||||
'左后轮(外)': one('t3', '轮胎-左后外'),
|
||||
'右前轮': one('t4', '轮胎-右前'),
|
||||
'右后轮(内)': one('t5', '轮胎-右后内'),
|
||||
'右后轮(外)': one('t6', '轮胎-右后外'),
|
||||
'备胎': one('t7', '轮胎-备胎')
|
||||
},
|
||||
defect: [{ uid: 'd1', name: '瑕疵1.jpg', url: 'https://dummyimage.com/640x360/eee/666&text=Defect-1' }],
|
||||
other: [{ uid: 'o1', name: '其他1.jpg', url: 'https://dummyimage.com/640x360/eee/666&text=Other-1' }]
|
||||
};
|
||||
}, []);
|
||||
|
||||
// ---------- inspection checklist (只读样例) ----------
|
||||
var inspectionCategoryItems = useMemo(function () {
|
||||
return {
|
||||
'车灯': ['大灯', '转向灯', '小灯', '示廓灯', '刹车灯', '倒车灯', '牌照灯', '防雾灯', '室内灯'],
|
||||
'仪表盘': ['氢系统指示', '电控系统指示', '数值清晰准确', '故障报警灯'],
|
||||
'驾驶室': ['点烟器', '车窗升降', '按键开关', '雨刮器', '内后视镜是否正常', '内/外门把手', '安全带', '空调冷暖风', '仪表盘', '门锁功能', '手刹', '车钥匙功能是否正常', '喇叭', '音响功能', '遮阳板', '主副驾座椅', '方向盘', '内饰干净整洁'],
|
||||
'轮胎': ['前左胎', '前右胎', '后左胎', '后右胎', '备胎'],
|
||||
'液位检查': ['冷却液', '制动液', '玻璃水'],
|
||||
'外观检查': ['车身外观', '漆面', '玻璃'],
|
||||
'车辆外观': ['整车外观'],
|
||||
'其他': ['其他检查项'],
|
||||
'随车工具': ['三角牌', '灭火器', '反光背心'],
|
||||
'随车证件': ['行驶证', '营运证', '保险单'],
|
||||
'整车': ['整车状态'],
|
||||
'燃料电池系统': ['氢系统', '储氢瓶'],
|
||||
'冷机': ['冷机运行'],
|
||||
'制动系统': ['制动踏板', '驻车制动']
|
||||
};
|
||||
}, []);
|
||||
|
||||
var inspectionList = useMemo(function () {
|
||||
var list = [];
|
||||
var cats = Object.keys(inspectionCategoryItems);
|
||||
for (var i = 0; i < cats.length; i++) {
|
||||
var cat = cats[i];
|
||||
var items = inspectionCategoryItems[cat] || [];
|
||||
for (var j = 0; j < items.length; j++) {
|
||||
var it = items[j];
|
||||
var isTire = cat === '轮胎';
|
||||
list.push({
|
||||
key: 'ins-' + i + '-' + j,
|
||||
category: cat,
|
||||
item: it,
|
||||
checked: true,
|
||||
treadDepth: isTire ? '6.5' : '',
|
||||
remark: ''
|
||||
});
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}, [inspectionCategoryItems]);
|
||||
|
||||
var inspectionListRef = useRef(null);
|
||||
inspectionListRef.current = inspectionList;
|
||||
|
||||
var inspectionColumns = useMemo(function () {
|
||||
return [
|
||||
{
|
||||
title: '类别',
|
||||
dataIndex: 'category',
|
||||
key: 'category',
|
||||
width: 140,
|
||||
render: function (text, record, index) {
|
||||
var rows = inspectionListRef.current || [];
|
||||
var cat = record && record.category;
|
||||
if (!cat) return { children: text, props: { rowSpan: 1 } };
|
||||
|
||||
var isFirst = true;
|
||||
for (var i = index - 1; i >= 0; i--) {
|
||||
if (!rows[i] || rows[i].category !== cat) break;
|
||||
isFirst = false;
|
||||
break;
|
||||
}
|
||||
if (!isFirst) return { children: null, props: { rowSpan: 0 } };
|
||||
|
||||
var span = 1;
|
||||
for (var j = index + 1; j < rows.length; j++) {
|
||||
if (!rows[j] || rows[j].category !== cat) break;
|
||||
span++;
|
||||
}
|
||||
return { children: text, props: { rowSpan: span } };
|
||||
}
|
||||
},
|
||||
{ title: '检查项目', dataIndex: 'item', key: 'item', width: 220 },
|
||||
{
|
||||
title: '检查情况',
|
||||
dataIndex: 'checked',
|
||||
key: 'checked',
|
||||
width: 220,
|
||||
render: function (_, record) {
|
||||
var isTire = record && record.category === '轮胎';
|
||||
return isTire
|
||||
? React.createElement(Input, { value: record.treadDepth, disabled: true, placeholder: '请输入胎纹深度', addonAfter: 'mm' })
|
||||
: React.createElement(Switch, { checked: !!record.checked, disabled: true });
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'remark',
|
||||
key: 'remark',
|
||||
render: function (_, record) {
|
||||
return React.createElement(Input, { value: record.remark, disabled: true, placeholder: '请输入' });
|
||||
}
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
|
||||
// ---------- states ----------
|
||||
var previewState = useState({ open: false, url: '', title: '' });
|
||||
var inspectionDrawerOpenState = useState(false);
|
||||
|
||||
function openPreview(file) {
|
||||
if (!file) return;
|
||||
previewState[1]({ open: true, url: file.url, title: file.name || '预览' });
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
try {
|
||||
if (window.history && window.history.length > 1) window.history.back();
|
||||
else message.info('返回上一页(原型)');
|
||||
} catch (e) {
|
||||
message.info('返回上一页(原型)');
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- render helpers ----------
|
||||
function PhotoGridColumn(title, items, required) {
|
||||
return React.createElement('div', { style: { background: '#fff', border: '1px solid #f0f0f0', borderRadius: 8, padding: 12 } },
|
||||
React.createElement('div', { style: { fontWeight: 600, marginBottom: 12 } }, required ? RequiredLabel(title) : title),
|
||||
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 12 } },
|
||||
items.map(function (it) {
|
||||
return React.createElement('div', { key: it.key },
|
||||
ReadonlyUploadBox({ label: required ? RequiredLabel(it.label) : it.label, value: it.value, onPreview: openPreview })
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function PhotoGridSimple(title, value) {
|
||||
return React.createElement('div', { style: { background: '#fff', border: '1px solid #f0f0f0', borderRadius: 8, padding: 12 } },
|
||||
React.createElement('div', { style: { fontWeight: 600, marginBottom: 12 } }, title),
|
||||
ReadonlyUploadBox({ label: '照片', value: value, onPreview: openPreview })
|
||||
);
|
||||
}
|
||||
|
||||
return React.createElement('div', { style: layoutStyle },
|
||||
React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } },
|
||||
React.createElement(Breadcrumb, { items: [{ title: '运维管理' }, { title: '车辆业务' }, { title: '交车管理' }, { title: '交车单-查看' }] })
|
||||
),
|
||||
|
||||
React.createElement(Card, { title: '交车明细', style: cardStyle },
|
||||
React.createElement('div', null,
|
||||
React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '车牌号' },
|
||||
React.createElement(Select, {
|
||||
value: initialForm.plateNo,
|
||||
options: plateOptions,
|
||||
showSearch: true,
|
||||
filterOption: filterOption,
|
||||
placeholder: '请输入或选择车牌号',
|
||||
allowClear: true,
|
||||
disabled: true,
|
||||
style: { width: '100%' }
|
||||
})
|
||||
),
|
||||
React.createElement(FormItem, { label: '车辆类型' },
|
||||
React.createElement(Input, { value: initialForm.vehicleType, disabled: true })
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '品牌' },
|
||||
React.createElement(Input, { value: initialForm.brand, disabled: true })
|
||||
),
|
||||
React.createElement(FormItem, { label: '型号' },
|
||||
React.createElement(Input, { value: initialForm.model, disabled: true })
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '车辆识别代码' },
|
||||
React.createElement(Input, { value: initialForm.vin, disabled: true })
|
||||
),
|
||||
React.createElement('div', { style: styles.formItem })
|
||||
),
|
||||
|
||||
React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '车身广告及放大字', fullWidth: true },
|
||||
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 10 } },
|
||||
React.createElement(Switch, { checked: !!initialForm.hasAd, disabled: true }),
|
||||
React.createElement('span', { style: { color: '#666', fontSize: 12 } }, initialForm.hasAd ? '有车身广告' : '无车身广告')
|
||||
)
|
||||
)
|
||||
),
|
||||
initialForm.hasAd ? React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginBottom: 12 } },
|
||||
React.createElement('div', null,
|
||||
ReadonlyUploadBox({ label: '广告照片', value: initialForm.adPhoto, max: 1, onPreview: openPreview })
|
||||
),
|
||||
React.createElement('div', null,
|
||||
ReadonlyUploadBox({ label: '放大字照片', value: initialForm.bigWordPhoto, max: 1, onPreview: openPreview })
|
||||
)
|
||||
) : null,
|
||||
|
||||
React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '尾板', fullWidth: true },
|
||||
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 10 } },
|
||||
React.createElement(Switch, { checked: !!initialForm.hasTailboard, disabled: true }),
|
||||
React.createElement('span', { style: { color: '#666', fontSize: 12 } }, initialForm.hasTailboard ? '有尾板' : '无尾板')
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '备胎照片' },
|
||||
ReadonlyUploadBox({ label: '', value: initialForm.spareTirePhoto, onPreview: openPreview })
|
||||
),
|
||||
React.createElement(FormItem, { label: '备胎胎纹深度' },
|
||||
React.createElement(Input, { value: initialForm.spareTireDepth, placeholder: '请输入备胎胎纹深度', addonAfter: 'mm', disabled: true })
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '驾驶培训', fullWidth: true },
|
||||
React.createElement('span', { style: { color: '#52c41a', fontWeight: 600 } }, '已完成视频培训')
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '司机证照', fullWidth: true },
|
||||
React.createElement('div', null,
|
||||
(initialForm.driverLicenses && initialForm.driverLicenses.length > 0)
|
||||
? React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 12 } },
|
||||
initialForm.driverLicenses.map(function (f) {
|
||||
return React.createElement('div', { key: f.uid },
|
||||
makeThumb(f.url, function () { previewState[1]({ open: true, url: f.url, title: f.name || '预览' }); }),
|
||||
React.createElement('div', { style: { marginTop: 6, fontSize: 12, color: '#666' } }, f.name)
|
||||
);
|
||||
})
|
||||
)
|
||||
: React.createElement('div', { style: { fontSize: 12, color: '#999' } }, '上传验车码后显示')
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '交车里程' },
|
||||
React.createElement(Input, { value: initialForm.mileageKm, addonAfter: '公里', disabled: true })
|
||||
),
|
||||
React.createElement(FormItem, { label: '交车电量' },
|
||||
React.createElement(Input, { value: initialForm.batteryKwh, addonAfter: 'kWh', disabled: true })
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '交车氢量' },
|
||||
React.createElement(Input, { value: initialForm.hydrogenAmount, addonAfter: 'MPa', disabled: true })
|
||||
),
|
||||
React.createElement(FormItem, { label: '送车服务费' },
|
||||
React.createElement(Input, { value: isEmpty(initialForm.serviceFee) ? '' : toFixed2(initialForm.serviceFee), placeholder: '请输入送车服务费金额', addonAfter: '元', disabled: true })
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '车辆检查', fullWidth: true },
|
||||
React.createElement(Button, { onClick: function () { inspectionDrawerOpenState[1](true); } }, '交车检查单')
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement(Card, { title: '交车照片', style: cardStyle },
|
||||
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', gap: 12 } },
|
||||
PhotoGridColumn('车辆', Object.keys(photos.vehicle).map(function (k) {
|
||||
return { key: 'v-' + k, label: k, value: photos.vehicle[k] };
|
||||
}), true),
|
||||
PhotoGridColumn('底盘', Object.keys(photos.chassis).map(function (k) {
|
||||
return { key: 'c-' + k, label: k, value: photos.chassis[k] };
|
||||
}), true),
|
||||
PhotoGridColumn('轮胎', Object.keys(photos.tire).map(function (k) {
|
||||
return { key: 't-' + k, label: k, value: photos.tire[k] };
|
||||
}), true)
|
||||
),
|
||||
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', gap: 12, marginTop: 12 } },
|
||||
PhotoGridSimple('瑕疵', photos.defect),
|
||||
PhotoGridSimple('其他', photos.other),
|
||||
React.createElement('div', { style: { background: 'transparent' } })
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement('div', { style: footerStyle },
|
||||
React.createElement(Button, { onClick: goBack }, '返回')
|
||||
),
|
||||
|
||||
React.createElement(Modal, {
|
||||
open: !!previewState[0].open,
|
||||
title: previewState[0].title || '预览',
|
||||
footer: null,
|
||||
onCancel: function () { previewState[1]({ open: false, url: '', title: '' }); },
|
||||
width: 860
|
||||
},
|
||||
React.createElement('div', { style: { textAlign: 'center' } },
|
||||
previewState[0].url ? React.createElement('img', { src: previewState[0].url, style: { maxWidth: '100%', maxHeight: '70vh' } }) : null
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement(Drawer, {
|
||||
open: inspectionDrawerOpenState[0],
|
||||
title: '交车检查单',
|
||||
width: 920,
|
||||
placement: 'right',
|
||||
onClose: function () { inspectionDrawerOpenState[1](false); },
|
||||
styles: { body: { display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden', paddingBottom: 0 } },
|
||||
footer: React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-start', gap: 8 } },
|
||||
React.createElement(Button, { onClick: function () { inspectionDrawerOpenState[1](false); } }, '返回')
|
||||
)
|
||||
},
|
||||
React.createElement('div', { style: { flex: 1, minHeight: 0, overflow: 'auto' } },
|
||||
React.createElement(Table, { rowKey: 'key', columns: inspectionColumns, dataSource: inspectionList, pagination: false, bordered: true, size: 'small' })
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
854
web端/运维管理/车辆业务/交车管理-交车单-编辑.jsx
Normal file
854
web端/运维管理/车辆业务/交车管理-交车单-编辑.jsx
Normal file
@@ -0,0 +1,854 @@
|
||||
// 【重要】必须使用 const Component 作为组件变量名
|
||||
// 运维管理 - 车辆业务 - 交车管理 - 交车单 - 编辑
|
||||
|
||||
const Component = function () {
|
||||
var useState = React.useState;
|
||||
var useMemo = React.useMemo;
|
||||
var useCallback = React.useCallback;
|
||||
var useRef = React.useRef;
|
||||
|
||||
var antd = window.antd;
|
||||
var Breadcrumb = antd.Breadcrumb;
|
||||
var Card = antd.Card;
|
||||
var Table = antd.Table;
|
||||
var Button = antd.Button;
|
||||
var Input = antd.Input;
|
||||
var Select = antd.Select;
|
||||
var Switch = antd.Switch;
|
||||
var Modal = antd.Modal;
|
||||
var Drawer = antd.Drawer;
|
||||
var message = antd.message;
|
||||
|
||||
var TextArea = Input.TextArea;
|
||||
|
||||
// ---------- utils ----------
|
||||
function RequiredLabel(text) {
|
||||
return React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 4 } },
|
||||
React.createElement('span', { style: { color: '#f5222d', fontWeight: 600 } }, '*'),
|
||||
React.createElement('span', null, text)
|
||||
);
|
||||
}
|
||||
|
||||
function toFixed2(v) {
|
||||
if (v === null || v === undefined || v === '') return '';
|
||||
var n = typeof v === 'number' ? v : parseFloat(v);
|
||||
return isNaN(n) ? '' : n.toFixed(2);
|
||||
}
|
||||
|
||||
function isEmpty(v) {
|
||||
return v === null || v === undefined || String(v).trim() === '';
|
||||
}
|
||||
|
||||
function filterOption(input, option) {
|
||||
var label = (option && (option.label || option.children)) || '';
|
||||
return String(label).toLowerCase().indexOf(String(input || '').toLowerCase()) >= 0;
|
||||
}
|
||||
|
||||
function fileToDataUrl(file, cb) {
|
||||
try {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (e) { cb(null, (e && e.target && e.target.result) || ''); };
|
||||
reader.onerror = function () { cb(new Error('read error')); };
|
||||
reader.readAsDataURL(file);
|
||||
} catch (e) {
|
||||
cb(e);
|
||||
}
|
||||
}
|
||||
|
||||
function makeThumb(url, onPreview, onRemove, disabled) {
|
||||
return React.createElement('div', {
|
||||
style: {
|
||||
width: 64,
|
||||
height: 64,
|
||||
borderRadius: 4,
|
||||
border: '1px solid #f0f0f0',
|
||||
background: '#fafafa',
|
||||
position: 'relative',
|
||||
overflow: 'hidden'
|
||||
}
|
||||
},
|
||||
React.createElement('img', {
|
||||
src: url,
|
||||
style: { width: '100%', height: '100%', objectFit: 'cover', cursor: 'pointer' },
|
||||
onClick: disabled ? undefined : onPreview
|
||||
}),
|
||||
(disabled || !onRemove) ? null : React.createElement('div', {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
right: 6,
|
||||
top: 6,
|
||||
width: 20,
|
||||
height: 20,
|
||||
borderRadius: 999,
|
||||
background: 'rgba(0,0,0,0.55)',
|
||||
color: '#fff',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
cursor: 'pointer',
|
||||
fontSize: 12,
|
||||
userSelect: 'none'
|
||||
},
|
||||
onClick: function (e) { e.stopPropagation(); onRemove && onRemove(); }
|
||||
}, '×')
|
||||
);
|
||||
}
|
||||
|
||||
function UploadBox(props) {
|
||||
var label = props.label;
|
||||
var value = props.value; // array of {name,url}
|
||||
var max = props.max || 1;
|
||||
var onChange = props.onChange;
|
||||
var disabled = !!props.disabled;
|
||||
var tip = props.tip;
|
||||
|
||||
function handlePick(e) {
|
||||
var f = e && e.target && e.target.files && e.target.files[0];
|
||||
if (!f) return;
|
||||
fileToDataUrl(f, function (err, url) {
|
||||
if (err) { message.error('上传失败(原型)'); return; }
|
||||
var next = (value || []).slice();
|
||||
next.push({ uid: String(Date.now()), name: f.name || 'image', url: url });
|
||||
if (next.length > max) next = next.slice(next.length - max);
|
||||
onChange && onChange(next);
|
||||
});
|
||||
e.target.value = '';
|
||||
}
|
||||
|
||||
return React.createElement('div', null,
|
||||
React.createElement('div', { style: { fontSize: 12, color: '#666', marginBottom: 6 } }, label),
|
||||
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' } },
|
||||
(value || []).map(function (f) {
|
||||
return React.createElement('div', { key: f.uid },
|
||||
makeThumb(
|
||||
f.url,
|
||||
function () { previewState[1]({ open: true, url: f.url, title: f.name || '预览' }); },
|
||||
function () { onChange && onChange((value || []).filter(function (x) { return x.uid !== f.uid; })); },
|
||||
disabled
|
||||
)
|
||||
);
|
||||
}),
|
||||
disabled ? null : ((value || []).length >= max ? null : React.createElement('label', {
|
||||
style: {
|
||||
width: 64,
|
||||
height: 64,
|
||||
borderRadius: 4,
|
||||
border: '1px dashed #d9d9d9',
|
||||
background: '#fff',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: '#999',
|
||||
cursor: 'pointer'
|
||||
}
|
||||
},
|
||||
React.createElement('input', { type: 'file', accept: 'image/*', style: { display: 'none' }, onChange: handlePick }),
|
||||
'上传'
|
||||
))
|
||||
),
|
||||
tip ? React.createElement('div', { style: { marginTop: 6, fontSize: 12, color: '#999' } }, tip) : null
|
||||
);
|
||||
}
|
||||
|
||||
// ---------- styles ----------
|
||||
var layoutStyle = { padding: '16px 24px 88px', background: '#f5f5f5', minHeight: '100vh' };
|
||||
var cardStyle = { marginBottom: 16 };
|
||||
var footerStyle = { position: 'fixed', left: 0, right: 0, bottom: 0, background: '#fff', borderTop: '1px solid #f0f0f0', padding: '12px 24px', display: 'flex', justifyContent: 'flex-start', gap: 12, zIndex: 10 };
|
||||
|
||||
var styles = {
|
||||
formRow: { display: 'flex', gap: 16, marginBottom: 12, alignItems: 'flex-start' },
|
||||
formItem: { flex: 1, minWidth: 0 },
|
||||
label: { fontSize: 12, color: '#666', marginBottom: 6 }
|
||||
};
|
||||
|
||||
function FormItem(props) {
|
||||
var label = props.label;
|
||||
var required = !!props.required;
|
||||
var fullWidth = !!props.fullWidth;
|
||||
return React.createElement('div', { style: fullWidth ? Object.assign({}, styles.formItem, { flex: '0 0 100%' }) : styles.formItem },
|
||||
React.createElement('div', { style: styles.label }, required ? RequiredLabel(label) : label),
|
||||
props.children
|
||||
);
|
||||
}
|
||||
|
||||
// ---------- mock reserve vehicles ----------
|
||||
var reserveVehicles = useMemo(function () {
|
||||
return [
|
||||
{
|
||||
plateNo: '京A12345',
|
||||
vehicleType: '牵引车',
|
||||
brand: '东风',
|
||||
model: 'DFH1180',
|
||||
vin: 'LJNAU1A2XK1234567',
|
||||
hasAd: true,
|
||||
adPhoto: [{ uid: 'ad1', name: '广告照片.jpg', url: 'https://dummyimage.com/600x400/eee/666&text=Ad' }],
|
||||
bigWordPhoto: [{ uid: 'bw1', name: '放大字照片.jpg', url: 'https://dummyimage.com/600x400/eee/666&text=BigWord' }],
|
||||
hasTailboard: true
|
||||
},
|
||||
{
|
||||
plateNo: '浙F80088',
|
||||
vehicleType: '厢式车',
|
||||
brand: '福田',
|
||||
model: 'BJ1180',
|
||||
vin: 'LJNAU1A2XK7654321',
|
||||
hasAd: false,
|
||||
adPhoto: [],
|
||||
bigWordPhoto: [],
|
||||
hasTailboard: false
|
||||
},
|
||||
{
|
||||
plateNo: '沪A30003',
|
||||
vehicleType: '厢式车',
|
||||
brand: '重汽',
|
||||
model: 'HOWO-T5G',
|
||||
vin: 'LJNAU1A2XK9999000',
|
||||
hasAd: true,
|
||||
adPhoto: [{ uid: 'ad2', name: '广告照片2.jpg', url: 'https://dummyimage.com/600x400/eee/666&text=Ad2' }],
|
||||
bigWordPhoto: [{ uid: 'bw2', name: '放大字照片2.jpg', url: 'https://dummyimage.com/600x400/eee/666&text=BigWord2' }],
|
||||
hasTailboard: true
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
|
||||
var plateOptions = useMemo(function () {
|
||||
return reserveVehicles.map(function (v) { return { value: v.plateNo, label: v.plateNo }; });
|
||||
}, [reserveVehicles]);
|
||||
var vehicleByPlate = useMemo(function () {
|
||||
var map = {};
|
||||
reserveVehicles.forEach(function (v) { map[v.plateNo] = v; });
|
||||
return map;
|
||||
}, [reserveVehicles]);
|
||||
|
||||
// ---------- states ----------
|
||||
var previewState = useState({ open: false, url: '', title: '' });
|
||||
|
||||
var formState = useState({
|
||||
plateNo: undefined,
|
||||
vehicleType: '',
|
||||
brand: '',
|
||||
model: '',
|
||||
vin: '',
|
||||
|
||||
hasAd: false,
|
||||
adPhoto: [],
|
||||
bigWordPhoto: [],
|
||||
|
||||
hasTailboard: false,
|
||||
|
||||
spareTirePhoto: [],
|
||||
spareTireDepth: '',
|
||||
|
||||
trainingFile: null, // {name}
|
||||
trainingRecognized: false,
|
||||
|
||||
driverLicenses: [], // 4 photos {uid,name,url}
|
||||
|
||||
mileageKm: '',
|
||||
batteryKwh: '',
|
||||
hydrogenAmount: '',
|
||||
serviceFee: ''
|
||||
});
|
||||
var form = formState[0];
|
||||
var setForm = formState[1];
|
||||
|
||||
var ocrModalState = useState({ open: false, photoUrl: '', depth: '6.50' });
|
||||
|
||||
var inspectionDrawerOpenState = useState(false);
|
||||
|
||||
var requirementModalOpenState = useState(false);
|
||||
var requirementDocContent =
|
||||
'交车管理-交车单-编辑\n'
|
||||
+ '一个「数字化资产ONEOS运管平台」中的「交车管理-交车单-编辑」模块\n'
|
||||
+ '1.面包屑:\n'
|
||||
+ '1.1.运维管理-车辆业务-交车管理-交车单-编辑\n'
|
||||
+ '每个模块为一个单独卡片:\n\n'
|
||||
+ '2.交车明细:列表结构;\n'
|
||||
+ '2.1.车辆类型:输入框(禁用),根据车牌号反写车辆类型;\n'
|
||||
+ '2.2.品牌:输入框(禁用),根据车牌号反写品牌;\n'
|
||||
+ '2.2.型号:输入框(禁用),根据车牌号反写型号;\n'
|
||||
+ '2.3.车牌号:选择器,输入框,支持输入内容下拉模糊匹配选项,仅能选择备车库中车辆;\n'
|
||||
+ '2.4.车辆识别代码:输入框(禁用),根据车牌号反写车辆识别代码;\n'
|
||||
+ '2.5.车身广告及放大字:必选项,开关,选择车辆后拉取该车辆「后装设备」「车身广告」,如果该车辆有「车身广告」则勾选为开,如果无则勾选为无。同时如果手动进行操作,会同步到「后装设备」「车身广告」中, 安装时间以该条备车记录提交成功为准,不够选择广告照片和放大字照片字段隐藏不显示;\n'
|
||||
+ '2.6.广告照片:必填项,图片上传,最多支持上传1张图片,支持主流照片格式。上传后,上传按钮切换为显示已上传图片缩略图,支持点击预览和删除,删除后,切换为上传按钮,从备车记录自动反写;\n'
|
||||
+ '2.7.放大字照片:必填项,图片上传,最多支持上传1张图片,支持主流照片格式。上传后,上传按钮切换为显示已上传图片缩略图,支持点击预览和删除,删除后,切换为上传按钮,从备车记录自动反写;\n'
|
||||
+ '2.8.尾板:必填项,开关,选择车辆后拉取该车辆「后装设备」「尾板」,如果该车辆有「尾板」则勾选为开,如果无则勾选为无。同时如果手动进行操作,会同步到「后装设备」「尾板」中,安装时间以该条备车记录提交成功为准;\n'
|
||||
+ '2.9.备胎照片:必填项,图片上传,最多支持上传1张图片,支持主流照片格式。上传时弹出卡片,提示正在识别中,请勿关闭页面,之后卡片左侧显示备胎照片,右侧输入框显示识别出的胎纹深度,后缀单位为mm,点击卡片中确认按钮,反写至备胎胎纹深度字段下;\n'
|
||||
+ '2.10.备胎胎纹深度:必填项,输入框,反写备胎照片OCR识别胎纹深度结果,支持修改;\n'
|
||||
+ '2.11.驾驶培训:必填项,附件上传按钮,后方为提示信息:请上传司机现场培训二维码图片。识别成功后隐藏驾驶培训按钮和提示,显示:已完成视频培训;\n'
|
||||
+ '2.12.司机证照:显示4张照片、身份证(正面)、身份证(反面)、驾驶证、从业资格证,根据驾驶培训上传二维码识别后自动反写;\n'
|
||||
+ '2.13.交车里程:必填项,输入框,单位为公里;\n'
|
||||
+ '2.14.交车电量:必填项,输入框,单位为kWh;\n'
|
||||
+ '2.15.交车氢量:必填项,输入框,单位为%或MPa,根据型号参数中该车型实际仪表盘单位显示;\n'
|
||||
+ '2.16.送车服务费:选填项,输入框,精确至2位小数,格式为xx.xx元;\n'
|
||||
+ '2.17.车辆检查:按钮,文字为交车检查单,点击右侧展开抽屉,抽屉内显示列表,字段为类别、检查项目、检查情况、备注;\n'
|
||||
+ ' 2.11.1.类别:分为车灯、仪表盘、驾驶室、轮胎、液位检查、外观检查、车辆外观、其他、随车工具、随车证件、整车、燃料电池系统、冷机、制动系统;\n'
|
||||
+ ' 2.11.2.检查项目:车灯类别对应(大灯、转向灯、小灯、示廓灯、刹车灯、倒车灯、牌照灯、防雾灯、室内灯)、仪表盘对应(氢系统指示、电控系统指示、数值清晰准确、故障报警灯)、驾驶室对应(点烟器、车窗升降、按键开关、雨刮器、内后视镜是否正常、内/外门把手、安全带、空调冷暖风、仪表盘、门锁功能、手刹、车钥匙功能是否正常、喇叭、音响功能、遮阳板、主副驾座椅、方向盘、内饰干净整洁)\n'
|
||||
+ ' 2.11.3.检查情况:其他项为开关,在检查项目每项后方显示,从备车记录反写,可手动进行关闭,轮胎为输入框,提示请输入胎纹深度;\n'
|
||||
+ ' 2.11.4.备注:输入框;\n\n'
|
||||
+ '3.交车照片:多个模块分3列显示,由照片标题和照片上传按钮组成,照片点击上传,从本地文件上传单张图片,上传成功后可通过图片右上角删除按钮删除,点击图片可放大预览;;\n'
|
||||
+ '3.1.车辆:必填项,包括仪表盘、车辆正面、车辆左前方、车辆左后方、车辆右后方、车辆右前方;\n'
|
||||
+ '3.2.底盘:必填项,包括正前方底部、左侧前方底部、左侧后方底部、正后方底部、右侧后方底部、右侧前方底部;\n'
|
||||
+ '3.3.轮胎:必填项,包括左前轮、左后轮(内)、左后轮(外)、右前轮、右后轮(内)、右后轮(外)、备胎;\n'
|
||||
+ '3.4.瑕疵:必填项,包括照片上传按钮,最多支持4张照片;\n'
|
||||
+ '3.5.其他:必填项,包括照片上传按钮,最多支持4张照片;\n'
|
||||
+ '照片点击上传,从本地文件上传单张图片,上传成功后可通过图片右上角删除按钮删除,点击图片可放大预览;\n\n'
|
||||
+ '4.底部为提交、保存、取消按钮;\n'
|
||||
+ ' 4.1.提交:点击进行二次确认,点击确认完成该车辆交车;\n'
|
||||
+ ' 4.2.保存:点击暂存交车单(不做校验);\n'
|
||||
+ ' 4.3.取消:点击返回交车单页;\n';
|
||||
|
||||
var trainingInputRef = useRef(null);
|
||||
|
||||
// photo sections
|
||||
var photoState = useState({
|
||||
vehicle: {
|
||||
'仪表盘': [],
|
||||
'车辆正面': [],
|
||||
'车辆左前方': [],
|
||||
'车辆左后方': [],
|
||||
'车辆右后方': [],
|
||||
'车辆右前方': []
|
||||
},
|
||||
chassis: {
|
||||
'正前方底部': [],
|
||||
'左侧前方底部': [],
|
||||
'左侧后方底部': [],
|
||||
'正后方底部': [],
|
||||
'右侧后方底部': [],
|
||||
'右侧前方底部': []
|
||||
},
|
||||
tire: {
|
||||
'左前轮': [],
|
||||
'左后轮(内)': [],
|
||||
'左后轮(外)': [],
|
||||
'右前轮': [],
|
||||
'右后轮(内)': [],
|
||||
'右后轮(外)': [],
|
||||
'备胎': []
|
||||
},
|
||||
defect: [],
|
||||
other: []
|
||||
});
|
||||
var photos = photoState[0];
|
||||
var setPhotos = photoState[1];
|
||||
|
||||
function updateForm(patch) {
|
||||
setForm(function (p) { return Object.assign({}, p, patch); });
|
||||
}
|
||||
|
||||
function handlePlateChange(v) {
|
||||
var veh = vehicleByPlate[v];
|
||||
updateForm({
|
||||
plateNo: v,
|
||||
vehicleType: (veh && veh.vehicleType) || '',
|
||||
brand: (veh && veh.brand) || '',
|
||||
model: (veh && veh.model) || '',
|
||||
vin: (veh && veh.vin) || '',
|
||||
hasAd: !!(veh && veh.hasAd),
|
||||
adPhoto: (veh && (veh.adPhoto || [])) || [],
|
||||
bigWordPhoto: (veh && (veh.bigWordPhoto || [])) || [],
|
||||
hasTailboard: !!(veh && veh.hasTailboard),
|
||||
spareTirePhoto: [],
|
||||
spareTireDepth: '',
|
||||
trainingFile: null,
|
||||
trainingRecognized: false,
|
||||
driverLicenses: []
|
||||
});
|
||||
}
|
||||
|
||||
function handleSparePhotoChange(list) {
|
||||
updateForm({ spareTirePhoto: list || [] });
|
||||
if ((list || []).length > 0) {
|
||||
ocrModalState[1]({ open: true, photoUrl: list[0].url, depth: '6.50' });
|
||||
}
|
||||
}
|
||||
|
||||
function confirmOcr() {
|
||||
updateForm({ spareTireDepth: ocrModalState[0].depth });
|
||||
ocrModalState[1](Object.assign({}, ocrModalState[0], { open: false }));
|
||||
message.success('已反写备胎胎纹深度');
|
||||
}
|
||||
|
||||
function handleTrainingPick(e) {
|
||||
var f = e && e.target && e.target.files && e.target.files[0];
|
||||
if (!f) return;
|
||||
// 原型:上传即识别成功
|
||||
setTimeout(function () {
|
||||
updateForm({ trainingFile: { name: f.name || '二维码图片' }, trainingRecognized: true });
|
||||
// 自动反写司机证照(示例)
|
||||
updateForm({
|
||||
driverLicenses: [
|
||||
{ uid: 'id1', name: '身份证正面.jpg', url: 'https://dummyimage.com/600x400/eee/666&text=ID-Front' },
|
||||
{ uid: 'id2', name: '身份证反面.jpg', url: 'https://dummyimage.com/600x400/eee/666&text=ID-Back' },
|
||||
{ uid: 'dl', name: '驾驶证.jpg', url: 'https://dummyimage.com/600x400/eee/666&text=DriverLicense' },
|
||||
{ uid: 'qc', name: '从业资格证.jpg', url: 'https://dummyimage.com/600x400/eee/666&text=Qualification' }
|
||||
]
|
||||
});
|
||||
message.success('识别成功:已完成视频培训(原型)');
|
||||
}, 600);
|
||||
e.target.value = '';
|
||||
}
|
||||
|
||||
function validateSubmit() {
|
||||
if (isEmpty(form.plateNo)) return '请选择车牌号';
|
||||
// 车身广告及放大字:必选项(hasAd 视为必选,允许开/关,但若开则必须有两张图)
|
||||
if (form.hasAd) {
|
||||
if (!form.adPhoto || form.adPhoto.length === 0) return '请上传广告照片';
|
||||
if (!form.bigWordPhoto || form.bigWordPhoto.length === 0) return '请上传放大字照片';
|
||||
}
|
||||
if (form.hasTailboard !== true && form.hasTailboard !== false) return '请填写尾板';
|
||||
// 尾板必填:但开关值本身必选,这里只要求已选;若未选 plateChange 已赋默认 false
|
||||
// 备胎照片/胎纹深度必填
|
||||
if (!form.spareTirePhoto || form.spareTirePhoto.length === 0) return '请上传备胎照片';
|
||||
if (isEmpty(form.spareTireDepth)) return '请填写备胎胎纹深度';
|
||||
// 驾驶培训必填:识别成功才算完成
|
||||
if (!form.trainingRecognized) return '请上传验车码并完成识别';
|
||||
if (isEmpty(form.mileageKm)) return '请填写交车里程';
|
||||
if (isEmpty(form.batteryKwh)) return '请填写交车电量';
|
||||
if (isEmpty(form.hydrogenAmount)) return '请填写交车氢量';
|
||||
// 交车照片必填:车辆/底盘/轮胎(瑕疵、其他非必填)
|
||||
var requiredGroups = [
|
||||
{ key: 'vehicle', label: '车辆' },
|
||||
{ key: 'chassis', label: '底盘' },
|
||||
{ key: 'tire', label: '轮胎' }
|
||||
];
|
||||
for (var gi = 0; gi < requiredGroups.length; gi++) {
|
||||
var g = requiredGroups[gi];
|
||||
var groupMap = photos && photos[g.key];
|
||||
var keys = groupMap ? Object.keys(groupMap) : [];
|
||||
for (var ki = 0; ki < keys.length; ki++) {
|
||||
var k = keys[ki];
|
||||
var arr = groupMap[k] || [];
|
||||
if (!arr || arr.length === 0) return '请上传' + g.label + '照片:' + k;
|
||||
}
|
||||
}
|
||||
// 尾板为必填项:如果业务上要求必须开/关都可以,这里不做强制为开,仅保证已选择
|
||||
return '';
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
var err = validateSubmit();
|
||||
if (err) { message.error(err); return; }
|
||||
Modal.confirm({
|
||||
title: '确认交车',
|
||||
content: '请确认信息填写无误,点击确认完成该车辆交车。',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onOk: function () {
|
||||
message.success('交车成功(原型)');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleSave() {
|
||||
message.success('已保存(原型)');
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
message.info('返回交车单页(原型)');
|
||||
}
|
||||
|
||||
// ---------- inspection checklist ----------
|
||||
// 按需求 2.11.1~2.11.4 生成完整检查清单(原型:默认从备车记录反写为“开/正常”)
|
||||
var inspectionCategoryItems = {
|
||||
'车灯': ['大灯', '转向灯', '小灯', '示廓灯', '刹车灯', '倒车灯', '牌照灯', '防雾灯', '室内灯'],
|
||||
'仪表盘': ['氢系统指示', '电控系统指示', '数值清晰准确', '故障报警灯'],
|
||||
'驾驶室': ['点烟器', '车窗升降', '按键开关', '雨刮器', '内后视镜是否正常', '内/外门把手', '安全带', '空调冷暖风', '仪表盘', '门锁功能', '手刹', '车钥匙功能是否正常', '喇叭', '音响功能', '遮阳板', '主副驾座椅', '方向盘', '内饰干净整洁'],
|
||||
'轮胎': ['前左胎', '前右胎', '后左胎', '后右胎', '备胎'],
|
||||
'液位检查': ['冷却液', '制动液', '玻璃水'],
|
||||
'外观检查': ['车身外观', '漆面', '玻璃'],
|
||||
'车辆外观': ['整车外观'],
|
||||
'其他': ['其他检查项'],
|
||||
'随车工具': ['三角牌', '灭火器', '反光背心'],
|
||||
'随车证件': ['行驶证', '营运证', '保险单'],
|
||||
'整车': ['整车状态'],
|
||||
'燃料电池系统': ['氢系统', '储氢瓶'],
|
||||
'冷机': ['冷机运行'],
|
||||
'制动系统': ['制动踏板', '驻车制动']
|
||||
};
|
||||
|
||||
function buildInspectionList() {
|
||||
var list = [];
|
||||
var categories = Object.keys(inspectionCategoryItems);
|
||||
for (var i = 0; i < categories.length; i++) {
|
||||
var cat = categories[i];
|
||||
var items = inspectionCategoryItems[cat] || [];
|
||||
for (var j = 0; j < items.length; j++) {
|
||||
var it = items[j];
|
||||
var isTire = cat === '轮胎';
|
||||
list.push({
|
||||
key: 'ins-' + i + '-' + j,
|
||||
category: cat,
|
||||
item: it,
|
||||
checked: true,
|
||||
treadDepth: isTire ? '6.5' : '',
|
||||
remark: ''
|
||||
});
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
var inspectionListState = useState(function () {
|
||||
return buildInspectionList();
|
||||
});
|
||||
var inspectionList = inspectionListState[0];
|
||||
var setInspectionList = inspectionListState[1];
|
||||
var inspectionListRef = useRef(null);
|
||||
inspectionListRef.current = inspectionList;
|
||||
|
||||
function updateInspectionRow(key, patch) {
|
||||
setInspectionList(function (prev) {
|
||||
return (prev || []).map(function (r) {
|
||||
if (r.key !== key) return r;
|
||||
return Object.assign({}, r, patch);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var inspectionColumns = useMemo(function () {
|
||||
return [
|
||||
{
|
||||
title: '类别',
|
||||
dataIndex: 'category',
|
||||
key: 'category',
|
||||
width: 140,
|
||||
render: function (text, record, index) {
|
||||
var rows = inspectionListRef.current || [];
|
||||
var cat = record && record.category;
|
||||
if (!cat) return { children: text, props: { rowSpan: 1 } };
|
||||
|
||||
// 仅合并同类别的连续行
|
||||
var isFirst = true;
|
||||
for (var i = index - 1; i >= 0; i--) {
|
||||
if (!rows[i] || rows[i].category !== cat) break;
|
||||
isFirst = false;
|
||||
break;
|
||||
}
|
||||
if (!isFirst) return { children: null, props: { rowSpan: 0 } };
|
||||
|
||||
var span = 1;
|
||||
for (var j = index + 1; j < rows.length; j++) {
|
||||
if (!rows[j] || rows[j].category !== cat) break;
|
||||
span++;
|
||||
}
|
||||
return { children: text, props: { rowSpan: span } };
|
||||
}
|
||||
},
|
||||
{ title: '检查项目', dataIndex: 'item', key: 'item', width: 220 },
|
||||
{
|
||||
title: '检查情况',
|
||||
dataIndex: 'checked',
|
||||
key: 'checked',
|
||||
width: 220,
|
||||
render: function (_, record) {
|
||||
var isTire = record && (record.category === '轮胎' || String(record.item || '').indexOf('胎纹') >= 0);
|
||||
return isTire
|
||||
? React.createElement(Input, {
|
||||
value: record.treadDepth,
|
||||
placeholder: '请输入胎纹深度',
|
||||
addonAfter: 'mm',
|
||||
onChange: function (e) { updateInspectionRow(record.key, { treadDepth: e.target.value }); }
|
||||
})
|
||||
: React.createElement(Switch, {
|
||||
checked: !!record.checked,
|
||||
onChange: function (v) { updateInspectionRow(record.key, { checked: !!v }); }
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'remark',
|
||||
key: 'remark',
|
||||
render: function (_, record) {
|
||||
return React.createElement(Input, {
|
||||
value: record.remark,
|
||||
placeholder: '请输入',
|
||||
onChange: function (e) { updateInspectionRow(record.key, { remark: e.target.value }); }
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
|
||||
// ---------- render helpers ----------
|
||||
// 交车明细布局对齐「备车-新增」:使用 FormItem + styles.formRow
|
||||
|
||||
function PhotoGridColumn(title, items, required) {
|
||||
return React.createElement('div', { style: { background: '#fff', border: '1px solid #f0f0f0', borderRadius: 8, padding: 12 } },
|
||||
React.createElement('div', { style: { fontWeight: 600, marginBottom: 12 } }, required ? RequiredLabel(title) : title),
|
||||
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 12 } },
|
||||
items.map(function (it) {
|
||||
return React.createElement('div', { key: it.key },
|
||||
UploadBox({
|
||||
label: required ? RequiredLabel(it.label) : it.label,
|
||||
value: it.value,
|
||||
max: 1,
|
||||
onChange: it.onChange
|
||||
})
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function PhotoGridSimple(title, value, max, onChange) {
|
||||
return React.createElement('div', { style: { background: '#fff', border: '1px solid #f0f0f0', borderRadius: 8, padding: 12 } },
|
||||
React.createElement('div', { style: { fontWeight: 600, marginBottom: 12 } }, title),
|
||||
UploadBox({ label: '照片', value: value, max: max, onChange: onChange, tip: '最多支持上传' + max + '张' })
|
||||
);
|
||||
}
|
||||
|
||||
// 在线打印已按最新需求移除
|
||||
|
||||
// ---------- render ----------
|
||||
return React.createElement('div', { style: layoutStyle },
|
||||
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', onClick: function () { requirementModalOpenState[1](true); } }, '查看需求说明')
|
||||
),
|
||||
|
||||
React.createElement(Card, { title: '交车明细', style: cardStyle },
|
||||
React.createElement('div', null,
|
||||
React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '车牌号', required: true },
|
||||
React.createElement(Select, {
|
||||
value: form.plateNo,
|
||||
options: plateOptions,
|
||||
showSearch: true,
|
||||
filterOption: filterOption,
|
||||
placeholder: '请输入或选择车牌号',
|
||||
allowClear: true,
|
||||
style: { width: '100%' },
|
||||
onChange: handlePlateChange
|
||||
})
|
||||
),
|
||||
React.createElement(FormItem, { label: '车辆类型' },
|
||||
React.createElement(Input, { value: form.vehicleType, disabled: true })
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '品牌' },
|
||||
React.createElement(Input, { value: form.brand, disabled: true })
|
||||
),
|
||||
React.createElement(FormItem, { label: '型号' },
|
||||
React.createElement(Input, { value: form.model, disabled: true })
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '车辆识别代码' },
|
||||
React.createElement(Input, { value: form.vin, disabled: true })
|
||||
),
|
||||
React.createElement('div', { style: styles.formItem })
|
||||
),
|
||||
|
||||
React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '车身广告及放大字', required: true, fullWidth: true },
|
||||
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 10 } },
|
||||
React.createElement(Switch, { checked: !!form.hasAd, onChange: function (v) { updateForm({ hasAd: !!v }); } }),
|
||||
React.createElement('span', { style: { color: '#666', fontSize: 12 } }, form.hasAd ? '有车身广告' : '无车身广告')
|
||||
)
|
||||
)
|
||||
),
|
||||
form.hasAd ? React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginBottom: 12 } },
|
||||
React.createElement('div', null,
|
||||
UploadBox({ label: RequiredLabel('广告照片'), value: form.adPhoto, max: 1, onChange: function (l) { updateForm({ adPhoto: l }); } })
|
||||
),
|
||||
React.createElement('div', null,
|
||||
UploadBox({ label: RequiredLabel('放大字照片'), value: form.bigWordPhoto, max: 1, onChange: function (l) { updateForm({ bigWordPhoto: l }); } })
|
||||
)
|
||||
) : null,
|
||||
|
||||
React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '尾板', required: true, fullWidth: true },
|
||||
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 10 } },
|
||||
React.createElement(Switch, { checked: !!form.hasTailboard, onChange: function (v) { updateForm({ hasTailboard: !!v }); } }),
|
||||
React.createElement('span', { style: { color: '#666', fontSize: 12 } }, form.hasTailboard ? '有尾板' : '无尾板')
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '备胎照片', required: true },
|
||||
UploadBox({ label: '', value: form.spareTirePhoto, max: 1, onChange: handleSparePhotoChange })
|
||||
),
|
||||
React.createElement(FormItem, { label: '备胎胎纹深度', required: true },
|
||||
React.createElement(Input, { value: form.spareTireDepth, placeholder: '请输入备胎胎纹深度', addonAfter: 'mm', onChange: function (e) { updateForm({ spareTireDepth: e.target.value }); } })
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '驾驶培训', required: true, fullWidth: true },
|
||||
React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 6 } },
|
||||
form.trainingRecognized
|
||||
? React.createElement('span', { style: { color: '#52c41a', fontWeight: 600 } }, '已完成视频培训')
|
||||
: React.createElement(React.Fragment, null,
|
||||
React.createElement('div', { style: { display: 'inline-flex', alignItems: 'center', gap: 8 } },
|
||||
React.createElement('input', { type: 'file', ref: trainingInputRef, accept: 'image/*', style: { display: 'none' }, onChange: handleTrainingPick }),
|
||||
React.createElement(Button, { onClick: function () { trainingInputRef.current && trainingInputRef.current.click(); } }, '上传验车码')
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '司机证照', fullWidth: true },
|
||||
React.createElement('div', null,
|
||||
(form.driverLicenses && form.driverLicenses.length > 0)
|
||||
? React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 12 } },
|
||||
form.driverLicenses.map(function (f) {
|
||||
return React.createElement('div', { key: f.uid },
|
||||
makeThumb(
|
||||
f.url,
|
||||
function () { previewState[1]({ open: true, url: f.url, title: f.name || '预览' }); },
|
||||
null,
|
||||
false
|
||||
),
|
||||
React.createElement('div', { style: { marginTop: 6, fontSize: 12, color: '#666' } }, f.name)
|
||||
);
|
||||
})
|
||||
)
|
||||
: React.createElement('div', { style: { fontSize: 12, color: '#999' } }, '上传验车码后显示')
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '交车里程', required: true },
|
||||
React.createElement(Input, { value: form.mileageKm, placeholder: '请输入', addonAfter: '公里', onChange: function (e) { updateForm({ mileageKm: e.target.value }); } })
|
||||
),
|
||||
React.createElement(FormItem, { label: '交车电量', required: true },
|
||||
React.createElement(Input, { value: form.batteryKwh, placeholder: '请输入', addonAfter: 'kWh', onChange: function (e) { updateForm({ batteryKwh: e.target.value }); } })
|
||||
)
|
||||
),
|
||||
React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '交车氢量', required: true },
|
||||
React.createElement(Input, { value: form.hydrogenAmount, placeholder: '请输入交车氢量', addonAfter: 'MPa', onChange: function (e) { updateForm({ hydrogenAmount: e.target.value }); } })
|
||||
),
|
||||
React.createElement(FormItem, { label: '送车服务费' },
|
||||
React.createElement(Input, { value: form.serviceFee, placeholder: '请输入送车服务费金额', addonAfter: '元', onChange: function (e) { updateForm({ serviceFee: e.target.value }); } })
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '车辆检查', fullWidth: true },
|
||||
React.createElement(Button, { onClick: function () { inspectionDrawerOpenState[1](true); } }, '交车检查单')
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement(Card, { title: '交车照片', style: cardStyle },
|
||||
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', gap: 12 } },
|
||||
PhotoGridColumn('车辆', Object.keys(photos.vehicle).map(function (k) {
|
||||
return {
|
||||
key: 'v-' + k,
|
||||
label: k,
|
||||
value: photos.vehicle[k],
|
||||
onChange: function (l) { setPhotos(function (p) { var n = Object.assign({}, p); n.vehicle = Object.assign({}, p.vehicle); n.vehicle[k] = l; return n; }); }
|
||||
};
|
||||
}), true),
|
||||
PhotoGridColumn('底盘', Object.keys(photos.chassis).map(function (k) {
|
||||
return {
|
||||
key: 'c-' + k,
|
||||
label: k,
|
||||
value: photos.chassis[k],
|
||||
onChange: function (l) { setPhotos(function (p) { var n = Object.assign({}, p); n.chassis = Object.assign({}, p.chassis); n.chassis[k] = l; return n; }); }
|
||||
};
|
||||
}), true),
|
||||
PhotoGridColumn('轮胎', Object.keys(photos.tire).map(function (k) {
|
||||
return {
|
||||
key: 't-' + k,
|
||||
label: k,
|
||||
value: photos.tire[k],
|
||||
onChange: function (l) { setPhotos(function (p) { var n = Object.assign({}, p); n.tire = Object.assign({}, p.tire); n.tire[k] = l; return n; }); }
|
||||
};
|
||||
}), true)
|
||||
),
|
||||
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', gap: 12, marginTop: 12 } },
|
||||
PhotoGridSimple('瑕疵', photos.defect, 4, function (l) { setPhotos(function (p) { return Object.assign({}, p, { defect: l }); }); }),
|
||||
PhotoGridSimple('其他', photos.other, 4, function (l) { setPhotos(function (p) { return Object.assign({}, p, { other: l }); }); })
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement('div', { style: footerStyle },
|
||||
React.createElement(Button, { type: 'primary', onClick: handleSubmit }, '提交'),
|
||||
React.createElement(Button, { onClick: handleSave }, '保存'),
|
||||
React.createElement(Button, { onClick: handleCancel }, '取消')
|
||||
),
|
||||
|
||||
// preview modal
|
||||
React.createElement(Modal, {
|
||||
open: !!previewState[0].open,
|
||||
title: previewState[0].title || '预览',
|
||||
footer: null,
|
||||
onCancel: function () { previewState[1]({ open: false, url: '', title: '' }); },
|
||||
width: 860
|
||||
},
|
||||
previewState[0].url ? React.createElement('img', { src: previewState[0].url, style: { width: '100%', maxHeight: '70vh', objectFit: 'contain' } }) : null
|
||||
),
|
||||
|
||||
// OCR modal
|
||||
React.createElement(Modal, {
|
||||
open: !!ocrModalState[0].open,
|
||||
title: '正在识别中(原型)',
|
||||
onCancel: function () { ocrModalState[1](Object.assign({}, ocrModalState[0], { open: false })); },
|
||||
onOk: confirmOcr,
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
width: 860
|
||||
},
|
||||
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 320px', gap: 16, alignItems: 'start' } },
|
||||
React.createElement('div', { style: { border: '1px solid #f0f0f0', borderRadius: 8, overflow: 'hidden', background: '#fafafa' } },
|
||||
ocrModalState[0].photoUrl ? React.createElement('img', { src: ocrModalState[0].photoUrl, style: { width: '100%', maxHeight: 420, objectFit: 'contain', display: 'block' } }) : null
|
||||
),
|
||||
React.createElement('div', null,
|
||||
React.createElement('div', { style: { fontSize: 12, color: '#666', marginBottom: 6 } }, '识别出的胎纹深度'),
|
||||
React.createElement(Input, {
|
||||
value: ocrModalState[0].depth,
|
||||
addonAfter: 'mm',
|
||||
onChange: function (e) { ocrModalState[1](Object.assign({}, ocrModalState[0], { depth: e.target.value })); }
|
||||
}),
|
||||
React.createElement('div', { style: { marginTop: 8, fontSize: 12, color: '#999', lineHeight: 1.7 } }, '提示:识别中请勿关闭页面;点击确认后将反写至“备胎胎纹深度”。')
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
// inspection drawer
|
||||
React.createElement(Drawer, {
|
||||
open: inspectionDrawerOpenState[0],
|
||||
title: '交车检查单',
|
||||
width: 920,
|
||||
placement: 'right',
|
||||
onClose: function () { inspectionDrawerOpenState[1](false); },
|
||||
styles: { body: { display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden', paddingBottom: 0 } },
|
||||
footer: React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-start', gap: 8 } },
|
||||
React.createElement(Button, {
|
||||
type: 'primary',
|
||||
onClick: function () {
|
||||
message.success('提交成功(原型)');
|
||||
inspectionDrawerOpenState[1](false);
|
||||
}
|
||||
}, '提交'),
|
||||
React.createElement(Button, { onClick: function () { inspectionDrawerOpenState[1](false); } }, '返回')
|
||||
)
|
||||
},
|
||||
React.createElement('div', { style: { flex: 1, minHeight: 0, overflow: 'auto' } },
|
||||
React.createElement(Table, { rowKey: 'key', columns: inspectionColumns, dataSource: inspectionList, pagination: false, bordered: true, size: 'small' })
|
||||
)
|
||||
),
|
||||
React.createElement(Modal, {
|
||||
open: requirementModalOpenState[0],
|
||||
onCancel: function () { requirementModalOpenState[1](false); },
|
||||
onOk: function () { requirementModalOpenState[1](false); },
|
||||
title: '需求说明',
|
||||
width: 860
|
||||
},
|
||||
React.createElement('div', { style: { maxHeight: '70vh', overflow: 'auto', whiteSpace: 'pre-wrap', lineHeight: 1.7, color: '#333' } }, requirementDocContent)
|
||||
),
|
||||
|
||||
// 在线打印已按最新需求移除
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,8 +13,13 @@ const Component = function () {
|
||||
var Select = antd.Select;
|
||||
var Table = antd.Table;
|
||||
var DatePicker = antd.DatePicker;
|
||||
var Modal = antd.Modal;
|
||||
var message = antd.message;
|
||||
|
||||
var requirementModalOpen = useState(false);
|
||||
var setRequirementModalOpen = requirementModalOpen[1];
|
||||
var requirementDocContent = '交车管理-交车单\n一个「数字化资产ONEOS运管平台」中的「交车管理-交车单」模块\n\n1.面包屑:\n1.1.运维管理-车辆业务-交车管理-交车单\n\n每个模块为一个单独卡片:\n\n2.项目详情:表单结构:\n2.1.项目名称:输入框禁用,显示该交车单对应车辆租赁合同中项目名称;\n2.2.合同编码:输入框禁用,显示该交车单对应车辆租赁合同中项目合同编码;\n2.3.客户名称:输入框禁用,显示该交车单对应车辆租赁合同中客户名称;\n2.4.预计交车日期:日期选择器禁用,显示该交车单预计交车日期,分为单日和日期区间两种,格式为:YYYY-MM-DD、YYYY-MM-DD至YYYY-MM-DD;\n2.5.交车区域:输入框禁用,显示该交车单对应车辆租赁合同中交车区域;\n2.6.交车地点:输入框禁用,显示该交车单对应车辆租赁合同中交车地点;\n2.7.业务部门:输入框禁用,显示该交车单对应车辆租赁合同中业务部门;\n2.8.业务负责人:输入框禁用,显示该交车单对应车辆租赁合同中业务负责人;\n\n3.交车明细:列表结构;\n3.1.序号:与车辆租赁合同中序号对应,显示该交车任务所有车辆序号;\n3.2.品牌:选择器禁用,显示该交车单对应车辆租赁合同中品牌;\n3.3.型号:选择器禁用,显示该交车单对应车辆租赁合同中型号;\n3.4.车牌号:已交车车辆显示车牌号,未交车车辆显示-;\n3.5.实际交车日期:日期选择器禁用,显示该交车单实际交车日期,精确至日,格式为:YYYY-MM-DD;\n3.6.交车人:输入框禁用,显示该交车单中该车辆实际交车人员(注意这里是交车单中某辆车的交车人员,一个交车单可同时多个交车人员操作同时交多辆车,而不是最终提交整个交车单的人员);\n3.7.交车状态:已完成、待提交、已签章;\n 3.7.1.已完成:已完成交车提交,但还未完成被授权人签章;\n 3.7.2.待提交:仅点击保存,未完成交车提交;\n 3.7.3.已签章:已完成交车提交并完成被授权人签章;\n3.8.操作:查看、编辑、下载签章文件;\n 3.8.1.查看:点击跳转该车辆交车明细;\n 3.8.2.编辑:交车状态为待提交时显示,点击弹出卡片至交车明细编辑页;\n 3.8.3.下载签章文件:交车完成并完成被授权人签章时显示,点击下载签章文件;\n\n4.底部为提交、取消按钮;\n 4.1.提交:提交按钮必须所有车辆交车状态为已签章时才可提交,否则为禁用状态;\n 4.2.取消:点击返回交车管理列表页;';
|
||||
|
||||
// 项目详情(只读,来自交车任务/租赁合同)
|
||||
var projectDetail = useMemo(function () {
|
||||
return {
|
||||
@@ -93,19 +98,13 @@ const Component = function () {
|
||||
message.success('下载签章文件(原型)');
|
||||
}, []);
|
||||
|
||||
var card1Collapsed = useState(false)[0];
|
||||
var setCard1Collapsed = useState(false)[1];
|
||||
var card2Collapsed = useState(false)[0];
|
||||
var setCard2Collapsed = useState(false)[1];
|
||||
|
||||
var styles = {
|
||||
page: { padding: '16px 24px 48px', backgroundColor: '#f5f5f5', minHeight: '100vh', fontFamily: '"PingFang SC", "苹方-简", -apple-system, BlinkMacSystemFont, "Microsoft YaHei", 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' },
|
||||
cardHeader: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '16px 20px', borderBottom: '1px solid #f0f0f0', cursor: 'pointer' },
|
||||
cardHeader: { display: 'flex', alignItems: 'center', padding: '16px 20px', borderBottom: '1px solid #f0f0f0' },
|
||||
cardTitle: { fontSize: 16, fontWeight: 600, color: '#333' },
|
||||
cardToggle: { color: '#999', fontSize: 14 },
|
||||
cardBody: { padding: '20px 24px' },
|
||||
formRow: { display: 'flex', flexWrap: 'wrap', marginBottom: 16 },
|
||||
formCol: { flex: '0 0 33.33%', minWidth: 200, paddingRight: 16, marginBottom: 8 },
|
||||
@@ -117,11 +116,10 @@ const Component = function () {
|
||||
|
||||
var CardBlock = function (props) {
|
||||
return React.createElement('div', { id: props.id, style: styles.card },
|
||||
React.createElement('div', { style: styles.cardHeader, onClick: function () { props.setCollapsed(!props.collapsed); } },
|
||||
React.createElement('span', { style: styles.cardTitle }, props.title),
|
||||
React.createElement('span', { style: styles.cardToggle }, props.collapsed ? '展开' : '收起')
|
||||
React.createElement('div', { style: styles.cardHeader },
|
||||
React.createElement('span', { style: styles.cardTitle }, props.title)
|
||||
),
|
||||
!props.collapsed ? React.createElement('div', { style: styles.cardBody }, props.children) : null
|
||||
React.createElement('div', { style: styles.cardBody }, props.children)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -143,16 +141,11 @@ const Component = function () {
|
||||
key: 'plateNo',
|
||||
width: 120,
|
||||
render: function (v, record, index) {
|
||||
return React.createElement(Select, {
|
||||
placeholder: '请选择或搜索',
|
||||
style: { width: '100%' },
|
||||
value: v || undefined,
|
||||
onChange: function (val) { updateDetailRow(index, 'plateNo', val || ''); },
|
||||
showSearch: true,
|
||||
allowClear: true,
|
||||
options: reservePlateOptions,
|
||||
filterOption: function (input, opt) { return (opt && opt.label && String(opt.label).toLowerCase().indexOf((input || '').toLowerCase()) >= 0); }
|
||||
});
|
||||
var isDelivered = record.status === '已签章' || record.status === '已完成';
|
||||
if (isDelivered) {
|
||||
return React.createElement(Input, { value: v || '', disabled: true, style: { width: '100%', background: '#f5f5f5' } });
|
||||
}
|
||||
return React.createElement(Input, { value: '-', disabled: true, style: { width: '100%', background: '#f5f5f5' } });
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -186,17 +179,20 @@ const Component = function () {
|
||||
];
|
||||
|
||||
return React.createElement('div', { style: styles.page },
|
||||
React.createElement('div', { style: styles.breadcrumb },
|
||||
React.createElement('span', null, '运维管理'),
|
||||
React.createElement('span', { style: styles.breadcrumbSep }, ' / '),
|
||||
React.createElement('span', null, '车辆业务'),
|
||||
React.createElement('span', { style: styles.breadcrumbSep }, ' / '),
|
||||
React.createElement('span', null, '交车管理'),
|
||||
React.createElement('span', { style: styles.breadcrumbSep }, ' / '),
|
||||
React.createElement('span', { style: { color: '#1890ff' } }, '交车单')
|
||||
React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } },
|
||||
React.createElement('div', { style: styles.breadcrumb },
|
||||
React.createElement('span', null, '运维管理'),
|
||||
React.createElement('span', { style: styles.breadcrumbSep }, ' / '),
|
||||
React.createElement('span', null, '车辆业务'),
|
||||
React.createElement('span', { style: styles.breadcrumbSep }, ' / '),
|
||||
React.createElement('span', null, '交车管理'),
|
||||
React.createElement('span', { style: styles.breadcrumbSep }, ' / '),
|
||||
React.createElement('span', { style: { color: '#1890ff' } }, '交车单')
|
||||
),
|
||||
React.createElement(Button, { type: 'link', onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明')
|
||||
),
|
||||
|
||||
React.createElement('div', { id: 'card-project' }, React.createElement(CardBlock, { id: 'card-project', title: '项目详情', collapsed: card1Collapsed, setCollapsed: setCard1Collapsed }, React.createElement('div', { style: styles.formRow },
|
||||
React.createElement('div', { id: 'card-project' }, React.createElement(CardBlock, { id: 'card-project', title: '项目详情' }, React.createElement('div', { style: styles.formRow },
|
||||
React.createElement(FormItem, { label: '项目名称' }, React.createElement(Input, { value: projectDetail.projectName, disabled: true, style: styles.inputDisabled })),
|
||||
React.createElement(FormItem, { label: '合同编码' }, React.createElement(Input, { value: projectDetail.contractCode, disabled: true, style: styles.inputDisabled })),
|
||||
React.createElement(FormItem, { label: '客户名称' }, React.createElement(Input, { value: projectDetail.customerName, disabled: true, style: styles.inputDisabled })),
|
||||
@@ -207,7 +203,7 @@ const Component = function () {
|
||||
React.createElement(FormItem, { label: '业务负责人' }, React.createElement(Input, { value: projectDetail.businessOwner, disabled: true, style: styles.inputDisabled }))
|
||||
))),
|
||||
|
||||
React.createElement('div', { id: 'card-detail', style: { marginTop: 16 } }, React.createElement(CardBlock, { id: 'card-detail', title: '交车明细', collapsed: card2Collapsed, setCollapsed: setCard2Collapsed }, React.createElement(Table, {
|
||||
React.createElement('div', { id: 'card-detail', style: { marginTop: 16 } }, React.createElement(CardBlock, { id: 'card-detail', title: '交车明细' }, React.createElement(Table, {
|
||||
columns: columns,
|
||||
dataSource: detailList,
|
||||
rowKey: 'key',
|
||||
@@ -220,6 +216,15 @@ const Component = function () {
|
||||
React.createElement('div', { style: styles.footer },
|
||||
React.createElement(Button, { type: 'primary', disabled: !allSigned, onClick: handleSubmit }, '提交'),
|
||||
React.createElement(Button, { onClick: handleCancel }, '取消')
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement(Modal, {
|
||||
title: '需求说明',
|
||||
open: requirementModalOpen[0],
|
||||
onCancel: function () { setRequirementModalOpen(false); },
|
||||
footer: React.createElement(Button, { onClick: function () { setRequirementModalOpen(false); } }, '关闭'),
|
||||
width: 640,
|
||||
destroyOnClose: true
|
||||
}, React.createElement('div', { style: { maxHeight: 560, overflowY: 'auto', whiteSpace: 'pre-wrap', lineHeight: 1.6, fontSize: 13 } }, requirementDocContent))
|
||||
);
|
||||
};
|
||||
|
||||
@@ -52,7 +52,7 @@ const Component = function () {
|
||||
var requirementModalOpen = useState(false);
|
||||
var setRequirementModalOpen = requirementModalOpen[1];
|
||||
|
||||
var requirementDocContent = '交车管理\n一个「数字化资产ONEOS运管平台」中的「交车管理」模块\n\n1.面包屑:\n1.1.运维管理-车辆业务-交车管理\n\n2.筛选:\n2.1.合同编码:选择器,默认为所有合同;提示信息为:请输入或选择合同编码,支持从输入框输入内容进行模糊搜索,下拉显示结果;\n2.2.项目名称:选择器,默认为所有项目;提示信息为:请输入或选择项目名称,支持从输入框输入内容进行模糊搜索,下拉显示结果;\n2.3.客户名称:选择器,默认为所有客户;提示信息为:请输入或选择客户名称,支持从输入框输入内容进行模糊搜索,下拉显示结果;\n2.4.交车区域:地区选择器,支持省-市2级筛选;\n2.5.交车时间:日期选择器,默认提示信息为:请选择交车开始时间 请选择交车结束时间,单输入框,双日历,支持时间段选择,精确至天,格式为:YYYY-MM-DD - YYYY-MM-DD;\n2.6.交车人:选择器,默认为所有交车人;提示信息为:请输入或选择交车人姓名,支持从输入框输入内容进行模糊搜索,下拉显示结果;\n2.7.查询:点击查询,根据单个或多个筛选条件(且)联动表格进行查询;\n2.8.重置:点击清空查询条件至默认;\n\n3.列表:分为两个tab:待处理、历史记录,默认显示为待处理tab;\n3.1.待处理tab:列表显示预计交车时间、合同编码、项目名称、客户名称、交车区域、交车地点、交车数量(重点色,点击气泡卡片:车辆类型、品牌、型号、车牌号、交车时间、交车人员)、创建时间、创建人、最后修改时间、最后修改人、操作(查看、交车单);\n3.2.历史记录tab:同列,操作仅 查看(跳转交车管理-查看页)。';
|
||||
var requirementDocContent = '交车管理\n一个「数字化资产ONEOS运管平台」中的「交车管理」模块\n\n1.面包屑:\n1.1.运维管理-车辆业务-交车管理\n\n2.筛选:\n2.1.合同编码:选择器,默认为所有合同;提示信息为:请输入或选择合同编码,支持从输入框输入内容进行模糊搜索,下拉显示结果;\n2.2.项目名称:选择器,默认为所有项目;提示信息为:请输入或选择项目名称,支持从输入框输入内容进行模糊搜索,下拉显示结果;\n2.3.客户名称:选择器,默认为所有客户;提示信息为:请输入或选择客户名称,支持从输入框输入内容进行模糊搜索,下拉显示结果;\n2.4.交车区域:地区选择器,支持省-市2级筛选;\n2.5.交车时间:日期选择器,日期选择器,默认提示信息为:请选择交车开始时间 请选择交车结束时间,单输入框,双日历,支持时间段选择,精确至天,格式为:YYYY-MM-DD - YYYY-MM-DD;\n2.6.交车人:选择器,默认为所有交车人;提示信息为:请输入或选择交车人姓名,支持从输入框输入内容进行模糊搜索,下拉显示结果;\n2.7.查询:点击查询,根据单个或多个筛选条件(且)联动表格进行查询;\n2.8.重置:点击清空查询条件至默认;\n\n3.列表:\n分为两个tab:待处理、历史记录,默认显示为待处理tab;\n3.1.待处理tab:列表显示以下字段:\n3.1.1.预计交车时间:支持单日及开始-结束日期两种方式,格式为:YYYY-MM-DD及YYYY-MM-DD至YYYY-MM-DD,取自对应交车任务中预计交车时间;\n3.1.2.合同编码:显示租赁/自营合同编码;\n3.1.3.项目名称:显示租赁/自营合同项目名称;\n3.1.4.客户名称:显示租赁/自营合同客户名称;\n3.1.5.交车区域:显示交车区域,交车区域来自车辆租赁合同-交车区域,格式为省-市;\n3.1.6.交车地点:显示交车地点,交车地点来自车辆租赁合同-交车地点,显示详细地址;\n3.1.7.交车数量:显示交车数量,重点色显示,点击弹出气泡卡片,列表显示:车辆类型、品牌、型号、车牌号、交车时间、交车人员;\n 3.1.7.1.车辆类型:显示车辆类型;\n 3.1.7.2.品牌:显示车辆品牌;\n 3.1.7.3.型号:显示车辆型号;\n 3.1.7.4.车牌号:显示交车车辆车牌号,如果该车还未交车则显示为-;\n 3.1.7.5.交车时间:显示实际车辆完成交车时间,格式为:YYYY-MM-DD;\n 3.1.7.6.交车人员:显示实际车辆完成交车操作人姓名;\n3.1.8.创建时间:显示交车任务单创建时间,格式为:YYYY-MM-DD HH:MM;\n3.1.9.创建人:显示交车任务单创建人;\n3.1.10.最后修改时间:显示交车任务单最后修改时间,格式为:YYYY-MM-DD HH:MM;\n3.1.11.最后修改人:显示交车任务单最后修改人姓名;\n3.1.12.操作:交车;\n 3.1.12.1.交车单:点击跳转交车管理-交车单;\n\n3.2.历史记录:显示以下字段:\n3.2.1.预计交车时间:支持单日及开始-结束日期两种方式,格式为:YYYY-MM-DD及YYYY-MM-DD至YYYY-MM-DD,取自对应交车任务中预计交车时间;\n3.1.2.合同编码:显示租赁/自营合同编码;\n3.1.3.项目名称:显示租赁/自营合同项目名称;\n3.1.4.客户名称:显示租赁/自营合同客户名称;\n3.1.5.交车区域:显示交车区域,交车区域来自车辆租赁合同-交车区域,格式为省-市;\n3.1.6.交车地点:显示交车地点,交车地点来自车辆租赁合同-交车地点,显示详细地址;\n3.1.7.交车数量:显示交车数量,重点色显示,点击弹出气泡卡片,列表显示:车辆类型、品牌、型号、车牌号、交车时间、交车人员;\n 3.1.7.1.车辆类型:显示车辆类型;\n 3.1.7.2.品牌:显示车辆品牌;\n 3.1.7.3.型号:显示车辆型号;\n 3.1.7.4.车牌号:显示交车车辆车牌号,如果该车还未交车则显示为-;\n 3.1.7.5.交车时间:显示实际车辆完成交车时间,格式为:YYYY-MM-DD;\n 3.1.7.6.交车人员:显示实际车辆完成交车操作人姓名;\n3.1.8.创建时间:显示交车任务单创建时间,格式为:YYYY-MM-DD HH:MM;\n3.1.9.创建人:显示交车任务单创建人;\n3.1.10.最后修改时间:显示交车任务单最后修改时间,格式为:YYYY-MM-DD HH:MM;\n3.1.11.最后修改人:显示交车任务单最后修改人姓名;\n3.1.12.操作:查看、交车;\n 3.1.12.1.查看:跳转交车管理-查看页;';
|
||||
|
||||
// 交车区域:省-市 二级
|
||||
var regionOptions = [
|
||||
@@ -318,7 +318,7 @@ const Component = function () {
|
||||
);
|
||||
}
|
||||
|
||||
// 待处理:预计交车时间、合同编码、项目名称、客户名称、交车区域、交车地点、交车数量、创建时间、创建人、最后修改时间、最后修改人、操作(查看、交车单)
|
||||
// 待处理:预计交车时间、合同编码、项目名称、客户名称、交车区域、交车地点、交车数量、创建时间、创建人、最后修改时间、最后修改人、操作(交车单)
|
||||
var pendingColumns = [
|
||||
{ title: '预计交车时间', dataIndex: 'expectedDate', key: 'expectedDate', width: 220, ellipsis: true },
|
||||
{ title: '合同编码', dataIndex: 'contractCode', key: 'contractCode', width: 150, ellipsis: true },
|
||||
@@ -331,11 +331,8 @@ const Component = function () {
|
||||
{ title: '创建人', dataIndex: 'createBy', key: 'createBy', width: 90, ellipsis: true },
|
||||
{ title: '最后修改时间', dataIndex: 'lastModifyTime', key: 'lastModifyTime', width: 160, ellipsis: true },
|
||||
{ title: '最后修改人', dataIndex: 'lastModifyBy', key: 'lastModifyBy', width: 90, ellipsis: true },
|
||||
{ title: '操作', key: 'action', width: 120, fixed: 'right', render: function (_, r) {
|
||||
return React.createElement(React.Fragment, null,
|
||||
React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('跳转交车管理-查看页'); } }, '查看'),
|
||||
React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('跳转交车管理-交车单'); } }, '交车单')
|
||||
);
|
||||
{ title: '操作', key: 'action', width: 80, fixed: 'right', render: function (_, r) {
|
||||
return React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('跳转交车管理-交车单'); } }, '交车单');
|
||||
} }
|
||||
];
|
||||
|
||||
|
||||
303
web端/运维管理/车辆业务/证照管理.jsx
Normal file
303
web端/运维管理/车辆业务/证照管理.jsx
Normal file
@@ -0,0 +1,303 @@
|
||||
// 【重要】必须使用 const Component 作为组件变量名
|
||||
// 运维管理 - 车辆管理 - 证照管理
|
||||
|
||||
const Component = function () {
|
||||
var useState = React.useState;
|
||||
var useMemo = React.useMemo;
|
||||
var useCallback = React.useCallback;
|
||||
|
||||
var antd = window.antd;
|
||||
var Breadcrumb = antd.Breadcrumb;
|
||||
var Card = antd.Card;
|
||||
var Table = antd.Table;
|
||||
var Button = antd.Button;
|
||||
var Select = antd.Select;
|
||||
var Input = antd.Input;
|
||||
var DatePicker = antd.DatePicker;
|
||||
var Modal = antd.Modal;
|
||||
var message = antd.message;
|
||||
|
||||
function pad2(n) { return n < 10 ? '0' + n : '' + n; }
|
||||
function fmtDate(d) { return d.getFullYear() + '-' + pad2(d.getMonth() + 1) + '-' + pad2(d.getDate()); }
|
||||
function todayPlus(days) { var d = new Date(); d.setDate(d.getDate() + days); return fmtDate(d); }
|
||||
|
||||
var layoutStyle = { padding: '16px 24px', background: '#f5f5f5', minHeight: '100vh' };
|
||||
var cardStyle = { marginBottom: 16 };
|
||||
|
||||
// 筛选
|
||||
var filtersState = useState({
|
||||
customerName: undefined,
|
||||
contractCode: undefined,
|
||||
plateNo: undefined,
|
||||
vin: undefined,
|
||||
licenseInspectValid: null,
|
||||
operationPermitValid: null,
|
||||
passPermitValid: null
|
||||
});
|
||||
var filters = filtersState[0];
|
||||
var setFilters = filtersState[1];
|
||||
var moreOpenState = useState(false);
|
||||
|
||||
// mock 列表数据
|
||||
var tableDataState = useState(function () {
|
||||
var rows = [];
|
||||
for (var i = 1; i <= 20; i++) {
|
||||
rows.push({
|
||||
key: 'row-' + i,
|
||||
plateNo: i < 10 ? ('粤A1234' + i) : ('京A5432' + i),
|
||||
vin: 'LJ8ABC' + (100000 + i),
|
||||
licenseRegDate: todayPlus(-500 - i),
|
||||
licenseScrapDate: todayPlus(2000 + i),
|
||||
licenseValidDate: todayPlus(60 + i),
|
||||
operationCertNo: 'YYZ-' + (10000 + i),
|
||||
operationRegDate: todayPlus(-480 - i),
|
||||
operationInspectValid: todayPlus(90 + i),
|
||||
operationValid: todayPlus(365 + i),
|
||||
passPermitNo: 'TXZ-' + (20000 + i),
|
||||
passArea: i % 3 === 0 ? '上海市-浦东新区' : i % 3 === 1 ? '广东省-广州市' : '北京市-朝阳区',
|
||||
passValid: todayPlus(120 + i),
|
||||
h2CertCode: 'JQZ-' + (30000 + i),
|
||||
h2CertInspectDate: todayPlus(-30 - i),
|
||||
h2CardCode: 'JQK-' + (40000 + i),
|
||||
safetyValveInspectDate: todayPlus(-20 - i),
|
||||
safetyValveCycleMonth: 12,
|
||||
pressureGaugeInspectDate: todayPlus(-10 - i),
|
||||
pressureGaugeCycleMonth: 12,
|
||||
h2BottleVendor: i % 2 === 0 ? '亿华' : '中集',
|
||||
h2BottleInspectDate: todayPlus(-5 - i),
|
||||
h2BottleCycleMonth: 24
|
||||
});
|
||||
}
|
||||
return rows;
|
||||
});
|
||||
var tableData = tableDataState[0];
|
||||
var setTableData = tableDataState[1];
|
||||
|
||||
// 下拉选项(来自列表 mock)
|
||||
var options = useMemo(function () {
|
||||
var customers = ['嘉兴某某物流有限公司', '上海某某运输公司', '北京某某租赁有限公司'];
|
||||
var contracts = ['HI-2024-001', 'HI-2024-002', 'HI-2024-003'];
|
||||
return {
|
||||
customerName: customers.map(function (v) { return { value: v, label: v }; }),
|
||||
contractCode: contracts.map(function (v) { return { value: v, label: v }; }),
|
||||
plateNo: tableData.map(function (r) { return r.plateNo; }).slice(0, 20).map(function (v) { return { value: v, label: v }; }),
|
||||
vin: tableData.map(function (r) { return r.vin; }).slice(0, 20).map(function (v) { return { value: v, label: v }; })
|
||||
};
|
||||
}, [tableData]);
|
||||
|
||||
var filteredData = useMemo(function () {
|
||||
var list = tableData;
|
||||
if (filters.customerName) {
|
||||
// mock:按 index 分组匹配
|
||||
list = list.filter(function (_, idx) { return (idx % 3) === 0; });
|
||||
}
|
||||
if (filters.contractCode) {
|
||||
list = list.filter(function (_, idx) { return (idx % 3) === 1; });
|
||||
}
|
||||
if (filters.plateNo) list = list.filter(function (r) { return r.plateNo === filters.plateNo; });
|
||||
if (filters.vin) list = list.filter(function (r) { return r.vin === filters.vin; });
|
||||
return list;
|
||||
}, [tableData, filters.customerName, filters.contractCode, filters.plateNo, filters.vin]);
|
||||
|
||||
// 选中
|
||||
var selectedRowKeysState = useState([]);
|
||||
|
||||
// 导入弹窗
|
||||
var importOpenState = useState(false);
|
||||
var importFileState = useState(null);
|
||||
|
||||
function resetFilters() {
|
||||
setFilters({
|
||||
customerName: undefined,
|
||||
contractCode: undefined,
|
||||
plateNo: undefined,
|
||||
vin: undefined,
|
||||
licenseInspectValid: null,
|
||||
operationPermitValid: null,
|
||||
passPermitValid: null
|
||||
});
|
||||
message.success('已重置');
|
||||
}
|
||||
|
||||
function handleQuery() {
|
||||
message.success('已查询(原型)');
|
||||
}
|
||||
|
||||
function openAdd() {
|
||||
message.info('进入证照录入页(原型)');
|
||||
}
|
||||
|
||||
function confirmDeleteSelected() {
|
||||
var keys = selectedRowKeysState[0] || [];
|
||||
if (!keys.length) return;
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '确定要删除选中的证照记录吗?删除后将无法恢复。',
|
||||
okText: '确认删除',
|
||||
cancelText: '取消',
|
||||
onOk: function () {
|
||||
setTableData(function (p) { return p.filter(function (r) { return keys.indexOf(r.key) === -1; }); });
|
||||
selectedRowKeysState[1]([]);
|
||||
message.success('已删除');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function openExport() {
|
||||
message.info('导出当前筛选结果(原型)');
|
||||
}
|
||||
|
||||
function openImport() {
|
||||
importFileState[1](null);
|
||||
importOpenState[1](true);
|
||||
}
|
||||
|
||||
function onPickImportFile(e) {
|
||||
var f = e && e.target && e.target.files && e.target.files[0];
|
||||
if (!f) return;
|
||||
var name = String(f.name || '').toLowerCase();
|
||||
if (!(name.endsWith('.xls') || name.endsWith('.xlsx'))) {
|
||||
message.error('仅支持 .xls、.xlsx 格式');
|
||||
return;
|
||||
}
|
||||
importFileState[1](f);
|
||||
}
|
||||
|
||||
function doImportUpload() {
|
||||
if (!importFileState[0]) {
|
||||
message.error('请先选取要上传的文件');
|
||||
return;
|
||||
}
|
||||
message.success('上传成功(原型)');
|
||||
importOpenState[1](false);
|
||||
}
|
||||
|
||||
// 表格列
|
||||
var columns = useMemo(function () {
|
||||
return [
|
||||
{ title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 110, fixed: 'left' },
|
||||
{
|
||||
title: 'vin码',
|
||||
dataIndex: 'vin',
|
||||
key: 'vin',
|
||||
width: 150,
|
||||
render: function (v) {
|
||||
return React.createElement('span', { style: { color: '#f5222d', fontWeight: 600 } }, v || '-');
|
||||
}
|
||||
},
|
||||
{ title: '行驶证 注册日期', dataIndex: 'licenseRegDate', key: 'licenseRegDate', width: 140 },
|
||||
{ title: '行驶证 强制报废日期', dataIndex: 'licenseScrapDate', key: 'licenseScrapDate', width: 160 },
|
||||
{ title: '行驶证 有效期', dataIndex: 'licenseValidDate', key: 'licenseValidDate', width: 130 },
|
||||
{ title: '营运证 编号', dataIndex: 'operationCertNo', key: 'operationCertNo', width: 140 },
|
||||
{ title: '营运证 注册日期', dataIndex: 'operationRegDate', key: 'operationRegDate', width: 140 },
|
||||
{ title: '营运证 审验有效期', dataIndex: 'operationInspectValid', key: 'operationInspectValid', width: 160 },
|
||||
{ title: '营运证 有效期', dataIndex: 'operationValid', key: 'operationValid', width: 140 },
|
||||
{ title: '通行证 编号', dataIndex: 'passPermitNo', key: 'passPermitNo', width: 140 },
|
||||
{ title: '通行区域', dataIndex: 'passArea', key: 'passArea', width: 160, ellipsis: true },
|
||||
{ title: '通行证 有效期', dataIndex: 'passValid', key: 'passValid', width: 140 },
|
||||
{ title: '加氢证 编码', dataIndex: 'h2CertCode', key: 'h2CertCode', width: 140 },
|
||||
{ title: '加氢证 检验日期', dataIndex: 'h2CertInspectDate', key: 'h2CertInspectDate', width: 150 },
|
||||
{ title: '加氢卡 编码', dataIndex: 'h2CardCode', key: 'h2CardCode', width: 140 },
|
||||
{ title: '安全阀 检验日期', dataIndex: 'safetyValveInspectDate', key: 'safetyValveInspectDate', width: 150 },
|
||||
{ title: '安全阀 检验周期:单位 (月)', dataIndex: 'safetyValveCycleMonth', key: 'safetyValveCycleMonth', width: 200 },
|
||||
{ title: '压力表 检验日期', dataIndex: 'pressureGaugeInspectDate', key: 'pressureGaugeInspectDate', width: 150 },
|
||||
{ title: '压力表 检验周期:单位 (月)', dataIndex: 'pressureGaugeCycleMonth', key: 'pressureGaugeCycleMonth', width: 200 },
|
||||
{ title: '氢气瓶 厂家', dataIndex: 'h2BottleVendor', key: 'h2BottleVendor', width: 120 },
|
||||
{ title: '氢气瓶 检验日期', dataIndex: 'h2BottleInspectDate', key: 'h2BottleInspectDate', width: 150 },
|
||||
{ title: '氢气瓶 检验周期:单位 (月)', dataIndex: 'h2BottleCycleMonth', key: 'h2BottleCycleMonth', width: 200 },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 120,
|
||||
fixed: 'right',
|
||||
render: function (_, r) {
|
||||
return React.createElement('div', { style: { display: 'flex', gap: 8 } },
|
||||
React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('编辑:' + (r.plateNo || '')); } }, '编辑'),
|
||||
React.createElement(Button, { type: 'link', size: 'small', onClick: function () { message.info('查看:' + (r.plateNo || '')); } }, '查看')
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
|
||||
function filterOption(input, opt) {
|
||||
return String((opt && opt.label) || '').toLowerCase().indexOf(String(input || '').toLowerCase()) !== -1;
|
||||
}
|
||||
|
||||
return React.createElement('div', { style: layoutStyle },
|
||||
React.createElement('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 } },
|
||||
React.createElement(Breadcrumb, { items: [{ title: '运维管理' }, { title: '车辆管理' }, { title: '证照管理' }] })
|
||||
),
|
||||
|
||||
React.createElement(Card, { title: '筛选与操作区', style: cardStyle },
|
||||
React.createElement('div', { style: { display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16 } },
|
||||
React.createElement('div', { style: { flex: 1 } },
|
||||
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(4, minmax(0, 1fr))', gap: 12 } },
|
||||
React.createElement(Select, { placeholder: '请选择客户名称', value: filters.customerName, onChange: function (v) { setFilters(function (p) { var n = {}; for (var k in p) n[k] = p[k]; n.customerName = v; return n; }); }, allowClear: true, showSearch: true, filterOption: filterOption, options: options.customerName }),
|
||||
React.createElement(Select, { placeholder: '请选择合同编码', value: filters.contractCode, onChange: function (v) { setFilters(function (p) { var n = {}; for (var k in p) n[k] = p[k]; n.contractCode = v; return n; }); }, allowClear: true, showSearch: true, filterOption: filterOption, options: options.contractCode }),
|
||||
React.createElement(Select, { placeholder: '请选择单车车牌号', value: filters.plateNo, onChange: function (v) { setFilters(function (p) { var n = {}; for (var k in p) n[k] = p[k]; n.plateNo = v; return n; }); }, allowClear: true, showSearch: true, filterOption: filterOption, options: options.plateNo }),
|
||||
React.createElement(Select, { placeholder: '请选择车辆VIN', value: filters.vin, onChange: function (v) { setFilters(function (p) { var n = {}; for (var k in p) n[k] = p[k]; n.vin = v; return n; }); }, allowClear: true, showSearch: true, filterOption: filterOption, options: options.vin })
|
||||
),
|
||||
moreOpenState[0]
|
||||
? React.createElement('div', { style: { marginTop: 12, display: 'grid', gridTemplateColumns: 'repeat(4, minmax(0, 1fr))', gap: 12 } },
|
||||
React.createElement(DatePicker, { placeholder: '请选择审验有效期', style: { width: '100%' }, value: filters.licenseInspectValid, onChange: function (v) { setFilters(function (p) { var n = {}; for (var k in p) n[k] = p[k]; n.licenseInspectValid = v; return n; }); } }),
|
||||
React.createElement(DatePicker, { placeholder: '请选择证件有效期', style: { width: '100%' }, value: filters.operationPermitValid, onChange: function (v) { setFilters(function (p) { var n = {}; for (var k in p) n[k] = p[k]; n.operationPermitValid = v; return n; }); } }),
|
||||
React.createElement(DatePicker, { placeholder: '请选择有效期', style: { width: '100%' }, value: filters.passPermitValid, onChange: function (v) { setFilters(function (p) { var n = {}; for (var k in p) n[k] = p[k]; n.passPermitValid = v; return n; }); } }),
|
||||
React.createElement('div', null)
|
||||
)
|
||||
: null,
|
||||
React.createElement('div', { style: { marginTop: 12, display: 'flex', alignItems: 'center', gap: 8 } },
|
||||
React.createElement(Button, { type: 'link', onClick: function () { moreOpenState[1](!moreOpenState[0]); } }, moreOpenState[0] ? '收起条件 ▲' : '更多条件 ▼'),
|
||||
React.createElement(Button, { onClick: resetFilters }, '重置'),
|
||||
React.createElement(Button, { type: 'primary', onClick: handleQuery }, '查询')
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement('div', { style: { width: 360, display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 10 } },
|
||||
React.createElement('div', { style: { color: '#666', fontSize: 13 } },
|
||||
'选中 ', (selectedRowKeysState[0] || []).length, '/', (filteredData || []).length, ' 条'
|
||||
),
|
||||
React.createElement('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap', justifyContent: 'flex-end' } },
|
||||
React.createElement(Button, { type: 'primary', onClick: openAdd }, '+ 新增'),
|
||||
React.createElement(Button, { danger: true, disabled: !(selectedRowKeysState[0] || []).length, onClick: confirmDeleteSelected }, '删除'),
|
||||
React.createElement(Button, { onClick: openExport }, '导出'),
|
||||
React.createElement(Button, { onClick: openImport }, '导入')
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
React.createElement(Card, { title: '证照列表', style: cardStyle },
|
||||
React.createElement(Table, {
|
||||
rowKey: 'key',
|
||||
size: 'middle',
|
||||
bordered: true,
|
||||
columns: columns,
|
||||
dataSource: filteredData,
|
||||
rowSelection: {
|
||||
selectedRowKeys: selectedRowKeysState[0],
|
||||
onChange: function (keys) { selectedRowKeysState[1](keys || []); }
|
||||
},
|
||||
scroll: { x: 2600 },
|
||||
pagination: { pageSize: 20, showSizeChanger: true, pageSizeOptions: ['20', '50', '100'], showTotal: function (t) { return '共 ' + t + ' 条'; }, showQuickJumper: true }
|
||||
})
|
||||
),
|
||||
|
||||
React.createElement(Modal, {
|
||||
title: '证件导入',
|
||||
open: importOpenState[0],
|
||||
onCancel: function () { importOpenState[1](false); },
|
||||
okText: '上传',
|
||||
cancelText: '取消',
|
||||
onOk: doImportUpload
|
||||
},
|
||||
React.createElement('div', { style: { marginBottom: 12, color: '#666', fontSize: 13 } }, '*支持文件类型 .xls .xlsx'),
|
||||
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12 } },
|
||||
React.createElement(Button, { onClick: function () { message.info('下载模板:证件信息模板.xlsx(原型)'); } }, '模板下载'),
|
||||
React.createElement('input', { type: 'file', accept: '.xls,.xlsx', onChange: onPickImportFile })
|
||||
),
|
||||
React.createElement('div', { style: { fontSize: 13, color: '#333' } }, '已选文件:', (importFileState[0] && importFileState[0].name) ? importFileState[0].name : '—')
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
交车单查看
|
||||
一个「数字化资产ONEOS运管平台」中的「交车单查看」模块
|
||||
一个「数字化资产ONEOS运管平台」中的「交车管理-查看」模块
|
||||
1.面包屑:
|
||||
1.1.运维管理-车辆业务-交车管理-查看交车信息
|
||||
|
||||
@@ -33,9 +33,8 @@
|
||||
3.1.9.创建人:显示交车任务单创建人;
|
||||
3.1.10.最后修改时间:显示交车任务单最后修改时间,格式为:YYYY-MM-DD HH:MM;
|
||||
3.1.11.最后修改人:显示交车任务单最后修改人姓名;
|
||||
3.1.12.操作:查看、交车;
|
||||
3.1.12.1.查看:跳转交车管理-查看页;
|
||||
3.1.12.2.交车单:点击跳转交车管理-交车单;
|
||||
3.1.12.操作:交车;
|
||||
3.1.12.1.交车单:点击跳转交车管理-交车单;
|
||||
|
||||
3.2.历史记录:显示以下字段:
|
||||
3.2.1.预计交车时间:支持单日及开始-结束日期两种方式,格式为:YYYY-MM-DD及YYYY-MM-DD至YYYY-MM-DD,取自对应交车任务中预计交车时间;
|
||||
|
||||
35
web端/需求说明/财务管理/还车应结款
Normal file
35
web端/需求说明/财务管理/还车应结款
Normal file
@@ -0,0 +1,35 @@
|
||||
一个「数字化资产ONEOS运管平台」中的「还车应结款」模块
|
||||
#面包屑:财务管理-还车应结款
|
||||
|
||||
1.筛选;
|
||||
1.1.合同编号:选择器,支持输入框内输入合同编号模糊搜索下拉显示匹配项;
|
||||
1.2.客户名称:选择器,支持输入框内输入客户名称模糊搜索下拉显示匹配项;
|
||||
1.3.项目名称:选择器,支持输入框内输入项目名称模糊搜索下拉显示匹配项;
|
||||
1.4.车牌号:选择器,支持输入框内输入车牌号模糊搜索下拉显示匹配项;
|
||||
1.5.还车时间:日期选择器,支持单输入框双日历选择开始-结束日期,精确至日;
|
||||
1.6.审批状态:选择器,分为待提交、待审批、审批中、审批完成、审批驳回、撤回;
|
||||
|
||||
2.还车应结款列表:右侧为导出;
|
||||
2.1.合同编号:显示合同编号;
|
||||
2.2.提交情况(合并表头):分为安全组、业务服务组、运维组、能源组,内容为各组提交人姓名,前方为已提交/未提交图标;
|
||||
2.3.审批状态:待提交、待审批、审批中、审批完成、审批驳回、撤回;
|
||||
2.3.1.待提交:未完成4部门提交也未提交审批;
|
||||
2.3.2.待审批:已提交审批,但没有任意节点进行审批;
|
||||
2.3.3.审批中:已提交审批,已有节点完成审批,但未完成最终节点审批;
|
||||
2.3.4.审批完成:已提交审批,并完成最终节点审批;
|
||||
2.3.5.审批驳回:已提交审批,但在任意节点被驳回;
|
||||
2.3.6.撤回:已提交审批,但在最终节点审批完成前主动撤回;
|
||||
2.4.客户名称:显示合同对应客户名称;
|
||||
2.5.项目名称:显示合同对应项目名称;
|
||||
2.6.车牌号:显示合同对应车牌号;
|
||||
2.7.业务部门:显示合同对应业务部门;
|
||||
2.8.业务负责人:显示合同对应业务负责人;
|
||||
2.9.交车时间:显示该车辆交车时间;
|
||||
2.10.还车时间:显示该车辆还车时间;
|
||||
2.11.还车人:显示该车辆还车人姓名;
|
||||
2.12.操作:查看、生成账单、费用明细、撤回;
|
||||
2.12.1.查看:点击跳转还车应结款-查看页面;
|
||||
2.12.2.生成账单:点击生成账单,弹框显示账单界面,审批状态为待审批的记录才可生成账单,其他状态生成账单隐藏;
|
||||
2.12.3.费用明细:点击跳转还车应结款-费用明细页面,审批状态为待审批、审批中、审批完成时,不显示费用明细;
|
||||
2.12.4.撤回:审批状态为审批中时,不显示费用明细点击二次确认,提示:是否确认撤回,点击确定后,提示:撤回成功,同时审批状态修改为撤回;
|
||||
2.13.右下角为分页符,支持单页查看数据条数;
|
||||
109
web端/需求说明/财务管理/还车应结款-费用明细
Normal file
109
web端/需求说明/财务管理/还车应结款-费用明细
Normal file
@@ -0,0 +1,109 @@
|
||||
还车应结款
|
||||
一个「数字化资产ONEOS运管平台」中的「还车应结款」「费用明细」模块
|
||||
#面包屑:
|
||||
1.财务管理-还车应结款-费用明细
|
||||
每个模块为一个单独卡片:
|
||||
|
||||
2.还车车辆明细;
|
||||
2.1.车牌号:显示还车车牌号;
|
||||
2.2.合同编码:显示还车车辆合同编码;
|
||||
2.3.项目名称:显示还车车辆对应项目名称;
|
||||
2.4.客户名称:显示还车车辆对应客户名称;
|
||||
2.5.交车时间:显示还车车辆完成交车时间;
|
||||
2.6.还车时间:显示还车车辆完成还车时间;
|
||||
2.7.易损保:显示还车车辆是否购买易损保,显示为是或否,后方为提示图标,文案为:提供刹车片、灯泡、蓄电池、雨刮等易损件租期内免费更换服务(不含轮胎,只限自行到服务站更换);
|
||||
2.8.轮胎保:显示还车车辆是否购买轮胎保,显示为是或否,后方为提示图标,文案为:每个租赁年度内提供1次车辆轮胎因自然磨损产生的替换服务;
|
||||
2.9.养护保:显示还车车辆是否购买养护保,显示为是或否,后方为提示图标,文案为:按厂家保养要求提供定期保养服务;
|
||||
|
||||
|
||||
3.还车费用明细:
|
||||
#上方为还车费用统计数据,包括保证金总额、待结算总额、应退还总额、应补缴总额等相关信息,该部分只有业务管理组能查看;
|
||||
3.1.保证金总额:显示该车辆保证金金额,格式为xx.xx元;
|
||||
3.2.待结算总额:显示该车辆待结算金额,格式为xx.xx元,点击以气泡卡片列表显示:费用项、金额。计算方式为:「业务服务部所有费用项-金额总和」+「业务服务部-车辆应退租金」+「能源采购组-氢量差补缴金额」+「能源采购组-氢费补缴金额」+「能源采购组-电费补缴金额」-「能源采购组-预付款退费金额」+「运维部所有费用项-金额总额」;
|
||||
3.3.应退还总额:显示该车辆应退还金额,格式为xx.xx元,点击以气泡卡片列表显示:费用项、金额。计算方式为:「保证金金额」-「待结算金额」如果是正数,则显示在应退还总额中;
|
||||
3.4.应补缴总额:显示该车辆应补缴金额,格式为xx.xx元,点击以气泡卡片列表显示:费用项、金额。计算方式为:「保证金金额」-「待结算金额」如果是负数,则显示在应补缴总额中;
|
||||
|
||||
#下方为业务服务组、能源采购组、运维部、安全组4部分,支持展开/收起功能;
|
||||
4.业务服务组:仅由业务服务组人员进行填写;标题栏标题为业务服务组,后方为:总金额、提交人、状态(待提交、已提交),该部分只有业务管理组能查看;
|
||||
4.1.总金额:显示所有费用总金额;
|
||||
4.2.提交人:显示提交人;
|
||||
4.3.状态:分为待提交(点击保存按钮)、已提交(点击提交按钮);
|
||||
4.4.保存:点击保存已填写内容,如果已提交,则隐藏保存按钮;
|
||||
4.5.提交:点击进行二次确认,提示信息:请确认业务服务组金额填写无误,点击确认完成提交。如果已提交,隐藏提交按钮;
|
||||
4.6.撤回:已提交后,显示撤回按钮。点击进行二次确认,提示信息:是否确认撤回,点击确认,重新显示保存、提交按钮;
|
||||
|
||||
下方为列表,列表字段为:序号、费用项、金额、备注、照片、附件、操作;
|
||||
4.7.序号:1、2、3....依次类推;
|
||||
4.8.费用项:固定显示违章处理违约金、保险上浮、ETC-客户未缴费用、ETC卡缺损费、ETC设备缺损费,该部分不能删除;可通过点击下方新增一行,添加新的条目(该部分可删除);
|
||||
4.8.1.违章处理违约金:
|
||||
4.8.2.保险上浮:
|
||||
4.8.3.ETC-客户未缴费用:
|
||||
4.8.4.ETC卡缺损费:
|
||||
4.8.5.ETC设备缺损费:
|
||||
4.9.金额:输入框,支持2位小数,后缀为元;
|
||||
4.10.备注:文本域,支持自定义输入;
|
||||
4.11.最后更新时间:显示最后更新时间,格式为:YYYY-MM-DD HH:MM;
|
||||
4.12.照片:照片上传按钮,支持照片上传,支持多个附件;
|
||||
4.13.附件:附件上传按钮,文案为:上传附件,支持多个附件;
|
||||
4.14.操作:除固定费用项外,其余操作中为删除;
|
||||
列表下方为车辆租金:包含本期账单已收租金、车辆实际租金、车辆应退租金;
|
||||
4.15.本期账单已收租金:输入框(禁用),反写本期账单实际到账租金(从租赁账单中,首期由于未和用友YS打通,先显示为0,对接完成后显示财务实际到账金额);
|
||||
4.16.车辆实际租金:输入框(可编辑),自动计算车辆实际租金,按照:(车辆月租金/30)*(账单开始日期-还车日期总天数,取整)
|
||||
4.17.车辆应退租金:输入框(可编辑),根据:本期账单已收租金-车辆实际租金计算并反写;
|
||||
|
||||
5.能源采购组:仅由能源采购组人员进行填写;标题栏标题为能源采购组,后方为总金额、提交人、状态(待提交、已提交),该部分只有能源采购组能查看;
|
||||
5.1.总金额:显示所有费用总金额;
|
||||
5.2.提交人:显示提交人;
|
||||
5.3.状态:分为待提交(点击保存按钮)、已提交(点击提交按钮);
|
||||
5.4.保存:点击保存已填写内容,如果已提交,则隐藏保存按钮;
|
||||
5.5.提交:点击进行二次确认,提示信息:请确认业务服务组金额填写无误,点击确认完成提交。如果已提交,隐藏提交按钮;
|
||||
5.6.撤回:已提交后,显示撤回按钮。点击进行二次确认,提示信息:是否确认撤回,点击确认,重新显示保存、提交按钮;
|
||||
下方为填写表单:
|
||||
5.7.氢量差补缴金额:输入框,支持2位小数,后缀为元,如果交车氢量>还车氢量时,根据“差额*退还车氢气单价”自动计算。
|
||||
5.8.交车氢量:格式为:xx.xxMPa,保留2位小数,从该车辆交车在该合同交车时间氢量获取;
|
||||
5.9.还车氢量:格式为:xx.xxMPa,保留2位小数,从该车辆交车在该合同还车时间氢量获取;
|
||||
5.10.退还车氢气单价:xx.xx元,从车辆租赁合同中获取;
|
||||
5.11.能源费补缴金额:右侧为2个输入框,分别为氢费补缴金额、电费补缴金额,支持2位小数,后缀为元;
|
||||
5.12.预付款退费金额:输入框,支持2位小数,后缀为元;输入框下方显示项目预充值余额;
|
||||
|
||||
6.运维部:仅由运维部人员进行填写;标题栏标题为运维部,后方为总金额、提交人、状态(待提交、已提交),该部分只有运维部能查看;
|
||||
6.1.总金额:显示运维部所有费用项费用金额总额;
|
||||
6.2.提交人:显示提交人;
|
||||
6.3.状态:分为待提交(点击保存按钮)、已提交(点击提交按钮);
|
||||
6.4.保存:点击保存已填写内容,如果已提交,则隐藏保存按钮;
|
||||
6.5.提交:点击进行二次确认,提示信息:请确认业务服务组金额填写无误,点击确认完成提交。如果已提交,隐藏提交按钮;
|
||||
6.6.撤回:已提交后,显示撤回按钮。点击进行二次确认,提示信息:是否确认撤回,点击确认,重新显示保存、提交按钮;
|
||||
下方为列表:
|
||||
6.7.序号:1、2、3....依次类推;
|
||||
6.8.费用项:固定显示项为:
|
||||
6.8.1.清洗费:输入框手动填写;
|
||||
6.8.2.未结算保养:输入框手动填写;
|
||||
6.8.3.未结算维修:输入框手动填写;
|
||||
6.8.4.车损费用:输入框手动填写;
|
||||
6.8.5.工具损坏丢失费用:输入框手动填写;
|
||||
6.8.6.证件丢失费用:输入框,自动计算金额,交车时做备车检查时有,还车时无得证件自动计算费用,计算标准为:行驶证:200元,牌照:300元,营运证:300元,加氢证:300元;如所有证件无丢失,或交车时就为无则不进行计算;
|
||||
6.8.7.广告损坏丢失费用:输入框手动填写;
|
||||
6.8.8.送车服务费:输入框(禁用),反写交车时送车服务费,如无则显示为0;
|
||||
6.8.9.接车服务费:输入框(禁用),反写还车时接车服务费,如无则显示为0;
|
||||
6.8.10.轮胎磨损费用:输入框(可编辑),轮胎磨损费用(元/mm)*(交车时所有轮胎及备胎胎纹深度-还车时所有轮胎及备胎胎纹深度)自动计算,此外如还车胎纹总额大于交车胎纹总额则不进行计算,此外轮胎磨损费用标题后增加说明图标,悬浮图标显示气泡卡片:卡片内为列表,标题为:轮胎名称、交车胎纹深度、还车胎纹深度、胎纹差、单价、总金额,每一行为一个单独的轮胎(包括备胎);
|
||||
该部分不能删除;可通过点击下方新增一行,添加新的条目(该部分可删除);
|
||||
|
||||
6.9.金额:输入框,支持2位小数,后缀为元;
|
||||
6.10.无忧包减免:输入框,支持2位小数,后缀为元。仅有三个费用项后有输入框,用户未购买易损保时,未结算维修费用后该输入框禁用,用户未购买轮胎保时间,轮胎磨损费用后该输入框为禁用,用户未购买养护保时,未结算保养后该输入框为禁用;
|
||||
6.11.备注:文本域,支持自定义输入;
|
||||
6.12.照片:照片上传按钮,支持照片上传;
|
||||
6.13.附件:附件上传按钮,文案为:上传附件;
|
||||
6.14.操作:除固定费用项外,其余操作中为删除;
|
||||
|
||||
7.安全组:仅由安全组人员进行确认提交;标题栏标题为安全组,后方为提交人、状态(待提交、已提交),该部分只有安全组能查看;
|
||||
7.1.提交人:显示提交人;
|
||||
7.2.状态:分为待提交(点击保存按钮)、已提交(点击提交按钮);
|
||||
7.3.保存:点击保存已填写内容,如果已提交,则隐藏保存按钮;
|
||||
7.4.提交:点击进行二次确认,提示信息:请确认业务服务组金额填写无误,点击确认完成提交。如果已提交,隐藏提交按钮;
|
||||
7.5.撤回:已提交后,显示撤回按钮。点击进行二次确认,提示信息:是否确认撤回,点击确认,重新显示保存、提交按钮;
|
||||
7.6.违章清单:违章编码、车牌号、违法行为、违法时间、罚款金额、缴费状态、计分值、是否处理、违章客户、违章照片、备注;
|
||||
7.7.事故清单:列表显示:事故编码、车牌号、事故时间、事故地点、事故类型、客户名称、我方定损金额、对方定损金额、责任划分、事故状态、结案时间、其他费用、备注;
|
||||
|
||||
8.底部为提交、取消按钮;
|
||||
8.1.提交审核:还车应结款生成后有15天时间倒计时,倒计时结束并且业务服务组、能源采购组、运维部、安全部均提交后才启用,平时禁用,倒计时显示在按钮上,格式为:x天x小时后可提交审核;
|
||||
8.2.取消:点击返回还车应结款列表页;
|
||||
34
web端/需求说明/运维管理-车务管理/交车单-查看
Normal file
34
web端/需求说明/运维管理-车务管理/交车单-查看
Normal file
@@ -0,0 +1,34 @@
|
||||
交车单查看
|
||||
一个「数字化资产ONEOS运管平台」中的「交车管理-查看」模块
|
||||
1.面包屑:
|
||||
1.1.运维管理-车辆业务-交车管理-查看交车信息
|
||||
|
||||
2.表单:
|
||||
分为项目详情、交车明细、签章文件卡片3部分;
|
||||
2.1.项目详情为表单结构:
|
||||
2.1.1.项目名称:输入框禁用,显示该交车单对应车辆租赁合同中项目名称;
|
||||
2.1.2.合同编码:输入框禁用,显示该交车单对应车辆租赁合同中项目合同编码;
|
||||
2.1.2.客户名称:输入框禁用,显示该交车单对应车辆租赁合同中客户名称;
|
||||
2.1.3.预计交车日期:日期选择器禁用,显示该交车单预计交车日期,分为单日和日期区间两种,格式为:YYYY-MM-DD、YYYY-MM-DD至YYYY-MM-DD;
|
||||
2.1.3.交车区域:输入框禁用,显示该交车单对应车辆租赁合同中交车区域;
|
||||
2.1.4.交车地点:输入框禁用,显示该交车单对应车辆租赁合同中交车地点;
|
||||
2.1.5.业务部门:输入框禁用,显示该交车单对应车辆租赁合同中业务部门;
|
||||
2.1.6.业务负责人:输入框禁用,显示该交车单对应车辆租赁合同中业务负责人;
|
||||
2.2.交车明细:
|
||||
交车明细为列表结构;
|
||||
2.2.1.车辆类型:选择器禁用,显示该交车单对应车辆租赁合同中车辆类型;
|
||||
2.2.2.品牌:选择器禁用,显示该交车单对应车辆租赁合同中品牌;
|
||||
2.2.3.型号:选择器禁用,显示该交车单对应车辆租赁合同中型号;
|
||||
2.2.4.车牌号:选择器禁用,显示该交车单对应车辆租赁合同中车牌号;
|
||||
2.2.5.停车场:选择器禁用,显示该车辆交车时对应停车场;
|
||||
2.2.6.实际交车日期:日期选择器禁用,显示该交车单实际交车日期,精确至日,格式为:YYYY-MM-DD;
|
||||
2.2.7.交车人:输入框禁用,显示该交车单中该车辆实际交车人员(注意这里是交车单中某辆车的交车人员,一个交车单可同时多个交车人员操作同时交多辆车,而不是最终提交整个交车单的人员);
|
||||
2.2.8.完成时间:日期选择器禁用,显示该车辆完成交车时间(注意这里是完成交车,而不是完成交车单);
|
||||
2.2.9.司机姓名:显示交车时对应司机姓名;
|
||||
2.2.10.司机身份证:显示交车时对应司机身份证号码;
|
||||
2.2.11.司机手机号:显示交车时对应司机手机号;
|
||||
|
||||
2.3.签章文件:
|
||||
2.3.1.最终授权人:显示最终签字授权人姓名,由小程序交车单最终提交时选择授权人;
|
||||
2.3.2.签章文件:点击查看该交车单对应签章文件信息;
|
||||
2.3.3.授权人签字时间:显示授权人通过E签宝签字的完成时间;
|
||||
31
web端/需求说明/运维管理-车务管理/交车明细-查看
Normal file
31
web端/需求说明/运维管理-车务管理/交车明细-查看
Normal file
@@ -0,0 +1,31 @@
|
||||
4.交车明细-查看:
|
||||
点击时弹层,弹层中表单显示一下内容:
|
||||
4.1.车牌号:必选项,选择器,支持从输入框内输入内容进行模糊搜索,默认拉取「车牌管理」中所有「车牌号」;
|
||||
4.2.车辆类型:输入框禁用,选择车牌号后自动拉取该车牌号对应「车辆类型」;
|
||||
4.3.品牌:输入框禁用,选择车牌号后自动拉取该车牌号对应「品牌」;
|
||||
4.4.型号:输入框禁用,选择车牌号后自动拉取该车牌号对应「型号」
|
||||
4.5.车辆识别代码:输入框禁用,选择车牌号后自动拉取该车牌号「车辆识别代码」;
|
||||
4.6.车身广告及放大字:必选项,开关,选择车辆后拉取该车辆「后装设备」「车身广告」,如果该车辆有「车身广告」则勾选为开,如果无则勾选为无。同时如果手动进行操作,会同步到「后装设备」「车身广告」中,安装时间以该条备车记录提交成功为准,不够选择广告照片和放大字照片字段隐藏不显示;
|
||||
4.7.广告照片:必填项,图片上传,最多支持上传1张图片,支持主流照片格式。上传后,上传按钮切换为显示已上传图片缩略图,支持点击预览和删除,删除后,切换为上传按钮,从备车记录自动反写;
|
||||
4.8.放大字照片:必填项,图片上传,最多支持上传1张图片,支持主流照片格式。上传后,上传按钮切换为显示已上传图片缩略图,支持点击预览和删除,删除后,切换为上传按钮,从备车记录自动反写;
|
||||
4.8.尾板:开关,选择车辆后拉取该车辆「后装设备」「尾板」,如果该车辆有「尾板」则勾选为开,如果无则勾选为无。同时如果手动进行操作,会同步到「后装设备」「尾板」中,安装时间以该条备车记录提交成功为准;
|
||||
4.9.备胎照片:图片上传,最多支持上传1张图片,支持主流照片格式。上传时弹出卡片,提示正在识别中,请勿关闭页面,之后卡片左侧显示备胎照片,右侧输入框显示识别出的胎纹深度,后缀单位为mm,点击卡片中确认按钮,反写至备胎胎纹深度字段下;
|
||||
4.10.备胎胎纹深度:输入框,反写备胎照片OCR识别胎纹深度结果,支持修改;
|
||||
4.11.驾驶培训:附件上传按钮,上传司机现场培训二维码图片,识别成功后驾驶培训后方显示:已完成视频培训;
|
||||
4.12.司机证照:根据司机链接中的司机证照正面及反面照片及驾驶证、从业资格证照片显示;
|
||||
4.13.交车里程:必填项,输入框,单位为公里;
|
||||
4.14.交车电量:必填项,输入框,单位为kWh;
|
||||
4.15.交车氢量:必填项,输入框,单位为%或MPa,根据型号参数中该车型实际仪表盘单位显示;
|
||||
4.16.送车服务费:选填项,输入框,精确至2位小数,格式为xx.xx元;
|
||||
4.17.车辆检查:按钮,文字为备车检查单,点击右侧展开抽屉,抽屉内显示列表,字段为类别、检查项目、选择、备注;
|
||||
2.11.1.类别:分为车灯、仪表盘、驾驶室、轮胎、液位检查、外观检查、车辆外观、其他、随车工具、随车证件、整车、燃料电池系统、冷机、制动系统;
|
||||
2.11.2.检查项目:车灯类别对应(大灯、转向灯、小灯、示廓灯、刹车灯、倒车灯、牌照灯、防雾灯、室内灯)、仪表盘对应(氢系统指示、电控系统指示、数值清晰准确、故障报警灯)、驾驶室对应(点烟器、车窗升降、按键开关、雨刮器、内后视镜是否正常、内/外门把手、安全带、空调冷暖风、仪表盘、门锁功能、手刹、车钥匙功能是否正常、喇叭、音响功能、遮阳板、主副驾座椅、方向盘、内饰干净整洁)
|
||||
2.11.3.检查情况:其他项为开关,在检查项目每项后方显示,默认为开,可手动进行关闭,轮胎为输入框,提示请输入胎纹深度;
|
||||
2.11.4.备注:输入框;
|
||||
|
||||
4.交车照片:多个模块分3列显示,由照片标题和照片上传按钮组成;
|
||||
4.1.车辆:仪表盘、车辆正面、车辆左前方、车辆左后方、车辆右后方、车辆右前方;
|
||||
4.2.底盘:正前方底部、左侧前方底部、左侧后方底部、正后方底部、右侧后方底部、右侧前方底部;
|
||||
4.3.轮胎:左前轮、左后轮(内)、左后轮(外)、右前轮、右后轮(内)、右后轮(外)、备胎;
|
||||
4.4.瑕疵:照片上传按钮,最多支持4张照片;
|
||||
4.5.其他:照片上传按钮,最多支持4张照片;
|
||||
35
web端/需求说明/运维管理-车务管理/交车管理-交车单
Normal file
35
web端/需求说明/运维管理-车务管理/交车管理-交车单
Normal file
@@ -0,0 +1,35 @@
|
||||
交车管理-交车单
|
||||
一个「数字化资产ONEOS运管平台」中的「交车管理-交车单」模块
|
||||
1.面包屑:
|
||||
1.1.运维管理-车辆业务-交车管理-交车单
|
||||
每个模块为一个单独卡片:
|
||||
|
||||
2.项目详情:表单结构:
|
||||
2.1.项目名称:输入框禁用,显示该交车单对应车辆租赁合同中项目名称;
|
||||
2.2.合同编码:输入框禁用,显示该交车单对应车辆租赁合同中项目合同编码;
|
||||
2.3.客户名称:输入框禁用,显示该交车单对应车辆租赁合同中客户名称;
|
||||
2.4.预计交车日期:日期选择器禁用,显示该交车单预计交车日期,分为单日和日期区间两种,格式为:YYYY-MM-DD、YYYY-MM-DD至YYYY-MM-DD;
|
||||
2.5.交车区域:输入框禁用,显示该交车单对应车辆租赁合同中交车区域;
|
||||
2.6.交车地点:输入框禁用,显示该交车单对应车辆租赁合同中交车地点;
|
||||
2.7.业务部门:输入框禁用,显示该交车单对应车辆租赁合同中业务部门;
|
||||
2.8.业务负责人:输入框禁用,显示该交车单对应车辆租赁合同中业务负责人;
|
||||
|
||||
3.交车明细:列表结构;
|
||||
3.1.序号:与车辆租赁合同中序号对应,显示该交车任务所有车辆序号;
|
||||
3.2.品牌:选择器禁用,显示该交车单对应车辆租赁合同中品牌;
|
||||
3.3.型号:选择器禁用,显示该交车单对应车辆租赁合同中型号;
|
||||
3.4.车牌号:已交车车辆显示车牌号,未交车车辆显示-;
|
||||
3.5.实际交车日期:日期选择器禁用,显示该交车单实际交车日期,精确至日,格式为:YYYY-MM-DD;
|
||||
3.6.交车人:输入框禁用,显示该交车单中该车辆实际交车人员(注意这里是交车单中某辆车的交车人员,一个交车单可同时多个交车人员操作同时交多辆车,而不是最终提交整个交车单的人员);
|
||||
3.7.交车状态:已完成、待提交、已签章;
|
||||
3.7.1.已完成:已完成交车提交,但还未完成被授权人签章;
|
||||
3.7.2.待提交:仅点击保存,未完成交车提交;
|
||||
3.7.3.已签章:已完成交车提交并完成被授权人签章;
|
||||
3.8.操作:查看、编辑、下载签章文件;
|
||||
3.8.1.查看:点击跳转该车辆交车明细;
|
||||
3.8.2.编辑:交车状态为待提交时显示,点击弹出卡片至交车明细编辑页;
|
||||
3.8.3.下载签章文件:交车完成并完成被授权人签章时显示,点击下载签章文件;
|
||||
|
||||
4.底部为提交、取消按钮;
|
||||
4.1.提交:提交按钮必须所有车辆交车状态为已签章时才可提交,否则为禁用状态;
|
||||
4.2.取消:点击返回交车管理列表页;
|
||||
42
web端/需求说明/运维管理-车务管理/交车管理-交车单-编辑
Normal file
42
web端/需求说明/运维管理-车务管理/交车管理-交车单-编辑
Normal file
@@ -0,0 +1,42 @@
|
||||
交车管理-交车单-编辑
|
||||
一个「数字化资产ONEOS运管平台」中的「交车管理-交车单-编辑」模块
|
||||
1.面包屑:
|
||||
1.1.运维管理-车辆业务-交车管理-交车单-编辑
|
||||
每个模块为一个单独卡片:
|
||||
|
||||
2.交车明细:列表结构;
|
||||
2.1.车辆类型:输入框(禁用),根据车牌号反写车辆类型;
|
||||
2.2.品牌:输入框(禁用),根据车牌号反写品牌;
|
||||
2.2.型号:输入框(禁用),根据车牌号反写型号;
|
||||
2.3.车牌号:选择器,输入框,支持输入内容下拉模糊匹配选项,仅能选择备车库中车辆;
|
||||
2.4.车辆识别代码:输入框(禁用),根据车牌号反写车辆识别代码;
|
||||
2.5.车身广告及放大字:必选项,开关,选择车辆后拉取该车辆「后装设备」「车身广告」,如果该车辆有「车身广告」则勾选为开,如果无则勾选为无。同时如果手动进行操作,会同步到「后装设备」「车身广告」中, 安装时间以该条备车记录提交成功为准,不够选择广告照片和放大字照片字段隐藏不显示;
|
||||
2.6.广告照片:必填项,图片上传,最多支持上传1张图片,支持主流照片格式。上传后,上传按钮切换为显示已上传图片缩略图,支持点击预览和删除,删除后,切换为上传按钮,从备车记录自动反写;
|
||||
2.7.放大字照片:必填项,图片上传,最多支持上传1张图片,支持主流照片格式。上传后,上传按钮切换为显示已上传图片缩略图,支持点击预览和删除,删除后,切换为上传按钮,从备车记录自动反写;
|
||||
2.8.尾板:必填项,开关,选择车辆后拉取该车辆「后装设备」「尾板」,如果该车辆有「尾板」则勾选为开,如果无则勾选为无。同时如果手动进行操作,会同步到「后装设备」「尾板」中,安装时间以该条备车记录提交成功为准;
|
||||
2.9.备胎照片:必填项,图片上传,最多支持上传1张图片,支持主流照片格式。上传时弹出卡片,提示正在识别中,请勿关闭页面,之后卡片左侧显示备胎照片,右侧输入框显示识别出的胎纹深度,后缀单位为mm,点击卡片中确认按钮,反写至备胎胎纹深度字段下;
|
||||
2.10.备胎胎纹深度:必填项,输入框,反写备胎照片OCR识别胎纹深度结果,支持修改;
|
||||
2.11.驾驶培训:必填项,附件上传按钮,后方为提示信息:请上传司机现场培训二维码图片。识别成功后隐藏驾驶培训按钮和提示,显示:已完成视频培训;
|
||||
2.12.司机证照:显示4张照片、身份证(正面)、身份证(反面)、驾驶证、从业资格证,根据驾驶培训上传二维码识别后自动反写;
|
||||
2.13.交车里程:必填项,输入框,单位为公里;
|
||||
2.14.交车电量:必填项,输入框,单位为kWh;
|
||||
2.15.交车氢量:必填项,输入框,单位为%或MPa,根据型号参数中该车型实际仪表盘单位显示;
|
||||
2.16.送车服务费:选填项,输入框,精确至2位小数,格式为xx.xx元;
|
||||
2.17.车辆检查:按钮,文字为交车检查单,点击右侧展开抽屉,抽屉内显示列表,字段为类别、检查项目、检查情况、备注;
|
||||
2.11.1.类别:分为车灯、仪表盘、驾驶室、轮胎、液位检查、外观检查、车辆外观、其他、随车工具、随车证件、整车、燃料电池系统、冷机、制动系统;
|
||||
2.11.2.检查项目:车灯类别对应(大灯、转向灯、小灯、示廓灯、刹车灯、倒车灯、牌照灯、防雾灯、室内灯)、仪表盘对应(氢系统指示、电控系统指示、数值清晰准确、故障报警灯)、驾驶室对应(点烟器、车窗升降、按键开关、雨刮器、内后视镜是否正常、内/外门把手、安全带、空调冷暖风、仪表盘、门锁功能、手刹、车钥匙功能是否正常、喇叭、音响功能、遮阳板、主副驾座椅、方向盘、内饰干净整洁)
|
||||
2.11.3.检查情况:其他项为开关,在检查项目每项后方显示,从备车记录反写,可手动进行关闭,轮胎为输入框,提示请输入胎纹深度;
|
||||
2.11.4.备注:输入框;
|
||||
|
||||
3.交车照片:多个模块分3列显示,由照片标题和照片上传按钮组成,照片点击上传,从本地文件上传单张图片,上传成功后可通过图片右上角删除按钮删除,点击图片可放大预览;;
|
||||
3.1.车辆:必填项,包括仪表盘、车辆正面、车辆左前方、车辆左后方、车辆右后方、车辆右前方;
|
||||
3.2.底盘:必填项,包括正前方底部、左侧前方底部、左侧后方底部、正后方底部、右侧后方底部、右侧前方底部;
|
||||
3.3.轮胎:必填项,包括左前轮、左后轮(内)、左后轮(外)、右前轮、右后轮(内)、右后轮(外)、备胎;
|
||||
3.4.瑕疵:必填项,包括照片上传按钮,最多支持4张照片;
|
||||
3.5.其他:必填项,包括照片上传按钮,最多支持4张照片;
|
||||
照片点击上传,从本地文件上传单张图片,上传成功后可通过图片右上角删除按钮删除,点击图片可放大预览;
|
||||
|
||||
4.底部为提交、取消按钮;
|
||||
4.1.提交:点击进行二次确认,点击确认完成该车辆交车;
|
||||
4.2.保存:点击暂存交车单(不做校验);
|
||||
4.2.取消:点击返回交车单页;
|
||||
Reference in New Issue
Block a user