From fd695a221eaa5d01833f0cd9e9df53db26982bfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B7=A6=E5=93=A5?= <17630302050@163.com> Date: Wed, 6 Aug 2025 23:28:08 +0800 Subject: [PATCH] sadsd --- api/ocr.js | 46 +++--- api/{ocr copy.js => ocrReact copy.js} | 25 ++-- api/ocrReact.js | 193 ++++++++++++++++++++++++++ pages/followUp/index.js | 100 ++++++------- pages/my/index.less | 23 +-- pages/my/index.wxml | 10 +- pages/my/info-edit/index.js | 7 +- 7 files changed, 313 insertions(+), 91 deletions(-) rename api/{ocr copy.js => ocrReact copy.js} (92%) create mode 100644 api/ocrReact.js diff --git a/api/ocr.js b/api/ocr.js index 56b9bd8..b7230f7 100644 --- a/api/ocr.js +++ b/api/ocr.js @@ -1,3 +1,4 @@ +import { getOcrReact } from './ocrReact' export const getOcr = (url) => { return new Promise((resolve, reject) => { wx.request({ @@ -28,10 +29,23 @@ export const getOcr = (url) => { }] }, success(res) { + if(res.data.choices[0].message.content.includes('rotate_rect')){ + getOcrReact(url).then(res=>{ + resolve(res) + }).catch(err=>{ + wx.showToast({ + title: '识别失败,请重新选择照片!', + icon: 'none', + duration: 5000 + }); + reject(err) + }) + return + } let data = parseJsonBlock(res.data.choices[0].message.content) if(data.length == 0){ wx.showToast({ - title: '识别失败,请选择带有符合表单字段的照片!', + title: '识别失败,请重新选择照片!', icon: 'none', duration: 5000 }); @@ -39,26 +53,26 @@ export const getOcr = (url) => { return } // 判断是否为 rotate_rect 格式 - if (Array.isArray(data) && data.length && data[0].rotate_rect) { - let items = extractCheckItemsFromOcrTable(data); - if(items.length==0){ - wx.showToast({ - title: '识别失败,请选择带有符合表单字段的照片!', - icon: 'none', - duration: 5000 - }); - reject('识别失败!') - return - } - resolve(items); - return; - } + // if (Array.isArray(data) && data.length && data[0].rotate_rect) { + // let items = extractCheckItemsFromOcrTable(data); + // if(items.length==0){ + // wx.showToast({ + // title: '识别失败,请重新选择照片!', + // icon: 'none', + // duration: 5000 + // }); + // reject('识别失败!') + // return + // } + // resolve(items); + // return; + // } resolve(data); }, fail(err) { wx.showToast({ - title: '识别失败,请选择带有符合表单字段的照片!', + title: '识别失败,请重新选择照片!', icon: 'none', duration: 5000 }) diff --git a/api/ocr copy.js b/api/ocrReact copy.js similarity index 92% rename from api/ocr copy.js rename to api/ocrReact copy.js index a1e3072..a584240 100644 --- a/api/ocr copy.js +++ b/api/ocrReact copy.js @@ -1,4 +1,4 @@ -export const getOcr = (url) => { +export const getOcrReact = (url) => { return new Promise((resolve, reject) => { wx.request({ url: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions', @@ -29,6 +29,7 @@ export const getOcr = (url) => { }, success(res) { let data = parseOcrResult(res.data.choices[0].message.content) + console.log(data) // 新增:统一字段名 if (Array.isArray(data)) { data = data.map(item => { @@ -115,21 +116,27 @@ 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 isSeparator = line => line.split('|').every(cell => cell.trim() === '' || /^-+$/.test(cell.trim())); - // 数据行从第三行开始(第二行为分隔符) - const dataLines = lines.slice(2); + let header = []; + let dataLines = []; + if (lines.length > 1 && isSeparator(lines[1])) { + // 没有表头,第一行是数据 + const colCount = lines[0].split('|').length; + header = Array.from({ length: colCount }, (_, i) => `col${i + 1}`); + dataLines = [lines[0], ...lines.slice(2)]; + } else { + // 有表头 + header = lines[0].split('|').map(h => h.trim()); + 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; diff --git a/api/ocrReact.js b/api/ocrReact.js new file mode 100644 index 0000000..ae14dc9 --- /dev/null +++ b/api/ocrReact.js @@ -0,0 +1,193 @@ +export const getOcrReact = (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) + // 新增:统一字段名 + if (Array.isArray(data)) { + // 找到包含"项目"和"结果"的字段名 + let projectField = null; + let resultField = null; + + // 检查第一条数据的所有字段 + if (data.length > 0) { + Object.keys(data[0]).forEach(key => { + if (typeof data[0][key] === 'string' && data[0][key].includes('项目')) { + projectField = key; + } + if (typeof data[0][key] === 'string' && data[0][key].includes('结果')) { + resultField = key; + } + }); + } + + // 重新映射所有数据 + data = data.map(item => { + const newItem = {}; + + if (projectField && item[projectField]) { + newItem.name = item[projectField]; + } + if (resultField && item[resultField]) { + newItem.value = item[resultField]; + } + + return newItem; + }); + } + 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 isSeparator = line => line.split('|').every(cell => cell.trim() === '' || /^-+$/.test(cell.trim())); + + let header = []; + let dataLines = []; + if (lines.length > 1 && isSeparator(lines[1])) { + // 没有表头,第一行是数据 + const colCount = lines[0].split('|').length; + header = Array.from({ length: colCount }, (_, i) => `col${i + 1}`); + dataLines = [lines[0], ...lines.slice(2)]; + } else { + // 有表头 + header = lines[0].split('|').map(h => h.trim()); + dataLines = lines.slice(2); + } + + // 解析每一行 + const result = dataLines.map(line => { + const cells = line.split('|').map(cell => cell.trim()); + const obj = {}; + header.forEach((key, idx) => { + 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/pages/followUp/index.js b/pages/followUp/index.js index 52b6a9a..059f495 100644 --- a/pages/followUp/index.js +++ b/pages/followUp/index.js @@ -3,6 +3,7 @@ import uploadFileToOSS from '~/api/upload'; import { getOcr } from '~/api/ocr'; import { getBOcr } from '~/api/BOcr'; +import { getOcrReact } from '~/api/ocrReact'; Page({ /** @@ -248,7 +249,7 @@ Page({ [`form.${mode}`]: e.detail.value }) }, - + handleUpload(e) { const { mode } = e.currentTarget.dataset; if (this.data.form[mode].length >= 9) { @@ -262,16 +263,16 @@ Page({ this.setData({ [`form.${mode}`]: arr }) - if(mode == 'mdt_image'){ + if (mode == 'mdt_image') { wx.showToast({ title: '上传成功!', icon: 'none' }); - } else if(mode == 'b_mode_image'){ + } else if (mode == 'b_mode_image') { wx.showToast({ title: '上传成功,正在识别内容!', icon: 'none' }); getBOcr(imageUrl).then(ocrRes => { console.log(ocrRes) wx.showToast({ title: '识别完成!', icon: 'none' }) this.setFormData(ocrRes, mode) }) - } else{ + } else { wx.showToast({ title: '上传成功,正在识别内容!', icon: 'none' }); getOcr(imageUrl).then(ocrRes => { console.log(ocrRes) @@ -280,7 +281,7 @@ Page({ }) } }) - + }, imageKey: '', @@ -353,24 +354,25 @@ Page({ // 遍历识别结果,填充字段 setFormData(ocrs, mode) { ocrs.forEach(ocr => { + const name = ocr.name ? ocr.name.replace(/(.*?)|\(.*?\)/g, '').trim() : ''; // 血常规 if (mode == 'blood_routine_image') { - if (ocr.name == '血红蛋白' || ocr.name == '血红蛋白检查') { + if (name == '血红蛋白' || name == '血红蛋白检查') { this.setData({ [`form.hemoglobin`]: ocr.value }) } - if (ocr.name == '血小板' || ocr.name == '血小板计数') { + if (name == '血小板' || name == '血小板计数') { this.setData({ [`form.platelets`]: ocr.value }) } - if (ocr.name == '白细胞' || ocr.name == '白细胞计数') { + if (name == '白细胞' || name == '白细胞计数') { this.setData({ [`form.white_blood_cells`]: ocr.value }) } - if (ocr.name == '红细胞' || ocr.name == '红细胞计数') { + if (name == '红细胞' || name == '红细胞计数') { this.setData({ [`form.red_blood_cells`]: ocr.value }) @@ -378,52 +380,52 @@ Page({ } // 凝血功能 if (mode == 'coagulation_function_image') { - if (ocr.name == 'C-反应蛋白' || ocr.name == 'c-反应蛋白' || ocr.name == 'CRP') { + if (name == 'C-反应蛋白' || name == 'c-反应蛋白' || name == 'CRP') { this.setData({ [`form.crp`]: ocr.value }) } - if (ocr.name == 'DDR' || ocr.name == 'ddr') { + if (name == 'DDR' || name == 'ddr') { this.setData({ [`form.ddr`]: ocr.value }) } - if (ocr.name == '凝血酶原时间' || ocr.name == 'Pt') { + if (name == '凝血酶原时间' || name == 'Pt') { this.setData({ [`form.pt`]: ocr.value }) } - if (ocr.name == '凝血酶原活动度' || ocr.name == 'Pt-a' || ocr.name == 'PtA') { + if (name == '凝血酶原活动度' || name == 'Pt-a' || name == 'PtA') { this.setData({ [`form.pta`]: ocr.value }) } - if (ocr.name == '国际标准化比值' || ocr.name == 'INR') { + if (name == '国际标准化比值' || name == 'INR' || name == 'INR值' || name == 'PT国际标准化比值') { this.setData({ [`form.inr`]: ocr.value }) } - if (ocr.name == '活化部分凝血活酶时间' || ocr.name == 'APTT') { + if (name == '活化部分凝血活酶时间' || name == 'APTT') { this.setData({ [`form.aptt`]: ocr.value }) } - if (ocr.name == '凝血酶时间' || ocr.name == 'TT') { + if (name == '凝血酶时间' || name == 'TT') { this.setData({ [`form.tt`]: ocr.value }) } - if (ocr.name == '纤维蛋白原' || ocr.name == 'FIB') { + if (name == '纤维蛋白原' || name == 'FIB') { this.setData({ [`form.fib`]: ocr.value }) } - if (ocr.name == 'NPDP' || ocr.name == 'npdp') { + if (name == 'NPDP' || name == 'npdp') { this.setData({ [`form.npdp`]: ocr.value }) } - if (ocr.name == 'MMP-7' || ocr.name == 'MMP7' || ocr.name == '基质金属蛋白酶-7') { + if (name == 'MMP-7' || name == 'MMP7' || name == '基质金属蛋白酶-7') { this.setData({ [`form.mmp_7`]: ocr.value }) @@ -432,43 +434,43 @@ Page({ // 肝功能 if (mode == 'liver_function_image') { - if (ocr.name == '总胆红素') { + if (name == '总胆红素') { this.setData({ [`form.total_bilirubin`]: ocr.value }) } - if (ocr.name == '直接胆红素') { + if (name == '直接胆红素') { this.setData({ [`form.direct_bilirubin`]: ocr.value }) } - if (ocr.name == '总胆汁酸') { + if (name == '总胆汁酸') { this.setData({ [`form.total_bile_acid`]: ocr.value }) } - if (ocr.name == '白蛋白') { + if (name == '白蛋白') { this.setData({ [`form.albumin`]: ocr.value }) } - if (ocr.name == '谷草' || ocr.name == '谷草转氨酶') { + if (name == '谷草' || name == '谷草转氨酶') { this.setData({ [`form.grain_grass`]: ocr.value }) } - if (ocr.name == '谷丙' || ocr.name == '谷丙转氨酶') { + if (name == '谷丙' || name == '谷丙转氨酶') { this.setData({ [`form.gu_bing`]: ocr.value }) } - if (ocr.name == 'γ-谷氨酰转肽酶' || ocr.name == '谷氨酰转肽酶') { + if (name == 'γ-谷氨酰转肽酶' || name == '谷氨酰转肽酶') { this.setData({ [`form.ggt`]: ocr.value }) } - - if (ocr.name == '碱性磷酸酶') { + + if (name == '碱性磷酸酶') { this.setData({ [`form.alp`]: ocr.value }) @@ -476,32 +478,32 @@ Page({ } // 营养指数 if (mode == 'nutritional_indicator_image') { - if (ocr.name == '25-羟基维生素D3' || ocr.name == '25-OH-Vitamin D3' || ocr.name == '25(OH)D3') { + if (name == '25-羟基维生素D3' || name == '25-OH-Vitamin D3' || 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') { + if (name == '25-羟基维生素D2' || name == '25-OH-Vitamin D2' || 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') { + if (name == '25-羟基维生素D' || name == '25-OH-Vitamin D' || 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') { + if (name == '维生素A' || name == '维生素a' || name == 'vitamin a' || 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') { + if (name == '维生素K' || name == '维生素k' || name == 'vitamin k' || 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') { + if (name == '维生素E' || name == '维生素e' || name == 'vitamin e' || name == 'vit E') { this.setData({ [`form.vitamin_e`]: ocr.value }) @@ -509,77 +511,77 @@ Page({ } // B超 if (mode == 'b_mode_image') { - if(ocr.name == '肝肋下'){ + if (name == '肝肋下') { this.setData({ [`form.under_the_liver_rib`]: ocr.value }) } - if(ocr.name == '肝剑突下'){ + if (name == '肝剑突下') { this.setData({ [`form.under_the_xiphoid_liver`]: ocr.value }) } - if(ocr.name == '脾肋下'){ + if (name == '脾肋下') { this.setData({ [`form.spleen_rib_area`]: ocr.value }) } - if(ocr.name == '门静脉主干内径'){ + if (name == '门静脉主干内径') { this.setData({ [`form.main_portal_vein`]: ocr.value }) } - if(ocr.name == '肝回声'){ + if (name == '肝回声') { this.setData({ [`form.liver_echo`]: ocr.value }) } - if(ocr.name == '胆囊大小'){ + if (name == '胆囊大小') { this.setData({ [`form.gallbladder_size`]: ocr.value }) } - if(ocr.name == '胆总管'){ + if (name == '胆总管') { this.setData({ [`form.common_bile_duct`]: ocr.value }) } - if(ocr.name == '纤维块大小'){ + if (name == '纤维块大小') { this.setData({ [`form.fiber_block_size`]: ocr.value }) } - if(ocr.name == '门静脉流速' || ocr.name == 'PVV' || ocr.name == 'PVV流速' || ocr.name == 'pvv'){ + if (name == '门静脉流速' || name == 'PVV' || name == 'PVV流速' || name == 'pvv') { this.setData({ [`form.pvv`]: ocr.value }) } - if(ocr.name == '肝弹性值'){ + if (name == '肝弹性值') { this.setData({ [`form.liver_elasticity_value`]: ocr.value }) } - if(ocr.name == '有无肝囊肿'){ + if (name == '有无肝囊肿') { this.setData({ [`form.is_have_cyst`]: ocr.value == '无' ? '2' : '1' }) } - if(ocr.name == '有无腹水'){ + if (name == '有无腹水') { this.setData({ [`form.is_have_ascites`]: ocr.value == '无' ? '2' : '1' }) } - if(ocr.name == '弹性成像最小值'){ + if (name == '弹性成像最小值') { this.setData({ [`form.elastography_minimum`]: ocr.value }) } - if(ocr.name == '弹性成像最大值'){ + if (name == '弹性成像最大值') { this.setData({ [`form.elastography_maximum`]: ocr.value }) } - if(ocr.name == '弹性成像中位数'){ + if (name == '弹性成像中位数') { this.setData({ [`form.elastography_median`]: ocr.value }) diff --git a/pages/my/index.less b/pages/my/index.less index c20afbd..fb47fc6 100644 --- a/pages/my/index.less +++ b/pages/my/index.less @@ -325,10 +325,11 @@ position: absolute; left: 24rpx; top: 408rpx; - width: 702rpx; + width: calc(100% - 48rpx); + right: 24rpx; // height: 210rpx; - display: flex; - flex-direction: column; + // display: flex; + // flex-direction: column; } .block_5 { background-color: rgba(255,255,255,0.010000); @@ -338,10 +339,10 @@ padding: 38rpx 32rpx 32rpx 32rpx; } .image-text_5 { - width: 192rpx; - flex-direction: row; - display: flex; - justify-content: space-between; + // width: 192rpx; + // flex-direction: row; + // display: flex; + // justify-content: space-between; } .thumbnail_2 { width: 40rpx; @@ -387,14 +388,14 @@ z-index: 0; } .image-text_6 { - width: 192rpx; - flex-direction: row; - display: flex; - justify-content: space-between; + // width: 192rpx; + } .thumbnail_4 { width: 40rpx; height: 40rpx; + vertical-align: bottom; + margin-right: 20rpx; } .text-group_7 { overflow-wrap: break-word; diff --git a/pages/my/index.wxml b/pages/my/index.wxml index 8d939a3..8ea14ee 100644 --- a/pages/my/index.wxml +++ b/pages/my/index.wxml @@ -63,10 +63,10 @@ - - - - 用药方案 + + + + 用药方案 @@ -81,7 +81,7 @@ - MMP-7 + MMP-7 diff --git a/pages/my/info-edit/index.js b/pages/my/info-edit/index.js index e492b6b..a5cff0c 100644 --- a/pages/my/info-edit/index.js +++ b/pages/my/info-edit/index.js @@ -165,7 +165,8 @@ Page({ avatar: info.avatar ? info.avatar : 'https://image-fudan.oss-cn-beijing.aliyuncs.com/mini_images/my/baby.png', } }) - if (wx.getStorageSync('scrm_userid')) { + + if (wx.getStorageSync('scrm_userid') && info.operative_date) { this.scrmBindTag(wx.getStorageSync('scrm_userid')) } }, @@ -476,6 +477,9 @@ Page({ obj.gestational_week = Number(obj.gestational_week); await request('patient/set_personal_information', 'post', obj); + if (wx.getStorageSync('scrm_userid')) { + this.scrmBindTag(wx.getStorageSync('scrm_userid')) + } wx.showToast({ title: '提交成功', icon: 'success', @@ -486,6 +490,7 @@ Page({ }) } }) + }, onIdInput(e) { // 18位身份证正则