提交
This commit is contained in:
1 parent
a52fe77e34
commit
1938a9b76a
62 files changed
+612436
-10819
No files matched your search
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 从中国大陆 18 位身份证号解析出生日期并计算周岁年龄。
|
||||
* 号码不足 18 位或出生日期无效时返回 null(不强制校验码,便于输入过程中回填)。
|
||||
*/
|
||||
export function calcAgeFromChineseIdCard(idCard: string | null | undefined): number | null {
|
||||
if (!idCard || typeof idCard !== 'string') return null
|
||||
const s = idCard.trim().toUpperCase()
|
||||
if (!/^\d{17}[\dX]$/.test(s)) return null
|
||||
|
||||
const year = Number(s.slice(6, 10))
|
||||
const month = Number(s.slice(10, 12))
|
||||
const day = Number(s.slice(12, 14))
|
||||
if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) return null
|
||||
if (month < 1 || month > 12 || day < 1 || day > 31) return null
|
||||
|
||||
const birth = new Date(year, month - 1, day)
|
||||
if (
|
||||
birth.getFullYear() !== year ||
|
||||
birth.getMonth() !== month - 1 ||
|
||||
birth.getDate() !== day
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
const today = new Date()
|
||||
let age = today.getFullYear() - year
|
||||
const beforeBirthday =
|
||||
today.getMonth() < month - 1 ||
|
||||
(today.getMonth() === month - 1 && today.getDate() < day)
|
||||
if (beforeBirthday) age -= 1
|
||||
|
||||
if (age < 0 || age > 120) return null
|
||||
return age
|
||||
}
|
||||
@@ -8,14 +8,14 @@
|
||||
* 姓名保留姓、手机保留前 3 后 4、身份证保留前 6 后 4、车牌保留省份简称后打码
|
||||
*/
|
||||
export const PL_PASTE_SAMPLE_TEXT = [
|
||||
'皖****** 皖******',
|
||||
'车牌:皖******皖******挂',
|
||||
'核载:34 吨',
|
||||
'驾驶员:张**',
|
||||
'身份证号:340000********0000',
|
||||
'手机号:138****0000',
|
||||
'身份证:340000********0000',
|
||||
'押运员:李**',
|
||||
'电话:139****0000',
|
||||
'身份证:340000********0000'
|
||||
'身份证号:340000********0000',
|
||||
'电话:139****0000'
|
||||
].join('\n')
|
||||
|
||||
export interface PlPastePersonnelDraft {
|
||||
@@ -38,8 +38,47 @@ export interface PlPasteRecognizeResult {
|
||||
warnings: string[]
|
||||
}
|
||||
|
||||
const PLATE_LINE =
|
||||
/^[\u4e00-\u9fa5A-Z]{1,2}[A-Z0-9]+(?:\s+[\u4e00-\u9fa5A-Z]{1,2}[A-Z0-9]+)?$/i
|
||||
/** 普通蓝牌 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[] = []
|
||||
@@ -54,13 +93,20 @@ export function parsePlCustomerVehicleText(raw: string): PlPasteRecognizeResult
|
||||
let section: 'none' | 'driver' | 'escort' = 'none'
|
||||
|
||||
for (const line of lines) {
|
||||
const normalizedPlateLine = line.replace(/\s+/g, ' ')
|
||||
if (!vehicle && PLATE_LINE.test(normalizedPlateLine)) {
|
||||
const parts = normalizedPlateLine.split(/\s+/).filter(Boolean)
|
||||
vehicle = {
|
||||
licensePlate: parts[0] || '',
|
||||
trailerNo: parts[1] || undefined
|
||||
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
|
||||
}
|
||||
|
||||
@@ -103,7 +149,7 @@ export function parsePlCustomerVehicleText(raw: string): PlPasteRecognizeResult
|
||||
}
|
||||
}
|
||||
|
||||
const idCard = line.match(/^身份证[::]?\s*([0-9Xx]{15,18})$/)
|
||||
const idCard = line.match(ID_CARD_LINE)
|
||||
if (idCard) {
|
||||
const no = idCard[1].toUpperCase()
|
||||
if (section === 'driver' && driver) {
|
||||
|
||||
Reference in new issue
Block a user