Files
ONE-OS/web端/运维管理/车辆业务/交车管理.jsx
王冕 d432d51eed feat(web): 同步 web 端目录更新至 Gitea
包含加氢站站点信息、运维交车/故障、台账与数据分析等页面新增与改动。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-04 19:57:30 +08:00

2988 lines
138 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 【重要】必须使用 const Component 作为组件变量名
// 车辆业务 - 交车管理ONEOS运管平台布局参照新增租赁合同
var DV_KPI_STYLE = ''
+ '.dv-kpi-stats-row{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-bottom:16px;}'
+ '@media (max-width:768px){.dv-kpi-stats-row{grid-template-columns:repeat(1,minmax(0,1fr));}}'
+ '.lc-alert-card{display:flex;align-items:flex-start;gap:12px;padding:14px 30px 14px 16px;border-radius:12px;border:1px solid #e2e8f0;background:#fff;position:relative;overflow:hidden;min-width:0;}'
+ '.lc-alert-card-main{flex:1;min-width:0;}'
+ '.lc-alert-card-icon{flex-shrink:0;width:40px;height:40px;border-radius:10px;display:flex;align-items:center;justify-content:center;}'
+ '.lc-alert-card-val{font-size:26px;font-weight:800;line-height:1.1;color:#0f172a;font-variant-numeric:tabular-nums;}'
+ '.lc-alert-card-title{font-size:13px;font-weight:600;color:#334155;margin-top:2px;}'
+ '.lc-alert-card-tip-anchor{position:absolute;top:8px;right:8px;z-index:2;line-height:0;}'
+ '.lc-alert-card-tip{width:18px;height:18px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;color:#94a3b8;background:rgba(255,255,255,.92);border:1px solid #e2e8f0;cursor:help;line-height:0;}'
+ '.lc-alert-card-tip:hover{color:#64748b;border-color:#cbd5e1;background:#fff;}'
+ '.lc-alert-card--total{background:linear-gradient(135deg,#f8fafc 0%,#fff 100%);}'
+ '.lc-alert-card--total .lc-alert-card-icon{background:#e2e8f0;color:#475569;}'
+ '.lc-alert-card--progress{background:linear-gradient(135deg,#fff7ed 0%,#fff 55%);border-color:#fed7aa;}'
+ '.lc-alert-card--progress .lc-alert-card-icon{background:#ffedd5;color:#ea580c;}'
+ '.lc-alert-card--progress .lc-alert-card-val{color:#c2410c;}'
+ '.lc-alert-card--completed{background:linear-gradient(135deg,#ecfdf5 0%,#fff 55%);border-color:#bbf7d0;}'
+ '.lc-alert-card--completed .lc-alert-card-icon{background:#d1fae5;color:#059669;}'
+ '.lc-alert-card--completed .lc-alert-card-val{color:#047857;}'
+ '.lc-alert-card-clickable{cursor:pointer;transition:box-shadow .2s ease,border-color .2s ease,transform .2s ease;}'
+ '.lc-alert-card-clickable:hover{box-shadow:0 4px 14px rgba(15,23,42,.08);}'
+ '.lc-alert-card-active{box-shadow:0 0 0 2px rgba(22,93,255,.2)!important;border-color:#165dff!important;}';
var DV_KPI_ICONS = {
total: React.createElement('svg', { width: 18, height: 18, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round' },
React.createElement('rect', { x: 3, y: 3, width: 7, height: 7 }), React.createElement('rect', { x: 14, y: 3, width: 7, height: 7 }),
React.createElement('rect', { x: 14, y: 14, width: 7, height: 7 }), React.createElement('rect', { x: 3, y: 14, width: 7, height: 7 })),
progress: React.createElement('svg', { width: 18, height: 18, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round' },
React.createElement('circle', { cx: 12, cy: 12, r: 10 }), React.createElement('polyline', { points: '12 6 12 12 16 14' })),
completed: React.createElement('svg', { width: 18, height: 18, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round' },
React.createElement('path', { d: 'M22 11.08V12a10 10 0 1 1-5.93-9.14' }), React.createElement('polyline', { points: '22 4 12 14.01 9 11.01' }))
};
var DV_KPI_TIP_SVG = React.createElement('svg', { width: 12, height: 12, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2.2, strokeLinecap: 'round', strokeLinejoin: 'round' },
React.createElement('circle', { cx: 12, cy: 12, r: 10 }), React.createElement('line', { x1: 12, y1: 16, x2: 12, y2: 12 }), React.createElement('line', { x1: 12, y1: 8, x2: 12.01, y2: 8 }));
function formatDeliveryRegion(region) {
if (!region || region === '-') return '-';
var r = String(region).trim();
if (r.indexOf('-') === -1) {
var m = r.match(/^(.+?(?:省|市|自治区|特别行政区))(.+)$/);
if (m && m[2]) r = m[1] + '-' + m[2];
}
return r;
}
function parseDeliveryRegionParts(region) {
if (!region || region === '-') return { province: '', city: '', raw: '' };
var r = String(region).trim();
var idx = r.indexOf('-');
if (idx >= 0) {
return { province: r.slice(0, idx).trim(), city: r.slice(idx + 1).trim(), raw: r };
}
if (r.indexOf('省') >= 0) return { province: r, city: '', raw: r };
if (r.indexOf('市') >= 0) return { province: '', city: r, raw: r };
return { province: r, city: '', raw: r };
}
function regionCityMatch(cityA, cityB) {
if (!cityA || !cityB) return false;
return cityA === cityB || cityA.indexOf(cityB) >= 0 || cityB.indexOf(cityA) >= 0;
}
/** 运维人员区域权限是否覆盖目标区域(省-市) */
function matchRegionPermission(permissions, targetRegion) {
var target = parseDeliveryRegionParts(formatDeliveryRegion(targetRegion));
if (!target.province && !target.city) return true;
var perms = permissions || [];
for (var i = 0; i < perms.length; i++) {
var perm = String(perms[i] || '').trim();
if (!perm) continue;
if (perm.indexOf('-') >= 0) {
var scoped = parseDeliveryRegionParts(perm);
if (scoped.province && target.province && scoped.province !== target.province) continue;
if (scoped.city) {
if (regionCityMatch(target.city, scoped.city)) return true;
continue;
}
if (scoped.province && scoped.province === target.province) return true;
continue;
}
if (perm.indexOf('省') >= 0) {
if (target.province === perm) return true;
continue;
}
if (regionCityMatch(target.city, perm)) return true;
}
return false;
}
var DV_RESERVE_VEHICLE_STATUS_READY = '已备车';
function getOperatorRegionPermissions() {
if (typeof window !== 'undefined' && window.DV_MOCK_OPERATOR_REGION_PERMISSIONS && window.DV_MOCK_OPERATOR_REGION_PERMISSIONS.length) {
return window.DV_MOCK_OPERATOR_REGION_PERMISSIONS.slice();
}
return ['浙江省-嘉兴市'];
}
function filterRowsByOperatorRegion(rows) {
var perms = getOperatorRegionPermissions();
return (rows || []).filter(function (row) {
return matchRegionPermission(perms, row.deliveryRegion);
});
}
function filterSelectableReserveVehicles(vehicles, operatorPermissions) {
var perms = operatorPermissions || getOperatorRegionPermissions();
return (vehicles || []).filter(function (vehicle) {
if ((vehicle.vehicleStatus || '') !== DV_RESERVE_VEHICLE_STATUS_READY) return false;
return matchRegionPermission(perms, vehicle.parkingRegion);
});
}
function isDeliverySignedStatus(status) {
return status === '客户已签章' || status === '已签章';
}
function buildDeliverySignFileName(record) {
var plate = (record.plateNo && String(record.plateNo).trim()) ? String(record.plateNo).trim() : '车牌待选';
var orderId = record.orderId != null ? String(record.orderId) : 'unknown';
var vehicleKey = record.vehicleKey != null ? String(record.vehicleKey) : '';
return '交车签章文件_' + orderId + (vehicleKey ? '_' + vehicleKey : '') + '_' + plate + '.pdf';
}
function buildDeliverySignFileContent(record) {
var plate = (record.plateNo && String(record.plateNo).trim()) ? String(record.plateNo).trim() : '车牌待选';
return [
'交车签章文件(原型 Mock联调后对接 E 签宝签章 PDF',
'',
'合同编号:' + (record.contractCode || '-'),
'项目名称:' + (record.projectName || '-'),
'客户名称:' + (record.customerName || '-'),
'车牌号:' + plate,
'品牌型号:' + (record.brand || '-') + ' / ' + (record.model || '-'),
'交车人:' + (record.deliveryPerson || '-'),
'完成交车时间:' + (record.deliveryTime || '-'),
'签章状态:客户已签章',
'',
'生成时间:' + new Date().toLocaleString('zh-CN', { hour12: false })
].join('\n');
}
function escapeSignHtml(text) {
return String(text == null ? '-' : text)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
function buildDeliverySignPreviewHtml(record) {
var filename = buildDeliverySignFileName(record);
var plate = (record.plateNo && String(record.plateNo).trim()) ? String(record.plateNo).trim() : '车牌待选';
var rows = [
['文件名称', filename],
['合同编号', record.contractCode || '-'],
['项目名称', record.projectName || '-'],
['客户名称', record.customerName || '-'],
['车牌号', plate],
['品牌型号', (record.brand || '-') + ' / ' + (record.model || '-')],
['交车人', record.deliveryPerson || '-'],
['完成交车时间', record.deliveryTime || '-'],
['签章状态', '客户已签章'],
['签章方', record.customerName || '-'],
['预览时间', new Date().toLocaleString('zh-CN', { hour12: false })]
];
var bodyRows = rows.map(function (row) {
return '<tr><th>' + escapeSignHtml(row[0]) + '</th><td>' + escapeSignHtml(row[1]) + '</td></tr>';
}).join('');
return '<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">'
+ '<title>' + escapeSignHtml(filename) + '</title>'
+ '<style>'
+ 'body{margin:0;background:#f1f5f9;color:#0f172a;font-family:"PingFang SC","Microsoft YaHei",sans-serif;}'
+ '.wrap{max-width:920px;margin:24px auto;padding:0 16px 32px;}'
+ '.card{background:#fff;border:1px solid #e2e8f0;border-radius:12px;box-shadow:0 8px 24px rgba(15,23,42,.06);overflow:hidden;}'
+ '.head{padding:20px 24px;border-bottom:1px solid #f1f5f9;background:linear-gradient(135deg,#ecfdf5 0%,#fff 55%);}'
+ '.badge{display:inline-block;padding:4px 10px;border-radius:999px;background:#16a34a;color:#fff;font-size:12px;font-weight:600;}'
+ 'h1{margin:10px 0 6px;font-size:22px;}'
+ '.sub{color:#64748b;font-size:13px;}'
+ '.body{padding:20px 24px 24px;}'
+ 'table{width:100%;border-collapse:collapse;font-size:14px;}'
+ 'th,td{padding:12px 14px;border-bottom:1px solid #f1f5f9;text-align:left;vertical-align:top;}'
+ 'th{width:140px;color:#64748b;font-weight:600;background:#f8fafc;}'
+ '.note{margin-top:16px;padding:12px 14px;border-radius:8px;background:#fff7ed;border:1px solid #fed7aa;color:#9a3412;font-size:12px;line-height:1.6;}'
+ '</style></head><body><div class="wrap"><div class="card"><div class="head"><span class="badge">E签宝签章文件</span>'
+ '<h1>' + escapeSignHtml(filename) + '</h1><div class="sub">数字化资产 ONEOS · 交车管理 · 签章文件预览(原型 Mock</div></div>'
+ '<div class="body"><table>' + bodyRows + '</table>'
+ '<div class="note">本页为原型预览占位,联调后将展示 E 签宝返回的真实签章 PDF 或在线预览地址。</div>'
+ '</div></div></div></body></html>';
}
function previewDeliverySignFile(record) {
if (!record || !isDeliverySignedStatus(record.deliveryStatus || record.status)) return;
if (typeof window === 'undefined') return;
var html = buildDeliverySignPreviewHtml(record);
var blob = new Blob([html], { type: 'text/html;charset=utf-8' });
var url = URL.createObjectURL(blob);
var opened = window.open(url, '_blank', 'noopener,noreferrer');
if (!opened) {
URL.revokeObjectURL(url);
if (typeof message !== 'undefined' && message.warning) message.warning('请允许浏览器弹出窗口以预览签章文件');
return;
}
if (typeof message !== 'undefined' && message.success) message.success('已在新页面打开签章文件预览');
setTimeout(function () { URL.revokeObjectURL(url); }, 120000);
}
function downloadDeliverySignFile(record) {
if (!record || !isDeliverySignedStatus(record.deliveryStatus || record.status)) return;
var filename = buildDeliverySignFileName(record);
var content = buildDeliverySignFileContent(record);
var blob = new Blob([content], { type: 'application/octet-stream' });
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
if (typeof message !== 'undefined' && message.success) message.success('已开始下载签章文件');
}
// 查看交车单:禁用输入框/选择器可读性增强(背景更淡、文字纯黑)
var DV_VIEW_READONLY_CTRL_CSS = ''
+ '.dv-edit-drawer-view .ant-input[disabled],'
+ '.dv-edit-drawer-view textarea.ant-input[disabled]{'
+ 'color:#000!important;-webkit-text-fill-color:#000!important;'
+ 'background-color:#fafafa!important;border-color:#e5e7eb!important;'
+ 'cursor:default!important;opacity:1!important;'
+ '}'
+ '.dv-edit-drawer-view .ant-input-affix-wrapper-disabled,'
+ '.dv-edit-drawer-view .ant-input-affix-wrapper[disabled]{'
+ 'color:#000!important;background-color:#fafafa!important;'
+ 'border-color:#e5e7eb!important;cursor:default!important;opacity:1!important;'
+ '}'
+ '.dv-edit-drawer-view .ant-input-affix-wrapper-disabled input[disabled],'
+ '.dv-edit-drawer-view .ant-input-affix-wrapper[disabled] input[disabled]{'
+ 'color:#000!important;-webkit-text-fill-color:#000!important;'
+ 'background-color:transparent!important;cursor:default!important;'
+ '}'
+ '.dv-edit-drawer-view .ant-input-affix-wrapper-disabled .ant-input-suffix,'
+ '.dv-edit-drawer-view .ant-input-affix-wrapper[disabled] .ant-input-suffix{'
+ 'color:rgba(0,0,0,.65)!important;'
+ '}'
+ '.dv-edit-drawer-view .ant-select-disabled.ant-select .ant-select-selector{'
+ 'color:#000!important;background-color:#fafafa!important;'
+ 'border-color:#e5e7eb!important;cursor:default!important;opacity:1!important;'
+ '}'
+ '.dv-edit-drawer-view .ant-select-disabled .ant-select-selection-item,'
+ '.dv-edit-drawer-view .ant-select-disabled .ant-select-selection-placeholder{'
+ 'color:#000!important;'
+ '}';
// 使用方式window.DeliveryEditDrawer(props)
function DeliveryEditDrawer(props) {
var useState = React.useState;
var useMemo = React.useMemo;
var useCallback = React.useCallback;
var useRef = React.useRef;
var useEffect = React.useEffect;
var open = props.open;
var record = props.record;
var onClose = props.onClose;
var onSave = props.onSave;
var onSubmit = props.onSubmit;
var readOnly = props.readOnly === true || props.mode === 'view';
var antd = window.antd;
var Drawer = antd.Drawer;
var Button = antd.Button;
var Input = antd.Input;
var Select = antd.Select;
var Switch = antd.Switch;
var Modal = antd.Modal;
var Table = antd.Table;
var Tag = antd.Tag;
var message = antd.message;
function RequiredLabel(text) {
return React.createElement('span', { style: { display: 'inline-flex', alignItems: 'center', gap: 4 } },
React.createElement('span', { style: { color: '#ef4444', fontWeight: 600 } }, '*'),
React.createElement('span', null, text)
);
}
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); }
}
var reserveVehicles = useMemo(function () {
return [
{ plateNo: '浙F80088', vehicleStatus: DV_RESERVE_VEHICLE_STATUS_READY, parkingRegion: '浙江省-嘉兴市', parkingLot: '嘉兴港区氢能停车场', vehicleType: '厢式车', brand: '福田', model: 'BJ1180', vin: 'LJNAU1A2XK7654321', hasAd: false, adPhoto: [], bigWordPhoto: [], hasTailboard: false },
{ plateNo: '浙F88601', vehicleStatus: DV_RESERVE_VEHICLE_STATUS_READY, parkingRegion: '浙江省-嘉兴市', parkingLot: '平湖指定停车场', vehicleType: '4.5吨冷链车', brand: '现代', model: '帕力安牌4.5吨冷链车', vin: 'LNBSCPKB8RR123888', hasAd: false, adPhoto: [], bigWordPhoto: [], hasTailboard: true },
{ plateNo: '浙A10088', vehicleStatus: DV_RESERVE_VEHICLE_STATUS_READY, parkingRegion: '浙江省-杭州市', parkingLot: '未来科技城地下停车场', vehicleType: '城配货车', brand: '福田', model: 'BJ1190', vin: 'LJNAU1A2XK8888001', hasAd: true, adPhoto: [{ uid: 'ad-hz', name: '广告照片.jpg', url: 'https://dummyimage.com/600x400/e2e8f0/475569&text=Ad-HZ' }], bigWordPhoto: [], hasTailboard: false },
{ plateNo: '粤AGP4598', vehicleStatus: DV_RESERVE_VEHICLE_STATUS_READY, parkingRegion: '广东省-广州市', parkingLot: '广州南沙物流园停车场', vehicleType: '4.5吨冷链车', brand: '现代', model: '帕力安牌4.5吨冷链车', vin: 'LNBSCPKB9RR223402', hasAd: false, adPhoto: [], bigWordPhoto: [], hasTailboard: false },
{ plateNo: '川A99999', vehicleStatus: DV_RESERVE_VEHICLE_STATUS_READY, parkingRegion: '四川省-成都市', parkingLot: '成都龙泉驿停车场', vehicleType: '4.5吨冷链车', brand: '现代', model: '帕力安牌4.5吨冷链车', vin: 'LNBSCPKB9RR223999', hasAd: false, adPhoto: [], bigWordPhoto: [], hasTailboard: false },
{ plateNo: '京A12345', vehicleStatus: '备车中', parkingRegion: '浙江省-嘉兴市', parkingLot: '嘉兴测试停车场', vehicleType: '牵引车', brand: '东风', model: 'DFH1180', vin: 'LJNAU1A2XK1234567', hasAd: true, adPhoto: [{ uid: 'ad1', name: '广告照片.jpg', url: 'https://dummyimage.com/600x400/e2e8f0/475569&text=Ad' }], bigWordPhoto: [{ uid: 'bw1', name: '放大字.jpg', url: 'https://dummyimage.com/600x400/e2e8f0/475569&text=BigWord' }], hasTailboard: true },
{ plateNo: '沪A30003', vehicleStatus: DV_RESERVE_VEHICLE_STATUS_READY, parkingRegion: '上海市-上海市', parkingLot: '浦东停车场', vehicleType: '厢式车', brand: '重汽', model: 'HOWO-T5G', vin: 'LJNAU1A2XK9999000', hasAd: true, adPhoto: [{ uid: 'ad2', name: '广告2.jpg', url: 'https://dummyimage.com/600x400/e2e8f0/475569&text=Ad2' }], bigWordPhoto: [{ uid: 'bw2', name: '放大字2.jpg', url: 'https://dummyimage.com/600x400/e2e8f0/475569&text=BW2' }], hasTailboard: true }
];
}, []);
var plateOptions = useMemo(function () {
return filterSelectableReserveVehicles(reserveVehicles, getOperatorRegionPermissions()).map(function (v) {
return {
value: v.plateNo,
label: v.plateNo + ' · ' + (v.parkingLot || formatDeliveryRegion(v.parkingRegion))
};
});
}, [reserveVehicles]);
var vehicleByPlate = useMemo(function () {
var map = {};
reserveVehicles.forEach(function (v) { map[v.plateNo] = v; });
return map;
}, [reserveVehicles]);
function isPlatePendingInForm(plateNo) {
if (plateNo == null || plateNo === undefined) return true;
var s = String(plateNo).trim();
return s === '' || s === '-';
}
function displayDisabledField(v) {
var s = v == null || v === undefined ? '' : String(v).trim();
return s || '-';
}
function displayFormVin(plateNo, vin) {
if (isPlatePendingInForm(plateNo)) return '-';
return displayDisabledField(vin);
}
function syncVehicleInfoFromReserveRecord(reserveRecord, deliveryRec, plate) {
// 备车记录 → 交车单「车辆信息」同步(驾驶培训字段不参与同步,见 buildInitialForm / handlePlateChange
var hasPlate = plate && !isPlatePendingInForm(plate);
return {
vehicleType: (reserveRecord && reserveRecord.vehicleType) || (deliveryRec && deliveryRec.vehicleType) || '',
brand: (reserveRecord && reserveRecord.brand) || (deliveryRec && deliveryRec.brand) || '',
model: (reserveRecord && reserveRecord.model) || (deliveryRec && deliveryRec.model) || '',
vin: hasPlate ? ((reserveRecord && reserveRecord.vin) || (deliveryRec && deliveryRec.vin) || '') : '',
hasAd: !!(reserveRecord && reserveRecord.hasAd),
adPhoto: (reserveRecord && reserveRecord.adPhoto) ? reserveRecord.adPhoto.slice() : [],
bigWordPhoto: (reserveRecord && reserveRecord.bigWordPhoto) ? reserveRecord.bigWordPhoto.slice() : [],
hasTailboard: reserveRecord ? !!reserveRecord.hasTailboard : false,
spareTirePhoto: (reserveRecord && reserveRecord.spareTirePhoto) ? reserveRecord.spareTirePhoto.slice() : [],
spareTireDepth: (reserveRecord && reserveRecord.spareTireDepth) || ''
};
}
function buildInitialForm(rec) {
var plate = (rec && rec.plateNo && String(rec.plateNo).trim()) || undefined;
var reserveRecord = plate ? vehicleByPlate[plate] : null;
if (!reserveRecord && rec && rec.brand && plate) {
reserveRecord = { plateNo: plate, vehicleType: rec.vehicleType || '', brand: rec.brand || '', model: rec.model || '', vin: rec.vin || '', hasAd: false, adPhoto: [], bigWordPhoto: [], hasTailboard: false };
}
var synced = syncVehicleInfoFromReserveRecord(reserveRecord, rec, plate);
return Object.assign({
plateNo: plate,
trainingRecognized: false,
driverLicenses: [],
driverFrontPhoto: [],
mileageKm: rec && rec.deliveryMileage != null ? String(rec.deliveryMileage) : '',
batteryPct: rec && rec.deliveryElec != null ? String(rec.deliveryElec) : '',
hydrogenAmount: rec && rec.deliveryH2 != null ? String(rec.deliveryH2) : '',
hydrogenUnit: (rec && rec.deliveryH2Unit === 'MPa') ? 'MPa' : '%',
serviceFee: ''
}, synced);
}
var formState = useState(buildInitialForm(null));
var form = formState[0];
var setForm = formState[1];
var activeSectionState = useState('basic');
var activeSection = activeSectionState[0];
var setActiveSection = activeSectionState[1];
var submittingState = useState(false);
var submitting = submittingState[0];
var setSubmitting = submittingState[1];
var previewState = useState({ open: false, url: '', title: '', gallery: [], index: 0 });
var ocrModalState = useState({ open: false, photoUrl: '', depth: '6.50' });
var trainingInputRef = useRef(null);
useEffect(function () {
if (open && record) {
var initial = buildInitialForm(record);
var nextPhotos = createEmptyPhotos();
if (readOnly && record.deliveryStatus && record.deliveryStatus !== '未开始' && record.deliveryStatus !== '已保存') {
initial = Object.assign({}, initial, buildDriverInfoFromPickupCode(), buildViewFormPhotoExtras());
nextPhotos = buildViewDeliveryPhotos();
}
setForm(initial);
setActiveSection('basic');
setSubmitting(false);
setPhotos(nextPhotos);
setInspectionList(buildInspectionList());
}
}, [open, record && record.id, readOnly]);
function updateForm(patch) {
setForm(function (p) { return Object.assign({}, p, patch); });
}
function handlePlateChange(v) {
if (!v) {
updateForm({
plateNo: undefined,
vehicleType: '',
brand: '',
model: '',
vin: '',
hasAd: false,
adPhoto: [],
bigWordPhoto: [],
hasTailboard: false,
spareTirePhoto: [],
spareTireDepth: '',
trainingRecognized: false,
driverLicenses: [],
driverFrontPhoto: []
});
return;
}
var reserveRecord = vehicleByPlate[v];
updateForm(Object.assign({
plateNo: v,
trainingRecognized: false,
driverLicenses: [],
driverFrontPhoto: []
}, syncVehicleInfoFromReserveRecord(reserveRecord, null, v)));
}
function makeThumb(url, onPreview, onRemove) {
return React.createElement('div', { style: { width: 72, height: 72, borderRadius: 8, border: '1px solid #e2e8f0', overflow: 'hidden', position: 'relative', background: '#f8fafc' } },
React.createElement('img', { src: url, style: { width: '100%', height: '100%', objectFit: 'cover', cursor: 'pointer' }, onClick: onPreview }),
onRemove ? React.createElement('button', {
type: 'button',
'aria-label': '删除图片',
style: { position: 'absolute', right: 4, top: 4, width: 22, height: 22, borderRadius: 999, border: 'none', background: 'rgba(15,23,42,.65)', color: '#fff', cursor: 'pointer', fontSize: 12, lineHeight: '22px', padding: 0 },
onClick: function (e) { e.stopPropagation(); onRemove(); }
}, '×') : null
);
}
function mockViewPhoto(uid, name) {
return [{ uid: uid, name: name + '.jpg', url: 'https://dummyimage.com/640x360/e2e8f0/475569&text=' + encodeURIComponent(name) }];
}
function mockDeliveryPhotoItem(uid, seed, slotLabel) {
return {
uid: uid,
name: slotLabel + '.jpg',
url: 'https://picsum.photos/seed/dv-' + seed + '/960/540'
};
}
function mockDeliveryPhotoSlot(uid, seed, slotLabel) {
return [mockDeliveryPhotoItem(uid, seed, slotLabel)];
}
function mockDeliveryPhotoList(items) {
return items.map(function (it) {
return mockDeliveryPhotoItem(it.uid, it.seed, it.label);
});
}
function buildDeliveryPhotoGallery(photoData) {
var list = [];
var slotGroups = [
{ key: 'vehicle', label: '车辆' },
{ key: 'chassis', label: '底盘' },
{ key: 'tire', label: '轮胎' }
];
slotGroups.forEach(function (group) {
var groupMap = (photoData && photoData[group.key]) || {};
Object.keys(groupMap).forEach(function (slotLabel) {
(groupMap[slotLabel] || []).forEach(function (file) {
list.push({
uid: file.uid,
url: file.url,
name: file.name,
title: group.label + ' · ' + slotLabel
});
});
});
});
['defect', 'other'].forEach(function (groupKey) {
var groupLabel = groupKey === 'defect' ? '瑕疵' : '其他';
((photoData && photoData[groupKey]) || []).forEach(function (file, idx) {
var files = (photoData && photoData[groupKey]) || [];
list.push({
uid: file.uid,
url: file.url,
name: file.name,
title: files.length > 1 ? groupLabel + ' · ' + (file.name || (groupLabel + (idx + 1))) : groupLabel + ' · ' + (file.name || groupLabel)
});
});
});
return list;
}
function closePhotoPreview() {
previewState[1]({ open: false, url: '', title: '', gallery: [], index: 0 });
}
function openPhotoPreview(options) {
var gallery = (options && options.gallery) || [];
var index = 0;
if (gallery.length && options && options.uid) {
for (var i = 0; i < gallery.length; i++) {
if (gallery[i].uid === options.uid) {
index = i;
break;
}
}
}
var current = gallery.length ? gallery[index] : { url: options.url, title: options.title };
previewState[1]({
open: true,
url: current.url,
title: current.title || (options && options.title) || '预览',
gallery: gallery,
index: index
});
}
function shiftPhotoPreview(step) {
previewState[1](function (prev) {
var gallery = prev.gallery || [];
if (gallery.length <= 1) return prev;
var nextIndex = prev.index + step;
if (nextIndex < 0) nextIndex = gallery.length - 1;
if (nextIndex >= gallery.length) nextIndex = 0;
var item = gallery[nextIndex];
return Object.assign({}, prev, {
index: nextIndex,
url: item.url,
title: item.title
});
});
}
function buildViewFormPhotoExtras() {
return {
spareTirePhoto: mockViewPhoto('sp1', '备胎照片'),
spareTireDepth: '6.50'
};
}
function buildViewDeliveryPhotos() {
return {
vehicle: {
'仪表盘': mockDeliveryPhotoSlot('v1', 'vehicle-dash', '仪表盘'),
'车辆正前': mockDeliveryPhotoSlot('v2', 'vehicle-front', '车辆正前'),
'车辆左前方': mockDeliveryPhotoSlot('v3', 'vehicle-left-front', '车辆左前方'),
'车辆左后方': mockDeliveryPhotoSlot('v4', 'vehicle-left-rear', '车辆左后方'),
'车辆右前方': mockDeliveryPhotoSlot('v5', 'vehicle-right-front', '车辆右前方'),
'车辆右后方': mockDeliveryPhotoSlot('v6', 'vehicle-right-rear', '车辆右后方')
},
chassis: {
'正前方位底部': mockDeliveryPhotoSlot('c1', 'chassis-front', '正前方位底部'),
'左侧前方底部': mockDeliveryPhotoSlot('c2', 'chassis-left-front', '左侧前方底部'),
'左侧后方底部': mockDeliveryPhotoSlot('c3', 'chassis-left-rear', '左侧后方底部'),
'正后方位底部': mockDeliveryPhotoSlot('c4', 'chassis-rear', '正后方位底部'),
'右侧前方底部': mockDeliveryPhotoSlot('c5', 'chassis-right-front', '右侧前方底部'),
'右侧后方底部': mockDeliveryPhotoSlot('c6', 'chassis-right-rear', '右侧后方底部')
},
tire: {
'左前': mockDeliveryPhotoSlot('t1', 'tire-left-front', '左前'),
'右前': mockDeliveryPhotoSlot('t2', 'tire-right-front', '右前'),
'左后内': mockDeliveryPhotoSlot('t3', 'tire-left-rear-in', '左后内'),
'左后外': mockDeliveryPhotoSlot('t4', 'tire-left-rear-out', '左后外'),
'右后内': mockDeliveryPhotoSlot('t5', 'tire-right-rear-in', '右后内'),
'右后外': mockDeliveryPhotoSlot('t6', 'tire-right-rear-out', '右后外')
},
defect: mockDeliveryPhotoList([
{ uid: 'd1', seed: 'defect-scratch', label: '瑕疵-刮擦' },
{ uid: 'd2', seed: 'defect-dent', label: '瑕疵-凹陷' },
{ uid: 'd3', seed: 'defect-paint', label: '瑕疵-漆面' }
]),
other: mockDeliveryPhotoList([
{ uid: 'o1', seed: 'other-tools', label: '其他-随车工具' },
{ uid: 'o2', seed: 'other-cargo', label: '其他-车厢物品' }
])
};
}
function ReadonlyPhotoBox(boxProps) {
var label = boxProps.label;
var value = boxProps.value || [];
var gallery = boxProps.gallery;
return React.createElement('div', null,
label ? React.createElement('div', { style: { fontSize: 13, color: '#475569', marginBottom: 8, fontWeight: 500 } }, label) : null,
value.length > 0
? React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' } },
value.map(function (f) {
return React.createElement('div', { key: f.uid },
makeThumb(f.url, function () {
openPhotoPreview({
gallery: gallery && gallery.length ? gallery : null,
uid: f.uid,
url: f.url,
title: (gallery && gallery.length)
? ((gallery.filter(function (g) { return g.uid === f.uid; })[0] || {}).title || f.name || label || '预览')
: (f.name || label || '预览')
});
})
);
})
)
: React.createElement('span', { style: { fontSize: 13, color: '#94a3b8' } }, '-')
);
}
function UploadBox(uploadProps) {
var label = uploadProps.label;
var value = uploadProps.value || [];
var unlimited = !!uploadProps.unlimited;
var max = unlimited ? Infinity : (uploadProps.max || 1);
var onChange = uploadProps.onChange;
var disabled = !!uploadProps.disabled;
function handlePick(e) {
if (disabled) return;
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 (!unlimited && next.length > max) next = next.slice(next.length - max);
onChange && onChange(next);
});
e.target.value = '';
}
return React.createElement('div', null,
label ? React.createElement('div', { style: { fontSize: 13, color: '#475569', marginBottom: 8, fontWeight: 500 } }, label) : null,
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 10, 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 }); }, disabled ? null : function () { onChange && onChange(value.filter(function (x) { return x.uid !== f.uid; })); })
);
}),
(!disabled && (unlimited || value.length < max)) ? React.createElement('label', { style: { width: 72, height: 72, borderRadius: 8, border: '1px dashed #cbd5e1', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', color: '#64748b', cursor: 'pointer', fontSize: 20, lineHeight: 1, background: '#fff', transition: 'border-color .2s' } },
React.createElement('input', { type: 'file', accept: 'image/*', style: { display: 'none' }, onChange: handlePick }),
React.createElement('span', { style: { fontSize: 22, lineHeight: 1, marginBottom: 2 } }, '+'),
React.createElement('span', { style: { fontSize: 12 } }, '上传')
) : null
),
uploadProps.tip ? React.createElement('div', { style: { marginTop: 6, fontSize: 12, color: '#94a3b8', lineHeight: 1.5 } }, uploadProps.tip) : null
);
}
var EDIT_PHOTO_UPLOAD_HINT = '照片上传说明jpg、jpeg、png、gif、webp 格式,单张不超过 5MB支持预览与删除适用于本页所有照片上传项';
function createEmptyPhotos() {
return {
vehicle: {
'仪表盘': [],
'车辆正前': [],
'车辆左前方': [],
'车辆左后方': [],
'车辆右前方': [],
'车辆右后方': []
},
chassis: {
'正前方位底部': [],
'左侧前方底部': [],
'左侧后方底部': [],
'正后方位底部': [],
'右侧前方底部': [],
'右侧后方底部': []
},
tire: {
'左前': [],
'右前': [],
'左后内': [],
'左后外': [],
'右后内': [],
'右后外': []
},
defect: [],
other: []
};
}
function updatePhotoSlot(groupKey, slotKey, list) {
setPhotos(function (p) {
var n = Object.assign({}, p);
if (slotKey) {
n[groupKey] = Object.assign({}, p[groupKey] || {});
n[groupKey][slotKey] = list;
} else {
n[groupKey] = list;
}
return n;
});
}
function PhotoModuleTitle(title, required) {
return React.createElement('div', {
style: {
display: 'flex',
alignItems: 'center',
gap: 8,
marginBottom: 10,
fontSize: 14,
fontWeight: 700,
color: '#0f172a'
}
},
React.createElement('span', { style: { width: 3, height: 14, borderRadius: 2, background: '#2563eb', flexShrink: 0 } }),
(required && !readOnly) ? RequiredLabel(title) : title
);
}
function PhotoGridColumn(title, items, required, previewGallery) {
return React.createElement('div', { style: { marginBottom: 20, minWidth: 0 } },
PhotoModuleTitle(title, required),
React.createElement('div', { style: { border: '1px solid #e2e8f0', borderRadius: 10, padding: 12, background: '#fafbfc' } },
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', gap: 12 } },
items.map(function (it) {
return React.createElement('div', { key: it.key },
readOnly
? ReadonlyPhotoBox({ label: it.label, value: it.value, gallery: previewGallery })
: UploadBox({
label: required ? RequiredLabel(it.label) : it.label,
value: it.value,
max: 1,
disabled: readOnly,
onChange: it.onChange
})
);
})
)
)
);
}
function PhotoGridSimple(title, value, onChange, previewGallery) {
return React.createElement('div', { style: { marginBottom: 20, minWidth: 0 } },
PhotoModuleTitle(title, false),
React.createElement('div', { style: { border: '1px solid #e2e8f0', borderRadius: 10, padding: 12, background: '#fafbfc' } },
readOnly
? ReadonlyPhotoBox({ value: value, gallery: previewGallery })
: UploadBox({ value: value, unlimited: true, disabled: readOnly, onChange: onChange })
)
);
}
function buildPhotoGridItems(groupKey) {
var group = photos[groupKey] || {};
return Object.keys(group).map(function (k) {
return {
key: groupKey + '-' + k,
label: k,
value: group[k] || [],
onChange: function (l) { updatePhotoSlot(groupKey, k, l); }
};
});
}
function FormItem(itemProps) {
return React.createElement('div', { style: { marginBottom: 16, minWidth: 0 } },
React.createElement('div', { style: { fontSize: 13, color: '#475569', marginBottom: 6, fontWeight: 500 } },
itemProps.required ? RequiredLabel(itemProps.label) : itemProps.label
),
itemProps.children
);
}
var inspectionCategoryItems = {
'车灯': ['大灯', '转向灯', '小灯', '示廓灯', '刹车灯', '倒车灯', '牌照灯', '防雾灯', '室内灯'],
'仪表盘': ['氢系统指示', '电控系统指示', '数值清晰准确', '故障报警灯'],
'驾驶室': ['点烟器', '车窗升降', '按键开关', '雨刮器', '内后视镜是否正常', '内/外门把手', '安全带', '空调冷暖风', '仪表盘', '门锁功能', '手刹', '车钥匙功能是否正常', '喇叭', '音响功能', '遮阳板', '主副驾座椅', '方向盘', '内饰干净整洁'],
'轮胎': ['前左胎', '前右胎', '后左胎', '后右胎', '备胎'],
'液位检查': ['冷却液', '制动液', '玻璃水'],
'外观检查': ['车身外观', '漆面', '玻璃'],
'车辆外观': ['整车外观'],
'其他': ['其他检查项'],
'随车工具': ['三角牌', '灭火器', '反光背心'],
'随车证件': ['行驶证', '营运证', '保险单'],
'整车': ['整车状态'],
'燃料电池系统': ['氢系统', '储氢瓶'],
'冷机': ['冷机运行'],
'制动系统': ['制动踏板', '驻车制动']
};
function buildInspectionList() {
var list = [];
var categories = Object.keys(inspectionCategoryItems);
for (var ci = 0; ci < categories.length; ci++) {
var cat = categories[ci];
var items = inspectionCategoryItems[cat] || [];
for (var ji = 0; ji < items.length; ji++) {
var it = items[ji];
var isTire = cat === '轮胎';
list.push({
key: 'ins-' + ci + '-' + ji,
category: cat,
item: it,
checked: true,
treadDepth: isTire ? '6.5' : '',
remark: ''
});
}
}
return list;
}
var inspectionListState = useState(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: 120,
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: 180 },
{
title: '检查情况',
dataIndex: 'checked',
key: 'checked',
width: 160,
render: function (_, insRecord) {
var isTire = insRecord && insRecord.category === '轮胎';
return isTire
? React.createElement(Input, {
value: insRecord.treadDepth,
placeholder: '请输入胎纹深度',
addonAfter: 'mm',
disabled: readOnly,
onChange: function (e) { updateInspectionRow(insRecord.key, { treadDepth: e.target.value }); }
})
: React.createElement(Switch, {
checked: !!insRecord.checked,
disabled: readOnly,
onChange: function (v) { updateInspectionRow(insRecord.key, { checked: !!v }); }
});
}
},
{
title: '备注',
dataIndex: 'remark',
key: 'remark',
render: function (_, insRecord) {
return React.createElement(Input, {
value: insRecord.remark,
placeholder: '请输入',
disabled: readOnly,
onChange: function (e) { updateInspectionRow(insRecord.key, { remark: e.target.value }); }
});
}
}
];
}, [readOnly]);
var photoState = useState(createEmptyPhotos);
var photos = photoState[0];
var setPhotos = photoState[1];
var showSignFileSection = readOnly && record && isDeliverySignedStatus(record.deliveryStatus || record.status);
var signFileName = showSignFileSection ? buildDeliverySignFileName(record) : '';
var sectionNav = [
{ key: 'basic', label: '交车车辆' },
{ key: 'equip', label: '车辆信息' },
{ key: 'metrics', label: '交车数据' },
{ key: 'inspection', label: '交车检查单' },
{ key: 'photos', label: '交车照片' }
];
if (showSignFileSection) {
sectionNav.push({ key: 'esign', label: 'E签宝签章' });
}
var SECTION_SCROLL_MARGIN = 12;
function scrollToSection(key) {
setActiveSection(key);
var el = document.getElementById('dv-edit-section-' + key);
if (el && el.scrollIntoView) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
function SectionCard(cardProps) {
return React.createElement('div', {
id: cardProps.id,
style: {
scrollMarginTop: SECTION_SCROLL_MARGIN,
marginBottom: 16,
background: '#fff',
borderRadius: 12,
border: '1px solid #e2e8f0',
boxShadow: '0 1px 2px rgba(15,23,42,.04)',
overflow: 'hidden'
}
},
React.createElement('div', {
style: {
padding: '14px 16px',
borderBottom: '1px solid #f1f5f9',
fontSize: 14,
fontWeight: 700,
color: '#0f172a',
display: 'flex',
alignItems: 'center',
gap: 8
}
},
React.createElement('span', { style: { width: 3, height: 14, borderRadius: 2, background: '#2563eb', flexShrink: 0 } }),
cardProps.title
),
React.createElement('div', { style: { padding: '16px 16px 8px' } }, cardProps.children)
);
}
function validateSubmit() {
if (isEmpty(form.plateNo)) return '请选择车牌号';
if (form.hasAd) {
if (!form.adPhoto.length) return '请上传广告照片';
if (!form.bigWordPhoto.length) return '请上传放大字照片';
}
if (!form.trainingRecognized) return '请上传司机提车码并完成识别';
if (isEmpty(form.mileageKm)) return '请填写交车里程';
if (isEmpty(form.batteryPct)) return '请填写交车电量';
if (isEmpty(form.hydrogenAmount)) return '请填写交车氢量';
var requiredPhotoGroups = [
{ key: 'vehicle', label: '车辆' },
{ key: 'chassis', label: '底盘' },
{ key: 'tire', label: '轮胎' }
];
for (var pi = 0; pi < requiredPhotoGroups.length; pi++) {
var pg = requiredPhotoGroups[pi];
var groupMap = photos && photos[pg.key];
var keys = groupMap ? Object.keys(groupMap) : [];
for (var ki = 0; ki < keys.length; ki++) {
var pk = keys[ki];
if (!(groupMap[pk] && groupMap[pk].length)) return '请上传' + pg.label + '照片:' + pk;
}
}
return '';
}
function buildPatchFromForm() {
return {
plateNo: form.plateNo || '',
vehicleType: form.vehicleType,
brand: form.brand,
model: form.model,
vin: form.vin,
deliveryMileage: form.mileageKm === '' ? null : Number(form.mileageKm),
deliveryElec: form.batteryPct === '' ? null : Number(form.batteryPct),
deliveryH2: form.hydrogenAmount === '' ? null : Number(form.hydrogenAmount),
deliveryH2Unit: form.hydrogenUnit,
deliveryStatus: '已保存'
};
}
function handleSaveClick() {
onSave && onSave(buildPatchFromForm());
message.success('已保存');
}
function handleSubmitClick() {
var err = validateSubmit();
if (err) { message.error(err); return; }
Modal.confirm({
title: '确认交车',
content: '请确认信息填写无误,点击确认完成该车辆交车。',
okText: '确认',
cancelText: '取消',
onOk: function () {
setSubmitting(true);
setTimeout(function () {
setSubmitting(false);
onSubmit && onSubmit(buildPatchFromForm());
message.success('交车成功');
onClose && onClose();
}, 400);
}
});
}
function isInvalidPickupCodeFile(file) {
var name = ((file && file.name) || '').toLowerCase();
return name.indexOf('invalid') >= 0 || name.indexOf('无效') >= 0;
}
function buildDriverInfoFromPickupCode() {
return {
trainingRecognized: true,
driverFrontPhoto: [{ uid: 'front', name: '司机正面照', url: 'https://dummyimage.com/600x400/e2e8f0/475569&text=Driver-Front' }],
driverLicenses: [
{ uid: 'id1', name: '身份证(正面)', url: 'https://dummyimage.com/600x400/e2e8f0/475569&text=ID-F' },
{ uid: 'id2', name: '身份证(反面)', url: 'https://dummyimage.com/600x400/e2e8f0/475569&text=ID-B' },
{ uid: 'dl', name: '驾驶证', url: 'https://dummyimage.com/600x400/e2e8f0/475569&text=DL' },
{ uid: 'qc', name: '从业资格证', url: 'https://dummyimage.com/600x400/e2e8f0/475569&text=QC' }
]
};
}
function handleTrainingPick(e) {
var f = e && e.target && e.target.files && e.target.files[0];
if (!f) return;
var pickedFile = f;
setTimeout(function () {
if (isInvalidPickupCodeFile(pickedFile)) {
updateForm({ trainingRecognized: false, driverFrontPhoto: [], driverLicenses: [] });
message.error('提车码无效,请重新选择');
return;
}
updateForm(buildDriverInfoFromPickupCode());
message.success('提车码识别成功,已加载司机证照');
}, 500);
e.target.value = '';
}
function openPickupCodePicker() {
trainingInputRef.current && trainingInputRef.current.click();
}
if (!open) return null;
var drawerTitle = readOnly ? '查看交车单' : '编辑交车单';
var summaryLabelWidth = 80;
function renderSummaryField(labelText, valueText) {
return React.createElement('div', { style: { display: 'flex', alignItems: 'flex-start', gap: 6, minWidth: 0 } },
React.createElement('span', { style: { color: '#94a3b8', flexShrink: 0, width: summaryLabelWidth, textAlign: 'left' } }, labelText),
React.createElement('span', { style: { color: '#334155', flex: 1, minWidth: 0, textAlign: 'left', wordBreak: 'break-all', lineHeight: 1.5 } }, valueText)
);
}
var summaryCard = record ? React.createElement('div', { style: { marginBottom: 16, padding: '12px 14px', borderRadius: 10, background: 'linear-gradient(135deg,#f8fafc 0%,#fff 100%)', border: '1px solid #e2e8f0', fontSize: 13 } },
React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center', marginBottom: 10, paddingBottom: 10, borderBottom: '1px solid #f1f5f9' } },
React.createElement(Tag, { style: { margin: 0, border: 'none', background: '#eff6ff', color: '#2563eb', fontWeight: 600 } }, record.customerName || '-'),
React.createElement('span', { style: { color: '#475569' } }, record.contractCode || '-'),
(function () {
var status = record.deliveryStatus || '未开始';
var signed = isDeliverySignedStatus(status);
return React.createElement(Tag, {
style: {
margin: 0,
border: 'none',
background: signed ? '#16a34a' : '#f1f5f9',
color: signed ? '#fff' : '#475569',
fontWeight: signed ? 600 : 400,
cursor: signed ? 'pointer' : 'default',
textDecoration: signed ? 'underline' : 'none',
textUnderlineOffset: signed ? '2px' : undefined
},
title: signed ? '点击下载签章文件' : undefined,
role: signed ? 'button' : undefined,
tabIndex: signed ? 0 : undefined,
onClick: signed ? function (e) { e.stopPropagation(); downloadDeliverySignFile(record); } : undefined,
onKeyDown: signed ? function (e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.stopPropagation(); downloadDeliverySignFile(record); } } : undefined
}, status);
})()
),
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(2,minmax(0,1fr))', gap: '8px 16px' } },
renderSummaryField('项目:', record.projectName || '-'),
renderSummaryField('交车地点:', formatDeliveryRegion(record.deliveryRegion)),
renderSummaryField('品牌型号:', (record.brand || '-') + ' / ' + (record.model || '-')),
renderSummaryField('任务来源:', record.taskSource || '-')
)
) : null;
var sectionNavEl = React.createElement('nav', {
'aria-label': '交车单分节导航',
style: {
width: 108,
flexShrink: 0,
position: 'sticky',
top: 0,
alignSelf: 'flex-start',
paddingTop: 4,
zIndex: 2
}
},
React.createElement('div', { style: { fontSize: 12, color: '#94a3b8', fontWeight: 600, marginBottom: 8, paddingLeft: 12, letterSpacing: '.02em' } }, '目录'),
sectionNav.map(function (s) {
var active = activeSection === s.key;
return React.createElement('button', {
key: s.key,
type: 'button',
onClick: function () { scrollToSection(s.key); },
style: {
display: 'block',
width: '100%',
textAlign: 'left',
border: 'none',
cursor: 'pointer',
padding: '9px 12px',
borderRadius: 8,
fontSize: 13,
lineHeight: 1.4,
fontWeight: active ? 600 : 500,
background: active ? '#eff6ff' : 'transparent',
color: active ? '#2563eb' : '#64748b',
borderLeft: active ? '3px solid #2563eb' : '3px solid transparent',
marginBottom: 2,
transition: 'background .2s,color .2s'
}
}, s.label);
})
);
var sectionBasic = React.createElement(SectionCard, { id: 'dv-edit-section-basic', title: '交车车辆' },
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(2,minmax(0,1fr))', gap: '0 16px' } },
React.createElement(FormItem, { label: '车牌号', required: !readOnly },
React.createElement(Select, { value: form.plateNo, options: plateOptions, showSearch: true, filterOption: filterOption, placeholder: '请选择已备车车辆(按区域权限过滤)', allowClear: !readOnly, disabled: readOnly, style: { width: '100%' }, onChange: handlePlateChange })
),
React.createElement(FormItem, { label: '车辆类型' }, React.createElement(Input, { value: displayDisabledField(form.vehicleType), disabled: true })),
React.createElement(FormItem, { label: '品牌' }, React.createElement(Input, { value: displayDisabledField(form.brand), disabled: true })),
React.createElement(FormItem, { label: '型号' }, React.createElement(Input, { value: displayDisabledField(form.model), disabled: true })),
React.createElement(FormItem, { label: '车辆识别代码' }, React.createElement(Input, { value: displayFormVin(form.plateNo, form.vin), disabled: true }))
)
);
var sectionEquip = React.createElement(SectionCard, { id: 'dv-edit-section-equip', title: '车辆信息' },
!readOnly ? React.createElement('div', { style: { marginBottom: 12, fontSize: 12, color: '#94a3b8', lineHeight: 1.6 } }, EDIT_PHOTO_UPLOAD_HINT) : null,
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0 16px', marginBottom: form.hasAd ? 8 : 0 } },
React.createElement(FormItem, { label: '车身广告及放大字', required: !readOnly },
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 10 } },
React.createElement(Switch, { checked: !!form.hasAd, disabled: readOnly, onChange: function (v) { updateForm({ hasAd: !!v }); } }),
React.createElement('span', { style: { fontSize: 13, color: '#64748b' } }, form.hasAd ? '有车身广告' : '无车身广告')
)
),
React.createElement(FormItem, { label: '尾板', required: !readOnly },
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 10 } },
React.createElement(Switch, { checked: !!form.hasTailboard, disabled: readOnly, onChange: function (v) { updateForm({ hasTailboard: !!v }); } }),
React.createElement('span', { style: { fontSize: 13, color: '#64748b' } }, form.hasTailboard ? '有尾板' : '无尾板')
)
)
),
form.hasAd ? React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16, marginBottom: 8 } },
readOnly
? React.createElement(React.Fragment, null,
React.createElement(ReadonlyPhotoBox, { label: '广告照片', value: form.adPhoto }),
React.createElement(ReadonlyPhotoBox, { label: '放大字照片', value: form.bigWordPhoto })
)
: React.createElement(React.Fragment, null,
React.createElement(UploadBox, { label: RequiredLabel('广告照片'), value: form.adPhoto, max: 1, disabled: readOnly, onChange: function (l) { updateForm({ adPhoto: l }); } }),
React.createElement(UploadBox, { label: RequiredLabel('放大字照片'), value: form.bigWordPhoto, max: 1, disabled: readOnly, onChange: function (l) { updateForm({ bigWordPhoto: l }); } })
)
) : null,
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 } },
readOnly
? React.createElement(ReadonlyPhotoBox, { label: '备胎照片', value: form.spareTirePhoto })
: React.createElement(UploadBox, { label: '备胎照片', value: form.spareTirePhoto, max: 1, disabled: readOnly, onChange: function (l) { updateForm({ spareTirePhoto: l }); if (!readOnly && l && l.length) ocrModalState[1]({ open: true, photoUrl: l[0].url, depth: '6.50' }); } }),
React.createElement(FormItem, { label: '备胎胎纹深度' },
React.createElement(Input, { value: form.spareTireDepth, placeholder: readOnly ? undefined : '请输入', addonAfter: 'mm', disabled: readOnly, onChange: function (e) { updateForm({ spareTireDepth: e.target.value }); } })
)
),
React.createElement(FormItem, { label: '驾驶培训', required: !readOnly },
form.trainingRecognized
? React.createElement('div', null,
readOnly
? React.createElement('span', { style: { color: '#16a34a', fontWeight: 600, fontSize: 13 } }, '已完成视频培训')
: React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' } },
React.createElement('span', { style: { color: '#16a34a', fontWeight: 600, fontSize: 13 } }, '已完成视频培训'),
React.createElement(Button, { type: 'link', size: 'small', style: { padding: 0, height: 'auto' }, onClick: openPickupCodePicker }, '重新选择提车码')
),
!readOnly ? React.createElement('div', { style: { marginTop: 8, fontSize: 12, color: '#64748b', lineHeight: 1.5 } }, '已根据提车码内绑定的司机信息,自动加载司机证照等照片') : null,
form.driverFrontPhoto && form.driverFrontPhoto.length ? React.createElement('div', { style: { marginTop: 12 } },
React.createElement('div', { style: { fontSize: 13, color: '#475569', marginBottom: 8, fontWeight: 500 } }, '司机正面照'),
makeThumb(form.driverFrontPhoto[0].url, function () { previewState[1]({ open: true, url: form.driverFrontPhoto[0].url, title: '司机正面照' }); })
) : null
)
: (readOnly
? React.createElement('span', { style: { fontSize: 13, color: '#94a3b8' } }, '-')
: React.createElement(React.Fragment, null,
React.createElement('input', { type: 'file', ref: trainingInputRef, accept: 'image/*', style: { display: 'none' }, onChange: handleTrainingPick }),
React.createElement(Button, { onClick: openPickupCodePicker }, '上传司机提车码'),
React.createElement('div', { style: { marginTop: 8, fontSize: 12, color: '#94a3b8', lineHeight: 1.5 } }, '上传后将自动识别提车码内绑定的司机信息,并展示司机证照等照片')
))
),
form.driverLicenses && form.driverLicenses.length ? React.createElement(FormItem, { label: '司机证照' },
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(4,minmax(0,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 }); }),
React.createElement('div', { style: { marginTop: 4, fontSize: 12, color: '#64748b', textAlign: 'center' } }, f.name)
);
})
)
) : null
);
var sectionInspection = React.createElement(SectionCard, { id: 'dv-edit-section-inspection', title: '交车检查单' },
React.createElement(Table, {
rowKey: 'key',
size: 'small',
pagination: false,
bordered: true,
dataSource: inspectionList,
columns: inspectionColumns,
scroll: { x: 640 }
})
);
var sectionMetrics = React.createElement(SectionCard, { id: 'dv-edit-section-metrics', title: '交车数据' },
React.createElement('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(2,minmax(0,1fr))', gap: '0 16px' } },
React.createElement(FormItem, { label: '交车里程', required: !readOnly },
React.createElement(Input, { value: form.mileageKm, placeholder: '请输入', addonAfter: 'km', disabled: readOnly, onChange: function (e) { updateForm({ mileageKm: e.target.value }); } })
),
React.createElement(FormItem, { label: '交车电量', required: !readOnly },
React.createElement(Input, { value: form.batteryPct, placeholder: '请输入', addonAfter: '%', disabled: readOnly, onChange: function (e) { updateForm({ batteryPct: e.target.value }); } })
),
React.createElement(FormItem, { label: '交车氢量', required: !readOnly },
React.createElement(Input, { value: form.hydrogenAmount, placeholder: '请输入', addonAfter: form.hydrogenUnit, disabled: readOnly, onChange: function (e) { updateForm({ hydrogenAmount: e.target.value }); } })
),
React.createElement(FormItem, { label: '送车服务费' },
React.createElement(Input, { value: form.serviceFee, placeholder: '选填', addonAfter: '元', disabled: readOnly, onChange: function (e) { updateForm({ serviceFee: e.target.value }); } })
)
)
);
var deliveryPhotoGallery = readOnly ? buildDeliveryPhotoGallery(photos) : [];
var sectionPhotos = React.createElement(SectionCard, { id: 'dv-edit-section-photos', title: '交车照片' },
!readOnly ? React.createElement('div', { style: { marginBottom: 12, fontSize: 12, color: '#94a3b8', lineHeight: 1.6 } }, EDIT_PHOTO_UPLOAD_HINT) : null,
PhotoGridColumn('车辆', buildPhotoGridItems('vehicle'), !readOnly, deliveryPhotoGallery),
PhotoGridColumn('底盘', buildPhotoGridItems('chassis'), !readOnly, deliveryPhotoGallery),
PhotoGridColumn('轮胎', buildPhotoGridItems('tire'), !readOnly, deliveryPhotoGallery),
PhotoGridSimple('瑕疵', photos.defect || [], function (l) { updatePhotoSlot('defect', null, l); }, deliveryPhotoGallery),
PhotoGridSimple('其他', photos.other || [], function (l) { updatePhotoSlot('other', null, l); }, deliveryPhotoGallery)
);
var sectionEsign = showSignFileSection ? React.createElement(SectionCard, { id: 'dv-edit-section-esign', title: 'E签宝签章文件' },
React.createElement('div', {
style: {
display: 'flex',
alignItems: 'flex-start',
gap: 14,
padding: '12px 14px',
borderRadius: 10,
border: '1px solid #e2e8f0',
background: 'linear-gradient(135deg,#f8fafc 0%,#fff 100%)'
}
},
React.createElement('div', {
style: {
width: 44,
height: 44,
borderRadius: 10,
background: '#fee2e2',
color: '#dc2626',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 12,
fontWeight: 700,
flexShrink: 0
}
}, 'PDF'),
React.createElement('div', { style: { flex: 1, minWidth: 0 } },
React.createElement('div', { style: { fontSize: 14, fontWeight: 600, color: '#0f172a', wordBreak: 'break-all' } }, signFileName),
React.createElement('div', { style: { marginTop: 6, fontSize: 12, color: '#64748b', lineHeight: 1.6 } },
React.createElement('div', null, '签章时间:', record.deliveryTime || '-'),
React.createElement('div', null, '签章方:', record.customerName || '-')
)
),
React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 8, flexShrink: 0 } },
React.createElement(Button, { size: 'small', onClick: function () { previewDeliverySignFile(record); } }, '预览'),
React.createElement(Button, { type: 'primary', size: 'small', onClick: function () { downloadDeliverySignFile(record); } }, '下载')
)
)
) : null;
return React.createElement(React.Fragment, null,
readOnly ? React.createElement('style', null, DV_VIEW_READONLY_CTRL_CSS) : null,
React.createElement(Drawer, {
open: open,
onClose: onClose,
width: Math.min(960, typeof window !== 'undefined' ? window.innerWidth - 24 : 960),
title: drawerTitle,
destroyOnClose: true,
styles: {
body: { padding: '16px 20px 88px', background: '#f8fafc' },
footer: { borderTop: '1px solid #e2e8f0', padding: '12px 20px' }
},
footer: readOnly
? React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end' } },
React.createElement(Button, { onClick: onClose }, '关闭')
)
: React.createElement('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: 10 } },
React.createElement(Button, { onClick: onClose, disabled: submitting }, '取消'),
React.createElement(Button, { onClick: handleSaveClick, disabled: submitting }, '保存'),
React.createElement(Button, { type: 'primary', loading: submitting, onClick: handleSubmitClick }, '提交')
)
},
React.createElement('div', { className: readOnly ? 'dv-edit-drawer-view' : undefined },
summaryCard,
React.createElement('div', { style: { display: 'flex', gap: 16, alignItems: 'flex-start' } },
sectionNavEl,
React.createElement('div', { style: { flex: 1, minWidth: 0 } },
sectionBasic,
sectionEquip,
sectionMetrics,
sectionInspection,
sectionPhotos,
sectionEsign
)
)
)
),
React.createElement(Modal, {
open: !!previewState[0].open,
title: previewState[0].title || '预览',
footer: null,
onCancel: closePhotoPreview,
width: 920,
styles: { body: { padding: '12px 20px 20px' } }
},
previewState[0].url ? React.createElement('div', null,
React.createElement('div', { style: { textAlign: 'center', background: '#0f172a', borderRadius: 10, padding: 12 } },
React.createElement('img', {
src: previewState[0].url,
alt: previewState[0].title,
style: { width: '100%', maxHeight: '72vh', objectFit: 'contain' }
})
),
previewState[0].gallery && previewState[0].gallery.length > 1 ? React.createElement('div', {
style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, marginTop: 16 }
},
React.createElement(Button, { onClick: function () { shiftPhotoPreview(-1); } }, '上一张'),
React.createElement('span', { style: { fontSize: 13, color: '#64748b', fontVariantNumeric: 'tabular-nums' } },
String(previewState[0].index + 1) + ' / ' + previewState[0].gallery.length
),
React.createElement(Button, { type: 'primary', onClick: function () { shiftPhotoPreview(1); } }, '下一张')
) : null
) : null
),
React.createElement(Modal, {
open: !!ocrModalState[0].open,
title: '备胎识别',
onCancel: function () { ocrModalState[1](Object.assign({}, ocrModalState[0], { open: false })); },
onOk: function () { updateForm({ spareTireDepth: ocrModalState[0].depth }); ocrModalState[1](Object.assign({}, ocrModalState[0], { open: false })); message.success('已反写胎纹深度'); },
okText: '确认',
cancelText: '取消'
},
React.createElement(Input, { value: ocrModalState[0].depth, addonAfter: 'mm', onChange: function (e) { ocrModalState[1](Object.assign({}, ocrModalState[0], { depth: e.target.value })); } })
)
);
}
if (typeof window !== 'undefined') {
window.DeliveryEditDrawer = DeliveryEditDrawer;
}
function getDeliveryManageRequirementDoc() {
return [
'交车管理 — 产品需求说明',
'模块:数字化资产 ONEOS 运管平台 · 运维管理 · 车辆业务 · 交车管理',
'版本:与当前原型一致(列表一车一行 + 抽屉编辑交车单)',
'',
'══════════════════════════════════════',
'一、模块目标',
'══════════════════════════════════════',
'为运维人员提供交车任务的全流程管理能力:按车辆维度查看交车进度、筛选统计、导出报表,并在列表内通过抽屉完成单车交车单查看、编辑、保存与提交。',
'',
'══════════════════════════════════════',
'二、页面结构',
'══════════════════════════════════════',
'2.1 面包屑:运维管理 / 车辆业务 / 交车管理',
'2.2 右上角「查看需求说明」:打开本说明弹窗',
'2.3 页面自上而下:筛选区 → KPI 统计卡片 → 列表区(含车牌快捷筛选、导出)',
'',
'══════════════════════════════════════',
'三、数据统计KPI 卡片)',
'══════════════════════════════════════',
'替代原 Tab三张可点击卡片联动列表统计范围均为「当前筛选条件命中后的全部车辆行」',
'3.1 全部交车任务:进行中 + 已完成',
'3.2 进行中的交车任务:交车状态为「未开始」「已保存」「待客户签章」',
'3.3 已完成的交车任务:交车状态为「客户已签章」(运维与客户 E 签宝均完成)',
'3.4 卡片右上角问号:悬停展示指标说明;点击卡片切换列表数据,选中态高亮',
'3.5 默认选中:进行中的交车任务',
'',
'══════════════════════════════════════',
'四、筛选区',
'══════════════════════════════════════',
'4.1 默认展示 4 列,点击「展开」显示全部 16 项;「收起」恢复默认',
'4.2 筛选项(多条件且关系,点击「搜索」生效):',
' · 合同编号、项目名称、客户名称:可搜索下拉',
' · 交车区域:省-市二级 Cascader',
' · 完成交车时间:日期段 RangePicker精确至天',
' · 交车人、车牌号、车辆识别代码、车辆类型、品牌、型号',
' · 业务部门、业务负责人、任务来源、业务类型、是否延期',
'4.3 「重置」:清空筛选至默认',
'4.4 列表左上角「车牌号」:独立于筛选区,变更即生效(快捷筛选)',
'',
'══════════════════════════════════════',
'五、列表(一车一行)',
'══════════════════════════════════════',
'5.1 数据粒度:一个交车任务含多车时,按车辆拆分为独立行展示',
'5.1.1 区域权限过滤(列表数据范围):按当前登录运维人员区域权限过滤,无权限的交车任务不在列表展示(见十二、区域权限与车牌选择)',
'5.2 列顺序与含义:',
' (1) 车辆信息:三行 — 车牌号(待选时橙色「车牌待选」,可点击)、品牌-型号、车辆识别代码(待选时不展示 VIN显示 -);点击车牌号打开「查看交车单」抽屉',
' (2) 合同信息:客户名称+业务类型 Tag、合同编号链接、项目名称链接点击合同编号或项目名称跳转「合同管理-该合同详情页」',
' (3) 业务负责人:业务部门、业务负责人(两行)',
' (4) 任务来源:实心 Tag「替换车」悬停 Popover 展示 旧车 → 新车',
' (5) 交车地点:第一行交车区域(省-市);第二行停车场(车牌待选时显示 -',
' (6) 交车状态:实心 Tag未开始/已保存/待客户签章/客户已签章等);状态为「客户已签章」时可点击下载签章文件(指针+下划线,悬停提示「点击下载签章文件」)',
' (7) 完成交车时间:单行展示',
' (8) 交车人',
' (9) 是否归还(仅车辆已交车后展示归还状态,未交车显示 -',
' · 未归还:车辆已交车(待客户签章/客户已签章),尚未还车',
' · 已归还:车辆已交车且已还车(仅客户已签章后可标记;悬停展示还车时间、还车人)',
' · 未开始/已保存等未交车状态:显示 -',
' (10) 交车记录:三行合并展示 — 交车里程(km)、交车氢量(%或MPa)、交车电量(%)',
' (11) 交车任务创建时间:单行',
' (12) 交车任务创建人',
' (13) 操作:「查看」始终展示;「编辑」仅交车状态为「未开始」「已保存」时展示',
'5.3 合同跳转:写入 sessionStorage 合同编号/交车任务 ID跳转合同管理-该合同详情页(原型以 message 提示)',
'5.4 合同信息列宽可拖拽调整',
'5.5 分页:默认 10 条/页,可选 10/20/50',
'',
'══════════════════════════════════════',
'六、导出',
'══════════════════════════════════════',
'6.1 导出范围:当前 KPI 标签 + 全部筛选条件(含列表左上角车牌号)下的全部命中行,不受分页限制',
'6.2 导出列:与业务导出样表一致,交车里程/氢量/电量为分列(非列表「交车记录」合并列)',
'6.3 列表区展示当前 KPI 标签名称及导出说明',
'',
'══════════════════════════════════════',
'七、编辑交车单(抽屉)',
'══════════════════════════════════════',
'7.1 入口:列表操作列「编辑」(仅未开始/已保存)',
'7.2 抽屉标题:编辑交车单',
'7.3 顶部摘要卡:客户名称 Tag、合同编号、交车状态下方展示项目、交车地点省-市)、品牌型号、任务来源(标签固定宽度、内容左对齐,项目与品牌型号等内容列对齐);交车状态为「客户已签章」时可点击下载签章文件',
'7.4 左侧固定目录锚点(点击平滑滚动,不遮挡内容):',
' · 交车车辆 · 车辆信息 · 交车数据 · 交车检查单 · 交车照片',
'7.5 底部操作:取消、保存、提交',
'',
'--- 7.6 交车车辆 ---',
' · 车牌号(必填):可搜索选择停车场内「已备车」车辆;支持清空(详见十二、区域权限与车牌选择)',
' · 车牌待选时:车辆识别代码显示 -,不展示 VIN',
' · 车辆类型、品牌、型号、车辆识别代码:只读,随车牌联动',
'',
'--- 7.7 车辆信息 ---',
'【备车同步规则 — 后台逻辑,页面不单独提示】',
' · 打开编辑抽屉或选择车牌后,系统自动从该车辆对应备车记录同步以下字段至交车单:',
' 车身广告开关及广告/放大字照片、尾板、备胎照片、备胎胎纹深度,以及车辆类型/品牌/型号/VIN 等基础信息',
' · 不同步字段:驾驶培训相关(提车码识别状态、司机正面照、司机证照等),须在交车环节单独上传识别',
' · 手动修改车身广告开关时,与备车「后装设备-车身广告」双向同步(安装时间以备车记录提交成功为准)',
'',
' · 车身广告及放大字(必填开关):有广告时展示广告照片、放大字照片上传(各 1 张,必填)',
' · 尾板(必填开关):与车身广告同一行左右排列',
' · 备胎照片、备胎胎纹深度:非必填;上传备胎照片可 OCR 识别胎纹深度并反写',
' · 照片上传格式说明:编辑页同一条文案,在「车辆信息」「交车照片」卡片顶部展示,不在各照片位下方重复',
' · 驾驶培训(必填):',
' - 上传司机提车码;识别成功后显示「已完成视频培训」,并自动加载提车码绑定的司机正面照、身份证(正/反)、驾驶证、从业资格证',
' - 识别失败提示:「提车码无效,请重新选择」',
' - 支持「重新选择提车码」',
'',
'--- 7.8 交车数据 ---',
' · 交车里程必填km、交车电量必填%)、交车氢量(必填,单位随车辆为 % 或 MPa',
' · 送车服务费(选填,元)',
'',
'--- 7.9 交车检查单 ---',
' · 独立卡片内嵌表格,非抽屉',
' · 列:类别(同类合并)、检查项目、检查情况(开关或轮胎胎纹深度 mm、备注',
' · 清单覆盖:车灯、仪表盘、驾驶室、轮胎、液位、外观、随车工具/证件、燃料电池、冷机、制动等',
'',
'--- 7.10 交车照片 ---',
' · 模块标题「车辆」「底盘」「轮胎」「瑕疵」「其他」独立展示(蓝色竖线 + 标题行),下方为对应照片上传/预览区',
' · 车辆(必填 6 点):仪表盘、车辆正前、车辆左前/左后、车辆右前/右后方',
' · 底盘(必填 6 点):正前/正后方位底部,左/右侧前/后方底部',
' · 轮胎(必填 6 点):左前、右前、左后内/外、右后内/外',
' · 瑕疵、其他:各独立上传区,不限制张数',
' · 编辑页照片上传说明全页仅展示一条文案,在「车辆信息」「交车照片」卡片顶部各显示一次,各上传位下方不再重复格式提示',
' · 单点照片jpg/jpeg/png/gif/webp不超过 5MB支持预览、删除',
'',
'--- 7.11 保存与提交 ---',
' · 保存:交车状态 →「已保存」,回写车牌及交车数据等,抽屉不关闭',
' · 提交:校验必填项 → 二次确认 → 交车状态 →「待客户签章」,写入完成交车时间/交车人,关闭抽屉',
' · 提交校验含:车牌、广告(若开启)、提车码、交车里程/电量/氢量、车辆/底盘/轮胎全部必传照片点',
'',
'--- 7.12 区域权限与车牌选择(特别说明,后台逻辑) ---',
' · 详见「十二、区域权限与车牌选择(特别说明)」',
'',
'══════════════════════════════════════',
'八、查看交车单(抽屉)',
'══════════════════════════════════════',
'8.1 入口:列表车辆信息列点击车牌号;或操作列「查看」(所有状态均可用)',
'8.2 抽屉标题:查看交车单',
'8.3 布局与编辑页一致:摘要卡 + 左侧目录 + 交车车辆/车辆信息/交车数据/交车检查单/交车照片各独立卡片状态为「客户已签章」时额外展示「E签宝签章文件」卡片',
'8.4 只读规则:全部表单、开关、检查单编辑禁用;照片区不展示上传按钮及格式/操作说明,直接展示照片缩略图(可点击预览),无照片显示 -',
'8.4.1 查看页可读性:禁用 Input/Select 背景色 #fafafa更淡文字纯黑 #000增强对比度含带后缀的交车里程/电量等输入框',
'8.5 底部仅「关闭」按钮,无保存/提交',
'8.6 已提交交车单(非未开始/已保存查看时Mock 加载完整交车照片样例(车辆/底盘/轮胎 18 点位 + 瑕疵/其他);点击缩略图放大预览,支持「上一张/下一张」按交车点位顺序切换;驾驶培训区仅展示状态与证照图片',
'8.7 摘要卡交车状态为「客户已签章」时,点击状态 Tag 可下载签章文件',
'8.8 E签宝签章文件仅查看页且状态为「客户已签章」时展示',
' · 左侧目录增加「E签宝签章」锚点卡片标题「E签宝签章文件」',
' · 展示签章 PDF 文件名、签章时间、签章方(客户名称)',
' · 预览:新开浏览器页签打开签章文件预览(原型 Mock HTML 预览页,联调后对接 E 签宝 PDF/预览地址)',
' · 下载:下载签章文件(文件名含交车单 ID、车辆序号、车牌号',
'',
'══════════════════════════════════════',
'九、交车状态流转',
'══════════════════════════════════════',
'未开始 → 已保存(保存)→ 待客户签章(提交)→ 客户已签章(客户 E 签宝完成,列表归入「已完成」)',
'仅「未开始」「已保存」可编辑;任意状态均可查看',
'',
'══════════════════════════════════════',
'十、指标单位约定',
'══════════════════════════════════════',
' · 交车里程km',
' · 交车电量:%',
' · 交车氢量:% 或 MPa按车辆/合同配置)',
' · 列表「交车记录」合并展示;导出仍为三列',
'',
'══════════════════════════════════════',
'十一、非功能说明(原型)',
'══════════════════════════════════════',
' · 备车车辆、提车码识别、OCR、E 签宝、合同详情跳转、签章文件下载等为前端 Mock联调后对接真实接口',
' · 合同详情sessionStorage 写入 oneos_contract_code / oneos_delivery_order_id',
' · 签章文件:列表/摘要卡 Tag 点击下载查看页「E签宝签章文件」卡片支持预览新开页与下载',
' · 区域权限:列表与车牌下拉按 window.DV_MOCK_OPERATOR_REGION_PERMISSIONS 过滤(默认浙江省-嘉兴市)',
' · 提车码 Mock文件名含 invalid/无效 模拟识别失败',
'',
'══════════════════════════════════════',
'十二、区域权限与车牌选择(特别说明)',
'══════════════════════════════════════',
'适用于编辑交车单及列表数据可见范围,为后台权限逻辑,页面不单独弹窗提示。',
'',
'12.1 交车任务可见范围(列表/执行权限)',
' · 以交车单「交车区域」(省-市)为任务所属区域',
' · 当前运维人员须具备该区域或其上级区域的运维权限,方可查看并执行该交车任务',
' · 示例:交车区域为「浙江省-嘉兴市」时,区域权限为「浙江省」(省级)或「嘉兴市」(市级)的运维人员均可执行;',
' 仅具备「四川省」「浙江省-杭州市」等不重合权限的人员,列表中看不到该条数据',
' · 无权限:整条交车任务(含其下所有车辆行)均不可见,不可编辑',
'',
'12.2 停车场车牌号可选范围',
' · 编辑交车单选择车牌时,下拉仅展示车辆状态为「已备车」的车辆',
' · 非「已备车」状态(如备车中、待备车等)不可选',
'',
'12.3 当前运维操作人与停车场区域权限',
' · 在「已备车」前提下,还需满足:车辆所在停车场区域 ⊆ 当前运维人员区域权限覆盖范围',
' · 省级权限示例:区域权限为「浙江省」→ 可选浙江省内各停车场(杭州、嘉兴、宁波等)的全部已备车车辆',
' · 市级权限示例:区域权限为「嘉兴市」→ 仅可选嘉兴市各停车场内的已备车车辆,不可选杭州市等省内其他城市停车场车辆',
' · 下拉展示建议:车牌号 + 停车场名称,便于运维确认来源',
'',
'12.4 权限匹配规则摘要',
' · 省级权限:匹配该省全部省-市交车区域/停车场区域',
' · 市级权限:仅匹配该市交车区域/停车场区域',
' · 省-市组合权限(如「浙江省-嘉兴市」):等同市级嘉兴市范围',
'',
'12.5 原型 Mock',
' · 默认当前运维人员区域权限:浙江省-嘉兴市',
' · 可在控制台设置 window.DV_MOCK_OPERATOR_REGION_PERMISSIONS = [\'浙江省\'] 验证全省可见/可选',
''
].join('\n');
}
const Component = function () {
var useState = React.useState;
var useCallback = React.useCallback;
var useMemo = React.useMemo;
var useEffect = React.useEffect;
var antd = window.antd;
var Breadcrumb = antd.Breadcrumb;
var Card = antd.Card;
var DatePicker = antd.DatePicker;
var Select = antd.Select;
var Button = antd.Button;
var Table = antd.Table;
var Cascader = antd.Cascader;
var Tag = antd.Tag;
var Tooltip = antd.Tooltip;
var Popover = antd.Popover;
var Modal = antd.Modal;
var message = antd.message;
var RangePicker = DatePicker.RangePicker;
function createEmptyFilters() {
return {
contractCode: undefined,
projectName: undefined,
customerName: undefined,
deliveryRegion: undefined,
dateStart: '',
dateEnd: '',
deliveryPerson: undefined,
plateNo: undefined,
vin: undefined,
vehicleType: undefined,
brand: undefined,
model: undefined,
businessDept: undefined,
businessOwner: undefined,
taskSource: undefined,
bizType: undefined,
isDelayed: undefined
};
}
function patchFilters(prev, patch) {
var next = {};
var k;
for (k in prev) next[k] = prev[k];
for (k in patch) next[k] = patch[k];
return next;
}
var filterState = useState(createEmptyFilters);
var filters = filterState[0];
var setFilters = filterState[1];
var appliedFilterState = useState(createEmptyFilters);
var appliedFilters = appliedFilterState[0];
var setAppliedFilters = appliedFilterState[1];
var filterExpandedState = useState(false);
var filterExpanded = filterExpandedState[0];
var setFilterExpanded = filterExpandedState[1];
/** total | inProgress | completed */
var kpiFilterState = useState('inProgress');
var kpiFilter = kpiFilterState[0];
var setKpiFilter = kpiFilterState[1];
var pageState = useState(1);
var pageSizeState = useState(10);
var colWidthsState = useState({ contractInfo: 240 });
var colWidths = colWidthsState[0];
var setColWidths = colWidthsState[1];
var requirementModalOpen = useState(false);
var setRequirementModalOpen = requirementModalOpen[1];
var requirementDocContent = getDeliveryManageRequirementDoc();
// 交车区域:省-市 二级
var regionOptions = [
{ value: 'zhejiang', label: '浙江省', children: [{ value: 'hangzhou', label: '杭州市' }, { value: 'jiaxing', label: '嘉兴市' }, { value: 'ningbo', label: '宁波市' }] },
{ value: 'shanghai', label: '上海市', children: [{ value: 'shanghai', label: '上海市' }] },
{ value: 'guangdong', label: '广东省', children: [{ value: 'guangzhou', label: '广州市' }, { value: 'shenzhen', label: '深圳市' }] }
];
var contractCodeOptions = [
{ value: 'LNZLHT', label: 'LNZLHT' },
{ value: 'HT-ZL-2024', label: 'HT-ZL-2024' },
{ value: 'HT-ZL-2025', label: 'HT-ZL-2025' }
];
var projectNameOptions = [
{ value: 'p1', label: '桐乡韵达租赁4.5T*10' },
{ value: 'p2', label: '洛安供应链-租赁帕力安4.5T*30' },
{ value: 'p3', label: '嘉兴氢能示范项目' },
{ value: 'p4', label: '杭州城配租赁项目' }
];
var customerNameOptions = [
{ value: 'c1', label: '桐乡市丰韵快递有限责任公司' },
{ value: 'c2', label: '武汉洛安供应链有限公司' },
{ value: 'c3', label: '嘉兴某某物流有限公司' },
{ value: 'c4', label: '杭州某某租赁有限公司' }
];
var deliveryPersonOptions = [
{ value: '张三', label: '张三' },
{ value: '李四', label: '李四' },
{ value: '王五', label: '王五' },
{ value: '魏山', label: '魏山' },
{ value: '何苗苗', label: '何苗苗' }
];
var taskSourceOptions = [
{ value: '替换车', label: '替换车' },
{ value: '交车任务', label: '交车任务' }
];
var bizTypeOptions = [
{ value: '租赁', label: '租赁' },
{ value: '自营', label: '自营' }
];
var isDelayedOptions = [
{ value: 'yes', label: '是' },
{ value: 'no', label: '否' }
];
var DV_ORDER_KEY = 'oneos_delivery_order_id';
var DV_VEHICLE_KEY = 'oneos_delivery_vehicle_key';
var DV_NAV_KEY = 'oneos_delivery_navigate_target';
var DV_CONTRACT_CODE_KEY = 'oneos_contract_code';
var DV_LIST_LINK_STYLE = { color: '#165dff', cursor: 'pointer' };
/** 交车状态:进行中=未开始/已保存/待客户签章;历史=客户已签章(运维+客户 E 签宝均完成) */
var DELIVERY_STATUS_HISTORY = '客户已签章';
var DELIVERY_STATUS_IN_PROGRESS = ['未开始', '已保存', '待客户签章'];
function isDeliveryHistoryStatus(status) {
return status === DELIVERY_STATUS_HISTORY || status === '已签章';
}
function isDeliveryInProgressStatus(status) {
return DELIVERY_STATUS_IN_PROGRESS.indexOf(status || '未开始') >= 0;
}
/** 运维侧已完成交车提交(待签章或已签章)视为车辆已交车 */
function isVehicleDelivered(status) {
return status === '待客户签章' || isDeliveryHistoryStatus(status);
}
function canEditDeliveryRow(record) {
var s = record.deliveryStatus;
return s === '未开始' || s === '已保存';
}
// 交车任务(一单多车);列表按车辆拆分为独立行,表头对齐导出样表
var deliveryOrdersState = useState([
{
id: 'o1', expectedDate: '2025-02-28 至 2025-03-05', contractCode: 'LNZLHT 20260104001', projectName: '桐乡韵达租赁4.5T*10', customerName: '桐乡市丰韵快递有限责任公司', businessDept: '业务二部', businessOwner: '刘念念', taskSource: '替换车', replaceOldPlate: '浙A88601F', bizType: '租赁', deliveryRegion: '浙江省-嘉兴市', deliveryAddress: '平湖指定停车场', createTime: '2026-06-04 11:28', createBy: '赵小峰',
vehicleList: [
{ vehicleKey: 1, seq: 1, vehicleType: '4.5吨冷链车', brand: '现代', model: '帕力安牌4.5吨冷链车', vin: 'LNBSCPKB8RR123401', replaceOldPlate: '浙A88601F', plateNo: '', deliveryTime: '', deliveryPerson: '', deliveryStatus: '未开始', deliveryMileage: null, deliveryH2: null, deliveryElec: null },
{ vehicleKey: 2, seq: 2, vehicleType: '4.5吨冷链车', brand: '现代', model: '帕力安牌4.5吨冷链车', vin: 'LNBSCPKB8RR123402', replaceOldPlate: '浙A88602F', plateNo: '', deliveryTime: '', deliveryPerson: '', deliveryStatus: '未开始', deliveryMileage: null, deliveryH2: null, deliveryElec: null }
]
},
{
id: 'o2', expectedDate: '2025-03-01', contractCode: 'LNZLHT2026040301-042', projectName: '洛安供应链-租赁帕力安4.5T*30', customerName: '武汉洛安供应链有限公司', businessDept: '业务三部', businessOwner: '金可鹏', taskSource: '交车任务', bizType: '租赁', deliveryRegion: '四川省-成都市', deliveryAddress: '成都龙泉驿停车场', createTime: '2026-05-31 14:07', createBy: '何苗苗',
vehicleList: [
{ vehicleKey: 1, seq: 1, vehicleType: '4.5吨冷链车', brand: '现代', model: '帕力安牌4.5吨冷链车', vin: 'LNBSCPKB9RR223401', plateNo: '粤AGP9827', deliveryTime: '2026-06-03 18:20', deliveryPerson: '魏山', deliveryStatus: '待客户签章', deliveryMileage: 48202, deliveryH2: 19, deliveryH2Unit: '%', deliveryElec: 82 },
{ vehicleKey: 2, seq: 2, vehicleType: '4.5吨冷链车', brand: '现代', model: '帕力安牌4.5吨冷链车', vin: 'LNBSCPKB9RR223402', plateNo: '粤AGP4598', deliveryTime: '2026-06-02 11:00', deliveryPerson: '魏山', deliveryStatus: '已保存', deliveryMileage: null, deliveryH2: null, deliveryElec: null }
]
},
{
id: 'o3', expectedDate: '2025-03-08', contractCode: 'LNZLHT2025042201', projectName: '炽瑞-租赁现代4.5T', customerName: '东莞沙田炽瑞物流有限公司', businessDept: '业务三部', businessOwner: '金可鹏', taskSource: '替换车', replaceOldPlate: '粤B58888F', bizType: '租赁', deliveryRegion: '广东省-广州市', deliveryAddress: '广州南沙物流园停车场', createTime: '2026-05-28 20:30', createBy: '童军林',
vehicleList: [
{ vehicleKey: 1, seq: 1, vehicleType: '4.5吨货车', brand: '现代', model: '4.5吨货车', vin: 'LNBSCPKB7RR323401', replaceOldPlate: '粤B58888F', plateNo: '', deliveryTime: '', deliveryPerson: '', deliveryStatus: '未开始', deliveryMileage: null, deliveryH2: null, deliveryElec: null }
]
},
{
id: 'o4', expectedDate: '2024-11-15', contractCode: 'LNZLHT2024111401', projectName: '聚德11月新增苏龙18T*2', customerName: '沈阳聚德物流有限公司', businessDept: '业务三部', businessOwner: '金可鹏', taskSource: '交车任务', bizType: '租赁', deliveryRegion: '浙江省-嘉兴市', deliveryAddress: '嘉兴港区氢能停车场', createTime: '2024-11-15 15:05', createBy: '何苗苗',
vehicleList: [
{ vehicleKey: 1, seq: 1, vehicleType: '18吨双飞翼货车', brand: '苏龙', model: '海格牌18吨双飞翼货车', vin: 'LKLG7C4E4NA774701', plateNo: '浙F80088', deliveryTime: '2026-06-02 16:00', deliveryPerson: '魏山', deliveryStatus: '待客户签章', deliveryMileage: 46200, deliveryH2: 21, deliveryH2Unit: '%', deliveryElec: 80 },
{ vehicleKey: 2, seq: 2, vehicleType: '18吨双飞翼货车', brand: '苏龙', model: '海格牌18吨双飞翼货车', vin: 'LKLG7C4E4NA774702', plateNo: '沪A03802F', deliveryTime: '2025-11-20 09:30', deliveryPerson: '何苗苗', deliveryStatus: '已保存', deliveryMileage: null, deliveryH2: null, deliveryElec: null }
]
},
{
id: 'o5', expectedDate: '2025-02-15', contractCode: 'HT-ZL-2024-001', projectName: '嘉兴氢能示范项目', customerName: '嘉兴某某物流有限公司', businessDept: '业务一部', businessOwner: '张经理', taskSource: '交车任务', bizType: '租赁', deliveryRegion: '浙江省-嘉兴市', deliveryAddress: '南湖科技大道停车场', createTime: '2025-02-10 09:00', createBy: '系统',
vehicleList: [
{ vehicleKey: 1, seq: 1, vehicleType: '厢式货车', brand: '东风', model: 'DFH1180', vin: 'LKLG7C4E4NA774759', plateNo: '京A12345', deliveryTime: '2025-02-15 10:30', deliveryPerson: '张三', deliveryStatus: '客户已签章', deliveryMileage: 12580, deliveryH2: 35, deliveryH2Unit: 'MPa', deliveryElec: 45, vehicleReturned: true, returnTime: '2025-08-20 11:30', returnPerson: '王五' },
{ vehicleKey: 2, seq: 2, vehicleType: '厢式货车', brand: '福田', model: 'BJ1180', vin: 'LKLG7C4E4NA774760', plateNo: '京C11111', deliveryTime: '2025-02-15 14:00', deliveryPerson: '李四', deliveryStatus: '客户已签章', deliveryMileage: 13200, deliveryH2: 68, deliveryH2Unit: '%', deliveryElec: 38, vehicleReturned: false }
]
},
{
id: 'o6', expectedDate: '2025-02-18', contractCode: 'HT-ZL-2024-003', projectName: '杭州城配租赁项目', customerName: '杭州某某租赁有限公司', businessDept: '业务二部', businessOwner: '李经理', taskSource: '交车任务', bizType: '租赁', deliveryRegion: '浙江省-杭州市', deliveryAddress: '未来科技城地下停车场', createTime: '2025-02-12 08:30', createBy: '李四',
vehicleList: [
{ vehicleKey: 1, seq: 1, vehicleType: '城配货车', brand: '重汽', model: 'ZZ1180', vin: 'LKLG7C4E4NA774801', plateNo: '浙A10001', deliveryTime: '2025-02-18 09:15', deliveryPerson: '张三', deliveryStatus: '客户已签章', deliveryMileage: 9800, deliveryH2: 10.8, deliveryH2Unit: '%', deliveryElec: 52, vehicleReturned: true, returnTime: '2026-05-12 09:20', returnPerson: '张三' },
{ vehicleKey: 2, seq: 2, vehicleType: '城配货车', brand: '东风', model: 'DFH1190', vin: 'LKLG7C4E4NA774802', plateNo: '浙A10002', deliveryTime: '2025-02-18 11:40', deliveryPerson: '李四', deliveryStatus: '客户已签章', deliveryMileage: 10120, deliveryH2: 9.6, deliveryH2Unit: '%', deliveryElec: 48, vehicleReturned: false },
{ vehicleKey: 3, seq: 3, vehicleType: '城配货车', brand: '福田', model: 'BJ1190', vin: 'LKLG7C4E4NA774803', plateNo: '浙A10003', deliveryTime: '2025-02-18 16:20', deliveryPerson: '王五', deliveryStatus: '客户已签章', deliveryMileage: 11500, deliveryH2: 32, deliveryH2Unit: 'MPa', deliveryElec: 55, vehicleReturned: false }
]
}
]);
var deliveryOrders = deliveryOrdersState[0];
var setDeliveryOrders = deliveryOrdersState[1];
var editDrawerState = useState({ open: false, record: null, mode: 'edit' });
var editDrawer = editDrawerState[0];
var setEditDrawer = editDrawerState[1];
function resolveVehicleReturnedFields(deliveryStatus, v) {
if (!isVehicleDelivered(deliveryStatus || '未开始')) {
return { vehicleReturned: false, returnTime: '', returnPerson: '' };
}
if (!isDeliveryHistoryStatus(deliveryStatus || '未开始')) {
return { vehicleReturned: false, returnTime: '', returnPerson: '' };
}
return {
vehicleReturned: v.vehicleReturned === true || !!(v.returnTime && String(v.returnTime).trim()),
returnTime: v.returnTime || '',
returnPerson: (v.returnPerson && v.returnPerson !== '-') ? String(v.returnPerson).trim() : ''
};
}
function expandOrdersToVehicleRows(orders) {
var rows = [];
(orders || []).forEach(function (order) {
(order.vehicleList || []).forEach(function (v, idx) {
var vk = v.vehicleKey != null ? v.vehicleKey : idx + 1;
var deliveryStatus = v.deliveryStatus || '未开始';
var returnFields = resolveVehicleReturnedFields(deliveryStatus, v);
rows.push({
id: order.id + '_v' + vk,
orderId: order.id,
vehicleKey: vk,
seq: v.seq != null ? v.seq : idx + 1,
deliveryTime: v.deliveryTime || v.actualDate || '',
deliveryPerson: (v.deliveryPerson && v.deliveryPerson !== '-') ? v.deliveryPerson : '',
plateNo: (v.plateNo && v.plateNo !== '-') ? String(v.plateNo).trim() : '',
vin: (v.plateNo && v.plateNo !== '-' && String(v.plateNo).trim()) ? (v.vin || '') : '',
vehicleType: v.vehicleType || '',
brand: v.brand || '',
model: v.model || '',
contractCode: order.contractCode,
customerName: order.customerName,
projectName: order.projectName,
businessDept: order.businessDept || '-',
businessOwner: order.businessOwner || '-',
taskSource: order.taskSource || '-',
replaceOldPlate: (v.replaceOldPlate || order.replaceOldPlate || '').trim(),
bizType: order.bizType || '-',
deliveryRegion: order.deliveryRegion || '-',
deliveryAddress: order.deliveryAddress || '-',
deliveryStatus: deliveryStatus,
deliveryMileage: v.deliveryMileage,
deliveryH2: v.deliveryH2,
deliveryH2Unit: v.deliveryH2Unit === 'MPa' ? 'MPa' : '%',
deliveryElec: v.deliveryElec,
createTime: order.createTime,
createBy: order.createBy,
expectedDate: order.expectedDate,
vehicleReturned: returnFields.vehicleReturned,
returnTime: returnFields.returnTime,
returnPerson: returnFields.returnPerson
});
});
});
return rows;
}
function formatDeliveryMileage(v) {
if (v === null || v === undefined || v === '') return '-';
return String(v) + ' km';
}
function formatDeliveryH2(v, unit) {
if (v === null || v === undefined || v === '') return '-';
var u = unit === 'MPa' ? 'MPa' : '%';
return String(v) + ' ' + u;
}
function formatDeliveryElec(v) {
if (v === null || v === undefined || v === '') return '-';
return String(v) + ' %';
}
function renderDeliveryRecordCell(record) {
return React.createElement('div', null,
React.createElement('div', { style: cellLineSubStyle }, '里程 ', formatDeliveryMileage(record.deliveryMileage)),
React.createElement('div', { style: cellLineSubStyle }, '氢量 ', formatDeliveryH2(record.deliveryH2, record.deliveryH2Unit)),
React.createElement('div', { style: cellLineSubStyle }, '电量 ', formatDeliveryElec(record.deliveryElec))
);
}
function rowDateKey(row) {
var t = row.deliveryTime || '';
if (t && t.length >= 10) return t.slice(0, 10);
return (row.createTime || '').slice(0, 10);
}
function parseExpectedEndDate(expectedDate) {
if (!expectedDate) return '';
var s = String(expectedDate).trim();
if (s.indexOf('至') >= 0) {
var seg = s.split('至');
return (seg[seg.length - 1] || '').trim().slice(0, 10);
}
return s.slice(0, 10);
}
function computeRowDelayed(row) {
if (isDeliveryHistoryStatus(row.deliveryStatus)) return false;
var end = parseExpectedEndDate(row.expectedDate);
if (!end) return false;
return end < '2026-06-01';
}
function filterSelectOption(input, option) {
var label = (option && (option.label || option.children)) || '';
return String(label).toLowerCase().indexOf(String(input || '').toLowerCase()) >= 0;
}
function filterVehicleRows(rows) {
var list = rows.slice();
var f = appliedFilters;
if (f.contractCode) {
var cc = (contractCodeOptions.find(function (o) { return o.value === f.contractCode; }) || {}).label || f.contractCode;
list = list.filter(function (r) { return (r.contractCode || '').indexOf(cc) !== -1; });
}
if (f.projectName) {
var projLabel = (projectNameOptions.find(function (o) { return o.value === f.projectName; }) || {}).label;
if (projLabel) list = list.filter(function (r) { return r.projectName === projLabel; });
}
if (f.customerName) {
var custLabel = (customerNameOptions.find(function (o) { return o.value === f.customerName; }) || {}).label;
if (custLabel) list = list.filter(function (r) { return r.customerName === custLabel; });
}
if (f.deliveryRegion) list = list.filter(function (r) { return (r.deliveryRegion || '').indexOf(f.deliveryRegion) !== -1; });
if (f.dateStart) list = list.filter(function (r) { return rowDateKey(r) >= f.dateStart; });
if (f.dateEnd) list = list.filter(function (r) { return rowDateKey(r) <= f.dateEnd; });
if (f.deliveryPerson) list = list.filter(function (r) { return (r.deliveryPerson || '').indexOf(f.deliveryPerson) !== -1; });
if (f.plateNo) list = list.filter(function (r) { return !isPlatePending(r.plateNo) && String(r.plateNo).indexOf(f.plateNo) !== -1; });
if (f.vin) list = list.filter(function (r) { return (r.vin || '').indexOf(f.vin) !== -1; });
if (f.vehicleType) list = list.filter(function (r) { return r.vehicleType === f.vehicleType; });
if (f.brand) list = list.filter(function (r) { return r.brand === f.brand; });
if (f.model) list = list.filter(function (r) { return r.model === f.model; });
if (f.businessDept) list = list.filter(function (r) { return (r.businessDept || '').indexOf(f.businessDept) !== -1; });
if (f.businessOwner) list = list.filter(function (r) { return (r.businessOwner || '').indexOf(f.businessOwner) !== -1; });
if (f.taskSource) list = list.filter(function (r) { return r.taskSource === f.taskSource; });
if (f.bizType) list = list.filter(function (r) { return r.bizType === f.bizType; });
if (f.isDelayed === 'yes') list = list.filter(function (r) { return computeRowDelayed(r); });
if (f.isDelayed === 'no') list = list.filter(function (r) { return !computeRowDelayed(r); });
return list;
}
var allVehicleRows = useMemo(function () {
return expandOrdersToVehicleRows(deliveryOrders);
}, [deliveryOrders]);
var operatorVisibleRows = useMemo(function () {
return filterRowsByOperatorRegion(allVehicleRows);
}, [allVehicleRows]);
function buildSelectOptions(values) {
var seen = {};
var opts = [];
(values || []).forEach(function (v) {
var s = v == null ? '' : String(v).trim();
if (!s || seen[s]) return;
seen[s] = true;
opts.push({ value: s, label: s });
});
opts.sort(function (a, b) { return a.label.localeCompare(b.label, 'zh-CN'); });
return opts;
}
var dynamicFilterOptions = useMemo(function () {
var plates = [];
var vins = [];
var vehicleTypes = [];
var brands = [];
var models = [];
var depts = [];
var owners = [];
operatorVisibleRows.forEach(function (r) {
if (!isPlatePending(r.plateNo)) plates.push(r.plateNo);
if (r.vin) vins.push(r.vin);
if (r.vehicleType) vehicleTypes.push(r.vehicleType);
if (r.brand) brands.push(r.brand);
if (r.model) models.push(r.model);
if (r.businessDept && r.businessDept !== '-') depts.push(r.businessDept);
if (r.businessOwner && r.businessOwner !== '-') owners.push(r.businessOwner);
});
return {
plateNoOptions: buildSelectOptions(plates),
vinOptions: buildSelectOptions(vins),
vehicleTypeOptions: buildSelectOptions(vehicleTypes),
brandOptions: buildSelectOptions(brands),
modelOptions: buildSelectOptions(models),
businessDeptOptions: buildSelectOptions(depts),
businessOwnerOptions: buildSelectOptions(owners)
};
}, [operatorVisibleRows]);
var filteredBySearch = useMemo(function () {
return filterVehicleRows(operatorVisibleRows);
}, [operatorVisibleRows, appliedFilters]);
var kpiStats = useMemo(function () {
var inProgress = 0;
var completed = 0;
filteredBySearch.forEach(function (r) {
if (isDeliveryHistoryStatus(r.deliveryStatus)) completed++;
else if (isDeliveryInProgressStatus(r.deliveryStatus)) inProgress++;
});
return { total: filteredBySearch.length, inProgress: inProgress, completed: completed };
}, [filteredBySearch]);
function matchKpiFilter(row, filterKey) {
if (filterKey === 'total') return true;
if (filterKey === 'inProgress') return isDeliveryInProgressStatus(row.deliveryStatus);
if (filterKey === 'completed') return isDeliveryHistoryStatus(row.deliveryStatus);
return true;
}
var filteredList = useMemo(function () {
return filteredBySearch.filter(function (r) { return matchKpiFilter(r, kpiFilter); });
}, [filteredBySearch, kpiFilter]);
function patchVehicleInOrder(orderId, vehicleKey, patch) {
setDeliveryOrders(function (orders) {
return orders.map(function (order) {
if (order.id !== orderId) return order;
return Object.assign({}, order, {
vehicleList: (order.vehicleList || []).map(function (v) {
var vk = v.vehicleKey != null ? v.vehicleKey : v.seq;
if (vk !== vehicleKey) return v;
return Object.assign({}, v, patch);
})
});
});
});
}
var openEditDrawer = useCallback(function (record) {
setEditDrawer({ open: true, record: record, mode: 'edit' });
}, []);
var openViewDrawer = useCallback(function (record) {
setEditDrawer({ open: true, record: record, mode: 'view' });
}, []);
var closeEditDrawer = useCallback(function () {
setEditDrawer({ open: false, record: null, mode: 'edit' });
}, []);
var handleEditSave = useCallback(function (patch) {
var rec = editDrawer.record;
if (!rec) return;
patchVehicleInOrder(rec.orderId, rec.vehicleKey, Object.assign({}, patch, { deliveryStatus: '已保存' }));
}, [editDrawer.record]);
var handleEditSubmit = useCallback(function (patch) {
var rec = editDrawer.record;
if (!rec) return;
patchVehicleInOrder(rec.orderId, rec.vehicleKey, Object.assign({}, patch, { deliveryStatus: '待客户签章', deliveryTime: patch.deliveryTime || '2026-06-04 10:00', deliveryPerson: patch.deliveryPerson || '魏山' }));
closeEditDrawer();
}, [editDrawer.record, closeEditDrawer]);
var navigateToContractDetail = useCallback(function (record) {
try {
sessionStorage.setItem(DV_CONTRACT_CODE_KEY, record.contractCode || '');
sessionStorage.setItem(DV_ORDER_KEY, record.orderId || '');
} catch (e) {}
message.info('跳转 合同管理-该合同详情页(合同编号 ' + (record.contractCode || '-') + '');
}, []);
var navigateDelivery = useCallback(function (record, target) {
try {
sessionStorage.setItem(DV_ORDER_KEY, record.orderId || '');
sessionStorage.setItem(DV_VEHICLE_KEY, String(record.vehicleKey != null ? record.vehicleKey : ''));
sessionStorage.setItem(DV_NAV_KEY, target || 'order');
} catch (e) {}
var pageMap = { order: '交车管理-交车单', edit: '交车管理-交车单-编辑', view: '交车管理-交车单-查看' };
message.info('跳转 ' + (pageMap[target] || target) + '(交车单 ' + record.orderId + ' · 车辆 ' + record.plateNo + '');
}, []);
var page = pageState[0];
var setPage = pageState[1];
var pageSize = pageSizeState[0];
var setPageSize = pageSizeState[1];
var handleKpiCardClick = useCallback(function (key) {
setKpiFilter(key);
setPage(1);
}, []);
var totalCount = filteredList.length;
var displayList = useMemo(function () {
var start = (page - 1) * pageSize;
return filteredList.slice(start, start + pageSize);
}, [filteredList, page, pageSize]);
var kpiExportLabelMap = { total: '全部交车任务', inProgress: '进行中的交车任务', completed: '已完成的交车任务' };
function normalizeRegionFilter(regionVal) {
if (Array.isArray(regionVal) && regionVal.length >= 2) {
var prov = regionOptions.find(function (r) { return r.value === regionVal[0]; });
var city = prov && prov.children && prov.children.find(function (c) { return c.value === regionVal[1]; });
return prov && city ? prov.label + '-' + city.label : undefined;
}
if (Array.isArray(regionVal) && regionVal.length === 1) {
var pOnly = regionOptions.find(function (r) { return r.value === regionVal[0]; });
return pOnly ? pOnly.label : undefined;
}
return regionVal;
}
var handleQuery = useCallback(function () {
var next = patchFilters(filters, { deliveryRegion: normalizeRegionFilter(filters.deliveryRegion) });
setFilters(next);
setAppliedFilters(patchFilters(next, {}));
setPage(1);
}, [filters]);
var handleReset = useCallback(function () {
var empty = createEmptyFilters();
setFilters(empty);
setAppliedFilters(empty);
setPage(1);
}, []);
var handleListPlateNoChange = useCallback(function (v) {
setFilters(function (f) { return patchFilters(f, { plateNo: v }); });
setAppliedFilters(function (f) { return patchFilters(f, { plateNo: v }); });
setPage(1);
}, []);
var getExportRowsByKpi = useCallback(function (filterKey) {
return filteredBySearch.filter(function (r) { return matchKpiFilter(r, filterKey); });
}, [filteredBySearch]);
var handleExport = useCallback(function () {
var tabLabel = kpiExportLabelMap[kpiFilter] || '交车任务';
var rows = getExportRowsByKpi(kpiFilter);
if (!rows || rows.length === 0) {
message.warning('当前「' + tabLabel + '」无数据可导出');
return;
}
var escapeCsv = function (v) {
var s = v == null ? '' : String(v);
if (s.indexOf(',') !== -1 || s.indexOf('"') !== -1 || s.indexOf('\n') !== -1) return '"' + s.replace(/"/g, '""') + '"';
return s;
};
var headers = ['完成交车时间', '交车人', '车牌号', '品牌', '型号', '合同编号', '客户名称', '项目名称', '业务部门', '业务负责人', '任务来源', '业务类型', '交车区域', '交车地点', '交车状态', '交车里程', '交车氢量', '交车电量', '创建时间', '创建人'];
var rowToCells = function (r) {
return [formatCompletedDeliveryTimeExport(r.deliveryTime), r.deliveryPerson || '-', displayPlateNo(r.plateNo), r.brand || '-', r.model || '-', r.contractCode, r.customerName, r.projectName, r.businessDept, r.businessOwner, r.taskSource, r.bizType, r.deliveryRegion, r.deliveryAddress, r.deliveryStatus, formatDeliveryMileage(r.deliveryMileage), formatDeliveryH2(r.deliveryH2, r.deliveryH2Unit), formatDeliveryElec(r.deliveryElec), r.createTime, r.createBy];
};
var csv = headers.map(escapeCsv).join(',') + '\n';
rows.forEach(function (r) {
csv += rowToCells(r).map(escapeCsv).join(',') + '\n';
});
var blob = new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8' });
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = '交车管理_' + tabLabel + '_' + new Date().getTime() + '.csv';
a.click();
URL.revokeObjectURL(url);
message.success('已导出「' + tabLabel + '」共 ' + rows.length + ' 条(含当前筛选条件)');
}, [kpiFilter, getExportRowsByKpi]);
var dateRangeValue = useMemo(function () {
if (!filters.dateStart && !filters.dateEnd) return null;
try {
if (typeof window !== 'undefined' && window.dayjs && filters.dateStart && filters.dateEnd) {
return [window.dayjs(filters.dateStart), window.dayjs(filters.dateEnd)];
}
} catch (e) {}
return null;
}, [filters.dateStart, filters.dateEnd]);
var onDateRangeChange = useCallback(function (dates, dateStrings) {
setFilters(function (f) {
var g = {}; for (var k in f) g[k] = f[k];
g.dateStart = (dateStrings && dateStrings[0]) || '';
g.dateEnd = (dateStrings && dateStrings[1]) || '';
return g;
});
}, []);
// 交车区域 Cascader 的 value用 appliedFilters 反推或存 value 数组。筛选用字符串比较,表单用 Cascader 选省-市
var deliveryRegionValue = useMemo(function () {
var s = filters.deliveryRegion;
if (!s || typeof s !== 'string') return undefined;
var parts = s.split('-');
if (parts.length < 2) return undefined;
for (var i = 0; i < regionOptions.length; i++) {
var prov = regionOptions[i];
if (prov.label !== parts[0]) continue;
for (var j = 0; j < (prov.children || []).length; j++) {
if (prov.children[j].label === parts[1]) return [prov.value, prov.children[j].value];
}
}
return undefined;
}, [filters.deliveryRegion]);
var solidTagBaseStyle = { margin: 0, border: 'none', fontWeight: 600, color: '#fff', lineHeight: '20px', flexShrink: 0, flexGrow: 0 };
function renderSolidTag(text, bgColor) {
if (!text || text === '-') return '-';
return React.createElement(Tag, {
style: Object.assign({}, solidTagBaseStyle, { backgroundColor: bgColor || '#64748b' })
}, text);
}
function renderDeliveryStatus(status, record) {
var bg = '#8c8c8c';
if (status === '客户已签章' || status === '已签章') bg = '#16a34a';
else if (status === '待客户签章') bg = '#2563eb';
else if (status === '已保存') bg = '#ea580c';
else if (status === '未开始') bg = '#64748b';
var signed = isDeliverySignedStatus(status);
var style = Object.assign({}, solidTagBaseStyle, { backgroundColor: bg || '#64748b' });
if (signed) {
style.cursor = 'pointer';
style.textDecoration = 'underline';
style.textUnderlineOffset = '2px';
}
return React.createElement(Tag, {
style: style,
title: signed ? '点击下载签章文件' : undefined,
role: signed ? 'button' : undefined,
tabIndex: signed ? 0 : undefined,
onClick: signed ? function (e) { e.stopPropagation(); downloadDeliverySignFile(record); } : undefined,
onKeyDown: signed ? function (e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.stopPropagation(); downloadDeliverySignFile(record); } } : undefined
}, status);
}
function renderBizTypeTag(bizType) {
if (!bizType || bizType === '-') return null;
var bg = '#64748b';
if (bizType === '租赁') bg = '#2563eb';
else if (bizType === '自营') bg = '#7c3aed';
return React.createElement(Tag, {
style: Object.assign({}, solidTagBaseStyle, {
backgroundColor: bg,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
textAlign: 'center',
alignSelf: 'center',
minWidth: 44,
height: 22,
padding: '0 10px',
boxSizing: 'border-box'
})
}, bizType);
}
function renderTaskSourceTag(text, bgColor) {
return React.createElement(Tag, {
style: Object.assign({}, solidTagBaseStyle, { backgroundColor: bgColor, cursor: 'default' })
}, text);
}
var handleColumnResizeStart = useCallback(function (colKey, e) {
e.preventDefault();
e.stopPropagation();
var startX = e.clientX;
var startW = colWidths[colKey] || 240;
function onMove(ev) {
var nextW = Math.max(140, Math.min(560, startW + ev.clientX - startX));
setColWidths(function (prev) {
var next = {};
for (var k in prev) next[k] = prev[k];
next[colKey] = nextW;
return next;
});
}
function onUp() {
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
}
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
}, [colWidths]);
function renderResizableTitle(title, colKey) {
return React.createElement('div', { style: { position: 'relative', paddingRight: 10, userSelect: 'none' } },
title,
React.createElement('span', {
role: 'separator',
'aria-orientation': 'vertical',
title: '拖动调整列宽',
style: {
position: 'absolute',
right: 0,
top: 0,
bottom: 0,
width: 8,
cursor: 'col-resize',
zIndex: 1
},
onMouseDown: function (ev) { handleColumnResizeStart(colKey, ev); }
})
);
}
function renderContractInfoCell(record) {
var customerName = record.customerName || '-';
var showTip = customerName !== '-';
var contractCode = record.contractCode || '-';
var projectName = record.projectName || '-';
return React.createElement('div', { style: { minWidth: 0, width: '100%' } },
React.createElement('div', {
style: {
display: 'flex',
alignItems: 'center',
flexWrap: 'nowrap',
gap: 6,
minWidth: 0,
width: '100%',
lineHeight: 1.45
}
},
React.createElement(Tooltip, { title: showTip ? customerName : null, placement: 'topLeft' },
React.createElement('span', {
style: {
flex: '1 1 auto',
minWidth: 0,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
color: '#333',
display: 'block'
}
}, customerName)
),
React.createElement('span', {
style: {
flexShrink: 0,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center'
}
}, renderBizTypeTag(record.bizType))
),
contractCode !== '-'
? React.createElement('div', {
style: Object.assign({}, cellLineSubStyle, DV_LIST_LINK_STYLE, { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }),
role: 'button',
tabIndex: 0,
onClick: function () { navigateToContractDetail(record); },
onKeyDown: function (e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); navigateToContractDetail(record); } }
}, contractCode)
: React.createElement('div', { style: cellLineSubStyle }, '-'),
projectName !== '-'
? React.createElement('div', {
style: Object.assign({}, cellLineSubStyle, DV_LIST_LINK_STYLE, { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }),
role: 'button',
tabIndex: 0,
onClick: function () { navigateToContractDetail(record); },
onKeyDown: function (e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); navigateToContractDetail(record); } }
}, projectName)
: React.createElement('div', { style: Object.assign({}, cellLineSubStyle, { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }) }, '-')
);
}
function renderDeliveryPlaceCell(record) {
var regionText = '交车区域:' + formatDeliveryRegion(record.deliveryRegion);
var parkingText = isPlatePending(record.plateNo) ? '-' : (record.deliveryAddress || '-');
return renderCellLines(regionText, [parkingText]);
}
function parseCompletedDeliveryTime(raw) {
if (!raw || String(raw).trim() === '' || raw === '-') return { date: '-', time: '-' };
var s = String(raw).trim();
if (s.indexOf(' ') >= 0) {
var parts = s.split(/\s+/);
var datePart = parts[0] || '-';
var timePart = parts[1] || '-';
if (timePart.length >= 5) timePart = timePart.slice(0, 5);
return { date: datePart, time: timePart };
}
return { date: s, time: '-' };
}
function renderCompletedDeliveryTimeCell(record) {
var parsed = parseCompletedDeliveryTime(record.deliveryTime);
return renderCellLines(parsed.date, [parsed.time]);
}
function renderTaskCreateTimeCell(record) {
var parsed = parseCompletedDeliveryTime(record.createTime);
if (parsed.date === '-' && parsed.time === '-') return '-';
if (parsed.time === '-') return parsed.date;
return parsed.date + ' ' + parsed.time;
}
function renderTaskCreateByCell(createBy) {
if (createBy == null || createBy === undefined) return '-';
var name = String(createBy).trim();
return name === '' || name === '-' ? '-' : name;
}
function isVehicleReturned(record) {
if (!record || !isVehicleDelivered(record.deliveryStatus) || !isDeliveryHistoryStatus(record.deliveryStatus)) return false;
return record.vehicleReturned === true;
}
function renderVehicleReturnedCell(record) {
if (!record || !isVehicleDelivered(record.deliveryStatus)) {
return '-';
}
if (!isVehicleReturned(record)) {
return React.createElement('span', { style: { color: '#64748b' } }, '未归还');
}
var label = React.createElement('span', {
style: { color: '#16a34a', fontWeight: 600, cursor: 'default', borderBottom: '1px dashed #86efac' }
}, '已归还');
return React.createElement(Popover, {
content: renderReturnVehiclePopoverContent(record),
trigger: 'hover',
placement: 'topLeft',
mouseEnterDelay: 0.15,
mouseLeaveDelay: 0.1,
destroyTooltipOnHide: true
}, label);
}
function formatReturnTimeDisplay(raw) {
var parsed = parseCompletedDeliveryTime(raw);
if (parsed.date === '-' && parsed.time === '-') return '-';
if (parsed.time === '-') return parsed.date;
return parsed.date + ' ' + parsed.time;
}
function renderReturnVehiclePopoverContent(record) {
return React.createElement('div', { style: { minWidth: 168, fontSize: 13, lineHeight: 1.65 } },
React.createElement('div', null,
React.createElement('span', { style: { color: '#64748b' } }, '还车时间:'),
React.createElement('span', { style: { color: '#334155', fontWeight: 600 } }, formatReturnTimeDisplay(record.returnTime))
),
React.createElement('div', { style: { marginTop: 4 } },
React.createElement('span', { style: { color: '#64748b' } }, '还车人:'),
React.createElement('span', { style: { color: '#334155', fontWeight: 600 } }, record.returnPerson || '-')
)
);
}
function formatCompletedDeliveryTimeExport(raw) {
var parsed = parseCompletedDeliveryTime(raw);
if (parsed.date === '-' && parsed.time === '-') return '-';
if (parsed.time === '-') return parsed.date;
return parsed.date + ' ' + parsed.time;
}
var cellLineMainStyle = { lineHeight: 1.45, color: '#333', wordBreak: 'break-all' };
var cellLineSubStyle = { lineHeight: 1.4, fontSize: 12, color: '#8c8c8c', marginTop: 2, wordBreak: 'break-all' };
function renderCellLines(mainText, subLines) {
var subs = subLines || [];
return React.createElement('div', null,
React.createElement('div', { style: cellLineMainStyle }, mainText || '-'),
subs.map(function (line, i) {
return React.createElement('div', { key: i, style: cellLineSubStyle }, line || '-');
})
);
}
function isPlatePending(plateNo) {
if (plateNo == null || plateNo === undefined) return true;
var s = String(plateNo).trim();
return s === '' || s === '-';
}
function displayPlateNo(plateNo) {
return isPlatePending(plateNo) ? '车牌待选' : String(plateNo).trim();
}
function displayBrandModel(brand, model) {
var b = brand && brand !== '-' ? String(brand).trim() : '';
var m = model && model !== '-' ? String(model).trim() : '';
if (b && m) return b + '-' + m;
if (b) return b;
if (m) return m;
return '-';
}
function displayVin(vin, plateNo) {
if (plateNo != null && isPlatePending(plateNo)) return '-';
var s = vin == null || vin === undefined ? '' : String(vin).trim();
return s || '-';
}
function displayReplaceOldPlate(plate) {
var s = plate == null ? '' : String(plate).trim();
return s || '-';
}
function renderReplaceVehiclePopoverContent(record) {
var oldPlate = displayReplaceOldPlate(record.replaceOldPlate);
var newPlate = displayPlateNo(record.plateNo);
return React.createElement('div', { style: { minWidth: 168, fontSize: 13, lineHeight: 1.5 } },
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' } },
React.createElement('span', { style: { color: '#64748b' } }, '旧车'),
React.createElement('span', { style: { fontWeight: 600, color: '#334155' } }, oldPlate),
React.createElement('span', { style: { color: '#94a3b8', fontWeight: 600 } }, '→'),
React.createElement('span', { style: { color: '#64748b' } }, '新车'),
React.createElement('span', {
style: { fontWeight: 600, color: isPlatePending(record.plateNo) ? '#d48806' : '#334155' }
}, newPlate)
)
);
}
function renderTaskSourceCell(value, record) {
if (value === '替换车') {
return React.createElement(Popover, {
content: renderReplaceVehiclePopoverContent(record),
trigger: 'hover',
placement: 'topLeft',
mouseEnterDelay: 0.15,
mouseLeaveDelay: 0.1,
destroyTooltipOnHide: true
}, renderTaskSourceTag('替换车', '#ea580c'));
}
if (value === '交车任务') return renderTaskSourceTag('交车任务', '#2563eb');
return value || '-';
}
function renderVehicleInfoCell(record) {
var pending = isPlatePending(record.plateNo);
var plateStyle = Object.assign({}, cellLineMainStyle, { cursor: 'pointer' }, pending ? { color: '#d48806', fontWeight: 500 } : { color: '#165dff' });
return React.createElement('div', null,
React.createElement('div', {
style: plateStyle,
role: 'button',
tabIndex: 0,
onClick: function () { openViewDrawer(record); },
onKeyDown: function (e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openViewDrawer(record); } }
}, displayPlateNo(record.plateNo)),
React.createElement('div', { style: cellLineSubStyle }, displayBrandModel(record.brand, record.model)),
React.createElement('div', { style: cellLineSubStyle }, displayVin(record.vin, record.plateNo))
);
}
// 列表列(按车辆一行;待处理/历史记录列顺序一致)
var colDeliveryTime = { title: '完成交车时间', key: 'completedDeliveryTime', width: 118, render: function (_, r) { return renderCompletedDeliveryTimeCell(r); } };
var colDeliveryPerson = { title: '交车人', dataIndex: 'deliveryPerson', key: 'deliveryPerson', width: 80, render: function (v) { return v || '-'; } };
var colDeliveryStatus = { title: '交车状态', dataIndex: 'deliveryStatus', key: 'deliveryStatus', width: 108, render: function (v, r) { return renderDeliveryStatus(v, r); } };
var sharedListColumns = useMemo(function () {
return [
{
title: '车辆信息',
key: 'vehicleInfo',
width: 188,
render: function (_, r) { return renderVehicleInfoCell(r); }
},
{
title: renderResizableTitle('合同信息', 'contractInfo'),
key: 'contractInfo',
width: colWidths.contractInfo,
render: function (_, r) { return renderContractInfoCell(r); }
},
{
title: '业务负责人',
key: 'businessInfo',
width: 120,
render: function (_, r) {
return renderCellLines(r.businessDept, [r.businessOwner]);
}
},
{ title: '任务来源', dataIndex: 'taskSource', key: 'taskSource', width: 92, ellipsis: true, render: function (v, r) { return renderTaskSourceCell(v, r); } },
{ title: '交车地点', key: 'deliveryPlace', width: 168, render: function (_, r) { return renderDeliveryPlaceCell(r); } },
colDeliveryStatus,
colDeliveryTime,
colDeliveryPerson,
{ title: '是否归还', key: 'vehicleReturned', width: 88, render: function (_, r) { return renderVehicleReturnedCell(r); } },
{
title: '交车记录',
key: 'deliveryRecord',
width: 118,
render: function (_, r) { return renderDeliveryRecordCell(r); }
},
{ title: '交车任务创建时间', key: 'taskCreateTime', width: 150, ellipsis: true, render: function (_, r) { return renderTaskCreateTimeCell(r); } },
{ title: '交车任务创建人', dataIndex: 'createBy', key: 'taskCreateBy', width: 108, ellipsis: true, render: function (v) { return renderTaskCreateByCell(v); } }
];
}, [colWidths.contractInfo, handleColumnResizeStart, navigateToContractDetail]);
var listColumns = useMemo(function () {
return sharedListColumns.concat([
{
title: '操作', key: 'action', width: 96, fixed: 'right', render: function (_, r) {
var nodes = [
React.createElement(Button, { key: 'view', type: 'link', size: 'small', onClick: function () { openViewDrawer(r); } }, '查看')
];
if (canEditDeliveryRow(r)) {
nodes.push(React.createElement(Button, { key: 'edit', type: 'link', size: 'small', onClick: function () { openEditDrawer(r); } }, '编辑'));
}
return React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 2, flexWrap: 'nowrap' } }, nodes);
}
}
]);
}, [sharedListColumns, openEditDrawer, openViewDrawer]);
var kpiCards = useMemo(function () {
return [
{ key: 'total', type: 'total', title: '全部交车任务', desc: '当前筛选条件下的全部交车任务(含进行中与已完成)', val: kpiStats.total, icon: DV_KPI_ICONS.total },
{ key: 'inProgress', type: 'progress', title: '进行中的交车任务', desc: '交车状态为「未开始」「已保存」「待客户签章」的任务(客户未完成最终签章)', val: kpiStats.inProgress, icon: DV_KPI_ICONS.progress },
{ key: 'completed', type: 'completed', title: '已完成的交车任务', desc: '客户已完成最终签章步骤的所有交车任务(状态为「客户已签章」)', val: kpiStats.completed, icon: DV_KPI_ICONS.completed }
];
}, [kpiStats]);
function renderKpiCard(card) {
var active = kpiFilter === card.key;
return React.createElement('div', {
key: card.key,
role: 'button',
tabIndex: 0,
className: 'lc-alert-card lc-alert-card--' + card.type + ' lc-alert-card-clickable' + (active ? ' lc-alert-card-active' : ''),
onClick: function () { handleKpiCardClick(card.key); },
onKeyDown: function (e) {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleKpiCardClick(card.key);
}
}
},
React.createElement('div', { className: 'lc-alert-card-tip-anchor' },
React.createElement(Tooltip, { title: card.desc, placement: 'topRight', overlayStyle: { maxWidth: 360 } },
React.createElement('span', {
className: 'lc-alert-card-tip',
role: 'img',
'aria-label': card.title + '说明',
onClick: function (e) { e.stopPropagation(); },
onMouseDown: function (e) { e.stopPropagation(); }
}, DV_KPI_TIP_SVG)
)
),
React.createElement('div', { className: 'lc-alert-card-icon' }, card.icon),
React.createElement('div', { className: 'lc-alert-card-main' },
React.createElement('div', { className: 'lc-alert-card-val' }, card.val),
React.createElement('div', { className: 'lc-alert-card-title' }, card.title)
)
);
}
var tablePagination = useMemo(function () {
return {
current: page,
pageSize: pageSize,
total: totalCount,
showSizeChanger: true,
showTotal: function (t) { return '共 ' + t + ' 条'; },
pageSizeOptions: ['10', '20', '50'],
onChange: function (p, size) { setPage(p); if (size !== pageSize) setPageSize(size); }
};
}, [page, pageSize, totalCount]);
var filterLabelStyle = { display: 'block', marginBottom: 4, color: '#333', fontSize: 14 };
var filterControlStyle = { width: '100%' };
var filterItemStyle = { minWidth: 0 };
var styles = {
page: { padding: '16px 24px 48px', backgroundColor: '#f5f5f5', minHeight: '100vh', 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' },
cardBody: { padding: '20px 24px' },
filterGrid: { display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: '16px 24px', alignItems: 'start' },
filterActions: { display: 'flex', justifyContent: 'flex-end', alignItems: 'center', gap: 8, marginTop: 16, paddingTop: 16, borderTop: '1px solid #f1f5f9' }
};
var filterItems = [
React.createElement('div', { key: 'contractCode', style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '合同编号'),
React.createElement(Select, {
placeholder: '请输入或选择合同编号',
allowClear: true,
showSearch: true,
optionFilterProp: 'label',
style: filterControlStyle,
value: filters.contractCode,
onChange: function (v) { setFilters(function (f) { return patchFilters(f, { contractCode: v }); }); },
options: contractCodeOptions
})
),
React.createElement('div', { key: 'projectName', style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '项目名称'),
React.createElement(Select, {
placeholder: '请输入或选择项目名称',
allowClear: true,
showSearch: true,
optionFilterProp: 'label',
style: filterControlStyle,
value: filters.projectName,
onChange: function (v) { setFilters(function (f) { return patchFilters(f, { projectName: v }); }); },
options: projectNameOptions
})
),
React.createElement('div', { key: 'customerName', style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '客户名称'),
React.createElement(Select, {
placeholder: '请输入或选择客户名称',
allowClear: true,
showSearch: true,
optionFilterProp: 'label',
style: filterControlStyle,
value: filters.customerName,
onChange: function (v) { setFilters(function (f) { return patchFilters(f, { customerName: v }); }); },
options: customerNameOptions
})
),
React.createElement('div', { key: 'deliveryRegion', style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '交车区域'),
React.createElement(Cascader, {
options: regionOptions,
placeholder: '请选择省-市',
allowClear: true,
style: filterControlStyle,
value: deliveryRegionValue,
onChange: function (value) {
var s;
if (value && value.length >= 2) {
var prov = regionOptions.find(function (r) { return r.value === value[0]; });
var city = prov && prov.children && prov.children.find(function (c) { return c.value === value[1]; });
s = prov && city ? prov.label + '-' + city.label : undefined;
} else { s = undefined; }
setFilters(function (f) { return patchFilters(f, { deliveryRegion: s }); });
},
displayRender: function (labels) { return labels && labels.length ? labels.join(' / ') : ''; }
})
),
React.createElement('div', { key: 'completedTime', style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '完成交车时间'),
React.createElement(RangePicker, {
style: filterControlStyle,
placeholder: ['请选择开始时间', '请选择结束时间'],
value: dateRangeValue,
onChange: onDateRangeChange
})
),
React.createElement('div', { key: 'deliveryPerson', style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '交车人'),
React.createElement(Select, {
placeholder: '请输入或选择交车人',
allowClear: true,
showSearch: true,
optionFilterProp: 'label',
style: filterControlStyle,
value: filters.deliveryPerson,
onChange: function (v) { setFilters(function (f) { return patchFilters(f, { deliveryPerson: v }); }); },
options: deliveryPersonOptions
})
),
React.createElement('div', { key: 'vin', style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '车辆识别代码'),
React.createElement(Select, {
placeholder: '请输入或选择车辆识别代码',
allowClear: true,
showSearch: true,
style: filterControlStyle,
value: filters.vin,
onChange: function (v) { setFilters(function (f) { return patchFilters(f, { vin: v }); }); },
options: dynamicFilterOptions.vinOptions,
filterOption: filterSelectOption
})
),
React.createElement('div', { key: 'vehicleType', style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '车辆类型'),
React.createElement(Select, {
placeholder: '请选择车辆类型',
allowClear: true,
showSearch: true,
style: filterControlStyle,
value: filters.vehicleType,
onChange: function (v) { setFilters(function (f) { return patchFilters(f, { vehicleType: v }); }); },
options: dynamicFilterOptions.vehicleTypeOptions,
filterOption: filterSelectOption
})
),
React.createElement('div', { key: 'brand', style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '品牌'),
React.createElement(Select, {
placeholder: '请选择品牌',
allowClear: true,
showSearch: true,
style: filterControlStyle,
value: filters.brand,
onChange: function (v) { setFilters(function (f) { return patchFilters(f, { brand: v }); }); },
options: dynamicFilterOptions.brandOptions,
filterOption: filterSelectOption
})
),
React.createElement('div', { key: 'model', style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '型号'),
React.createElement(Select, {
placeholder: '请选择或输入型号',
allowClear: true,
showSearch: true,
style: filterControlStyle,
value: filters.model,
onChange: function (v) { setFilters(function (f) { return patchFilters(f, { model: v }); }); },
options: dynamicFilterOptions.modelOptions,
filterOption: filterSelectOption
})
),
React.createElement('div', { key: 'businessDept', style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '业务部门'),
React.createElement(Select, {
placeholder: '请输入或选择业务部门',
allowClear: true,
showSearch: true,
style: filterControlStyle,
value: filters.businessDept,
onChange: function (v) { setFilters(function (f) { return patchFilters(f, { businessDept: v }); }); },
options: dynamicFilterOptions.businessDeptOptions,
filterOption: filterSelectOption
})
),
React.createElement('div', { key: 'businessOwner', style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '业务负责人'),
React.createElement(Select, {
placeholder: '请输入或选择业务负责人',
allowClear: true,
showSearch: true,
style: filterControlStyle,
value: filters.businessOwner,
onChange: function (v) { setFilters(function (f) { return patchFilters(f, { businessOwner: v }); }); },
options: dynamicFilterOptions.businessOwnerOptions,
filterOption: filterSelectOption
})
),
React.createElement('div', { key: 'taskSource', style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '任务来源'),
React.createElement(Select, {
placeholder: '请选择任务来源',
allowClear: true,
style: filterControlStyle,
value: filters.taskSource,
onChange: function (v) { setFilters(function (f) { return patchFilters(f, { taskSource: v }); }); },
options: taskSourceOptions
})
),
React.createElement('div', { key: 'bizType', style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '业务类型'),
React.createElement(Select, {
placeholder: '请选择业务类型',
allowClear: true,
style: filterControlStyle,
value: filters.bizType,
onChange: function (v) { setFilters(function (f) { return patchFilters(f, { bizType: v }); }); },
options: bizTypeOptions
})
),
React.createElement('div', { key: 'isDelayed', style: filterItemStyle },
React.createElement('div', { style: filterLabelStyle }, '是否延期'),
React.createElement(Select, {
placeholder: '请选择是否延期',
allowClear: true,
style: filterControlStyle,
value: filters.isDelayed,
onChange: function (v) { setFilters(function (f) { return patchFilters(f, { isDelayed: v }); }); },
options: isDelayedOptions
})
)
];
var filterVisibleCount = filterExpanded ? filterItems.length : 4;
var filterNodes = [];
var fi;
for (fi = 0; fi < filterVisibleCount && fi < filterItems.length; fi++) {
filterNodes.push(filterItems[fi]);
}
var breadcrumbItems = [
{ title: '运维管理' },
{ title: '车辆业务' },
{ title: '交车管理' }
];
return React.createElement('div', { style: styles.page },
React.createElement('style', null, DV_KPI_STYLE),
React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 } },
React.createElement(Breadcrumb, { items: breadcrumbItems }),
React.createElement(Button, { type: 'link', onClick: function () { setRequirementModalOpen(true); } }, '查看需求说明')
),
React.createElement(Card, { style: { marginBottom: 16 } },
React.createElement('div', { style: styles.cardBody },
React.createElement('div', { style: styles.filterGrid }, filterNodes),
React.createElement('div', { style: styles.filterActions },
React.createElement(Button, { onClick: handleReset }, '重置'),
React.createElement(Button, { type: 'primary', onClick: handleQuery }, '搜索'),
React.createElement(Button, {
type: 'link',
size: 'small',
onClick: function () { setFilterExpanded(!filterExpanded); },
style: { display: 'inline-flex', alignItems: 'center', gap: 4, padding: '0 4px' }
},
filterExpanded ? '收起' : '展开',
React.createElement('svg', {
width: 12,
height: 12,
viewBox: '0 0 24 24',
fill: 'none',
stroke: 'currentColor',
strokeWidth: 2,
strokeLinecap: 'round',
strokeLinejoin: 'round',
style: { transform: filterExpanded ? 'rotate(180deg)' : 'none', transition: 'transform 0.2s ease' }
}, React.createElement('polyline', { points: '6 9 12 15 18 9' }))
)
)
)
),
React.createElement(Card, null,
React.createElement('div', { style: styles.cardBody },
React.createElement('div', { className: 'dv-kpi-stats-row' }, kpiCards.map(renderKpiCard)),
React.createElement('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12, flexWrap: 'wrap', gap: 12 } },
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 } },
React.createElement('span', { style: { color: '#333', fontSize: 14, whiteSpace: 'nowrap' } }, '车牌号'),
React.createElement(Select, {
placeholder: '请输入或选择车牌号',
allowClear: true,
showSearch: true,
style: { width: 220 },
value: appliedFilters.plateNo,
onChange: handleListPlateNoChange,
options: dynamicFilterOptions.plateNoOptions,
filterOption: filterSelectOption
})
),
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap', marginLeft: 'auto' } },
React.createElement('span', { style: { fontSize: 13, color: '#64748b' } },
'当前标签:',
React.createElement('span', { style: { color: '#334155', fontWeight: 600 } }, kpiExportLabelMap[kpiFilter] || '-'),
' · 导出与列表一致(含筛选,全部命中行)'
),
React.createElement(Button, { onClick: handleExport }, '导出')
)
),
React.createElement(Table, {
columns: listColumns,
dataSource: displayList,
rowKey: 'id',
pagination: tablePagination,
tableLayout: 'fixed',
scroll: { x: 1760 },
size: 'middle'
})
)
),
React.createElement(Modal, {
title: '需求说明',
open: requirementModalOpen[0],
onCancel: function () { setRequirementModalOpen(false); },
footer: React.createElement(Button, { onClick: function () { setRequirementModalOpen(false); } }, '关闭'),
width: 800,
destroyOnClose: true
}, React.createElement('div', { style: { maxHeight: '72vh', overflowY: 'auto', whiteSpace: 'pre-wrap', lineHeight: 1.65, fontSize: 13, color: '#334155' } }, requirementDocContent)),
React.createElement(DeliveryEditDrawer, {
open: editDrawer.open,
record: editDrawer.record,
mode: editDrawer.mode,
onClose: closeEditDrawer,
onSave: handleEditSave,
onSubmit: handleEditSubmit
})
);
};
if (typeof window !== 'undefined') {
window.Component = Component;
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () {
var rootEl = document.getElementById('root');
if (rootEl && window.ReactDOM && window.React) {
var root = ReactDOM.createRoot(rootEl);
root.render(React.createElement(Component));
}
});
} else {
var rootEl = document.getElementById('root');
if (rootEl && window.ReactDOM && window.React) {
var root = ReactDOM.createRoot(rootEl);
root.render(React.createElement(Component));
}
}
}