ソースを参照

feat (1.65.2 -> 1.66.0): 重构二维码解析功能,支持多种数据格式并新增患者ID生成器

- 新增 convertGenderText 函数,支持多种性别文字格式转换
- 新增 parseFormat_NameAgeUnitIdCard 解析器,支持'姓名-年龄-单位-身份证号'格式
- 新增 parseFormat_NameIdAgeGender 解析器,支持'姓名-ID-年龄-性别'格式
- 重构 parseTabSeparatedText 函数,采用解析器模式支持多种格式自动检测
- 新增 patientIdGenerator.ts 模块,提供随机患者ID生成功能(模拟身份证号格式)
- 优化代码架构,提升二维码数据解析的灵活性和可扩展性

改动文件:
- src/domain/qrcode/qrCodeDataProcessor.ts
- src/domain/patient/patientIdGenerator.ts (新增)

研发需求ID: 75
dengdx 3 日 前
コミット
b6f47713cf
4 ファイル変更222 行追加49 行削除
  1. 21 0
      CHANGELOG.md
  2. 1 1
      package.json
  3. 46 0
      src/domain/patient/patientIdGenerator.ts
  4. 154 48
      src/domain/qrcode/qrCodeDataProcessor.ts

+ 21 - 0
CHANGELOG.md

@@ -3,6 +3,27 @@
 本项目的所有重要变更都将记录在此文件中.
 
 
+
+## [1.66.0] - 2026-01-15 12:46
+
+### 新增 (Added)
+
+- **重构二维码解析功能,支持多种数据格式并新增患者ID生成器** - 采用解析器模式重构二维码数据解析,支持多种格式自动检测,并新增患者ID生成工具
+  - 新增 convertGenderText 函数,支持多种性别文字格式转换(男/女/M/F/MALE/FEMALE)
+  - 新增 parseFormat_NameAgeUnitIdCard 解析器,支持"姓名-年龄-单位-身份证号"格式
+  - 新增 parseFormat_NameIdAgeGender 解析器,支持"姓名-ID-年龄-性别"格式
+  - 重构 parseTabSeparatedText 函数,采用解析器模式支持多种格式自动检测
+  - 新增 patientIdGenerator.ts 模块,提供随机患者ID生成功能(模拟身份证号格式)
+  - 优化代码架构,提升二维码数据解析的灵活性和可扩展性
+
+**改动文件:**
+
+- src/domain/qrcode/qrCodeDataProcessor.ts
+- src/domain/patient/patientIdGenerator.ts (新增)
+
+**研发需求ID:** 75
+
+
 ## [1.65.2] - 2026-01-12 20:35
 
 ### 修复 (Fixed)

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "zsis",
-  "version": "1.65.2",
+  "version": "1.66.0",
   "private": true,
   "description": "医学成像系统",
   "main": "main.js",

+ 46 - 0
src/domain/patient/patientIdGenerator.ts

@@ -0,0 +1,46 @@
+/**
+ * 患者ID生成器
+ * 用于生成随机的患者ID(模拟身份证号格式)
+ */
+
+/**
+ * 生成随机的患者ID(模拟身份证号格式)
+ * 格式:6位地区码 + 8位出生日期 + 3位顺序码 + 1位校验码
+ * @returns 18位随机身份证号
+ */
+export const generateRandomPatientId = (): string => {
+  // 生成随机地区码(6位)
+  const areaCode = Math.floor(100000 + Math.random() * 900000).toString();
+  
+  // 生成随机出生日期(8位:YYYYMMDD)
+  const year = Math.floor(1950 + Math.random() * 70); // 1950-2020
+  const month = Math.floor(1 + Math.random() * 12).toString().padStart(2, '0');
+  const day = Math.floor(1 + Math.random() * 28).toString().padStart(2, '0');
+  const birthDate = `${year}${month}${day}`;
+  
+  // 生成随机顺序码(3位)
+  const sequence = Math.floor(Math.random() * 1000).toString().padStart(3, '0');
+  
+  // 计算校验码
+  const idWithoutCheck = areaCode + birthDate + sequence;
+  const checkCode = calculateIdCheckCode(idWithoutCheck);
+  
+  return idWithoutCheck + checkCode;
+};
+
+/**
+ * 计算身份证校验码
+ * @param id17 前17位身份证号
+ * @returns 校验码(0-9或X)
+ */
+function calculateIdCheckCode(id17: string): string {
+  const weights = [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(id17[i]) * weights[i];
+  }
+  
+  return checkCodes[sum % 11];
+}

