227 lines
6.8 KiB
JavaScript
227 lines
6.8 KiB
JavaScript
// 使用示例:
|
||
// import IDCardParser from '@/utils/IDCardParser';
|
||
|
||
// const handleCheck = (idStr) => {
|
||
// const parser = new IDCardParser(idStr);
|
||
// const result = parser.getInfo();
|
||
// if(result.valid) {
|
||
// // 填充表单
|
||
// console.log(result.age, result.gender);
|
||
// } else {
|
||
// alert(result.message);
|
||
// }
|
||
// }
|
||
|
||
class IDCardParser {
|
||
constructor(idNumber) {
|
||
this.idNumber = idNumber ? idNumber.trim().toUpperCase() : "";
|
||
this.provinceMap = {
|
||
11: "北京市",
|
||
12: "天津市",
|
||
13: "河北省",
|
||
14: "山西省",
|
||
15: "内蒙古自治区",
|
||
21: "辽宁省",
|
||
22: "吉林省",
|
||
23: "黑龙江省",
|
||
31: "上海市",
|
||
32: "江苏省",
|
||
33: "浙江省",
|
||
34: "安徽省",
|
||
35: "福建省",
|
||
36: "江西省",
|
||
37: "山东省",
|
||
41: "河南省",
|
||
42: "湖北省",
|
||
43: "湖南省",
|
||
44: "广东省",
|
||
45: "广西壮族自治区",
|
||
46: "海南省",
|
||
50: "重庆市",
|
||
51: "四川省",
|
||
52: "贵州省",
|
||
53: "云南省",
|
||
54: "西藏自治区",
|
||
61: "陕西省",
|
||
62: "甘肃省",
|
||
63: "青海省",
|
||
64: "宁夏回族自治区",
|
||
65: "新疆维吾尔自治区",
|
||
71: "台湾省",
|
||
81: "香港特别行政区",
|
||
82: "澳门特别行政区"
|
||
};
|
||
|
||
// 初始化验证结果
|
||
const validation = this._validate();
|
||
this.isValid = validation.isValid;
|
||
this.errorMsg = validation.msg;
|
||
|
||
// 如果合法,预先解析数据
|
||
if (this.isValid) {
|
||
this._parseData();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 核心校验逻辑
|
||
*/
|
||
_validate() {
|
||
// 1. 基础正则 (18位,末位数字或X)
|
||
if (!/^\d{17}[\d|X]$/.test(this.idNumber)) {
|
||
return {
|
||
isValid: false,
|
||
msg: "格式错误:必须是18位,末位为数字或X"
|
||
};
|
||
}
|
||
|
||
// 2. 省份校验
|
||
const provinceCode = parseInt(this.idNumber.substring(0, 2));
|
||
if (!this.provinceMap[provinceCode]) {
|
||
return {
|
||
isValid: false,
|
||
msg: "省份代码错误"
|
||
};
|
||
}
|
||
|
||
// 3. 日期合法性严格校验
|
||
const year = parseInt(this.idNumber.substring(6, 10));
|
||
const month = parseInt(this.idNumber.substring(10, 12));
|
||
const day = parseInt(this.idNumber.substring(12, 14));
|
||
|
||
const date = new Date(year, month - 1, day);
|
||
// JS Date会自动纠错(例如2月30会变成3月),所以必须反向比对
|
||
if (
|
||
date.getFullYear() !== year ||
|
||
date.getMonth() + 1 !== month ||
|
||
date.getDate() !== day
|
||
) {
|
||
return {
|
||
isValid: false,
|
||
msg: "出生日期无效"
|
||
};
|
||
}
|
||
|
||
// 检查年份范围(可选,例如限制在1900年以后)
|
||
if (year < 1900 || date > new Date()) {
|
||
return {
|
||
isValid: false,
|
||
msg: "出生日期不在合理范围内"
|
||
};
|
||
}
|
||
|
||
// 4. 校验码计算 (ISO 7064:1983.MOD 11-2)
|
||
if (!this._checkSum()) {
|
||
return {
|
||
isValid: false,
|
||
msg: "校验码错误(身份证可能无效)"
|
||
};
|
||
}
|
||
|
||
return {
|
||
isValid: true,
|
||
msg: "验证通过"
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 校验码算法
|
||
*/
|
||
_checkSum() {
|
||
const factors = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
|
||
const checkCodes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
|
||
|
||
let sum = 0;
|
||
for (let i = 0; i < 17; i++) {
|
||
sum += parseInt(this.idNumber[i]) * factors[i];
|
||
}
|
||
|
||
const mod = sum % 11;
|
||
return checkCodes[mod] === this.idNumber[17];
|
||
}
|
||
|
||
/**
|
||
* 解析内部数据
|
||
*/
|
||
_parseData() {
|
||
const year = parseInt(this.idNumber.substring(6, 10));
|
||
const month = parseInt(this.idNumber.substring(10, 12));
|
||
const day = parseInt(this.idNumber.substring(12, 14));
|
||
|
||
this.birthDate = new Date(year, month - 1, day);
|
||
this.province = this.provinceMap[parseInt(this.idNumber.substring(0, 2))];
|
||
|
||
// 性别:第17位,奇数男,偶数女
|
||
const genderCode = parseInt(this.idNumber.substring(16, 17));
|
||
this.gender = genderCode % 2 !== 0 ? "男" : "女";
|
||
}
|
||
|
||
/**
|
||
* 获取所有信息
|
||
*/
|
||
getInfo() {
|
||
if (!this.isValid) {
|
||
return {
|
||
valid: false,
|
||
message: this.errorMsg
|
||
};
|
||
}
|
||
|
||
return {
|
||
valid: true,
|
||
idNumber: this.idNumber,
|
||
province: this.province,
|
||
birthday: this._formatDate(this.birthDate),
|
||
gender: this.gender,
|
||
age: this._calculateAge(),
|
||
zodiac: this._getZodiac(), // 生肖
|
||
constellation: this._getConstellation() // 星座
|
||
};
|
||
}
|
||
|
||
_formatDate(date) {
|
||
const y = date.getFullYear();
|
||
const m = String(date.getMonth() + 1).padStart(2, '0');
|
||
const d = String(date.getDate()).padStart(2, '0');
|
||
return `${y}-${m}-${d}`;
|
||
}
|
||
|
||
_calculateAge() {
|
||
const today = new Date();
|
||
let age = today.getFullYear() - this.birthDate.getFullYear();
|
||
const m = today.getMonth() - this.birthDate.getMonth();
|
||
|
||
// 如果没过生日,年龄减1
|
||
if (m < 0 || (m === 0 && today.getDate() < this.birthDate.getDate())) {
|
||
age--;
|
||
}
|
||
return age;
|
||
}
|
||
|
||
_getZodiac() {
|
||
// 猴鸡狗猪鼠牛虎兔龙蛇马羊 (对应余数 0-11)
|
||
const zodiacs = "猴鸡狗猪鼠牛虎兔龙蛇马羊";
|
||
return zodiacs.charAt(this.birthDate.getFullYear() % 12);
|
||
}
|
||
|
||
_getConstellation() {
|
||
const month = this.birthDate.getMonth() + 1;
|
||
const day = this.birthDate.getDate();
|
||
|
||
// 星座分割日
|
||
const dates = [20, 19, 21, 20, 21, 22, 23, 23, 23, 24, 23, 22];
|
||
const constellations = [
|
||
"摩羯座", "水瓶座", "双鱼座", "白羊座", "金牛座", "双子座",
|
||
"巨蟹座", "狮子座", "处女座", "天秤座", "天蝎座", "射手座", "摩羯座"
|
||
];
|
||
|
||
if (day < dates[month - 1]) {
|
||
return constellations[month - 1];
|
||
} else {
|
||
return constellations[month];
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
export default IDCardParser; |