From 2e178c78b71536553d9596a8380761a86e3b93fc Mon Sep 17 00:00:00 2001 From: "@zuopngfei" Date: Wed, 23 Jul 2025 16:41:26 +0800 Subject: [PATCH] ss --- api/drugOcr.js | 184 +++++++++----------- api/ocr copy.js | 225 ++++++++++++++++++++++++ api/ocr.js | 246 +++++++++------------------ pages/AddTherapeuticRegimen/index.js | 240 +++++++++++++++++--------- pages/followUp/index.js | 28 +-- pages/mmp-7/index.js | 8 +- pages/mmp-7/index.wxml | 1 + pages/my/index.wxml | 7 + pages/my/info-edit/index.js | 4 +- 9 files changed, 573 insertions(+), 370 deletions(-) create mode 100644 api/ocr copy.js diff --git a/api/drugOcr.js b/api/drugOcr.js index 65710b6..78f9e84 100644 --- a/api/drugOcr.js +++ b/api/drugOcr.js @@ -22,19 +22,46 @@ export const getOcr = (url) => { }, { "type": "text", - "text": "要求准确无误的提取上述关键信息、不要遗漏和捏造虚假信息,模糊或者强光遮挡的单个文字可以用英文问号?代替。返回数据格式以MD方式输出" + "text": "要求准确无误的提取图片中的药品名称、每次用量、每日服用次数等信息,模糊或者强光遮挡的单个文字可以用英文问号?代替,药品名称的key为'name',每次用量key为'jiliang',每日服用次数的key为'cishu',返回数据格式以JSON数组格式输出,不要将多个药品信息放到一个药品名称字段内,跟药品无关的信息不要" } ] }] }, success(res) { - let data = parseOcrResult(res.data.choices[0].message.content) - console.log(data) - - - resolve(data); + let data = parseJsonBlock(res.data.choices[0].message.content) + if(data.length == 0){ + wx.showToast({ + title: '识别失败,请重新选择照片!', + icon: 'none', + duration: 2000 + }); + reject('识别失败,请重新选择照片!') + return + } + if(data.some((item)=>{ + return !item.name && item.text; + })){ + let sss = extractDrugsFromOcr(data) + if(sss.length == 0){ + wx.showToast({ + title: '识别失败,请重新选择照片!', + icon: 'none', + duration: 2000 + }); + reject('识别失败!') + return + } + resolve(sss); + } else { + resolve(data); + } }, fail(err) { + wx.showToast({ + title: '识别失败,请重新选择照片!', + icon: 'none', + duration: 2000 + }) console.log(err) // 断网、服务器挂了都会fail回调,直接reject即可 reject(err); @@ -43,32 +70,7 @@ export const getOcr = (url) => { }) } -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 ... ``` 格式的字符串,提取检测项目数组 @@ -76,82 +78,58 @@ function parseMarkdownTable(md) { * @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; + // 匹配 ```json ... ``` 或 ``` ... ``` + const match = str.match(/```(?:json)?\s*([\s\S]*?)\s*```/i); + let jsonStr = match ? match[1] : str; + jsonStr = jsonStr.trim(); + + // 尝试直接解析 + try { + return JSON.parse(jsonStr); + } catch (e) { + // 替换单引号包裹的 key 和 value 为双引号 + let fixedStr = jsonStr + // 替换 key 的单引号(允许有空格、括号等) + .replace(/'([\w\u4e00-\u9fa5\(\)\s]+)'(?=\s*:)/g, '"$1"') + // 替换 value 的单引号(允许有空格、括号等,非贪婪匹配) + .replace(/:\s*'([^']*?)'/g, ': "$1"'); + try { + return JSON.parse(fixedStr); + + } catch (e2) { + console.error('JSON parse error:', e2, fixedStr); + return []; + } + } + } +function extractDrugsFromOcr(ocrArray) { + // 1. 合并所有 text 字段 + const lines = ocrArray.map(item => item.text.trim()).filter(Boolean); -/** - * 自动判断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 []; -} + // 2. 分组药品(以“1、”“2、”等开头) + const drugGroups = []; + let currentDrug = null; -/** - * 解析实验室结果字符串为结构化对象数组 - * @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] || '' - }); + // 判断是否为新药品的开始 + if (/^\d+、/.test(line)) { + if (currentDrug) drugGroups.push(currentDrug); + currentDrug = { name: line.replace(/^\d+、/, '').trim(), jiliang: '', cishu: '' }; + } else if (currentDrug) { + // 判断是否为用量 + if (/每次|mg|片|粒|ml|g|单位/.test(line) && !currentDrug.jiliang) { + currentDrug.jiliang = line; + } + // 判断是否为次数 + else if (/每日|每天|次|早|晚|中/.test(line) && !currentDrug.cishu) { + currentDrug.cishu = line; + } + // 其他信息可根据需要扩展 } }); - return result; + if (currentDrug) drugGroups.push(currentDrug); + + // 过滤掉无效项 + return drugGroups.filter(d => d.name); } \ No newline at end of file diff --git a/api/ocr copy.js b/api/ocr copy.js new file mode 100644 index 0000000..a1e3072 --- /dev/null +++ b/api/ocr copy.js @@ -0,0 +1,225 @@ +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) + // 新增:统一字段名 + 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) { + 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 a1e3072..25bd394 100644 --- a/api/ocr.js +++ b/api/ocr.js @@ -22,87 +22,46 @@ export const getOcr = (url) => { }, { "type": "text", - "text": "要求准确无误的提取上述关键信息、不要遗漏和捏造虚假信息,模糊或者强光遮挡的单个文字可以用英文问号?代替。返回数据格式以MD方式输出" + "text": "请只返回提取好的检查项目及其结果,格式为 JSON 数组,每个元素包含 'name' 和 'value' 两个字段。例如:[{\"name\": \"白细胞\", \"value\": \"5.2\"}]。不要返回包含 rotate_rect、text 等字段的原始 OCR 结构化表格数据。" } ] }] }, success(res) { - 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; + let data = parseJsonBlock(res.data.choices[0].message.content) + if(data.length == 0){ + wx.showToast({ + title: '识别失败,请重新选择照片!', + icon: 'none', + duration: 2000 }); + reject('识别失败!') + 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: 2000 + }); + reject('识别失败!') + return + } + resolve(items); + return; } resolve(data); + }, fail(err) { + wx.showToast({ + title: '识别失败,请重新选择照片!', + icon: 'none', + duration: 2000 + }) console.log(err) // 断网、服务器挂了都会fail回调,直接reject即可 reject(err); @@ -111,32 +70,7 @@ export const getOcr = (url) => { }) } -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 ... ``` 格式的字符串,提取检测项目数组 @@ -144,82 +78,62 @@ function parseMarkdownTable(md) { * @returns {Array} */ function parseJsonBlock(str) { - // 去除包裹的代码块标记 - const jsonStr = str.replace(/^[\s`]*```json[\s`]*|```$/g, '').replace(/↵/g, '\n').trim(); + // 匹配 ```json ... ``` 或 ``` ... ``` + const match = str.match(/```(?:json)?\s*([\s\S]*?)\s*```/i); + let jsonStr = match ? match[1] : str; + jsonStr = jsonStr.trim(); + + // 尝试直接解析 + try { + return JSON.parse(jsonStr); + } catch (e) { + // 替换单引号包裹的 key 和 value 为双引号 + let fixedStr = jsonStr + // 替换 key 的单引号(允许有空格、括号等) + .replace(/'([\w\u4e00-\u9fa5\(\)\s]+)'(?=\s*:)/g, '"$1"') + // 替换 value 的单引号(允许有空格、括号等,非贪婪匹配) + .replace(/:\s*'([^']*?)'/g, ': "$1"'); + try { + return JSON.parse(fixedStr); + + } catch (e2) { + console.error('JSON parse error:', e2, fixedStr); + return []; + } + } + +} - // 用正则提取所有 "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; +function extractCheckItemsFromOcrTable(ocrArray) { + // 1. 找到表头的 x 坐标 + const nameHeader = ocrArray.find(item => item.text.includes('检验项目')); + const valueHeader = ocrArray.find(item => item.text.includes('结果')); + if (!nameHeader || !valueHeader) return []; + + const nameX = nameHeader.rotate_rect[0]; + const valueX = valueHeader.rotate_rect[0]; + + // 2. 过滤掉表头,按 y 坐标分组(每一行) + const rows = {}; + ocrArray.forEach(item => { + if (item.text && !['检验项目', '结果', '单位', '提示', '参考区间'].includes(item.text)) { + // 以 y 坐标为 key,允许有一定误差(如±5) + const y = Math.round(item.rotate_rect[1] / 5) * 5; + if (!rows[y]) rows[y] = []; + rows[y].push(item); } }); - 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); + // 3. 每一行找 name/value 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] || '' - }); - } + Object.values(rows).forEach(items => { + let name = null, value = null; + items.forEach(item => { + if (Math.abs(item.rotate_rect[0] - nameX) < 50) name = item.text.trim(); + if (Math.abs(item.rotate_rect[0] - valueX) < 50) value = item.text.trim(); + }); + if (name && value) result.push({ name, value }); }); return result; } \ No newline at end of file diff --git a/pages/AddTherapeuticRegimen/index.js b/pages/AddTherapeuticRegimen/index.js index a43df8a..e206b7a 100644 --- a/pages/AddTherapeuticRegimen/index.js +++ b/pages/AddTherapeuticRegimen/index.js @@ -1,5 +1,7 @@ import request from '~/api/request'; -import { getOcr } from '~/api/drugOcr'; +import { + getOcr +} from '~/api/drugOcr'; let modeType = ''; let mode2 = ''; @@ -18,31 +20,31 @@ Page({ "dose": "", "frequency": "", "time": "饭前", - + }], reminder: { "morning": "08:00", "noon": "12:00", "evening": "18:00" - }, + }, start_date: '', end_date: '', - + // 下拉 selectList: [], selectValue: '', selectVisible: false, - + timeData: '', id: '', style: 'border: 0;', - frequencyList:[{ + frequencyList: [{ label: '每日1次', value: '1' - },{ + }, { label: '每日2次', value: '2' - },{ + }, { label: '每日3次', value: '3' }], @@ -52,7 +54,7 @@ Page({ minuteVisible: false, date: new Date('2021-12-23').getTime(), // 支持时间戳传入 dateText: '', - birthStart: new Date().getTime(), + birthStart: new Date().getTime(), filter(type, options) { if (type === 'year') { return options.sort((a, b) => b.value - a.value); @@ -103,7 +105,7 @@ Page({ }; }, - + gridConfig: { column: 4, width: 160, @@ -115,37 +117,44 @@ Page({ }, - showSelect(e){ - const { mode, index } = e.currentTarget.dataset; + showSelect(e) { + const { + mode, + index + } = e.currentTarget.dataset; modeText = mode modeIndex = index this.setData({ selectValue: [this.data.detail[index].frequency], selectVisible: true, - + }) }, - onSelectChange(e){ + onSelectChange(e) { this.setData({ [`detail[${modeIndex}].frequency`]: e.detail.value[0] }) }, showPicker(e) { - const { mode } = e.currentTarget.dataset; + const { + mode + } = e.currentTarget.dataset; modeType = mode; this.setData({ birthVisible: true, timeData: this.data[mode], }); - + }, - onPickerChange(e){ + onPickerChange(e) { this.setData({ [modeType]: e.detail.value, }); }, - showPickertime(e){ - const { mode } = e.currentTarget.dataset; + showPickertime(e) { + const { + mode + } = e.currentTarget.dataset; mode2 = mode; this.setData({ minuteVisible: true, @@ -153,34 +162,42 @@ Page({ minute: this.data.reminder[mode] }); }, - onConfirm(e){ + onConfirm(e) { this.setData({ minuteVisible: false, [`reminder.${mode2}`]: e.detail.value, }); }, - hidePickerMinute(){ + hidePickerMinute() { this.setData({ minuteVisible: false, }); }, - onRadioChange(e){ - const { index } = e.currentTarget.dataset; + onRadioChange(e) { + const { + index + } = e.currentTarget.dataset; this.setData({ [`detail[${index}].time`]: e.detail.value }) }, handleSuccess(e) { console.log(e.detail) - const { files } = e.detail; + const { + files + } = e.detail; this.setData({ originFiles: files, }); }, handleRemove(e) { console.log(e.detail.file); - const { index } = e.detail; - const { originFiles } = this.data; + const { + index + } = e.detail; + const { + originFiles + } = this.data; originFiles.splice(index, 1); this.setData({ originFiles, @@ -190,46 +207,51 @@ Page({ console.log(e.detail.file); }, - deleteItem(e){ - const { index } = e.currentTarget.dataset; + deleteItem(e) { + const { + index + } = e.currentTarget.dataset; this.setData({ detail: this.data.detail.filter((item, i) => i !== index) }) }, - onPickerCancel(){ + onPickerCancel() { this.setData({ selectVisible: false, }); }, - async addData(){ - const res = await request('/api/v1/patient/add_therapeutic_regimen','post',{ + async addData() { + const res = await request('/api/v1/patient/add_therapeutic_regimen', 'post', { detail: JSON.stringify(this.data.detail), reminder: JSON.stringify(this.data.reminder), start_date: this.data.start_date, end_date: this.data.end_date - + }) }, - addItem(){ + addItem() { this.setData({ detail: [...this.data.detail, { - "name": "", - "dose": "", - "frequency": "", - "time": "" + "name": "", + "dose": "", + "frequency": "", + "time": "" }] }) }, - onInput(e){ - const { mode, index } = e.currentTarget.dataset; + onInput(e) { + const { + mode, + index + } = e.currentTarget.dataset; this.setData({ [`detail[${index}].${mode}`]: e.detail.value, }) }, - async saveData(){ - if(!this.data.start_date || !this.data.end_date){ + async saveData() { + if (!this.data.start_date || !this.data.end_date) { wx.showToast({ title: '请选择用药周期', icon: 'none' @@ -256,17 +278,17 @@ Page({ }); return; } - - if(this.data.id){ - const res = await request(`patient/medicine_scheme/${this.data.id}`,'put',{ + + if (this.data.id) { + const res = await request(`patient/medicine_scheme/${this.data.id}`, 'put', { detail: JSON.stringify(this.data.detail), reminder: JSON.stringify(this.data.reminder), start_date: this.data.start_date, end_date: this.data.end_date }) wx.navigateBack() - }else{ - const res = await request('patient/medicine_scheme','post',{ + } else { + const res = await request('patient/medicine_scheme', 'post', { detail: JSON.stringify(this.data.detail), reminder: JSON.stringify(this.data.reminder), start_date: this.data.start_date, @@ -279,8 +301,8 @@ Page({ * 生命周期函数--监听页面加载 */ onLoad(options) { - console.log(options) - if(options.id){ + + if (options.id) { this.setData({ id: options.id }) @@ -288,48 +310,48 @@ Page({ } }, - async getData(){ - const obj = JSON.parse(await wx.getStorageSync('therapeuticRegimen')); - this.setData({ - detail: obj.detail, - start_date: obj.start_date, - end_date: obj.end_date, - reminder: JSON.parse(obj.reminder), - }) + async getData() { + const obj = JSON.parse(await wx.getStorageSync('therapeuticRegimen')); + this.setData({ + detail: obj.detail, + start_date: obj.start_date, + end_date: obj.end_date, + reminder: JSON.parse(obj.reminder), + }) }, //上传文件方法 async uploadFileToOSS(file, callback) { - + const policyData = await request('admin/policy_token', 'post') const res = JSON.parse(policyData.token) - const fileNameWithExt = file.tempFilePath.split('/').pop(); // hello.png - const fileName = fileNameWithExt.split('.').slice(0, -1).join('.'); // hello + const fileName = file.tempFilePath.split('/').pop(); // hello.png + // const fileName = fileNameWithExt.split('.').slice(0, -1).join('.'); // hello const formData = { - key: 'upload_file/' + fileName, //上传文件名称 - policy: res.policy, //表单域 - 'x-oss-signature-version': res.x_oss_signature_version, //指定签名的版本和算法 - 'x-oss-credential': res.x_oss_credential, //指明派生密钥的参数集 - 'x-oss-date': res.x_oss_date, //请求的时间 - 'x-oss-signature': res.signature, //签名认证描述信息 - 'x-oss-security-token': res.security_token, //安全令牌 - success_action_status: "200" //上传成功后响应状态码 + key: 'upload_file/' + fileName, //上传文件名称 + policy: res.policy, //表单域 + 'x-oss-signature-version': res.x_oss_signature_version, //指定签名的版本和算法 + 'x-oss-credential': res.x_oss_credential, //指明派生密钥的参数集 + 'x-oss-date': res.x_oss_date, //请求的时间 + 'x-oss-signature': res.signature, //签名认证描述信息 + 'x-oss-security-token': res.security_token, //安全令牌 + success_action_status: "200" //上传成功后响应状态码 }; // console.log(filePath) // return // 发送请求上传文件 wx.uploadFile({ - url: 'https://image-fudan.oss-cn-beijing.aliyuncs.com/', + url: 'https://image-fudan.oss-cn-beijing.aliyuncs.com/', method: 'put', filePath: file.tempFilePath, - name: 'file', //固定值为file + name: 'file', //固定值为file formData: formData, success(res) { console.log('上传响应:', res); if (res.statusCode === 200) { - callback(null, 'https://image-fudan.oss-cn-beijing.aliyuncs.com/upload_file/'+ fileName); // 上传成功 + callback(null, 'https://image-fudan.oss-cn-beijing.aliyuncs.com/upload_file/' + fileName); // 上传成功 } else { console.error('上传失败,状态码:', res.statusCode); console.error('失败响应:', res); @@ -338,11 +360,14 @@ Page({ }, fail(err) { console.error('上传失败:', err); // 输出错误信息 - wx.showToast({ title: '上传失败,请重试!', icon: 'none' }); + wx.showToast({ + title: '上传失败,请重试!', + icon: 'none' + }); callback(err); // 调用回调处理错误 } }); - + }, handleUpload(e) { wx.chooseMedia({ @@ -350,44 +375,91 @@ Page({ mediaType: ['image'], sourceType: ['album', 'camera'], success: (res) => { - wx.showToast({ title: '文件上传中,请稍等!', icon: 'none' }); + wx.showToast({ + title: '文件上传中,请稍等!', + icon: 'none' + }); console.log('选择的文件:', res.tempFiles); // 输出选择的文件信息 if (res.tempFiles.length > 0) { - const tempFilePath = res.tempFiles[0]; + const tempFilePath = res.tempFiles[0]; console.log('选择的文件路径:', tempFilePath); // 输出文件路径 this.uploadFileToOSS(tempFilePath, (error, data) => { if (error) { - wx.showToast({ title: '上传失败!', icon: 'none' }); + wx.showToast({ + title: '上传失败!', + icon: 'none' + }); console.error('上传失败:', error); // 输出具体的错误信息 } else { - wx.showToast({ title: '上传成功!', icon: 'success' }); - + wx.showToast({ title: '上传成功,正在识别内容!', icon: 'none' }); + this.setData({ imageFile: data }) - getOcr(data).then(res => { - + getOcr(data).then(res => { + wx.showToast({ title: '识别完成!', icon: 'none' }) + this.ocrAdditem(res) }) console.log('上传成功:', data); // 输出上传成功后的数据 } }); } else { - wx.showToast({ title: '未选择文件!', icon: 'none' }); + wx.showToast({ + title: '未选择文件!', + icon: 'none' + }); } }, fail: (err) => { - wx.showToast({ title: '选择文件失败!', icon: 'none' }); + wx.showToast({ + title: '选择文件失败!', + icon: 'none' + }); console.error('选择文件失败:', err); // 输出选择文件的错误信息 } }); }, - handleDelete(e){ + + ocrAdditem(data) { + let arr = []; + data.forEach(item => { + if (item.name) { + arr.push({ + "name": item.name, + "dose": item.jiliang, + "frequency": item.cishu.replace("次", ""), + "time": "" + }); + } + }); + if (arr.length === 0) return; + let detail = [...this.data.detail]; + const last = detail[detail.length - 1]; + const hasEmpty = !last.name || !last.dose || !last.frequency || !last.time; + if (hasEmpty) { + // 用 arr[0] 填充最后一条 + detail[detail.length - 1] = arr[0]; + // 剩余的插入末尾 + if (arr.length > 1) { + detail = detail.concat(arr.slice(1)); + } + this.setData({ + detail + }); + } else { + // 全部插入末尾 + this.setData({ + detail: detail.concat(arr) + }); + } + }, + handleDelete(e) { this.setData({ imageFile: '' }); }, - handleImagePreview(e){ - + handleImagePreview(e) { + this.setData({ imageList: [this.data.imageFile], imageIndex: 1, diff --git a/pages/followUp/index.js b/pages/followUp/index.js index 4ca8585..1bd133f 100644 --- a/pages/followUp/index.js +++ b/pages/followUp/index.js @@ -253,8 +253,8 @@ Page({ const policyData = await request('admin/policy_token', 'post') const res = JSON.parse(policyData.token) - const fileNameWithExt = file.tempFilePath.split('/').pop(); // hello.png - const fileName = fileNameWithExt.split('.').slice(0, -1).join('.'); // hello + const fileName = file.tempFilePath.split('/').pop(); // hello.png + // const fileName = fileNameWithExt.split('.').slice(0, -1).join('.'); // hello const formData = { key: 'upload_file/' + fileName, //上传文件名称 @@ -315,19 +315,23 @@ Page({ wx.showToast({ title: '上传失败!', icon: 'none' }); console.error('上传失败:', error); // 输出具体的错误信息 } else { - // getOcr(data) - wx.showToast({ title: '上传成功,正在识别内容!', icon: 'success' }); const { mode } = e.currentTarget.dataset; let arr = this.data.form[mode] arr.unshift(data) this.setData({ [`form.${mode}`]: arr }) - getOcr(data).then(ocrRes => { - console.log(ocrRes) - wx.showToast({ title: '识别完成!', icon: 'success' }) - this.setFormData(ocrRes, mode) - }) + if(mode != 'mdt_image'){ + wx.showToast({ title: '上传成功,正在识别内容!', icon: 'none' }); + getOcr(data).then(ocrRes => { + console.log(ocrRes) + wx.showToast({ title: '识别完成!', icon: 'none' }) + this.setFormData(ocrRes, mode) + }) + } else { + wx.showToast({ title: '上传成功!', icon: 'none' }); + } + console.log('上传成功:', data); // 输出上传成功后的数据 } }); @@ -414,17 +418,17 @@ Page({ ocrs.forEach(ocr => { // 血常规 if (mode == 'blood_routine_image') { - if (ocr.name == '血红蛋白') { + if (ocr.name == '血红蛋白' || ocr.name == '血红蛋白检查') { this.setData({ [`form.hemoglobin`]: ocr.value }) } - if (ocr.name == '血小板') { + if (ocr.name == '血小板' || ocr.name == '血小板计数') { this.setData({ [`form.platelets`]: ocr.value }) } - if (ocr.name == '白细胞') { + if (ocr.name == '白细胞' || ocr.name == '白细胞计数') { this.setData({ [`form.white_blood_cells`]: ocr.value }) diff --git a/pages/mmp-7/index.js b/pages/mmp-7/index.js index 0b7cc74..bff1ad3 100644 --- a/pages/mmp-7/index.js +++ b/pages/mmp-7/index.js @@ -30,7 +30,9 @@ Page({ formKey: '', - + getUserinfo(e) { + console.log(e) + }, //上传文件方法 async uploadFileToOSS(file, callback) { @@ -38,8 +40,8 @@ async uploadFileToOSS(file, callback) { const policyData = await request('admin/policy_token', 'post') const res = JSON.parse(policyData.token) - const fileNameWithExt = file.tempFilePath.split('/').pop(); // hello.png - const fileName = fileNameWithExt.split('.').slice(0, -1).join('.'); // hello + const fileName = file.tempFilePath.split('/').pop(); // hello.png + // const fileName = fileNameWithExt.split('.').slice(0, -1).join('.'); // hello const formData = { key: 'upload_file/' + fileName, //上传文件名称 diff --git a/pages/mmp-7/index.wxml b/pages/mmp-7/index.wxml index b8f273e..483cfdc 100644 --- a/pages/mmp-7/index.wxml +++ b/pages/mmp-7/index.wxml @@ -13,6 +13,7 @@ 手机号 + + + + + MMP-7 + + + diff --git a/pages/my/info-edit/index.js b/pages/my/info-edit/index.js index c3cec8d..bd2b822 100644 --- a/pages/my/info-edit/index.js +++ b/pages/my/info-edit/index.js @@ -364,8 +364,8 @@ Page({ const policyData = await request('admin/policy_token', 'post') const res = JSON.parse(policyData.token) - const fileNameWithExt = file.split('/').pop(); // hello.png - const fileName = fileNameWithExt.split('.').slice(0, -1).join('.'); // hello + const fileName = file.split('/').pop(); // hello.png + // const fileName = fileNameWithExt.split('.').slice(0, -1).join('.'); // hello const formData = { key: 'upload_file/' + fileName, //上传文件名称