diff --git a/api/drugOcr.js b/api/drugOcr.js new file mode 100644 index 0000000..65710b6 --- /dev/null +++ b/api/drugOcr.js @@ -0,0 +1,157 @@ +export const getOcr = (url) => { + return new Promise((resolve, reject) => { + wx.request({ + url: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions', + method: 'POST', + dataType: 'json', // 微信官方文档中介绍会对数据进行一次JSON.parse + header: { + 'Authorization': 'Bearer sk-52414b887aee47e4883caf16cbf801bd', + 'Content-Type': 'application/json' + }, + data: { + "model": "qwen-vl-ocr-latest", + "messages": [{ + "role": "user", + "content": [{ + "type": "image_url", + "image_url": { + "url": url + }, + "min_pixels": 3136, + "max_pixels": 6422528 + }, + { + "type": "text", + "text": "要求准确无误的提取上述关键信息、不要遗漏和捏造虚假信息,模糊或者强光遮挡的单个文字可以用英文问号?代替。返回数据格式以MD方式输出" + } + ] + }] + }, + success(res) { + let data = parseOcrResult(res.data.choices[0].message.content) + console.log(data) + + + resolve(data); + }, + fail(err) { + console.log(err) + // 断网、服务器挂了都会fail回调,直接reject即可 + reject(err); + }, + }); + }) +} + +function parseMarkdownTable(md) { + // 拆分行,去掉空行 + const lines = md.split('\n').filter(line => line.trim().length > 0); + + // 找到表头和数据行 + const headerLine = lines[0]; + // 保留空单元格 + const header = headerLine.split('|').map(h => h.trim()); + + // 数据行从第三行开始(第二行为分隔符) + const dataLines = lines.slice(2); + + // 解析每一行 + const result = dataLines.map(line => { + // 保留空单元格 + const cells = line.split('|').map(cell => cell.trim()); + const obj = {}; + header.forEach((key, idx) => { + // 这里 key 可能是空字符串,需跳过 + if (key) obj[key] = cells[idx] || ''; + }); + return obj; + }); + + return result; +} + +/** + * 解析类似 ```json ... ``` 格式的字符串,提取检测项目数组 + * @param {string} str + * @returns {Array} + */ +function parseJsonBlock(str) { + // 去除包裹的代码块标记 + const jsonStr = str.replace(/^[\s`]*```json[\s`]*|```$/g, '').replace(/↵/g, '\n').trim(); + + // 用正则提取所有 "key": "value" + const regex = /"([^"]+)":\s*"([^"]*)"/g; + const pairs = []; + let match; + while ((match = regex.exec(jsonStr)) !== null) { + pairs.push([match[1], match[2]]); + } + + // 按“序号”分组 + const items = []; + let current = {}; + const itemFields = ['序号', '项目名称', '缩写', '结果', '单位', '参考区间', '测定方法']; + pairs.forEach(([key, value]) => { + if (key === '序号' && Object.keys(current).length > 0) { + items.push({ ...current }); + current = {}; + } + if (itemFields.includes(key)) { + current[key] = value; + } + }); + if (Object.keys(current).length > 0) { + items.push({ ...current }); + } + return items; +} + +/** + * 自动判断OCR返回内容格式并调用对应解析方法 + * @param {string} content + * @returns {Array} + */ +function parseOcrResult(content) { + // 判断是否为JSON代码块 + if (/^```json/.test(content.trim())) { + return parseJsonBlock(content); + } + // 判断是否为Markdown表格(以|开头,且有---分隔行) + if (/\|.*\|/.test(content) && /\|[\s\-:|]+\|/.test(content)) { + return parseMarkdownTable(content); + } + // 判断是否为实验室结果格式(数字+中文+数字+单位+参考区间) + if (/^\d+[\u4e00-\u9fa5A-Za-z]+[\d.]+[a-zA-Zμ\/]+[\d.\-]+/m.test(content.replace(/↵/g, '\n'))) { + return parseLabResults(content); + } + // 其它情况返回空数组或原始内容 + return []; +} + +/** + * 解析实验室结果字符串为结构化对象数组 + * @param {string} str - 原始字符串 + * @returns {Array} 结构化结果数组 + */ +function parseLabResults(str) { + if (!str) return []; + // 替换特殊换行符为标准换行 + str = str.replace(/↵/g, '\n'); + const lines = str.split(/\n+/).filter(Boolean); + const result = []; + const regex = /^(\d+)([\u4e00-\u9fa5A-Za-z]+)([\d.]+)([a-zA-Zμ\/]+)?([\d.\-]+)?/; + lines.forEach(line => { + // 尝试用正则提取 + const match = line.match(/^(\d+)([\u4e00-\u9fa5A-Za-z]+)([\d.]+)([a-zA-Zμ\/]+)?([\d.\-]+)?/); + if (match) { + result.push({ + index: Number(match[1]), + name: match[2], + value: Number(match[3]), + unit: match[4] || '', + reference: match[5] || '' + }); + } + }); + return result; +} \ No newline at end of file diff --git a/api/ocr.js b/api/ocr.js index ecb5659..a1e3072 100644 --- a/api/ocr.js +++ b/api/ocr.js @@ -13,22 +13,93 @@ export const getOcr = (url) => { "messages": [{ "role": "user", "content": [{ - "type": "image_url", - "image_url": { - "url": url - }, - "min_pixels": 3136, - "max_pixels": 6422528 + "type": "image_url", + "image_url": { + "url": url }, - { - "type": "text", - "text": "要求准确无误的提取上述关键信息、不要遗漏和捏造虚假信息,模糊或者强光遮挡的单个文字可以用英文问号?代替。返回数据格式以MD方式输出" - } + "min_pixels": 3136, + "max_pixels": 6422528 + }, + { + "type": "text", + "text": "要求准确无误的提取上述关键信息、不要遗漏和捏造虚假信息,模糊或者强光遮挡的单个文字可以用英文问号?代替。返回数据格式以MD方式输出" + } ] }] }, success(res) { - const data = parseOcrResult(res.data.choices[0].message.content) + let data = parseOcrResult(res.data.choices[0].message.content) + // 新增:统一字段名 + if (Array.isArray(data)) { + data = data.map(item => { + const newItem = { ...item }; + if ('项目' in newItem) { + newItem.name = newItem['项目']; + delete newItem['项目']; + } else if ('项目名称' in newItem) { + newItem.name = newItem['项目名称']; + delete newItem['项目名称']; + } else if ('检验项目' in newItem) { + newItem.name = newItem['检验项目']; + delete newItem['检验项目']; + } else if ('检查项目' in newItem) { + newItem.name = newItem['检查项目']; + delete newItem['检查项目']; + } else if ('检查项目名称' in newItem) { + newItem.name = newItem['检查项目名称']; + delete newItem['检查项目名称']; + } else if ('项目全称' in newItem) { + newItem.name = newItem['项目全称']; + delete newItem['项目全称']; + } else if ('中文名称' in newItem) { + newItem.name = newItem['中文名称']; + delete newItem['中文名称']; + } else if ('分析项目' in newItem) { + newItem.name = newItem['分析项目']; + delete newItem['分析项目']; + } else if ('实验名称' in newItem) { + newItem.name = newItem['实验名称']; + delete newItem['实验名称']; + } else if ('N项目名称' in newItem) { + newItem.name = newItem['N项目名称']; + delete newItem['N项目名称']; + }else if ('序号检查项目' in newItem) { + newItem.name = newItem['序号检查项目']; + delete newItem['序号检查项目']; + } + if ('结果' in newItem) { + newItem.value = newItem['结果']; + delete newItem['结果']; + } else if ('值' in newItem) { + newItem.value = newItem['值']; + delete newItem['值']; + } else if ('检验结果' in newItem) { + newItem.value = newItem['检验结果']; + delete newItem['检验结果']; + } else if ('检查结果' in newItem) { + newItem.value = newItem['检查结果']; + delete newItem['检查结果']; + } else if ('结果值' in newItem) { + newItem.value = newItem['结果值']; + delete newItem['结果值']; + } else if ('结果浓度' in newItem) { + newItem.value = newItem['结果浓度']; + delete newItem['结果浓度']; + } else if ('测定结果' in newItem) { + newItem.value = newItem['测定结果']; + delete newItem['测定结果']; + } else if ('检验值' in newItem) { + newItem.value = newItem['检验值']; + delete newItem['检验值']; + } + // 去掉name中的括号及其内容 + if (typeof newItem.name === 'string') { + newItem.name = newItem.name.replace(/(.*?)|\(.*?\)/g, '').trim(); + } + console.log(newItem) + return newItem; + }); + } resolve(data); }, fail(err) { @@ -46,17 +117,20 @@ function parseMarkdownTable(md) { // 找到表头和数据行 const headerLine = lines[0]; - const header = headerLine.split('|').map(h => h.trim()).filter(Boolean); + // 保留空单元格 + const header = headerLine.split('|').map(h => h.trim()); // 数据行从第三行开始(第二行为分隔符) const dataLines = lines.slice(2); // 解析每一行 const result = dataLines.map(line => { - const cells = line.split('|').map(cell => cell.trim()).filter(Boolean); + // 保留空单元格 + const cells = line.split('|').map(cell => cell.trim()); const obj = {}; header.forEach((key, idx) => { - obj[key] = cells[idx]; + // 这里 key 可能是空字符串,需跳过 + if (key) obj[key] = cells[idx] || ''; }); return obj; }); @@ -114,6 +188,38 @@ function parseOcrResult(content) { if (/\|.*\|/.test(content) && /\|[\s\-:|]+\|/.test(content)) { return parseMarkdownTable(content); } + // 判断是否为实验室结果格式(数字+中文+数字+单位+参考区间) + if (/^\d+[\u4e00-\u9fa5A-Za-z]+[\d.]+[a-zA-Zμ\/]+[\d.\-]+/m.test(content.replace(/↵/g, '\n'))) { + return parseLabResults(content); + } // 其它情况返回空数组或原始内容 return []; +} + +/** + * 解析实验室结果字符串为结构化对象数组 + * @param {string} str - 原始字符串 + * @returns {Array} 结构化结果数组 + */ +function parseLabResults(str) { + if (!str) return []; + // 替换特殊换行符为标准换行 + str = str.replace(/↵/g, '\n'); + const lines = str.split(/\n+/).filter(Boolean); + const result = []; + const regex = /^(\d+)([\u4e00-\u9fa5A-Za-z]+)([\d.]+)([a-zA-Zμ\/]+)?([\d.\-]+)?/; + lines.forEach(line => { + // 尝试用正则提取 + const match = line.match(/^(\d+)([\u4e00-\u9fa5A-Za-z]+)([\d.]+)([a-zA-Zμ\/]+)?([\d.\-]+)?/); + if (match) { + result.push({ + index: Number(match[1]), + name: match[2], + value: Number(match[3]), + unit: match[4] || '', + reference: match[5] || '' + }); + } + }); + return result; } \ No newline at end of file diff --git a/pages/AddTherapeuticRegimen/index.js b/pages/AddTherapeuticRegimen/index.js index d71b837..a43df8a 100644 --- a/pages/AddTherapeuticRegimen/index.js +++ b/pages/AddTherapeuticRegimen/index.js @@ -1,4 +1,5 @@ import request from '~/api/request'; +import { getOcr } from '~/api/drugOcr'; let modeType = ''; let mode2 = ''; @@ -364,7 +365,9 @@ Page({ this.setData({ imageFile: data }) + getOcr(data).then(res => { + }) console.log('上传成功:', data); // 输出上传成功后的数据 } }); diff --git a/pages/followUp/index.js b/pages/followUp/index.js index 5d0d80b..4ca8585 100644 --- a/pages/followUp/index.js +++ b/pages/followUp/index.js @@ -40,8 +40,8 @@ Page({ }, mode: '', dateVisible: false, - date: new Date('2021-12-23').getTime(), // 支持时间戳传入 dateText: '', + startTimePicker: String(new Date().getFullYear()), filter(type, options) { if (type === 'year') { return options.sort((a, b) => b.value - a.value); @@ -346,7 +346,6 @@ Page({ handleImagePreview(e) { const { mode, index } = e.currentTarget.dataset; this.imageKey = mode - console.log(this.data.form[mode]) this.setData({ imageList: this.data.form[mode], imageIndex: index, @@ -368,7 +367,6 @@ Page({ }, onDelete(e) { - console.log(e) let arr = this.data.form[this.imageKey] arr.splice(e.detail.index, 1) // let imageList = this.data.imageList @@ -391,7 +389,6 @@ Page({ ['form.follow_name']: options.name, ['form.follow_date']: options.time }) - console.log(options) if (options.questionnaire_id != 0) { this.getDetail() } @@ -414,63 +411,233 @@ Page({ // 遍历识别结果,填充字段 setFormData(ocrs, mode) { - console.log(ocrs, mode) ocrs.forEach(ocr => { - console.log(ocr['项目名称'], ocr['结果']) // 血常规 if (mode == 'blood_routine_image') { - if (ocr['项目名称'] == '血红蛋白') { + if (ocr.name == '血红蛋白') { this.setData({ - [`form.hemoglobin`]: ocr['结果'] + [`form.hemoglobin`]: ocr.value + }) + } + if (ocr.name == '血小板') { + this.setData({ + [`form.platelets`]: ocr.value + }) + } + if (ocr.name == '白细胞') { + this.setData({ + [`form.white_blood_cells`]: ocr.value }) } } // 凝血功能 if (mode == 'coagulation_function_image') { - - if (ocr['项目名称'] == '凝血酶原时间') { + if (ocr.name == 'C-反应蛋白' || ocr.name == 'c-反应蛋白' || ocr.name == 'CRP') { this.setData({ - [`form.pt`]: ocr['结果'] + [`form.crp`]: ocr.value }) } - if (ocr['项目名称'] == '凝血酶原活动度') { + if (ocr.name == 'DDR' || ocr.name == 'ddr') { this.setData({ - [`form.pta`]: ocr['结果'] + [`form.ddr`]: ocr.value }) } - if (ocr['项目名称'] == '国际标准化比值') { + if (ocr.name == '凝血酶原时间' || ocr.name == 'Pt') { this.setData({ - [`form.inr`]: ocr['结果'] + [`form.pt`]: ocr.value }) } - if (ocr['项目名称'] == '活化部分凝血活酶时间') { + if (ocr.name == '凝血酶原活动度' || ocr.name == 'Pt-a' || ocr.name == 'PtA') { this.setData({ - [`form.aptt`]: ocr['结果'] + [`form.pta`]: ocr.value }) } - if (ocr['项目名称'] == '凝血酶时间') { + if (ocr.name == '国际标准化比值' || ocr.name == 'INR') { this.setData({ - [`form.tt`]: ocr['结果'] + [`form.inr`]: ocr.value }) } - if (ocr['项目名称'] == '纤维蛋白原') { + if (ocr.name == '活化部分凝血活酶时间' || ocr.name == 'APTT') { this.setData({ - [`form.fib`]: ocr['结果'] + [`form.aptt`]: ocr.value + }) + } + if (ocr.name == '凝血酶时间' || ocr.name == 'TT') { + this.setData({ + [`form.tt`]: ocr.value + }) + } + if (ocr.name == '纤维蛋白原' || ocr.name == 'FIB') { + this.setData({ + [`form.fib`]: ocr.value + }) + } + if (ocr.name == 'NPDP' || ocr.name == 'npdp') { + this.setData({ + [`form.npdp`]: ocr.value + }) + } + if (ocr.name == 'MMP-7' || ocr.name == 'MMP7' || ocr.name == '基质金属蛋白酶-7') { + this.setData({ + [`form.mmp_7`]: ocr.value }) } } // 肝功能 if (mode == 'liver_function_image') { - + if (ocr.name == '总胆红素') { + this.setData({ + [`form.total_bilirubin`]: ocr.value + }) + } + if (ocr.name == '直接胆红素') { + this.setData({ + [`form.direct_bilirubin`]: ocr.value + }) + } + if (ocr.name == '总胆汁酸') { + this.setData({ + [`form.total_bile_acid`]: ocr.value + }) + } + if (ocr.name == '白蛋白') { + this.setData({ + [`form.albumin`]: ocr.value + }) + } + if (ocr.name == '谷草' || ocr.name == '谷草转氨酶') { + this.setData({ + [`form.grain_grass`]: ocr.value + }) + } + if (ocr.name == '谷丙' || ocr.name == '谷丙转氨酶') { + this.setData({ + [`form.gu_bing`]: ocr.value + }) + } + if (ocr.name == 'γ-谷氨酰转肽酶' || ocr.name == '谷氨酰转肽酶') { + this.setData({ + [`form.ggt`]: ocr.value + }) + } + + if (ocr.name == '碱性磷酸酶') { + this.setData({ + [`form.alp`]: ocr.value + }) + } } // 营养指数 if (mode == 'nutritional_indicator_image') { - + if (ocr.name == '25-羟基维生素D3' || ocr.name == '25-OH-Vitamin D3' || ocr.name == '25(OH)D3') { + this.setData({ + [`form.oh_d3`]: ocr.value + }) + } + if (ocr.name == '25-羟基维生素D2' || ocr.name == '25-OH-Vitamin D2' || ocr.name == '25(OH)D2') { + this.setData({ + [`form.oh_d2`]: ocr.value + }) + } + if (ocr.name == '25-羟基维生素D' || ocr.name == '25-OH-Vitamin D' || ocr.name == '25(OH)D') { + this.setData({ + [`form.oh_d`]: ocr.value + }) + } + if (ocr.name == '维生素A' || ocr.name == '维生素a' || ocr.name == 'vitamin a' || ocr.name == 'vit A') { + this.setData({ + [`form.vitamin_a`]: ocr.value + }) + } + if (ocr.name == '维生素K' || ocr.name == '维生素k' || ocr.name == 'vitamin k' || ocr.name == 'vit K') { + this.setData({ + [`form.vitamin_k`]: ocr.value + }) + } + if (ocr.name == '维生素E' || ocr.name == '维生素e' || ocr.name == 'vitamin e' || ocr.name == 'vit E') { + this.setData({ + [`form.vitamin_e`]: ocr.value + }) + } } // B超 if (mode == 'b_mode_image') { - + if(ocr.name == '肝肋下'){ + this.setData({ + [`form.under_the_liver_rib`]: ocr.value + }) + } + if(ocr.name == '肝剑突下'){ + this.setData({ + [`form.under_the_xiphoid_liver`]: ocr.value + }) + } + if(ocr.name == '脾肋下'){ + this.setData({ + [`form.spleen_rib_area`]: ocr.value + }) + } + if(ocr.name == '门静脉主干内径'){ + this.setData({ + [`form.main_portal_vein`]: ocr.value + }) + } + if(ocr.name == '肝回声'){ + this.setData({ + [`form.liver_echo`]: ocr.value + }) + } + if(ocr.name == '胆囊大小'){ + this.setData({ + [`form.gallbladder_size`]: ocr.value + }) + } + if(ocr.name == '胆总管'){ + this.setData({ + [`form.common_bile_duct`]: ocr.value + }) + } + if(ocr.name == '纤维块大小'){ + this.setData({ + [`form.fiber_block_size`]: ocr.value + }) + } + if(ocr.name == '门静脉流速' || ocr.name == 'PVV' || ocr.name == 'PVV流速' || ocr.name == 'pvv'){ + this.setData({ + [`form.pvv`]: ocr.value + }) + } + if(ocr.name == '肝弹性值'){ + this.setData({ + [`form.liver_elasticity_value`]: ocr.value + }) + } + if(ocr.name == '有无肝囊肿'){ + this.setData({ + [`form.is_have_cyst`]: ocr.value == '无' ? '2' : '1' + }) + } + if(ocr.name == '有无腹水'){ + this.setData({ + [`form.is_have_ascites`]: ocr.value == '无' ? '2' : '1' + }) + } + if(ocr.name == '弹性成像最小值'){ + this.setData({ + [`form.elastography_minimum`]: ocr.value + }) + } + if(ocr.name == '弹性成像最大值'){ + this.setData({ + [`form.elastography_maximum`]: ocr.value + }) + } + if(ocr.name == '弹性成像中位数'){ + this.setData({ + [`form.elastography_median`]: ocr.value + }) + } } }) diff --git a/pages/followUp/index.wxml b/pages/followUp/index.wxml index 3db102e..32824cf 100644 --- a/pages/followUp/index.wxml +++ b/pages/followUp/index.wxml @@ -536,7 +536,7 @@ 弹性成像最小值(kPa) - + @@ -588,6 +588,6 @@ 提交 - + \ No newline at end of file diff --git a/pages/home/index.js b/pages/home/index.js index c41e351..d1022f5 100644 --- a/pages/home/index.js +++ b/pages/home/index.js @@ -241,7 +241,7 @@ Page({ canvas.setChart(chart); chart.setOption({ title: { text: '', left: 'left', fontSize: 6 }, - grid: { containLabel: true, top: '5%', left: '5%', right: '5%', bottom: '3%' }, + grid: { containLabel: true, top: '5%', left: '5%', right: '7%', bottom: '3%' }, tooltip: { show: true, trigger: 'axis', @@ -311,7 +311,7 @@ Page({ canvas.setChart(chart); chart.setOption({ title: { text: '', left: 'left', fontSize: 6 }, - grid: { containLabel: true, top: '5%', left: '5%', right: '5%', bottom: '3%' }, + grid: { containLabel: true, top: '5%', left: '5%', right: '7%', bottom: '3%' }, tooltip: { show: true, trigger: 'axis', diff --git a/pages/home/index.less b/pages/home/index.less index 9063148..46ae203 100644 --- a/pages/home/index.less +++ b/pages/home/index.less @@ -225,6 +225,9 @@ margin-top: 0; } } + .item-content-canvas{ + padding-right: 10rpx; + } } .item-content-1{ background: linear-gradient( 180deg, #EDF3F8 0%, #F7FAFC 100%); diff --git a/pages/home/index.wxml b/pages/home/index.wxml index f619516..766a72b 100644 --- a/pages/home/index.wxml +++ b/pages/home/index.wxml @@ -11,7 +11,7 @@ 宝宝生长曲线 - + 身高曲线 diff --git a/pages/message/index.js b/pages/message/index.js index 9f9790d..e4eb7f5 100644 --- a/pages/message/index.js +++ b/pages/message/index.js @@ -7,7 +7,7 @@ Page({ */ data: { dataList: [], - planId: '', + // planId: '', }, async getList(){ @@ -36,12 +36,12 @@ Page({ this.setData({ dataList: processedList }) - const actObj = processedList.find(item => item.status === 2); - if (actObj) { - this.setData({ - planId: actObj.id - }) - } + // const actObj = processedList.find(item => item.status === 2); + // if (actObj) { + // this.setData({ + // planId: actObj.id + // }) + // } }, /** @@ -102,7 +102,6 @@ Page({ }, toQuestionnaire(e) { const { item } = e.currentTarget.dataset - console.log(item) wx.navigateTo({ url: `/pages/followUp/index?planId=${item.id}&name=${item.plan_name}&time=${item.plan_date}&questionnaire_id=${item.questionnaire_id}` , }) diff --git a/pages/message/index.wxml b/pages/message/index.wxml index 8bcce17..6c4074c 100644 --- a/pages/message/index.wxml +++ b/pages/message/index.wxml @@ -24,7 +24,7 @@ - + 添加随访 diff --git a/pages/my/index.less b/pages/my/index.less index a3e8f6c..98a70b1 100644 --- a/pages/my/index.less +++ b/pages/my/index.less @@ -227,7 +227,7 @@ border: 1px solid rgba(255,255,255,1); padding-left: 20rpx; // flex-direction: row; - width: 320rpx; + width: 340rpx; height: 50rpx; display: inline-flex; box-sizing: border-box; diff --git a/pages/my/info-edit/index.js b/pages/my/info-edit/index.js index c9b787e..c3cec8d 100644 --- a/pages/my/info-edit/index.js +++ b/pages/my/info-edit/index.js @@ -32,7 +32,7 @@ Page({ ], birthVisible: false, - birthStart: '1970-01-01', + birthStart: '2010-01-01', birthEnd: new Date().toISOString().split('T')[0], birthTime: 0, birthFilter: (type, options) => (type === 'year' ? options.sort((a, b) => b.value - a.value) : options), diff --git a/utils/util.js b/utils/util.js index 98b7b71..28631c3 100644 --- a/utils/util.js +++ b/utils/util.js @@ -22,7 +22,36 @@ const getLocalUrl = (path, name) => { return tempFileName; }; +/** + * 解析实验室结果字符串为结构化对象数组 + * @param {string} str - 原始字符串 + * @returns {Array} 结构化结果数组 + */ +function parseLabResults(str) { + if (!str) return []; + // 替换特殊换行符为标准换行 + str = str.replace(/↵/g, '\n'); + const lines = str.split(/\n+/).filter(Boolean); + const result = []; + const regex = /^(\d+)([\u4e00-\u9fa5A-Za-z]+)([\d.]+)([a-zA-Zμ\/]+)?([\d.\-]+)?/; + lines.forEach(line => { + // 尝试用正则提取 + const match = line.match(/^(\d+)([\u4e00-\u9fa5A-Za-z]+)([\d.]+)([a-zA-Zμ\/]+)?([\d.\-]+)?/); + if (match) { + result.push({ + index: Number(match[1]), + name: match[2], + value: Number(match[3]), + unit: match[4] || '', + reference: match[5] || '' + }); + } + }); + return result; +} + module.exports = { formatTime, getLocalUrl, + parseLabResults, };