华西海圻ELN前端工程
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

182 lines
4.6 KiB

  1. /**
  2. * 浓度单位转换 同一类别内转换
  3. */
  4. class ConcentrationConverter {
  5. // 单位换算系数
  6. unitFactors = {
  7. // 质量单位(以g为基准)
  8. 'g': 1,
  9. 'mg': 1e-3,
  10. 'μg': 1e-6,
  11. 'ug': 1e-6, // μg的别名
  12. 'ng': 1e-9,
  13. 'pg': 1e-12,
  14. 'fg': 1e-15,
  15. // 体积单位(以L为基准)
  16. 'L': 1,
  17. 'mL': 1e-3,
  18. 'μL': 1e-6,
  19. 'uL': 1e-6, // μL的别名
  20. 'nL': 1e-9,
  21. 'pL': 1e-12,
  22. 'fL': 1e-15,
  23. // 摩尔单位(以mol为基准)
  24. 'mol': 1,
  25. 'mmol': 1e-3,
  26. 'μmol': 1e-6,
  27. 'umol': 1e-6, // μmol的别名
  28. 'nmol': 1e-9,
  29. 'pmol': 1e-12,
  30. 'fmol': 1e-15,
  31. // 活性单位(以U为基准)
  32. 'U': 1,
  33. 'IU': 1, // 假设1 IU = 1 U
  34. 'mU': 1e-3,
  35. 'mIU': 1e-3,
  36. };
  37. // 支持的浓度单位(按类别分组)
  38. unitCategories = {
  39. 'mass': ['mg/mL', 'μg/mL', 'ug/mL', 'ng/mL', 'pg/mL', 'fg/mL', 'g/L', 'mg/L', 'μg/L'],
  40. 'mole': ['mol/mL', 'mol/L', 'mmol/L', 'μmol/L', 'umol/L', 'nmol/L', 'pmol/L', 'mmol/mL', 'μmol/mL'],
  41. 'activity': ['U/mL', 'IU/mL', 'U/L', 'IU/L'],
  42. 'percent': ['%']
  43. };
  44. /**
  45. * 规范化单位统一别名
  46. */
  47. normalizeUnit(unit) {
  48. const aliasMap = {
  49. 'ug/mL': 'μg/mL',
  50. 'umol/L': 'μmol/L',
  51. 'uL': 'μL',
  52. 'ul': 'μL',
  53. 'IU/mL': 'U/mL',
  54. 'IU/L': 'U/L'
  55. };
  56. return aliasMap[unit] || unit;
  57. }
  58. /**
  59. * 获取单位类别
  60. */
  61. getUnitCategory(unit) {
  62. const normalizedUnit = this.normalizeUnit(unit);
  63. for (const [category, units] of Object.entries(this.unitCategories)) {
  64. if (units.includes(normalizedUnit)) {
  65. return category;
  66. }
  67. }
  68. throw new Error(`不支持的浓度单位: ${unit}`);
  69. }
  70. /**
  71. * 检查两个单位是否属于同一类别
  72. */
  73. isSameCategory(unit1, unit2) {
  74. try {
  75. const cat1 = this.getUnitCategory(unit1);
  76. const cat2 = this.getUnitCategory(unit2);
  77. return cat1 === cat2;
  78. } catch {
  79. return false;
  80. }
  81. }
  82. /**
  83. * 浓度单位转换核心方法
  84. * @param {number|string} value - 待转换的值
  85. * @param {string} targetUnit - 目标单位
  86. * @returns {number} 转换后的值
  87. */
  88. convert(value, targetUnit) {
  89. // 1. 解析输入值
  90. let inputValue, inputUnit;
  91. if (typeof value === 'string') {
  92. const match = value.toString().trim().match(/^([\d.]+(?:[eE][+-]?\d+)?)(?:\s*([a-zA-Zμ/%]+))?$/);
  93. if (!match) {
  94. throw new Error(`输入格式错误: ${value}`);
  95. }
  96. inputValue = parseFloat(match[1]);
  97. inputUnit = match[2] ? this.normalizeUnit(match[2]) : '';
  98. } else if (typeof value === 'number') {
  99. inputValue = value;
  100. inputUnit = ''; // 无量纲
  101. } else {
  102. throw new Error('输入值必须是数字或字符串');
  103. }
  104. const normalizedTargetUnit = this.normalizeUnit(targetUnit);
  105. // 2. 检查是否需要转换
  106. if (!inputUnit) {
  107. return inputValue; // 无量纲输入,直接返回
  108. }
  109. if (inputUnit === normalizedTargetUnit) {
  110. return inputValue; // 相同单位,直接返回
  111. }
  112. // 3. 检查是否为%单位
  113. if (inputUnit === '%' || normalizedTargetUnit === '%') {
  114. // %单位是单独的系列,不进行转换
  115. throw new Error(`单位类别不匹配: ${inputUnit} 不能转换为 ${targetUnit}`);
  116. }
  117. // 4. 验证单位类别
  118. if (!this.isSameCategory(inputUnit, normalizedTargetUnit)) {
  119. throw new Error(`单位类别不匹配: ${inputUnit} 不能转换为 ${targetUnit}`);
  120. }
  121. // 5. 执行转换
  122. return this._convertValue(inputValue, inputUnit, normalizedTargetUnit);
  123. }
  124. /**
  125. * 内部转换逻辑
  126. */
  127. _convertValue(value, fromUnit, toUnit) {
  128. // 解析浓度单位:分子/分母
  129. const [fromNum, fromDen] = fromUnit.split('/');
  130. const [toNum, toDen] = toUnit.split('/');
  131. // 获取各部分的换算系数
  132. const numFactor = this.unitFactors[fromNum] / this.unitFactors[toNum];
  133. const denFactor = this.unitFactors[toDen] / this.unitFactors[fromDen];
  134. // 计算总转换系数
  135. return value * numFactor * denFactor;
  136. }
  137. /**
  138. * 批量转换
  139. */
  140. convertAll(values, targetUnit) {
  141. if (!Array.isArray(values)) {
  142. throw new Error('输入必须是数组');
  143. }
  144. return values.map(value => this.convert(value, targetUnit));
  145. }
  146. /**
  147. * 验证单位是否支持
  148. */
  149. isSupportedUnit(unit) {
  150. try {
  151. this.getUnitCategory(unit);
  152. return true;
  153. } catch {
  154. return false;
  155. }
  156. }
  157. }
  158. export const convertConcentration = new ConcentrationConverter();