Files
ONE-OS/web端/运维管理/车辆业务/上牌管理.jsx
王冕 09cc45db36 Initial commit: ONE-OS project
Made-with: Cursor
2026-02-27 18:11:40 +08:00

939 lines
46 KiB
JavaScript
Executable File
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 作为组件变量名
// 车辆上牌管理 - 车辆资产管理后台模块
var ARCO_TOKEN = {
primary: '#165DFF',
primaryHover: '#4080FF',
danger: '#F53F3F',
success: '#00B42A',
neutral1: '#FFFFFF',
neutral2: '#F7F8FA',
neutral3: '#F2F3F5',
neutral4: '#E5E6EB',
neutral5: '#C9CDD4',
neutral6: '#86909C',
neutral7: '#4E5969',
neutral8: '#1D2129',
border: '#E5E6EB',
fill: '#F2F3F5',
fillSecondary: '#F7F8FA',
shadowLight: '0 1px 2px rgba(0,0,0,0.05)',
shadowMedium: '0 2px 8px rgba(0,0,0,0.08)',
radiusSmall: '2px',
radiusMedium: '4px',
radiusLarge: '8px',
spacing8: '8px',
spacing12: '12px',
spacing16: '16px',
spacing24: '24px',
fontSize14: '14px',
fontSize16: '16px',
fontFamily: '-apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Arial, sans-serif',
link: '#165DFF'
};
const Component = function () {
var antd = window.antd;
var Input = antd.Input;
var Select = antd.Select;
var Button = antd.Button;
var DatePicker = antd.DatePicker;
var Table = antd.Table;
var Modal = antd.Modal;
var message = antd.message;
var Option = Select.Option;
var Spin = antd.Spin;
var _useState = React.useState('');
var filterDateStart = _useState[0];
var setFilterDateStart = _useState[1];
var _useState2 = React.useState('');
var filterDateEnd = _useState2[0];
var setFilterDateEnd = _useState2[1];
var _useState3 = React.useState('');
var filterOperator = _useState3[0];
var setFilterOperator = _useState3[1];
var _useState4 = React.useState('');
var filterPlateNo = _useState4[0];
var setFilterPlateNo = _useState4[1];
var _useState5 = React.useState('');
var filterVin = _useState5[0];
var setFilterVin = _useState5[1];
var _useState5h = React.useState('');
var appliedDateStart = _useState5h[0];
var setAppliedDateStart = _useState5h[1];
var _useState5i = React.useState('');
var appliedDateEnd = _useState5i[0];
var setAppliedDateEnd = _useState5i[1];
var _useState5j = React.useState('');
var appliedOperator = _useState5j[0];
var setAppliedOperator = _useState5j[1];
var _useState5k = React.useState('');
var appliedPlateNo = _useState5k[0];
var setAppliedPlateNo = _useState5k[1];
var _useState5l = React.useState('');
var appliedVin = _useState5l[0];
var setAppliedVin = _useState5l[1];
var _useState6 = React.useState(1);
var currentPage = _useState6[0];
var setCurrentPage = _useState6[1];
var _useState7 = React.useState(10);
var pageSize = _useState7[0];
var setPageSize = _useState7[1];
var _useState9 = React.useState(null);
var viewPhotoRecord = _useState9[0];
var setViewPhotoRecord = _useState9[1];
var _useState10 = React.useState(false);
var ocrModalVisible = _useState10[0];
var setOcrModalVisible = _useState10[1];
var _useState11 = React.useState(false);
var confirmModalVisible = _useState11[0];
var setConfirmModalVisible = _useState11[1];
var _useState12 = React.useState(null);
var confirmData = _useState12[0];
var setConfirmData = _useState12[1];
var _useState13 = React.useState([]);
var batchConfirmList = _useState13[0];
var setBatchConfirmList = _useState13[1];
var _useState14 = React.useState(0);
var batchConfirmIndex = _useState14[0];
var setBatchConfirmIndex = _useState14[1];
var _useState15 = React.useState(false);
var isBatchMode = _useState15[0];
var setIsBatchMode = _useState15[1];
var _useState15b = React.useState(0);
var batchTotalCount = _useState15b[0];
var setBatchTotalCount = _useState15b[1];
var _useState16 = React.useState('');
var confirmVin = _useState16[0];
var setConfirmVin = _useState16[1];
var _useState17 = React.useState('');
var confirmPlateNo = _useState17[0];
var setConfirmPlateNo = _useState17[1];
var _useState17a = React.useState('');
var confirmScrapDate = _useState17a[0];
var setConfirmScrapDate = _useState17a[1];
var _useState17b = React.useState('');
var confirmInspectionExpiry = _useState17b[0];
var setConfirmInspectionExpiry = _useState17b[1];
var _useState18b = React.useState(false);
var showRequirementModal = _useState18b[0];
var setShowRequirementModal = _useState18b[1];
var _useState18c = React.useState(false);
var batchTaskCardVisible = _useState18c[0];
var setBatchTaskCardVisible = _useState18c[1];
var fileInputRef = React.useRef(null);
var batchFullListRef = React.useRef(null);
var batchUploadFromCardRef = React.useRef(false);
var batchFileInputRef = React.useRef(null);
var _useState18e = React.useState([]);
var batchTaskList = _useState18e[0];
var setBatchTaskList = _useState18e[1];
var mockVehicleList = [
{ id: 'v001', frameNo: 'LGW123456', brand: '比亚迪', model: '秦', vehicleType: '轿车' },
{ id: 'v002', frameNo: 'LGW789012', brand: '特斯拉', model: 'Model 3', vehicleType: '轿车' },
{ id: 'v003', frameNo: 'HZ111222', brand: '小鹏', model: 'P7', vehicleType: '轿车' }
];
var initialRecordList = [
{
id: 'r001',
plateDate: '2025-02-01',
operator: '张明',
plateNo: '粤A12345',
vin: 'LGW123456',
vehicleType: '轿车',
brand: '比亚迪',
model: '秦',
photoUrl: 'https://picsum.photos/300/200?random=1'
},
{
id: 'r002',
plateDate: '2025-02-03',
operator: '王芳',
plateNo: '粤A67890',
vin: 'LGW789012',
vehicleType: '轿车',
brand: '特斯拉',
model: 'Model 3',
photoUrl: 'https://picsum.photos/300/200?random=2'
}
];
// 近5条批量上牌任务时间、照片数量、完成进度进度100%时有 items 用于「识别」进入确认界面
var getInitialBatchTaskList = function () {
return [
{ id: 'bt1', createTime: '2025-02-12 10:30', photoCount: 3, progress: 100, items: [
{ photoUrl: 'https://picsum.photos/300/200?random=b1', vin: 'LGW123456', plateNo: '粤A11111', vehicle: mockVehicleList[0] },
{ photoUrl: 'https://picsum.photos/300/200?random=b2', vin: 'LGW789012', plateNo: '粤A22222', vehicle: mockVehicleList[1] },
{ photoUrl: 'https://picsum.photos/300/200?random=b3', vin: 'HZ111222', plateNo: '粤A33333', vehicle: mockVehicleList[2] }
]},
{ id: 'bt2', createTime: '2025-02-12 09:15', photoCount: 5, progress: 100, items: [
{ photoUrl: 'https://picsum.photos/300/200?random=b4', vin: 'LGW123456', plateNo: '粤A44444', vehicle: mockVehicleList[0] },
{ photoUrl: 'https://picsum.photos/300/200?random=b5', vin: 'LGW789012', plateNo: '粤A55555', vehicle: mockVehicleList[1] }
]},
{ id: 'bt3', createTime: '2025-02-11 16:20', photoCount: 4, progress: 80, items: null },
{ id: 'bt4', createTime: '2025-02-11 14:00', photoCount: 2, progress: 50, items: null },
{ id: 'bt5', createTime: '2025-02-10 11:30', photoCount: 6, progress: 30, items: null }
];
};
React.useEffect(function () {
setBatchTaskList(function (prev) {
if (prev.length === 0) return getInitialBatchTaskList();
return prev;
});
}, []);
var _useState18 = React.useState(initialRecordList);
var recordList = _useState18[0];
var setRecordList = _useState18[1];
var getUniqueOperators = function () {
var seen = {};
var list = [];
recordList.forEach(function (r) {
if (r.operator && !seen[r.operator]) {
seen[r.operator] = true;
list.push(r.operator);
}
});
return list.sort();
};
var getUniquePlateNos = function () {
var seen = {};
var list = [];
recordList.forEach(function (r) {
if (r.plateNo && !seen[r.plateNo]) {
seen[r.plateNo] = true;
list.push(r.plateNo);
}
});
return list.sort();
};
var getUniqueVins = function () {
var seen = {};
var list = [];
recordList.forEach(function (r) {
if (r.vin && !seen[r.vin]) {
seen[r.vin] = true;
list.push(r.vin);
}
});
mockVehicleList.forEach(function (v) {
if (v.frameNo && !seen[v.frameNo]) {
seen[v.frameNo] = true;
list.push(v.frameNo);
}
});
return list.sort();
};
var allOperators = getUniqueOperators();
var allPlateNos = getUniquePlateNos();
var allVins = getUniqueVins();
var getFilteredList = function () {
var list = recordList;
if (appliedDateStart) {
list = list.filter(function (r) { return r.plateDate >= appliedDateStart; });
}
if (appliedDateEnd) {
list = list.filter(function (r) { return r.plateDate <= appliedDateEnd; });
}
if (appliedOperator) {
list = list.filter(function (r) {
return r.operator && r.operator.indexOf(appliedOperator) >= 0;
});
}
if (appliedPlateNo) {
list = list.filter(function (r) {
return r.plateNo && r.plateNo.indexOf(appliedPlateNo) >= 0;
});
}
if (appliedVin) {
list = list.filter(function (r) {
return r.vin && r.vin.indexOf(appliedVin) >= 0;
});
}
return list;
};
var filteredList = getFilteredList();
var totalItems = filteredList.length;
var totalPages = Math.ceil(totalItems / pageSize) || 1;
var validPage = currentPage > totalPages && totalPages > 0 ? 1 : (currentPage < 1 ? 1 : currentPage);
var startIndex = (validPage - 1) * pageSize;
var endIndex = startIndex + pageSize;
var paginatedList = filteredList.slice(startIndex, endIndex);
var findVehicleByVin = function (vin) {
return mockVehicleList.find(function (v) { return v.frameNo === vin; });
};
var todayStr = function () {
var d = new Date();
var y = d.getFullYear();
var m = (d.getMonth() + 1).toString();
var day = d.getDate().toString();
if (m.length === 1) { m = '0' + m; }
if (day.length === 1) { day = '0' + day; }
return y + '-' + m + '-' + day;
};
var sampleScrapDates = ['2035-12-31', '2030-06-15', '2028-03-20'];
var sampleInspectionExpiries = ['2026-06', '2025-12', '2027-03'];
var getSampleScrapDate = function () { return sampleScrapDates[Math.floor(Math.random() * sampleScrapDates.length)]; };
var getSampleInspectionExpiry = function () { return sampleInspectionExpiries[Math.floor(Math.random() * sampleInspectionExpiries.length)]; };
var processFileAndGetItem = function (file, callback) {
var reader = new FileReader();
reader.onload = function () {
var photoUrl = reader.result;
var mockVin = mockVehicleList[0].frameNo;
var mockPlate = '粤A' + Math.floor(Math.random() * 90000 + 10000).toString();
var vehicle = findVehicleByVin(mockVin);
var item = {
photoUrl: photoUrl,
vin: mockVin,
plateNo: mockPlate,
vehicle: vehicle
};
callback(item);
};
reader.readAsDataURL(file);
};
var simulateOcrAndConfirm = function (items, isMulti) {
setOcrModalVisible(false);
if (isMulti && items.length > 1) {
batchFullListRef.current = items.slice();
setBatchConfirmList(items);
setBatchConfirmIndex(0);
setBatchTotalCount(items.length);
setConfirmData(items[0]);
setConfirmVin(items[0].vin);
setConfirmPlateNo(items[0].plateNo);
setConfirmScrapDate(getSampleScrapDate());
setConfirmInspectionExpiry(getSampleInspectionExpiry());
setConfirmModalVisible(true);
setIsBatchMode(true);
} else if (items.length > 0) {
setConfirmData(items[0]);
setConfirmVin(items[0].vin);
setConfirmPlateNo(items[0].plateNo);
setConfirmScrapDate(getSampleScrapDate());
setConfirmInspectionExpiry(getSampleInspectionExpiry());
setConfirmModalVisible(true);
setIsBatchMode(false);
}
};
var handleUpload = function (e, isBatch) {
var files = e.target.files;
if (!files || files.length === 0) return;
// 从批量上传弹框内「上传车牌」触发:不进入识别/确认页,只在弹框中新增一条识别任务
if (batchUploadFromCardRef.current) {
batchUploadFromCardRef.current = false;
var fileCount = files.length;
var collected = [];
var processed = 0;
var checkDone = function () {
processed = processed + 1;
if (processed === fileCount) {
var now = new Date();
var timeStr = now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getDate()).padStart(2, '0') + ' ' + String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0');
setBatchTaskList(function (prev) {
var next = [{ id: 'bt' + Date.now(), createTime: timeStr, photoCount: collected.length, progress: 100, items: collected }].concat(prev);
return next.slice(0, 5);
});
}
};
for (var i = 0; i < fileCount; i++) {
(function (idx) {
processFileAndGetItem(files[idx], function (item) {
collected.push(item);
checkDone();
});
})(i);
}
e.target.value = '';
return;
}
setOcrModalVisible(true);
var fileCount = files.length;
var collected = [];
var processed = 0;
var checkDone = function () {
processed = processed + 1;
if (processed === fileCount) {
setTimeout(function () {
simulateOcrAndConfirm(collected, isBatch && collected.length > 1);
}, 1500);
}
};
for (var i = 0; i < fileCount; i++) {
(function (idx) {
processFileAndGetItem(files[idx], function (item) {
collected.push(item);
checkDone();
});
})(i);
}
e.target.value = '';
};
var handleConfirmSubmit = function () {
var vehicle = confirmData && confirmData.vehicle;
var newRecord = {
id: 'r' + Date.now(),
plateDate: todayStr(),
operator: '当前用户',
plateNo: confirmPlateNo,
vin: confirmVin,
vehicleType: vehicle ? vehicle.vehicleType : '轿车',
brand: vehicle ? vehicle.brand : '-',
model: vehicle ? vehicle.model : '-',
photoUrl: confirmData && confirmData.photoUrl ? confirmData.photoUrl : 'https://picsum.photos/300/200?random=' + Date.now()
};
var nextList = recordList.slice();
nextList.unshift(newRecord);
setRecordList(nextList);
message.success('车辆上牌成功');
if (isBatchMode && batchConfirmList.length > 1) {
var nextBatch = batchConfirmList.slice(1);
var nextItem = nextBatch[0];
setBatchConfirmList(nextBatch);
setBatchConfirmIndex(batchConfirmIndex + 1);
if (nextItem) {
setConfirmData(nextItem);
setConfirmVin(nextItem.vin);
setConfirmPlateNo(nextItem.plateNo);
setConfirmScrapDate(getSampleScrapDate());
setConfirmInspectionExpiry(getSampleInspectionExpiry());
} else {
var fullList = batchFullListRef.current;
if (fullList && fullList.length > 0) {
var now = new Date();
var timeStr = now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getDate()).padStart(2, '0') + ' ' + String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0');
setBatchTaskList(function (prev) {
var next = [{ id: 'bt' + Date.now(), createTime: timeStr, photoCount: fullList.length, progress: 100, items: fullList }].concat(prev);
return next.slice(0, 5);
});
batchFullListRef.current = null;
}
setConfirmModalVisible(false);
setConfirmData(null);
setConfirmVin('');
setConfirmPlateNo('');
setConfirmScrapDate('');
setConfirmInspectionExpiry('');
setBatchConfirmList([]);
setBatchTotalCount(0);
setIsBatchMode(false);
}
} else {
setConfirmModalVisible(false);
setConfirmData(null);
setConfirmVin('');
setConfirmPlateNo('');
setConfirmScrapDate('');
setConfirmInspectionExpiry('');
setBatchConfirmList([]);
setBatchTotalCount(0);
setIsBatchMode(false);
}
};
var handleConfirmCancel = function () {
setConfirmModalVisible(false);
setConfirmData(null);
setConfirmVin('');
setConfirmPlateNo('');
setConfirmScrapDate('');
setConfirmInspectionExpiry('');
setBatchConfirmList([]);
setBatchConfirmIndex(0);
setBatchTotalCount(0);
setIsBatchMode(false);
};
var t = ARCO_TOKEN;
var styles = {
page: { padding: t.spacing24, fontFamily: t.fontFamily, backgroundColor: t.fill, minHeight: '100vh' },
breadcrumb: { marginBottom: t.spacing16, fontSize: t.fontSize14, color: t.neutral6, display: 'flex', alignItems: 'center', justifyContent: 'space-between' },
breadcrumbLeft: { display: 'flex', alignItems: 'center' },
breadcrumbLink: { color: t.link, textDecoration: 'none', marginRight: t.spacing8 },
breadcrumbCurrent: { color: t.neutral8 },
breadcrumbRight: { display: 'flex', alignItems: 'center' },
requirementLink: { color: t.link, textDecoration: 'none', fontSize: t.fontSize14, cursor: 'pointer' },
card: { backgroundColor: t.neutral1, borderRadius: t.radiusLarge, boxShadow: t.shadowLight, marginBottom: t.spacing16, padding: t.spacing16 },
filterRow: { display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: t.spacing12 },
filterRowRight: { display: 'flex', alignItems: 'center', gap: t.spacing8, marginLeft: 'auto' },
label: { marginRight: t.spacing8, fontSize: t.fontSize14, color: t.neutral8, whiteSpace: 'nowrap' },
input: { padding: '0 10px', height: '32px', width: '180px', borderRadius: '2px', border: '1px solid ' + t.border, fontSize: t.fontSize14, boxSizing: 'border-box' },
btn: { padding: t.spacing8 + ' ' + t.spacing16, borderRadius: t.radiusMedium, cursor: 'pointer', fontSize: t.fontSize14, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: '6px', boxSizing: 'border-box' },
btnFixed: { width: '82px', height: '32px', padding: 0, borderRadius: '2px', lineHeight: '1' },
btnIcon: { width: '14px', height: '14px', flexShrink: 0, display: 'block' },
btnFillBlue: { backgroundColor: t.primary, color: t.neutral1, border: 'none' },
btnOutlineBlue: { backgroundColor: t.neutral1, color: t.primary, border: '1px solid ' + t.primary },
btnDefault: { backgroundColor: t.neutral1, color: t.neutral8, border: '1px solid ' + t.border },
btnSize82: { width: '82px', height: '32px', padding: 0, lineHeight: '1', boxSizing: 'border-box' },
toolbar: { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: t.spacing12, marginBottom: t.spacing16 },
tableWrap: { overflowX: 'auto', backgroundColor: t.neutral1, borderRadius: t.radiusMedium, border: '1px solid ' + t.neutral4 },
table: { width: '100%', borderCollapse: 'separate', borderSpacing: 0, fontSize: t.fontSize14 },
th: { textAlign: 'left', padding: '12px 16px', backgroundColor: t.fillSecondary, borderBottom: '1px solid ' + t.neutral4, fontWeight: 600, color: t.neutral8, fontSize: t.fontSize14, whiteSpace: 'nowrap' },
td: { padding: '12px 16px', borderBottom: '1px solid ' + t.neutral4, color: t.neutral8, fontSize: t.fontSize14 },
pagination: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '16px', borderTop: '1px solid ' + t.neutral4, backgroundColor: t.neutral1 },
paginationLeft: { display: 'flex', alignItems: 'center', gap: '8px', fontSize: t.fontSize14, color: t.neutral7 },
paginationRight: { display: 'flex', alignItems: 'center', gap: '8px' },
paginationSelect: { padding: '4px 8px', height: '28px', borderRadius: '2px', border: '1px solid ' + t.border, fontSize: t.fontSize14, backgroundColor: t.neutral1 },
paginationBtn: { minWidth: '28px', height: '28px', padding: '0 8px', borderRadius: '2px', border: '1px solid ' + t.border, backgroundColor: t.neutral1, color: t.neutral8, cursor: 'pointer', fontSize: t.fontSize14, display: 'inline-flex', alignItems: 'center', justifyContent: 'center' },
paginationBtnActive: { backgroundColor: t.primary, color: t.neutral1, borderColor: t.primary },
paginationBtnDisabled: { opacity: 0.5, cursor: 'not-allowed' },
paginationInput: { width: '50px', height: '28px', padding: '0 8px', borderRadius: '2px', border: '1px solid ' + t.border, fontSize: t.fontSize14, textAlign: 'center' },
actionLink: { color: t.link, cursor: 'pointer', marginRight: t.spacing12, fontSize: t.fontSize14 },
modalMask: { position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.45)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center' },
modalBox: { backgroundColor: t.neutral1, borderRadius: t.radiusLarge, maxWidth: '90%', maxHeight: '90%', overflow: 'auto', padding: t.spacing24, minWidth: '500px', position: 'relative' },
modalTitle: { fontSize: t.fontSize16, fontWeight: 600, marginBottom: t.spacing16, color: t.neutral8 },
modalFooter: { marginTop: t.spacing24, display: 'flex', justifyContent: 'flex-end', gap: t.spacing8 },
toast: { position: 'fixed', top: t.spacing24, left: '50%', transform: 'translateX(-50%)', backgroundColor: 'rgba(0,0,0,0.75)', color: t.neutral1, padding: '10px 20px', borderRadius: t.radiusMedium, zIndex: 2000, fontSize: t.fontSize14 },
autocompleteWrap: { position: 'relative', width: '180px', display: 'inline-block' },
autocompletePanel: { position: 'absolute', left: 0, top: '100%', marginTop: '4px', backgroundColor: t.neutral1, borderRadius: t.radiusMedium, border: '1px solid ' + t.border, boxShadow: t.shadowMedium, zIndex: 100, minWidth: '100%', maxHeight: '200px', overflowY: 'auto' },
autocompleteOption: { padding: '8px 12px', fontSize: t.fontSize14, color: t.neutral8, cursor: 'pointer' },
autocompleteOptionHover: { backgroundColor: t.fill },
confirmCard: { display: 'flex', gap: t.spacing24, marginTop: t.spacing16 },
confirmPhoto: { flex: '0 0 300px', height: '200px', borderRadius: t.radiusMedium, overflow: 'hidden', backgroundColor: t.neutral3 },
confirmPhotoImg: { width: '100%', height: '100%', objectFit: 'cover' },
confirmForm: { flex: 1, display: 'flex', flexDirection: 'column', gap: t.spacing16 },
formLabel: { display: 'block', marginBottom: '6px', fontSize: t.fontSize14, color: t.neutral8 },
formInput: { padding: '8px 12px', height: '36px', borderRadius: t.radiusMedium, border: '1px solid ' + t.border, fontSize: t.fontSize14, width: '100%', boxSizing: 'border-box' },
photoViewModal: { maxWidth: '800px', textAlign: 'center' },
photoViewImg: { maxWidth: '100%', maxHeight: '70vh', borderRadius: t.radiusMedium },
modalCloseBtn: { position: 'absolute', right: t.spacing16, top: t.spacing16, width: '24px', height: '24px', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', borderRadius: t.radiusMedium, backgroundColor: 'transparent', border: 'none', color: t.neutral6, fontSize: '18px', lineHeight: '1', padding: 0 },
modalContent: { fontSize: t.fontSize14, color: t.neutral8, lineHeight: '1.6' },
requirementSection: { marginBottom: t.spacing16 },
requirementSectionTitle: { fontSize: t.fontSize16, fontWeight: 600, color: t.neutral8, marginBottom: t.spacing8 },
flowWrap: { marginTop: t.spacing8, marginBottom: t.spacing16 },
flowCol: { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 0 },
flowArrow: { width: '2px', height: '20px', backgroundColor: t.neutral5 },
flowNodeOval: { padding: '8px 20px', borderRadius: '20px', border: '1px solid ' + t.neutral5, backgroundColor: t.neutral2, fontSize: t.fontSize14, color: t.neutral8 },
flowNodeRect: { padding: '8px 20px', border: '1px solid ' + t.neutral5, backgroundColor: t.neutral1, fontSize: t.fontSize14, color: t.neutral8 },
flowNodeDiamond: { padding: '10px 16px', border: '1px solid ' + t.neutral5, backgroundColor: t.fillSecondary, fontSize: t.fontSize14, color: t.neutral8, transform: 'rotate(0deg)', width: '140px', textAlign: 'center', boxSizing: 'border-box', clipPath: 'polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)' },
flowNodeDiamondWrap: { padding: '8px 16px', border: '1px solid ' + t.neutral5, backgroundColor: t.fillSecondary, fontSize: t.fontSize14, color: t.neutral8, minWidth: '120px', textAlign: 'center' },
flowNodeToast: { padding: '8px 16px', borderRadius: '8px', border: '1px solid ' + t.neutral4, backgroundColor: t.neutral2, fontSize: t.fontSize14, color: t.neutral7 },
flowRow: { display: 'flex', alignItems: 'flex-start', justifyContent: 'center', gap: '24px', flexWrap: 'wrap' },
flowBranch: { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 0 },
requirementItem: { marginBottom: t.spacing8, paddingLeft: t.spacing16 },
requirementSubItem: { marginBottom: t.spacing4, paddingLeft: t.spacing16, fontSize: t.fontSize14, color: t.neutral7 },
batchTaskCardHeader: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: t.spacing16 },
batchTaskTable: { width: '100%', borderCollapse: 'collapse', fontSize: t.fontSize14 },
batchTaskTh: { textAlign: 'left', padding: '10px 12px', borderBottom: '1px solid ' + t.neutral4, color: t.neutral7, fontWeight: 500 },
batchTaskTd: { padding: '10px 12px', borderBottom: '1px solid ' + t.neutral4, color: t.neutral8 },
progressWrap: { width: '120px', height: '8px', backgroundColor: t.neutral4, borderRadius: '4px', overflow: 'hidden' },
progressBar: { height: '100%', backgroundColor: t.primary, borderRadius: '4px', transition: 'width 0.2s' },
recognizeLink: { color: t.link, cursor: 'pointer', fontSize: t.fontSize14 },
dateRangeWrap: { position: 'relative', display: 'inline-block' },
dateRangeTrigger: { display: 'flex', alignItems: 'center', height: '32px', padding: '0 12px', border: '1px solid ' + t.border, borderRadius: '2px', backgroundColor: t.neutral1, cursor: 'pointer', minWidth: '280px', boxSizing: 'border-box' },
dateRangeTriggerFocused: { borderColor: t.primary, outline: 'none' },
dateRangeLabel: { display: 'flex', alignItems: 'center', gap: '6px', padding: '0 8px', height: '100%', fontSize: t.fontSize14, color: t.neutral8 },
dateRangeLabelActive: { backgroundColor: 'rgba(22,93,255,0.1)', color: t.primary, borderBottom: '2px solid ' + t.primary },
dateRangeLabelText: { whiteSpace: 'nowrap' },
dateRangeDash: { color: t.neutral5, margin: '0 4px', fontSize: t.fontSize14 },
dateRangeIcon: { marginLeft: 'auto', width: '16px', height: '16px', color: t.neutral6, flexShrink: 0 },
dateRangePanel: { position: 'absolute', left: 0, top: '100%', marginTop: '4px', backgroundColor: t.neutral1, borderRadius: t.radiusMedium, border: '1px solid ' + t.border, boxShadow: t.shadowMedium, zIndex: 100, padding: '16px', display: 'flex', gap: '24px' },
dateRangeCalendar: { width: '280px' },
dateRangeCalendarHeader: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '12px', padding: '0 4px' },
dateRangeCalendarTitle: { fontSize: t.fontSize14, fontWeight: 500, color: t.neutral8 },
dateRangeCalendarNav: { display: 'flex', alignItems: 'center', gap: '4px' },
dateRangeNavBtn: { width: '28px', height: '28px', display: 'flex', alignItems: 'center', justifyContent: 'center', border: 'none', backgroundColor: 'transparent', color: t.neutral6, cursor: 'pointer', borderRadius: t.radiusSmall },
dateRangeNavBtnHover: { color: t.primary, backgroundColor: t.fill },
dateRangeWeekRow: { display: 'flex', marginBottom: '4px' },
dateRangeWeekCell: { width: '36px', height: '28px', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '12px', color: t.neutral6 },
dateRangeDayRow: { display: 'flex' },
dateRangeDayCell: { width: '36px', height: '36px', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', fontSize: t.fontSize14, cursor: 'pointer', borderRadius: t.radiusSmall, color: t.neutral8 },
dateRangeDayCellOther: { color: t.neutral5 },
dateRangeDayCellSelected: { color: t.primary },
dateRangeDayCellInRange: { backgroundColor: 'rgba(22,93,255,0.08)', color: t.primary },
dateRangeDayDot: { width: '4px', height: '4px', borderRadius: '50%', backgroundColor: t.primary, marginTop: '2px' }
};
var renderFilterSelect = function (value, setValue, options, placeholder) {
var opts = (options || []).map(function (o) { return React.createElement(Option, { key: o, value: o }, o); });
return React.createElement(Select, {
placeholder: placeholder || '请选择或输入搜索',
style: { width: 180 },
value: value || undefined,
onChange: function (v) { setValue(v || ''); },
showSearch: true,
allowClear: true,
filterOption: function (input, opt) {
var c = opt && opt.children;
return c && String(c).toLowerCase().indexOf((input || '').toLowerCase()) >= 0;
}
}, opts);
};
return React.createElement(
'div',
{ style: styles.page },
React.createElement(
'div',
{ style: styles.breadcrumb },
React.createElement(
'div',
{ style: styles.breadcrumbLeft },
React.createElement('a', { href: '#', style: styles.breadcrumbLink, onClick: function (e) { e.preventDefault(); } }, '运维管理'),
React.createElement('span', { style: { marginRight: '8px' } }, '/'),
React.createElement('a', { href: '#', style: styles.breadcrumbLink, onClick: function (e) { e.preventDefault(); } }, '车辆业务'),
React.createElement('span', { style: { marginRight: '8px' } }, '/'),
React.createElement('span', { style: styles.breadcrumbCurrent }, '上牌管理')
),
React.createElement(
'div',
{ style: styles.breadcrumbRight },
React.createElement('a', { href: '#', style: styles.requirementLink, onClick: function (e) { e.preventDefault(); setShowRequirementModal(true); } }, '查看需求说明')
)
),
React.createElement(
'div',
{ style: styles.card },
React.createElement(
'div',
{ style: styles.filterRow },
React.createElement('span', { style: styles.label }, '上牌日期:'),
React.createElement(DatePicker, {
style: { width: 180 },
format: 'YYYY-MM-DD',
placeholder: '开始日期',
value: filterDateStart && window.moment ? window.moment(filterDateStart, 'YYYY-MM-DD') : null,
onChange: function (d, dateStr) { setFilterDateStart(dateStr || ''); }
}),
React.createElement('span', { style: { color: t.neutral6, margin: '0 4px' } }, '至'),
React.createElement(DatePicker, {
style: { width: 180 },
format: 'YYYY-MM-DD',
placeholder: '结束日期',
value: filterDateEnd && window.moment ? window.moment(filterDateEnd, 'YYYY-MM-DD') : null,
onChange: function (d, dateStr) { setFilterDateEnd(dateStr || ''); }
}),
React.createElement('span', { style: styles.label }, '操作人:'),
renderFilterSelect(filterOperator, setFilterOperator, allOperators, '请选择操作人'),
React.createElement('span', { style: styles.label }, '车牌号:'),
renderFilterSelect(filterPlateNo, setFilterPlateNo, allPlateNos, '请选择车牌号'),
React.createElement('span', { style: styles.label }, '车辆识别代码:'),
renderFilterSelect(filterVin, setFilterVin, allVins, '请选择车辆识别代码'),
React.createElement(
'div',
{ style: styles.filterRowRight },
React.createElement(Button, {
type: 'primary',
onClick: function () {
setAppliedDateStart(filterDateStart);
setAppliedDateEnd(filterDateEnd);
setAppliedOperator(filterOperator);
setAppliedPlateNo(filterPlateNo);
setAppliedVin(filterVin);
setCurrentPage(1);
message.success('查询成功');
}
}, '查询'),
React.createElement(Button, {
onClick: function () {
setFilterDateStart('');
setFilterDateEnd('');
setAppliedDateStart('');
setAppliedDateEnd('');
setFilterOperator('');
setFilterPlateNo('');
setFilterVin('');
setAppliedOperator('');
setAppliedPlateNo('');
setAppliedVin('');
setCurrentPage(1);
}
}, '重置')
)
)
),
React.createElement(
'div',
{ style: styles.card },
React.createElement(
'div',
{ style: styles.toolbar },
React.createElement('input', {
type: 'file',
ref: fileInputRef,
accept: 'image/*',
style: { display: 'none' },
onChange: function (e) { handleUpload(e, false); }
}),
React.createElement('input', {
type: 'file',
ref: batchFileInputRef,
accept: 'image/*',
multiple: true,
style: { display: 'none' },
onChange: function (e) { handleUpload(e, true); }
}),
React.createElement(Button, {
type: 'primary',
onClick: function () { if (fileInputRef.current) fileInputRef.current.click(); }
}, '新增'),
React.createElement(Button, {
onClick: function () { setBatchTaskCardVisible(true); }
}, '批量上传')
),
React.createElement(Table, {
rowKey: 'id',
size: 'small',
columns: [
{ title: '上牌日期', dataIndex: 'plateDate', key: 'plateDate', width: 120 },
{ title: '操作人', dataIndex: 'operator', key: 'operator', width: 100 },
{ title: '车牌号', dataIndex: 'plateNo', key: 'plateNo', width: 120 },
{ title: '车辆识别代码', dataIndex: 'vin', key: 'vin', width: 140 },
{ title: '车辆类型', dataIndex: 'vehicleType', key: 'vehicleType', width: 100 },
{ title: '品牌', dataIndex: 'brand', key: 'brand', width: 100 },
{ title: '型号', dataIndex: 'model', key: 'model', width: 120 },
{ title: '操作', key: 'action', width: 80, render: function (_, row) { return React.createElement(Button, { type: 'link', size: 'small', onClick: function () { setViewPhotoRecord(row); } }, '查看'); } }
],
dataSource: paginatedList,
pagination: {
current: validPage,
pageSize: pageSize,
total: totalItems,
showSizeChanger: true,
showQuickJumper: true,
pageSizeOptions: ['10', '20', '50', '100'],
showTotal: function (total) { return '共 ' + total + ' 条'; },
onChange: function (page, size) {
setCurrentPage(page);
if (size !== pageSize) setPageSize(size);
}
}
})
),
React.createElement(Modal, {
title: '识别中,请勿关闭页面',
visible: ocrModalVisible,
footer: null,
closable: false,
maskClosable: false,
children: React.createElement('div', { style: { padding: '24px 0', textAlign: 'center' } },
React.createElement(Spin, { size: 'large' }),
React.createElement('p', { style: { marginTop: 12, color: t.neutral6 } }, '正在识别行驶证信息...')
)
}),
React.createElement(Modal, {
title: isBatchMode && batchTotalCount > 1 ? '确认上牌信息(' + (batchTotalCount - batchConfirmList.length + 1) + '/' + batchTotalCount + '' : '确认上牌信息',
visible: confirmModalVisible && !!confirmData,
onCancel: handleConfirmCancel,
onOk: handleConfirmSubmit,
okText: '确认',
cancelText: '取消',
width: 560,
children: confirmData ? React.createElement(
'div',
{ style: styles.confirmCard },
React.createElement('div', { style: styles.confirmPhoto },
React.createElement('img', { src: confirmData.photoUrl, alt: '行驶证', style: styles.confirmPhotoImg })
),
React.createElement('div', { style: styles.confirmForm },
React.createElement('div', { style: { marginBottom: 16 } },
React.createElement('label', { style: styles.formLabel }, '车辆识别代号'),
React.createElement(Input, {
style: { width: '100%' },
value: confirmVin,
onChange: function (e) { setConfirmVin(e.target.value); }
})
),
React.createElement('div', { style: { marginBottom: 16 } },
React.createElement('label', { style: styles.formLabel }, '车牌号'),
React.createElement(Input, {
style: { width: '100%' },
value: confirmPlateNo,
onChange: function (e) { setConfirmPlateNo(e.target.value); }
})
),
React.createElement('div', { style: { marginBottom: 16 } },
React.createElement('label', { style: styles.formLabel }, '强制报废日期'),
React.createElement(Input, {
style: { width: '100%' },
placeholder: 'YYYY-MM-DD',
value: confirmScrapDate,
onChange: function (e) { setConfirmScrapDate(e.target.value); }
})
),
React.createElement('div', null,
React.createElement('label', { style: styles.formLabel }, '检验有效期'),
React.createElement(Input, {
style: { width: '100%' },
placeholder: 'YYYY-MM',
value: confirmInspectionExpiry,
onChange: function (e) { setConfirmInspectionExpiry(e.target.value); }
})
)
)
) : null
}),
React.createElement(Modal, {
title: '行驶证照片',
visible: !!viewPhotoRecord,
footer: React.createElement(Button, { onClick: function () { setViewPhotoRecord(null); } }, '关闭'),
onCancel: function () { setViewPhotoRecord(null); },
width: 640,
children: viewPhotoRecord ? React.createElement('img', { src: viewPhotoRecord.photoUrl, alt: '行驶证', style: Object.assign({}, styles.photoViewImg, { width: '100%' }) }) : null
}),
React.createElement(Modal, {
title: '批量上牌任务',
visible: batchTaskCardVisible,
onCancel: function () { setBatchTaskCardVisible(false); },
footer: React.createElement(Button, { onClick: function () { setBatchTaskCardVisible(false); } }, '关闭'),
width: 560,
children: React.createElement(React.Fragment, null,
React.createElement('div', { style: Object.assign({}, styles.batchTaskCardHeader, { marginBottom: 16 }) },
React.createElement(Button, {
type: 'primary',
onClick: function () {
batchUploadFromCardRef.current = true;
if (batchFileInputRef.current) batchFileInputRef.current.click();
}
}, '批量上传')
),
React.createElement(Table, {
rowKey: 'id',
size: 'small',
columns: [
{ title: '任务时间', dataIndex: 'createTime', key: 'createTime', width: 160 },
{ title: '照片数量', dataIndex: 'photoCount', key: 'photoCount', width: 100, render: function (c) { return c + ' 张'; } },
{ title: '完成进度', key: 'progress', width: 160, render: function (_, task) {
return React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
React.createElement('div', { style: styles.progressWrap },
React.createElement('div', { style: Object.assign({}, styles.progressBar, { width: (task.progress || 0) + '%' }) })
),
React.createElement('span', { style: { fontSize: t.fontSize14, color: t.neutral7, minWidth: 36 } }, (task.progress || 0) + '%')
);
} },
{ title: '操作', key: 'action', width: 80, render: function (_, task) {
return task.progress === 100 && task.items && task.items.length > 0
? React.createElement(Button, { type: 'link', size: 'small', onClick: function () {
setBatchConfirmList(task.items);
setBatchConfirmIndex(0);
setBatchTotalCount(task.items.length);
setConfirmData(task.items[0]);
setConfirmVin(task.items[0].vin);
setConfirmPlateNo(task.items[0].plateNo);
setConfirmScrapDate(getSampleScrapDate());
setConfirmInspectionExpiry(getSampleInspectionExpiry());
setConfirmModalVisible(true);
setIsBatchMode(true);
setBatchTaskCardVisible(false);
} }, '识别')
: React.createElement('span', { style: { color: t.neutral5, fontSize: t.fontSize14 } }, '-');
} }
],
dataSource: batchTaskList.slice(0, 5),
pagination: false
})
)
}),
React.createElement(Modal, {
title: '需求说明',
visible: showRequirementModal,
onCancel: function () { setShowRequirementModal(false); },
footer: React.createElement(Button, { onClick: function () { setShowRequirementModal(false); } }, '关闭'),
width: 720,
children: React.createElement('div', { style: styles.modalContent },
React.createElement('div', { style: Object.assign({}, styles.requirementSection, { marginBottom: t.spacing24 }) },
React.createElement('div', { style: Object.assign({}, styles.requirementSectionTitle, { fontSize: '18px', marginBottom: t.spacing12 }) }, '上牌管理'),
React.createElement('div', { style: Object.assign({}, styles.requirementItem, { marginTop: 0, color: t.neutral7 }) }, '用以识别行驶证正反面识别车架号、车牌号、强制报废日期YYYY-MM-DD、检验有效期YYYY-MM')
),
React.createElement('div', { style: styles.requirementSection },
React.createElement('div', { style: styles.requirementSectionTitle }, '1.面包屑:'),
React.createElement('div', { style: styles.requirementItem }, '运维管理-车辆业务-上牌管理')
),
React.createElement('div', { style: styles.requirementSection },
React.createElement('div', { style: styles.requirementSectionTitle }, '2.筛选:'),
React.createElement('div', { style: styles.requirementSubItem }, '2.1.上牌日期:双日历日期选择器,支持选择开始-结束时间,支持手动修改日期,但检验格式及判断结束日期不得早于开始日期;'),
React.createElement('div', { style: styles.requirementSubItem }, '2.2.操作人:选择器,支持从输入框输入内容模糊搜索,默认提示内容为:请选择操作人;'),
React.createElement('div', { style: styles.requirementSubItem }, '2.3.车牌号:选择器,支持从输入框输入内容模糊搜索,默认提示内容为:请选择车牌号;'),
React.createElement('div', { style: styles.requirementSubItem }, '2.4.车辆识别代码:选择器,支持从输入框输入内容模糊搜索,默认提示内容为:请选择车辆识别代码;')
),
React.createElement('div', { style: styles.requirementSection },
React.createElement('div', { style: styles.requirementSectionTitle }, '3.列表:'),
React.createElement('div', { style: styles.requirementItem }, '列表右侧按钮为新增、批量上传,字段依次为上牌日期、操作人、车牌号、车辆识别代码、车辆类型、品牌、型号、操作;'),
React.createElement('div', { style: styles.requirementSubItem }, '3.1.上牌日期车辆上牌操作完成日期格式为YYYY-MM-DD'),
React.createElement('div', { style: styles.requirementSubItem }, '3.2.操作人:车辆上牌记录操作用户姓名;'),
React.createElement('div', { style: styles.requirementSubItem }, '3.3.车牌号显示上牌车牌号通过新增按钮上传行驶证照片后通过OCR识别技术自动识别出该车辆识别代号对应车牌号确认无误提交后自动反写'),
React.createElement('div', { style: styles.requirementSubItem }, '3.4.车辆识别代码:显示上牌车辆车辆识别代码;'),
React.createElement('div', { style: styles.requirementSubItem }, '3.5.车辆类型:显示该车辆识别代码对应车辆类型;'),
React.createElement('div', { style: styles.requirementSubItem }, '3.6.品牌:显示该车辆识别代码对应品牌;'),
React.createElement('div', { style: styles.requirementSubItem }, '3.7.型号:显示该车辆识别代码对应型号;'),
React.createElement('div', { style: styles.requirementSubItem }, '3.8.操作:行驶证,点击放大预览行驶证照片;'),
React.createElement('div', { style: styles.requirementItem }, '下方增加分页功能,支持选择单页数据条数;')
),
React.createElement('div', { style: styles.requirementSection },
React.createElement('div', { style: styles.requirementSectionTitle }, '4.新增:'),
React.createElement('div', { style: styles.requirementSubItem }, '4.1.点击新增按钮上传行驶证照片附件上传后OCR识别过程中弹出卡片提示识别中请勿关闭页面如果照片识别失败则提示识别失败请重新尝试'),
React.createElement('div', { style: styles.requirementSubItem }, '4.2.上传成功后确认卡片中显示照片、车辆识别代码、车牌号、强制报废日期、检验有效期,可通过二次编辑进行校正;'),
React.createElement('div', { style: styles.requirementSubItem }, '4.3.点击卡片页面底部"确认"按钮确认根据车辆识别代码判断是否存在该车辆如果是则toast提示"上牌成功"并在列表中生成操作如果否则toast提示该车辆不存在。')
),
React.createElement('div', { style: styles.requirementSection },
React.createElement('div', { style: styles.requirementSectionTitle }, '5.批量上传:'),
React.createElement('div', { style: styles.requirementItem }, '5.1.点击批量上牌按钮弹出批量上牌任务卡片再点击右上角上传车牌上传多张行驶证照片附件上传后弹出批量上牌任务卡片卡片中记录最近5条批量上牌任务任务字段为任务时间、照片数量、识别错误、完成进度、操作'),
React.createElement('div', { style: styles.requirementSubItem }, '5.1.1.任务时间上传批量照片的时间精确至分钟格式为YYYY-MM-DD HH:MM'),
React.createElement('div', { style: styles.requirementSubItem }, '5.1.2.照片数量:显示这一批照片总数;'),
React.createElement('div', { style: styles.requirementSubItem }, '5.1.3.识别错误显示识别失败的照片总数hover时气泡卡片显示识别失败的照片名称以;分隔;'),
React.createElement('div', { style: styles.requirementSubItem }, '5.1.4.完成进度显示ocr识别进度条和已完成百分比'),
React.createElement('div', { style: styles.requirementSubItem }, '5.1.5.操作:已完成任务操作栏显示识别,未完成任务操作栏显示-'),
React.createElement('div', { style: styles.requirementItem }, '5.2.识别:点击识别确认卡片中当前张数、显示照片、车辆识别代码、车牌号、强制报废日期、检验有效期,可通过二次编辑进行校正;当前张数显示在确认上牌信息标题右侧,格式为(当前/总计依次操作直到完成所有记录提交完成全部完成后toast提示批量上传成功'),
React.createElement('div', { style: styles.requirementItem }, '5.3.在识别确认卡片中点击确认后将立刻上传该照片,可再次通过点击批量上传拉取上传任务框的方式,自动跳转至最后一条上传记录进行操作;'),
React.createElement('div', { style: styles.requirementItem }, '5.4.批量上传任务中照片全部识别完成后,不再显示识别按钮;'),
React.createElement('div', { style: styles.requirementItem }, '5.5.具体识别流程请参考axure菜单目录中上牌管理下的流程图')
)
)
})
);
};
if (typeof window !== 'undefined') {
window.Component = Component;
function mount() {
var rootEl = document.getElementById('root');
if (rootEl && window.ReactDOM && window.React) {
var root = ReactDOM.createRoot(rootEl);
root.render(React.createElement(Component));
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', mount);
} else {
setTimeout(mount, 0);
}
}