From 4cb5141ff079a2319bfac2b657d85097f1f41968 Mon Sep 17 00:00:00 2001 From: luojie <125330818@qq.com> Date: Tue, 10 Feb 2026 15:50:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:[=E6=A8=A1=E6=9D=BF=E7=AE=A1=E7=90=86][upd?= =?UTF-8?q?ate]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Template/CustomTable.vue | 16 +- src/utils/conConverter.js | 183 +++++++++++++++++ src/utils/volConverter.js | 223 +++++++++++++++++++++ .../business/comps/template/comps/sp/SP003.vue | 3 + .../comps/template/mixins/templateMixin.js | 31 ++- 5 files changed, 446 insertions(+), 10 deletions(-) create mode 100644 src/utils/conConverter.js create mode 100644 src/utils/volConverter.js diff --git a/src/components/Template/CustomTable.vue b/src/components/Template/CustomTable.vue index 0ec4185..3364b46 100644 --- a/src/components/Template/CustomTable.vue +++ b/src/components/Template/CustomTable.vue @@ -3,6 +3,9 @@
+
+ 序号 +
@@ -33,6 +36,9 @@
+
+ {{ rowIndex + 1 }} +
@@ -235,6 +241,10 @@ export default { type: Boolean, default: true, }, + showSort: { + type: Boolean, + default: false, + }, }, data() { return { @@ -734,7 +744,7 @@ export default { } } - this.$emit("blur", { rowIndex, colKey, value,dataSource:this.localDataSource, item: this.localDataSource[rowIndex] }); + this.$emit("blur", { rowIndex, colKey, value,dataSource:this.localDataSource,headerSelectFields:this.headerSelectFields, item: this.localDataSource[rowIndex] }); }, onSubBlur(rowIndex, colKey, value) { // 查找对应的列配置 @@ -942,4 +952,8 @@ export default { .mr-5{ margin-right: 5px; } +.sort-cell{ + text-align: center; + width: 100px; +} \ No newline at end of file diff --git a/src/utils/conConverter.js b/src/utils/conConverter.js new file mode 100644 index 0000000..ffcd021 --- /dev/null +++ b/src/utils/conConverter.js @@ -0,0 +1,183 @@ +/** + * 浓度单位转换 (同一类别内转换) + */ + +class ConcentrationConverter { + // 单位换算系数 + unitFactors = { + // 质量单位(以g为基准) + 'g': 1, + 'mg': 1e-3, + 'μg': 1e-6, + 'ug': 1e-6, // μg的别名 + 'ng': 1e-9, + 'pg': 1e-12, + 'fg': 1e-15, + + // 体积单位(以L为基准) + 'L': 1, + 'mL': 1e-3, + 'μL': 1e-6, + 'uL': 1e-6, // μL的别名 + 'nL': 1e-9, + 'pL': 1e-12, + 'fL': 1e-15, + + // 摩尔单位(以mol为基准) + 'mol': 1, + 'mmol': 1e-3, + 'μmol': 1e-6, + 'umol': 1e-6, // μmol的别名 + 'nmol': 1e-9, + 'pmol': 1e-12, + 'fmol': 1e-15, + + // 活性单位(以U为基准) + 'U': 1, + 'IU': 1, // 假设1 IU = 1 U + 'mU': 1e-3, + 'mIU': 1e-3, + }; + + // 支持的浓度单位(按类别分组) + unitCategories = { + 'mass': ['mg/mL', 'μg/mL', 'ug/mL', 'ng/mL', 'pg/mL', 'fg/mL', 'g/L', 'mg/L', 'μg/L'], + 'mole': ['mol/mL', 'mol/L', 'mmol/L', 'μmol/L', 'umol/L', 'nmol/L', 'pmol/L', 'mmol/mL', 'μmol/mL'], + 'activity': ['U/mL', 'IU/mL', 'U/L', 'IU/L'] + }; + + /** + * 规范化单位(统一别名) + */ + normalizeUnit(unit) { + const aliasMap = { + 'ug/mL': 'μg/mL', + 'umol/L': 'μmol/L', + 'uL': 'μL', + 'ul': 'μL', + 'IU/mL': 'U/mL', + 'IU/L': 'U/L' + }; + return aliasMap[unit] || unit; + } + + /** + * 获取单位类别 + */ + getUnitCategory(unit) { + const normalizedUnit = this.normalizeUnit(unit); + + for (const [category, units] of Object.entries(this.unitCategories)) { + if (units.includes(normalizedUnit)) { + return category; + } + } + + throw new Error(`不支持的浓度单位: ${unit}`); + } + + /** + * 检查两个单位是否属于同一类别 + */ + isSameCategory(unit1, unit2) { + try { + const cat1 = this.getUnitCategory(unit1); + const cat2 = this.getUnitCategory(unit2); + return cat1 === cat2; + } catch { + return false; + } + } + + /** + * 浓度单位转换(核心方法) + * @param {number|string} value - 待转换的值 + * @param {string} targetUnit - 目标单位 + * @returns {number} 转换后的值 + */ + convert(value, targetUnit) { + // 1. 解析输入值 + let inputValue, inputUnit; + + if (typeof value === 'string') { + const match = value.toString().trim().match(/^([\d.]+(?:[eE][+-]?\d+)?)\s*([a-zA-Zμ/]+)$/); + if (!match) { + throw new Error(`输入格式错误: ${value}`); + } + inputValue = parseFloat(match[1]); + inputUnit = this.normalizeUnit(match[2]); + } else if (typeof value === 'number') { + inputValue = value; + inputUnit = ''; // 无量纲 + } else { + throw new Error('输入值必须是数字或字符串'); + } + + const normalizedTargetUnit = this.normalizeUnit(targetUnit); + + // 2. 检查是否需要转换 + if (!inputUnit) { + return inputValue; // 无量纲输入,直接返回 + } + + if (inputUnit === normalizedTargetUnit) { + return inputValue; // 相同单位,直接返回 + } + + // 3. 验证单位类别 + if (!this.isSameCategory(inputUnit, normalizedTargetUnit)) { + throw new Error(`单位类别不匹配: ${inputUnit} 不能转换为 ${targetUnit}`); + } + + // 4. 执行转换 + return this._convertValue(inputValue, inputUnit, normalizedTargetUnit); + } + + /** + * 内部转换逻辑 + */ + _convertValue(value, fromUnit, toUnit) { + // 解析浓度单位:分子/分母 + const [fromNum, fromDen] = fromUnit.split('/'); + const [toNum, toDen] = toUnit.split('/'); + + // 获取各部分的换算系数 + const numFactor = this.unitFactors[fromNum] / this.unitFactors[toNum]; + const denFactor = this.unitFactors[toDen] / this.unitFactors[fromDen]; + + // 计算总转换系数 + return value * numFactor * denFactor; + } + + /** + * 批量转换 + */ + convertAll(values, targetUnit) { + if (!Array.isArray(values)) { + throw new Error('输入必须是数组'); + } + + return values.map(value => this.convert(value, targetUnit)); + } + + /** + * 验证单位是否支持 + */ + isSupportedUnit(unit) { + try { + this.getUnitCategory(unit); + return true; + } catch { + return false; + } + } +} +// 将静态方法绑定到实例上,以便可以直接通过实例调用 +export const convertConcentration = new ConcentrationConverter(); + +// // 将静态方法复制到实例上 +// converterInstance.convert = ConcentrationConverter.convert.bind(ConcentrationConverter); +// converterInstance.convertAll = ConcentrationConverter.convertAll.bind(ConcentrationConverter); +// converterInstance.isSupportedUnit = ConcentrationConverter.isSupportedUnit.bind(ConcentrationConverter); + +// export const convertConcentration = converterInstance; \ No newline at end of file diff --git a/src/utils/volConverter.js b/src/utils/volConverter.js new file mode 100644 index 0000000..df8c124 --- /dev/null +++ b/src/utils/volConverter.js @@ -0,0 +1,223 @@ +/** + * 体积单位转换 + * 支持的体积单位:pL, nL, uL (μL), mL, L + */ + +class VolumeConverter { + // 体积单位换算系数(以升L为基准) + unitFactors = { + 'L': 1, + 'mL': 1e-3, // 毫升 + 'μL': 1e-6, // 微升 + 'uL': 1e-6, // 微升(别名) + 'nL': 1e-9, // 纳升 + 'pL': 1e-12 // 皮升 + }; + + // 支持的单位列表 + supportedUnits = ['L', 'mL', 'μL', 'uL', 'nL', 'pL']; + + /** + * 规范化单位(统一别名) + */ + normalizeUnit(unit) { + const aliasMap = { + 'ul': 'uL', + 'Ul': 'uL', + 'UL': 'uL', + 'μl': 'μL', + 'Μl': 'μL', + 'ΜL': 'μL' + }; + + // 转换为标准形式 + let normalized = unit; + + // 处理μ符号的各种表示 + if (unit.toLowerCase() === 'ul' || unit === 'μL' || unit === 'uL') { + normalized = 'μL'; + } + // 其他别名处理 + else if (aliasMap[unit]) { + normalized = aliasMap[unit]; + } + + return normalized; + } + + /** + * 验证单位是否支持 + */ + isSupportedUnit(unit) { + const normalizedUnit = this.normalizeUnit(unit); + return this.supportedUnits.includes(normalizedUnit); + } + + /** + * 获取所有支持的单位 + */ + getSupportedUnits() { + return [...this.supportedUnits]; + } + + /** + * 体积单位转换(核心方法) + * @param {number|string} value - 待转换的值 + * @param {string} targetUnit - 目标单位 + * @returns {number} 转换后的值 + */ + convert(value, targetUnit) { + // 1. 解析输入值 + let inputValue, inputUnit; + + if (typeof value === 'string') { + const result = this.parseValue(value); + inputValue = result.value; + inputUnit = result.unit; + } else if (typeof value === 'number') { + inputValue = value; + inputUnit = ''; // 无量纲 + } else { + throw new Error('输入值必须是数字或字符串'); + } + + const normalizedTargetUnit = this.normalizeUnit(targetUnit); + + // 2. 验证目标单位 + if (!this.isSupportedUnit(normalizedTargetUnit)) { + throw new Error(`不支持的单位: ${targetUnit}。支持的单位: ${this.supportedUnits.join(', ')}`); + } + + // 3. 检查是否需要转换 + if (!inputUnit) { + return inputValue; // 无量纲输入,直接返回 + } + + if (inputUnit === normalizedTargetUnit) { + return inputValue; // 相同单位,直接返回 + } + + // 4. 执行转换 + return this._convertValue(inputValue, inputUnit, normalizedTargetUnit); + } + + /** + * 解析带单位的数值字符串 + */ + parseValue(valueStr) { + const str = valueStr.toString().trim(); + + // 支持科学计数法 + const match = str.match(/^([+-]?\d*\.?\d+(?:[eE][+-]?\d+)?)\s*([a-zA-Zμ]+)$/); + + if (!match) { + throw new Error(`格式错误: ${valueStr}。正确格式如: "10mL", "5.5μL", "1.23e-6L"`); + } + + const value = parseFloat(match[1]); + const rawUnit = match[2]; + const unit = this.normalizeUnit(rawUnit); + + // 验证单位是否支持 + if (!this.isSupportedUnit(unit)) { + throw new Error(`不支持的单位: ${rawUnit}。支持的单位: ${this.supportedUnits.join(', ')}`); + } + + return { value, unit }; + } + + /** + * 内部转换逻辑 + */ + _convertValue(value, fromUnit, toUnit) { + // 统一处理μL的多种表示 + const normalizedFromUnit = this.normalizeUnit(fromUnit); + const normalizedToUnit = this.normalizeUnit(toUnit); + + // 获取换算系数 + const fromFactor = this.unitFactors[normalizedFromUnit]; + const toFactor = this.unitFactors[normalizedToUnit]; + + if (fromFactor === undefined || toFactor === undefined) { + throw new Error(`单位换算系数未定义: ${fromUnit} 或 ${toUnit}`); + } + + // 转换公式:value * (fromFactor / toFactor) + return value * (fromFactor / toFactor); + } + + /** + * 批量转换 + */ + convertAll(values, targetUnit) { + if (!Array.isArray(values)) { + throw new Error('输入必须是数组'); + } + + return values.map(value => this.convert(value, targetUnit)); + } + + /** + * 格式化输出 + */ + format(value, unit, precision = 6) { + const numValue = typeof value === 'string' ? parseFloat(value) : value; + + if (isNaN(numValue)) { + return 'NaN'; + } + + // 根据数值大小选择合适的显示格式 + const absValue = Math.abs(numValue); + + if (absValue === 0) { + return `0 ${unit}`; + } + + if (absValue < 0.001 || absValue >= 1000000) { + return `${numValue.toExponential(precision)} ${unit}`; + } + + // 确定小数位数 + let decimalPlaces = precision; + if (absValue < 1) { + decimalPlaces = Math.max(precision, Math.ceil(-Math.log10(absValue)) + 2); + } else if (absValue >= 100) { + decimalPlaces = Math.max(0, precision - 2); + } + + return `${numValue.toFixed(decimalPlaces)} ${unit}`; + } + + /** + * 获取单位换算关系 + */ + getConversionFactor(fromUnit, toUnit) { + const normalizedFromUnit = this.normalizeUnit(fromUnit); + const normalizedToUnit = this.normalizeUnit(toUnit); + + if (!this.isSupportedUnit(normalizedFromUnit) || !this.isSupportedUnit(normalizedToUnit)) { + throw new Error('单位不支持'); + } + + const fromFactor = this.unitFactors[normalizedFromUnit]; + const toFactor = this.unitFactors[normalizedToUnit]; + + return fromFactor / toFactor; + } + + /** + * 创建快捷转换方法 + */ + createShortcutMethods() {`` + const shortcuts = {}; + + this.supportedUnits.forEach(unit => { + const methodName = `to${unit.replace('μ', 'u')}`; + shortcuts[methodName] = (value) => this.convert(value, unit); + }); + + return shortcuts; + } +} +export const volumeConverter = new VolumeConverter(); \ No newline at end of file diff --git a/src/views/business/comps/template/comps/sp/SP003.vue b/src/views/business/comps/template/comps/sp/SP003.vue index 73f1a27..227ee77 100644 --- a/src/views/business/comps/template/comps/sp/SP003.vue +++ b/src/views/business/comps/template/comps/sp/SP003.vue @@ -51,6 +51,7 @@ import TableOpertaion from "@/components/Template/operation/TableOpertaion.vue"; import { EventBus } from "@/utils/eventBus"; import { addTj, uniqeResource, uniqeResourceOne, addDecimals } from "@/utils/calUnitTools"; import { isCommonUnit } from "@/utils/conTools"; +import { convertConcentration } from "@/utils/conConverter"; export default { name: "SP003", components: { BaseInfoFormPackage, LineLabel, TableList, Step, CustomTable, SelectReagentDialog, TableOpertaion }, @@ -429,6 +430,8 @@ export default { if (this.fillType === "actFill") { this.handleUpdateCode(formData); } + const re = convertConcentration.convert('100mg/mL',"ng/mL") + console.log(re,"res") }, methods: { //更新记录 diff --git a/src/views/business/comps/template/mixins/templateMixin.js b/src/views/business/comps/template/mixins/templateMixin.js index 23c1f58..c071b31 100644 --- a/src/views/business/comps/template/mixins/templateMixin.js +++ b/src/views/business/comps/template/mixins/templateMixin.js @@ -322,10 +322,9 @@ export default { onHandleBlur(fields) { const { key, - effectivePeriodUnit, - effectivePeriod, codeSTD, - targetStartSolution + targetStartSolution, + subTargetStartSolution,//预设起始源溶液浓度单位 } = fields const { startDate } = this.formData if (key === 'codeSTD') { @@ -340,19 +339,24 @@ export default { this.$refs.stepTableRef.updateDataSource(arr) } else if (key === 'targetStartSolution') { //起始溶液体积失焦时,更新目标溶液预计浓度 - const arr = this.$refs.stepTableRef?.getDataSource() + const arr = this.$refs.stepTableRef?.getDataSource(); + const {headerSelectFields} = this.$refs.stepTableRef?.getFilledFormData(); + const params = { + startUnit:subTargetStartSolution, + headerSelectFields + } arr.forEach((item, rowIndex) => { this.updateTargetStartSolutionVolume( - rowIndex, item, - targetStartSolution + targetStartSolution, + params ) }) } }, //统一处理table失焦事件 onHandleTableBlur(params) { - const { rowIndex, colKey, value, item, dataSource } = params + const { rowIndex, colKey, value, item, dataSource,headerSelectFields } = params console.log(params, "params"); if ( colKey === 'targetSolutionVolume' || @@ -364,10 +368,18 @@ export default { this.$refs.stepFormPackageRef?.getFormDataByKey( 'targetStartSolution' ) + const subTargetStartSolution = + this.$refs.stepFormPackageRef?.getFormDataByKey( + 'subTargetStartSolution' + ) + const params = { + startUnit:subTargetStartSolution, + headerSelectFields + } if (isValueEmpty(volume)) { this.$message.error('请先选择预设起始源溶液浓度') } else { - this.updateTargetStartSolutionVolume(item, volume) + this.updateTargetStartSolutionVolume(item, volume,params) } } else if ( colKey === 'actStartSolutionVolume' || @@ -444,7 +456,8 @@ export default { return this.calcNd(item, targetAcSolution) }, //更新起始溶液体积时,计算目标溶液预计浓度 - updateTargetStartSolutionVolume(item, volume) { + updateTargetStartSolutionVolume(item, volume,unitParams) { + const {startUnit,headerSelectFields} = unitParams const precision = item.targetStartSolutionVolumePrecision || 0 const concentration = item.targetSolutionConcentration || 0 const targetVolume = item.targetSolutionVolume || 0