+ 154 - 48
src/domain/qrcode/qrCodeDataProcessor.ts

@@ -5,6 +5,7 @@
 
 import { QRCodeData, ValidationResult } from '@/types/qrcode';
 import { validateQRCodeData, sanitizeData } from './qrCodeValidator';
+import { generateRandomPatientId } from '@/domain/patient/patientIdGenerator';
 import dayjs from 'dayjs';
 
 /**
@@ -31,7 +32,146 @@ const extractGenderFromIdNumber = (idNumber: string): 'M' | 'F' | undefined => {
 };
 
 /**
- * 解析制表符分隔的文本格式(姓名[TAB]年龄[TAB]岁[TAB]身份证号)
+ * 将性别文字转换为标准代码
+ * @param genderText 性别文字(男/女/M/F等)
+ * @returns 'M' | 'F' | 'O'
+ */
+const convertGenderText = (genderText: string): 'M' | 'F' | 'O' => {
+  const normalized = genderText.trim().toUpperCase();
+  
+  if (normalized === '男' || normalized === 'M' || normalized === 'MALE') {
+    return 'M';
+  }
+  if (normalized === '女' || normalized === 'F' || normalized === 'FEMALE') {
+    return 'F';
+  }
+  
+  return 'O'; // 其他情况返回 'O' (Other)
+};
+
+/**
+ * 格式解析器:姓名-年龄-单位-身份证号
+ * 格式:姓名 年龄 岁 身份证号
+ * 例如:吴玉花 68岁 122410560115085053
+ * @param parts 分割后的文本数组
+ * @returns 解析后的数据对象,失败返回 null
+ */
+const parseFormat_NameAgeUnitIdCard = (parts: string[]) => {
+  // 验证格式:至少需要4个部分
+  if (parts.length < 4) {
+    return null;
+  }
+
+  const name = parts[0];
+  const ageStr = parts[1];
+  const unitStr = parts[2];
+  const idNumber = parts[3];
+
+  // 验证年龄单位是否为预期的单位词
+  const validUnits = ['岁', '年', '月', '天', '日'];
+  if (!validUnits.includes(unitStr)) {
+    return null;
+  }
+
+  // 验证年龄是否为数字
+  const ageNumber = parseInt(ageStr);
+  if (isNaN(ageNumber)) {
+    return null;
+  }
+
+  // 验证身份证号格式(18位数字)
+  if (idNumber.length !== 18 || !/^\d{18}$/.test(idNumber)) {
+    return null;
+  }
+
+  // 转换年龄单位
+  let ageUnit: 'D' | 'M' | 'Y' = 'Y';
+  if (unitStr === '岁' || unitStr === '年') {
+    ageUnit = 'Y';
+  } else if (unitStr === '月') {
+    ageUnit = 'M';
+  } else if (unitStr === '天' || unitStr === '日') {
+    ageUnit = 'D';
+  }
+
+  // 从身份证号提取性别
+  const gender = extractGenderFromIdNumber(idNumber);
+  const patient_sex = gender || 'O';
+
+  // 从身份证提取出生日期
+  const birthYear = parseInt(idNumber.substring(6, 10));
+  const birthMonth = parseInt(idNumber.substring(10, 12));
+  const birthDay = parseInt(idNumber.substring(12, 14));
+  const patient_dob = dayjs().year(birthYear).month(birthMonth - 1).date(birthDay);
+
+  console.log('[QRCodeParse] 使用格式:NameAgeUnitIdCard');
+  return {
+    patient_name: name,
+    patient_age: {
+      number: ageNumber,
+      unit: ageUnit
+    },
+    patient_id: idNumber,
+    patient_sex: patient_sex,
+    patient_dob: patient_dob.isValid() ? patient_dob.toISOString() : undefined
+  };
+};
+
+/**
+ * 格式解析器:姓名-ID-年龄-性别
+ * 格式:姓名 ID号码 年龄 性别
+ * 例如:吴玉花 122410560115085053 68 女
+ * @param parts 分割后的文本数组
+ * @returns 解析后的数据对象,失败返回 null
+ */
+const parseFormat_NameIdAgeGender = (parts: string[]) => {
+  // 验证格式:至少需要4个部分
+  if (parts.length < 4) {
+    return null;
+  }
+
+  const name = parts[0];
+  const idNumber = parts[1];
+  const ageStr = parts[2];
+  const genderText = parts[3];
+
+  // 验证年龄是否为数字
+  const ageNumber = parseInt(ageStr);
+  if (isNaN(ageNumber)) {
+    return null;
+  }
+
+  // 验证性别文字是否为有效的性别标识
+  const validGenders = ['男', '女','M', 'F', 'O', 'MALE', 'FEMALE'];
+  if (!validGenders.some(g => genderText.trim().toUpperCase() === g.toUpperCase())) {
+    return null;
+  }
+
+  // 转换性别
+  const patient_sex = convertGenderText(genderText);
+
+  // 生成随机患者ID
+  const patient_id = generateRandomPatientId();
+
+  console.log('[QRCodeParse] 使用格式: NameIdAgeGender');
+  console.log('[QRCodeParse] 生成随机患者ID:', patient_id);
+  console.log('[QRCodeParse] 登记号:', idNumber);
+  
+  return {
+    patient_name: name,
+    patient_age: {
+      number: ageNumber,
+      unit: 'Y' as const // 默认单位为"岁"
+    },
+    patient_id: idNumber,           // 扫码获取的一个编号填充patient_id
+    patient_sex: patient_sex,
+    patient_dob: undefined // 新格式不包含出生日期信息
+  };
+};
+
+/**
+ * 解析制表符分隔的文本格式
+ * 支持多种格式,自动检测并使用合适的解析器
  * @param text 原始文本
  * @returns 解析后的数据对象
  */
