174 lines
4.9 KiB
TypeScript
174 lines
4.9 KiB
TypeScript
/**
|
||
* 三方车辆/人员客户文本粘贴解析
|
||
* 标准格式见 `PL_PASTE_SAMPLE_TEXT`。
|
||
*/
|
||
|
||
/**
|
||
* 粘贴识别输入框预填示例(格式与解析规则一致,敏感字段已脱敏,供用户对照改写)
|
||
* 姓名保留姓、手机保留前 3 后 4、身份证保留前 6 后 4、车牌保留省份简称后打码
|
||
*/
|
||
export const PL_PASTE_SAMPLE_TEXT = [
|
||
'车牌:皖******皖******挂',
|
||
'核载:34 吨',
|
||
'驾驶员:张**',
|
||
'身份证号:340000********0000',
|
||
'手机号:138****0000',
|
||
'押运员:李**',
|
||
'身份证号:340000********0000',
|
||
'电话:139****0000'
|
||
].join('\n')
|
||
|
||
export interface PlPastePersonnelDraft {
|
||
personnelName: string
|
||
phoneNo?: string
|
||
idCardNo?: string
|
||
personnelType: 'driver' | 'escort'
|
||
}
|
||
|
||
export interface PlPasteVehicleDraft {
|
||
licensePlate: string
|
||
trailerNo?: string
|
||
loadCapacity?: number
|
||
}
|
||
|
||
export interface PlPasteRecognizeResult {
|
||
vehicle?: PlPasteVehicleDraft
|
||
driver?: PlPastePersonnelDraft
|
||
escort?: PlPastePersonnelDraft
|
||
warnings: string[]
|
||
}
|
||
|
||
/** 普通蓝牌 7 位、挂车带「挂」、新能源约 8 位;每次新建避免 lastIndex 残留 */
|
||
function plateTokenRe() {
|
||
return /[\u4e00-\u9fa5][A-HJ-NP-Z][A-HJ-NP-Z0-9]{4,6}挂?/gi
|
||
}
|
||
|
||
const PLATE_LINE_LABEL = /^(?:车牌号|车牌|主车号|主车|号牌)[::]\s*/i
|
||
const TRAILER_LINE_LABEL = /^(?:挂车号|挂车)[::]\s*/i
|
||
const ID_CARD_LINE = /^(?:身份证号码|身份证号|身份证|证件号)[::]?\s*([0-9Xx]{15,18})$/
|
||
|
||
function extractPlates(text: string): string[] {
|
||
const compact = text.replace(/\s+/g, '').toUpperCase()
|
||
const found = compact.match(plateTokenRe()) || []
|
||
return found
|
||
}
|
||
|
||
function remainderAfterPlates(text: string, plates: string[]): string {
|
||
let rest = text.replace(/\s+/g, '').toUpperCase()
|
||
for (const plate of plates) {
|
||
rest = rest.replace(plate.toUpperCase(), '')
|
||
}
|
||
return rest.replace(/[,,、;;\-_//]/g, '')
|
||
}
|
||
|
||
function applyPlatesToVehicle(
|
||
vehicle: PlPasteVehicleDraft | undefined,
|
||
plates: string[]
|
||
): PlPasteVehicleDraft {
|
||
const next = vehicle || { licensePlate: '' }
|
||
if (plates[0] && !next.licensePlate) {
|
||
next.licensePlate = plates[0]
|
||
}
|
||
if (plates[1] && !next.trailerNo) {
|
||
next.trailerNo = plates[1]
|
||
} else if (plates.length === 1 && plates[0].endsWith('挂') && next.licensePlate && !next.trailerNo) {
|
||
next.trailerNo = plates[0]
|
||
if (next.licensePlate === plates[0]) {
|
||
next.licensePlate = ''
|
||
}
|
||
}
|
||
return next
|
||
}
|
||
|
||
export function parsePlCustomerVehicleText(raw: string): PlPasteRecognizeResult {
|
||
const warnings: string[] = []
|
||
const lines = raw
|
||
.split(/\r?\n/)
|
||
.map(l => l.trim())
|
||
.filter(Boolean)
|
||
|
||
let vehicle: PlPasteVehicleDraft | undefined
|
||
let driver: PlPastePersonnelDraft | undefined
|
||
let escort: PlPastePersonnelDraft | undefined
|
||
let section: 'none' | 'driver' | 'escort' = 'none'
|
||
|
||
for (const line of lines) {
|
||
if (TRAILER_LINE_LABEL.test(line)) {
|
||
const plates = extractPlates(line.replace(TRAILER_LINE_LABEL, ''))
|
||
if (plates.length) {
|
||
if (!vehicle) vehicle = { licensePlate: '' }
|
||
vehicle.trailerNo = plates[0]
|
||
continue
|
||
}
|
||
}
|
||
|
||
const plateLabeled = PLATE_LINE_LABEL.test(line)
|
||
const plateSource = plateLabeled ? line.replace(PLATE_LINE_LABEL, '') : line
|
||
const plates = extractPlates(plateSource)
|
||
if (plates.length && (plateLabeled || !remainderAfterPlates(plateSource, plates))) {
|
||
vehicle = applyPlatesToVehicle(vehicle, plates)
|
||
continue
|
||
}
|
||
|
||
const load = line.match(/核载[::]?\s*([\d.]+)\s*吨?/)
|
||
if (load) {
|
||
if (!vehicle) {
|
||
vehicle = { licensePlate: '' }
|
||
}
|
||
vehicle.loadCapacity = Number(load[1])
|
||
continue
|
||
}
|
||
|
||
if (/^驾驶员[::]/.test(line)) {
|
||
section = 'driver'
|
||
driver = {
|
||
personnelName: line.replace(/^驾驶员[::]\s*/, '').trim(),
|
||
personnelType: 'driver'
|
||
}
|
||
continue
|
||
}
|
||
|
||
if (/^押运员[::]/.test(line)) {
|
||
section = 'escort'
|
||
escort = {
|
||
personnelName: line.replace(/^押运员[::]\s*/, '').trim(),
|
||
personnelType: 'escort'
|
||
}
|
||
continue
|
||
}
|
||
|
||
const phone = line.match(/^(?:手机号|电话)[::]?\s*(\d{11})$/)
|
||
if (phone) {
|
||
if (section === 'driver' && driver) {
|
||
driver.phoneNo = phone[1]
|
||
continue
|
||
}
|
||
if (section === 'escort' && escort) {
|
||
escort.phoneNo = phone[1]
|
||
continue
|
||
}
|
||
}
|
||
|
||
const idCard = line.match(ID_CARD_LINE)
|
||
if (idCard) {
|
||
const no = idCard[1].toUpperCase()
|
||
if (section === 'driver' && driver) {
|
||
driver.idCardNo = no
|
||
continue
|
||
}
|
||
if (section === 'escort' && escort) {
|
||
escort.idCardNo = no
|
||
continue
|
||
}
|
||
}
|
||
|
||
warnings.push(`未识别行:${line}`)
|
||
}
|
||
|
||
if (vehicle && !vehicle.licensePlate.trim()) {
|
||
warnings.push('未识别到主车车牌')
|
||
}
|
||
|
||
return { vehicle, driver, escort, warnings }
|
||
}
|