/** * 入职登记表固定字段校验工具。 * * 前端校验用于即时提示,后端仍会执行同样的核心校验,避免绕过页面直接提交非法数据。 */ const mobilePhonePattern = /^1[3-9]\d{9}$/; const landlinePhonePattern = /^0\d{2,3}-?\d{7,8}$/; const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const bankCardPattern = /^\d{12,19}$/; const idCardWeights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]; const idCardCheckCodes = ["1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"]; export function normalizeIdCardNumber(value: unknown) { return String(value ?? "").trim().toUpperCase(); } export function normalizeBankCardNumber(value: unknown) { return String(value ?? "").replace(/[\s-]/g, ""); } export function isMobilePhone(value: unknown) { return mobilePhonePattern.test(String(value ?? "").trim()); } export function isContactPhone(value: unknown) { const phone = String(value ?? "").trim(); return mobilePhonePattern.test(phone) || landlinePhonePattern.test(phone); } export function isEmail(value: unknown) { return emailPattern.test(String(value ?? "").trim()); } export function isBankCardNumber(value: unknown) { return bankCardPattern.test(normalizeBankCardNumber(value)); } /** * 校验中国大陆 18 位居民身份证,包括出生日期和校验位。 * 返回空字符串表示校验通过,否则返回适合直接展示给用户的错误信息。 */ export function getIdCardValidationMessage(value: unknown) { const idCardNumber = normalizeIdCardNumber(value); if (!/^\d{17}[0-9X]$/.test(idCardNumber)) { return "请输入正确的18位身份证号码"; } const birthday = getIdCardBirthday(idCardNumber); if (!birthday || !isValidDateText(birthday) || birthday > getTodayText()) { return "身份证号码中的出生日期不正确"; } const total = idCardWeights.reduce( (sum, weight, index) => sum + Number(idCardNumber[index]) * weight, 0 ); if (idCardCheckCodes[total % 11] !== idCardNumber[17]) { return "身份证号码校验位不正确"; } return ""; } export function getIdCardBirthday(value: unknown) { const idCardNumber = normalizeIdCardNumber(value); if (!/^\d{17}[0-9X]$/.test(idCardNumber)) { return ""; } const text = idCardNumber.slice(6, 14); return `${text.slice(0, 4)}-${text.slice(4, 6)}-${text.slice(6, 8)}`; } export function getIdCardGender(value: unknown) { const idCardNumber = normalizeIdCardNumber(value); if (!/^\d{17}[0-9X]$/.test(idCardNumber)) { return ""; } const genderCode = Number(idCardNumber.charAt(16)); return genderCode % 2 === 1 ? "男" : "女"; } export function isNumberInRange(value: unknown, min: number, max: number) { const text = String(value ?? "").trim(); if (!/^\d+(\.\d+)?$/.test(text)) { return false; } const number = Number(text); return Number.isFinite(number) && number >= min && number <= max; } function isValidDateText(value: string) { const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value); if (!match) { return false; } const year = Number(match[1]); const month = Number(match[2]); const day = Number(match[3]); const date = new Date(year, month - 1, day); return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day; } function getTodayText() { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, "0"); const day = String(now.getDate()).padStart(2, "0"); return `${year}-${month}-${day}`; }