@@ -44,57 +184,23 @@ const parseTabSeparatedText = (text: string) => {
   console.log('[QRCodeParse] 清理后:', cleaned);
   console.log('[QRCodeParse] 分割结果:', parts);
 
-  if (parts.length >= 4) {
-    // 预期格式:[姓名, 年龄, 岁/年龄单位, 身份证号, ...]
-    const name = parts[0]; // 姓名
-    const ageStr = parts[1]; // 年龄数字
-    const unitStr = parts[2]; // 年龄单位(如"岁")
-    const idNumber = parts[3]; // 身份证号(第4个位置)
-
-    // 验证年龄是否为数字
-    const ageNumber = parseInt(ageStr);
-    if (isNaN(ageNumber)) {
-      console.error('[QRCodeParse] 年龄格式错误:', ageStr);
-      return null;
-    }
+  // 定义解析器列表,按优先级顺序尝试
+  const parsers = [
+    parseFormat_NameIdAgeGender,      // 新格式:姓名 ID 年龄 性别
+    parseFormat_NameAgeUnitIdCard,    // 旧格式:姓名 年龄 单位 身份证号
+    //未来可以在这里添加更多格式解析器
+  ];
 
-    // 转换年龄单位
-    let ageUnit: 'D' | 'M' | 'Y' = 'Y'; // 默认年
-    if (unitStr === '岁' || unitStr === '年') {
-      ageUnit = 'Y';
-    } else if (unitStr === '月') {
-      ageUnit = 'M';
-    } else if (unitStr === '天' || unitStr === '日') {
-      ageUnit = 'D';
+  // 依次尝试每个解析器
+  for (const parser of parsers) {
+    const result = parser(parts);
+    if (result) {
+      console.log('[QRCodeParse] 解析成功,结果:', result);
+      return result;
     }
-
-    // 从身份证号提取性别
-    const gender = extractGenderFromIdNumber(idNumber);
-    const patient_sex = gender || 'O'; // 如果无法判断,使用 'O' (Other)
-
-
-    // 从身份证提取出生日期
-    const birthYear = parseInt(idNumber.substring(6, 10));
-    const birthMonth = parseInt(idNumber.substring(10, 12));
-    const birthDay = parseInt(idNumber.substring(12, 14));
-    const patient_dob = dayjs().year(birthYear).month(birthMonth - 1).date(birthDay);
-
-    const result = {
-      patient_name: name,
-      patient_age: {
-        number: ageNumber,
-        unit: ageUnit
-      },
-      patient_id: idNumber,
-      patient_sex: patient_sex,
-      patient_dob: patient_dob.isValid() ? patient_dob.toISOString() : undefined
-    };
-    console.log('[QRCodeParse] 解析结果:', result);
-    console.log('[QRCodeParse] 性别判断:', gender ? `从身份证号提取: ${gender}` : '无法判断性别,使用默认值 O');
-    return result;
   }
 
-  console.error('[QRCodeParse] 文本格式不匹配,期望至少4个部分,实际:', parts.length);
+  console.error('[QRCodeParse] 所有格式解析器均失败,无法识别文本格式');
   return null;
 };