qrCodeDataProcessor.ts 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. /**
  2. * 二维码数据处理器
  3. * 用于将扫描到的二维码数据转换为表单数据格式
  4. */
  5. import { QRCodeData, ValidationResult } from '@/types/qrcode';
  6. import { validateQRCodeData, sanitizeData } from './qrCodeValidator';
  7. import dayjs from 'dayjs';
  8. /**
  9. * 从中国身份证号中提取性别
  10. * @param idNumber 身份证号(18位)
  11. * @returns 'M' (男) | 'F' (女) | undefined (无法判断)
  12. */
  13. const extractGenderFromIdNumber = (idNumber: string): 'M' | 'F' | undefined => {
  14. // 验证身份证号格式(18位)
  15. if (!idNumber || idNumber.length !== 18) {
  16. return undefined;
  17. }
  18. // 获取第17位(索引16)- 性别标识位
  19. const genderDigit = parseInt(idNumber[16]);
  20. // 验证是否为有效数字
  21. if (isNaN(genderDigit)) {
  22. return undefined;
  23. }
  24. // 奇数为男性,偶数为女性
  25. return genderDigit % 2 === 1 ? 'M' : 'F';
  26. };
  27. /**
  28. * 解析制表符分隔的文本格式(姓名[TAB]年龄[TAB]岁[TAB]身份证号)
  29. * @param text 原始文本
  30. * @returns 解析后的数据对象
  31. */
  32. const parseTabSeparatedText = (text: string) => {
  33. // 清理多余的空格和制表符,统一转换为单个空格
  34. const cleaned = text.replace(/[\t\s]+/g, ' ').trim();
  35. const parts = cleaned.split(/\s+/);
  36. console.log('[QRCodeParse] 解析文本:', text);
  37. console.log('[QRCodeParse] 清理后:', cleaned);
  38. console.log('[QRCodeParse] 分割结果:', parts);
  39. if (parts.length >= 4) {
  40. // 预期格式:[姓名, 年龄, 岁/年龄单位, 身份证号, ...]
  41. const name = parts[0]; // 姓名
  42. const ageStr = parts[1]; // 年龄数字
  43. const unitStr = parts[2]; // 年龄单位(如"岁")
  44. const idNumber = parts[3]; // 身份证号(第4个位置)
  45. // 验证年龄是否为数字
  46. const ageNumber = parseInt(ageStr);
  47. if (isNaN(ageNumber)) {
  48. console.error('[QRCodeParse] 年龄格式错误:', ageStr);
  49. return null;
  50. }
  51. // 转换年龄单位
  52. let ageUnit: 'D' | 'M' | 'Y' = 'Y'; // 默认年
  53. if (unitStr === '岁' || unitStr === '年') {
  54. ageUnit = 'Y';
  55. } else if (unitStr === '月') {
  56. ageUnit = 'M';
  57. } else if (unitStr === '天' || unitStr === '日') {
  58. ageUnit = 'D';
  59. }
  60. // 从身份证号提取性别
  61. const gender = extractGenderFromIdNumber(idNumber);
  62. const patient_sex = gender || 'O'; // 如果无法判断,使用 'O' (Other)
  63. const result = {
  64. patient_name: name,
  65. patient_age: {
  66. number: ageNumber,
  67. unit: ageUnit
  68. },
  69. patient_id: idNumber,
  70. patient_sex: patient_sex
  71. };
  72. console.log('[QRCodeParse] 解析结果:', result);
  73. console.log('[QRCodeParse] 性别判断:', gender ? `从身份证号提取: ${gender}` : '无法判断性别,使用默认值 O');
  74. return result;
  75. }
  76. console.error('[QRCodeParse] 文本格式不匹配,期望至少4个部分,实际:', parts.length);
  77. return null;
  78. };
  79. /**
  80. * 解析二维码原始字符串
  81. * @param rawData 原始字符串
  82. * @returns 解析后的对象,如果解析失败返回 null
  83. */
  84. export const parseQRCodeString = (rawData: string): any | null => {
  85. // 1. 首先尝试解析为 JSON
  86. try {
  87. const parsed = JSON.parse(rawData);
  88. console.log('[QRCodeParse] 成功解析为 JSON');
  89. return parsed;
  90. } catch (error) {
  91. console.log('[QRCodeParse] 不是有效的 JSON,尝试其他格式');
  92. }
  93. // 2. 尝试解析为制表符分隔的文本格式
  94. const textResult = parseTabSeparatedText(rawData);
  95. if (textResult) {
  96. console.log('[QRCodeParse] 成功解析为文本格式');
  97. return textResult;
  98. }
  99. console.error('[QRCodeParse] 无法解析二维码数据:', rawData);
  100. return null;
  101. };
  102. /**
  103. * 处理二维码数据
  104. * 包括解析、验证、消毒和转换
  105. * @param rawData 原始二维码字符串
  106. * @returns 处理结果
  107. */
  108. export const processQRCodeData = (
  109. rawData: string
  110. ): {
  111. success: boolean;
  112. data?: QRCodeData;
  113. validation?: ValidationResult;
  114. error?: string;
  115. } => {
  116. // 1. 解析 JSON
  117. const parsed = parseQRCodeString(rawData);
  118. if (!parsed) {
  119. return {
  120. success: false,
  121. error: '二维码格式错误,请扫描有效的患者信息二维码'
  122. };
  123. }
  124. // 2. 验证数据
  125. const validation = validateQRCodeData(parsed);
  126. if (!validation.success) {
  127. return {
  128. success: false,
  129. validation,
  130. error: validation.errors.join(';')
  131. };
  132. }
  133. // 3. 数据消毒
  134. const sanitized = sanitizeData(parsed);
  135. // 4. 返回处理后的数据
  136. return {
  137. success: true,
  138. data: sanitized as QRCodeData,
  139. validation
  140. };
  141. };
  142. /**
  143. * 将二维码数据转换为表单数据格式
  144. * @param qrData 二维码数据
  145. * @returns 表单数据
  146. */
  147. export const transformToFormData = (qrData: QRCodeData): any => {
  148. const formData: any = {};
  149. // 基础字段直接复制
  150. const directFields = [
  151. 'patient_id',
  152. 'patient_name',
  153. 'patient_sex',
  154. 'accession_number',
  155. 'patient_size',
  156. 'weight',
  157. 'thickness',
  158. 'length',
  159. 'ref_physician',
  160. 'operator_id',
  161. 'comment',
  162. 'owner_name',
  163. 'variety',
  164. 'chip_number',
  165. 'sex_neutered',
  166. 'pregnancy_status'
  167. ];
  168. directFields.forEach(field => {
  169. if (qrData[field as keyof QRCodeData] !== undefined) {
  170. formData[field] = qrData[field as keyof QRCodeData];
  171. }
  172. });
  173. // 年龄字段 - 已经是正确格式
  174. if (qrData.patient_age) {
  175. formData.patient_age = qrData.patient_age;
  176. } else {
  177. // 如果没有年龄,设置默认值
  178. formData.patient_age = { number: 0, unit: 'Y' };
  179. }
  180. // 出生日期 - 转换为 dayjs 对象
  181. if (qrData.patient_dob) {
  182. try {
  183. const dob = dayjs(qrData.patient_dob);
  184. if (dob.isValid()) {
  185. formData.patient_dob = dob;
  186. }
  187. } catch (error) {
  188. console.error('出生日期转换失败:', error);
  189. }
  190. }
  191. return formData;
  192. };
  193. /**
  194. * 检查二维码是否包含体位信息
  195. * @param qrData 二维码数据
  196. * @returns 是否包含体位
  197. */
  198. export const hasViews = (qrData: QRCodeData): boolean => {
  199. return !!(qrData.views && qrData.views.length > 0);
  200. };
  201. /**
  202. * 获取体位列表
  203. * @param qrData 二维码数据
  204. * @returns 体位列表
  205. */
  206. export const getViews = (qrData: QRCodeData): Array<{ view_id: string; procedure_id: string }> => {
  207. return qrData.views || [];
  208. };