This commit is contained in:
@zuopngfei 2025-07-23 16:41:26 +08:00
parent 48f566a560
commit 2e178c78b7
9 changed files with 573 additions and 370 deletions

View File

@ -22,19 +22,46 @@ export const getOcr = (url) => {
}, },
{ {
"type": "text", "type": "text",
"text": "要求准确无误的提取上述关键信息、不要遗漏和捏造虚假信息,模糊或者强光遮挡的单个文字可以用英文问号?代替。返回数据格式以MD方式输出" "text": "要求准确无误的提取图片中的药品名称、每次用量、每日服用次数等信息,模糊或者强光遮挡的单个文字可以用英文问号?代替药品名称的key为'name'每次用量key为'jiliang'每日服用次数的key为'cishu'返回数据格式以JSON数组格式输出不要将多个药品信息放到一个药品名称字段内跟药品无关的信息不要"
} }
] ]
}] }]
}, },
success(res) { success(res) {
let data = parseOcrResult(res.data.choices[0].message.content) let data = parseJsonBlock(res.data.choices[0].message.content)
console.log(data) if(data.length == 0){
wx.showToast({
title: '识别失败,请重新选择照片!',
resolve(data); 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) { fail(err) {
wx.showToast({
title: '识别失败,请重新选择照片!',
icon: 'none',
duration: 2000
})
console.log(err) console.log(err)
// 断网、服务器挂了都会fail回调直接reject即可 // 断网、服务器挂了都会fail回调直接reject即可
reject(err); 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 ... ``` 格式的字符串提取检测项目数组 * 解析类似 ```json ... ``` 格式的字符串提取检测项目数组
@ -76,82 +78,58 @@ function parseMarkdownTable(md) {
* @returns {Array<Object>} * @returns {Array<Object>}
*/ */
function parseJsonBlock(str) { function parseJsonBlock(str) {
// 去除包裹的代码块标记 // 匹配 ```json ... ``` 或 ``` ... ```
const jsonStr = str.replace(/^[\s`]*```json[\s`]*|```$/g, '').replace(/↵/g, '\n').trim(); const match = str.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
let jsonStr = match ? match[1] : str;
// 用正则提取所有 "key": "value" jsonStr = jsonStr.trim();
const regex = /"([^"]+)":\s*"([^"]*)"/g;
const pairs = []; // 尝试直接解析
let match; try {
while ((match = regex.exec(jsonStr)) !== null) { return JSON.parse(jsonStr);
pairs.push([match[1], match[2]]); } catch (e) {
} // 替换单引号包裹的 key 和 value 为双引号
let fixedStr = jsonStr
// 按“序号”分组 // 替换 key 的单引号(允许有空格、括号等)
const items = []; .replace(/'([\w\u4e00-\u9fa5\(\)\s]+)'(?=\s*:)/g, '"$1"')
let current = {}; // 替换 value 的单引号(允许有空格、括号等,非贪婪匹配)
const itemFields = ['序号', '项目名称', '缩写', '结果', '单位', '参考区间', '测定方法']; .replace(/:\s*'([^']*?)'/g, ': "$1"');
pairs.forEach(([key, value]) => { try {
if (key === '序号' && Object.keys(current).length > 0) { return JSON.parse(fixedStr);
items.push({ ...current });
current = {}; } catch (e2) {
} console.error('JSON parse error:', e2, fixedStr);
if (itemFields.includes(key)) { return [];
current[key] = value; }
} }
});
if (Object.keys(current).length > 0) {
items.push({ ...current });
}
return items;
} }
function extractDrugsFromOcr(ocrArray) {
// 1. 合并所有 text 字段
const lines = ocrArray.map(item => item.text.trim()).filter(Boolean);
/** // 2. 分组药品以“1、”“2、”等开头
* 自动判断OCR返回内容格式并调用对应解析方法 const drugGroups = [];
* @param {string} content let currentDrug = null;
* @returns {Array<Object>}
*/
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 => { lines.forEach(line => {
// 尝试用正则提取 // 判断是否为新药品的开始
const match = line.match(/^(\d+)([\u4e00-\u9fa5A-Za-z]+)([\d.]+)([a-zA-Zμ\/]+)?([\d.\-]+)?/); if (/^\d+、/.test(line)) {
if (match) { if (currentDrug) drugGroups.push(currentDrug);
result.push({ currentDrug = { name: line.replace(/^\d+、/, '').trim(), jiliang: '', cishu: '' };
index: Number(match[1]), } else if (currentDrug) {
name: match[2], // 判断是否为用量
value: Number(match[3]), if (/每次|mg|片|粒|ml|g|单位/.test(line) && !currentDrug.jiliang) {
unit: match[4] || '', currentDrug.jiliang = line;
reference: match[5] || '' }
}); // 判断是否为次数
else if (/每日|每天|次|早|晚|中/.test(line) && !currentDrug.cishu) {
currentDrug.cishu = line;
}
// 其他信息可根据需要扩展
} }
}); });
return result; if (currentDrug) drugGroups.push(currentDrug);
// 过滤掉无效项
return drugGroups.filter(d => d.name);
} }

225
api/ocr copy.js Normal file
View File

@ -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<Object>}
*/
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<Object>}
*/
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;
}

View File

@ -22,87 +22,46 @@ export const getOcr = (url) => {
}, },
{ {
"type": "text", "type": "text",
"text": "要求准确无误的提取上述关键信息、不要遗漏和捏造虚假信息,模糊或者强光遮挡的单个文字可以用英文问号?代替。返回数据格式以MD方式输出" "text": "请只返回提取好的检查项目及其结果,格式为 JSON 数组,每个元素包含 'name' 和 'value' 两个字段。例如:[{\"name\": \"白细胞\", \"value\": \"5.2\"}]。不要返回包含 rotate_rect、text 等字段的原始 OCR 结构化表格数据。"
} }
] ]
}] }]
}, },
success(res) { success(res) {
let data = parseOcrResult(res.data.choices[0].message.content) let data = parseJsonBlock(res.data.choices[0].message.content)
// 新增:统一字段名 if(data.length == 0){
if (Array.isArray(data)) { wx.showToast({
data = data.map(item => { title: '识别失败,请重新选择照片!',
const newItem = { ...item }; icon: 'none',
if ('项目' in newItem) { duration: 2000
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;
}); });
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); resolve(data);
}, },
fail(err) { fail(err) {
wx.showToast({
title: '识别失败,请重新选择照片!',
icon: 'none',
duration: 2000
})
console.log(err) console.log(err)
// 断网、服务器挂了都会fail回调直接reject即可 // 断网、服务器挂了都会fail回调直接reject即可
reject(err); 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 ... ``` 格式的字符串提取检测项目数组 * 解析类似 ```json ... ``` 格式的字符串提取检测项目数组
@ -144,82 +78,62 @@ function parseMarkdownTable(md) {
* @returns {Array<Object>} * @returns {Array<Object>}
*/ */
function parseJsonBlock(str) { function parseJsonBlock(str) {
// 去除包裹的代码块标记 // 匹配 ```json ... ``` 或 ``` ... ```
const jsonStr = str.replace(/^[\s`]*```json[\s`]*|```$/g, '').replace(/↵/g, '\n').trim(); 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]]);
}
// 按“序号”分组 function extractCheckItemsFromOcrTable(ocrArray) {
const items = []; // 1. 找到表头的 x 坐标
let current = {}; const nameHeader = ocrArray.find(item => item.text.includes('检验项目'));
const itemFields = ['序号', '项目名称', '缩写', '结果', '单位', '参考区间', '测定方法']; const valueHeader = ocrArray.find(item => item.text.includes('结果'));
pairs.forEach(([key, value]) => { if (!nameHeader || !valueHeader) return [];
if (key === '序号' && Object.keys(current).length > 0) {
items.push({ ...current }); const nameX = nameHeader.rotate_rect[0];
current = {}; const valueX = valueHeader.rotate_rect[0];
}
if (itemFields.includes(key)) { // 2. 过滤掉表头,按 y 坐标分组(每一行)
current[key] = value; 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;
}
/** // 3. 每一行找 name/value
* 自动判断OCR返回内容格式并调用对应解析方法
* @param {string} content
* @returns {Array<Object>}
*/
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 result = [];
const regex = /^(\d+)([\u4e00-\u9fa5A-Za-z]+)([\d.]+)([a-zA-Zμ\/]+)?([\d.\-]+)?/; Object.values(rows).forEach(items => {
lines.forEach(line => { let name = null, value = null;
// 尝试用正则提取 items.forEach(item => {
const match = line.match(/^(\d+)([\u4e00-\u9fa5A-Za-z]+)([\d.]+)([a-zA-Zμ\/]+)?([\d.\-]+)?/); if (Math.abs(item.rotate_rect[0] - nameX) < 50) name = item.text.trim();
if (match) { if (Math.abs(item.rotate_rect[0] - valueX) < 50) value = item.text.trim();
result.push({ });
index: Number(match[1]), if (name && value) result.push({ name, value });
name: match[2],
value: Number(match[3]),
unit: match[4] || '',
reference: match[5] || ''
});
}
}); });
return result; return result;
} }

View File

@ -1,5 +1,7 @@
import request from '~/api/request'; import request from '~/api/request';
import { getOcr } from '~/api/drugOcr'; import {
getOcr
} from '~/api/drugOcr';
let modeType = ''; let modeType = '';
let mode2 = ''; let mode2 = '';
@ -18,31 +20,31 @@ Page({
"dose": "", "dose": "",
"frequency": "", "frequency": "",
"time": "饭前", "time": "饭前",
}], }],
reminder: { reminder: {
"morning": "08:00", "morning": "08:00",
"noon": "12:00", "noon": "12:00",
"evening": "18:00" "evening": "18:00"
}, },
start_date: '', start_date: '',
end_date: '', end_date: '',
// 下拉 // 下拉
selectList: [], selectList: [],
selectValue: '', selectValue: '',
selectVisible: false, selectVisible: false,
timeData: '', timeData: '',
id: '', id: '',
style: 'border: 0;', style: 'border: 0;',
frequencyList:[{ frequencyList: [{
label: '每日1次', label: '每日1次',
value: '1' value: '1'
},{ }, {
label: '每日2次', label: '每日2次',
value: '2' value: '2'
},{ }, {
label: '每日3次', label: '每日3次',
value: '3' value: '3'
}], }],
@ -52,7 +54,7 @@ Page({
minuteVisible: false, minuteVisible: false,
date: new Date('2021-12-23').getTime(), // 支持时间戳传入 date: new Date('2021-12-23').getTime(), // 支持时间戳传入
dateText: '', dateText: '',
birthStart: new Date().getTime(), birthStart: new Date().getTime(),
filter(type, options) { filter(type, options) {
if (type === 'year') { if (type === 'year') {
return options.sort((a, b) => b.value - a.value); return options.sort((a, b) => b.value - a.value);
@ -103,7 +105,7 @@ Page({
}; };
}, },
gridConfig: { gridConfig: {
column: 4, column: 4,
width: 160, width: 160,
@ -115,37 +117,44 @@ Page({
}, },
showSelect(e){ showSelect(e) {
const { mode, index } = e.currentTarget.dataset; const {
mode,
index
} = e.currentTarget.dataset;
modeText = mode modeText = mode
modeIndex = index modeIndex = index
this.setData({ this.setData({
selectValue: [this.data.detail[index].frequency], selectValue: [this.data.detail[index].frequency],
selectVisible: true, selectVisible: true,
}) })
}, },
onSelectChange(e){ onSelectChange(e) {
this.setData({ this.setData({
[`detail[${modeIndex}].frequency`]: e.detail.value[0] [`detail[${modeIndex}].frequency`]: e.detail.value[0]
}) })
}, },
showPicker(e) { showPicker(e) {
const { mode } = e.currentTarget.dataset; const {
mode
} = e.currentTarget.dataset;
modeType = mode; modeType = mode;
this.setData({ this.setData({
birthVisible: true, birthVisible: true,
timeData: this.data[mode], timeData: this.data[mode],
}); });
}, },
onPickerChange(e){ onPickerChange(e) {
this.setData({ this.setData({
[modeType]: e.detail.value, [modeType]: e.detail.value,
}); });
}, },
showPickertime(e){ showPickertime(e) {
const { mode } = e.currentTarget.dataset; const {
mode
} = e.currentTarget.dataset;
mode2 = mode; mode2 = mode;
this.setData({ this.setData({
minuteVisible: true, minuteVisible: true,
@ -153,34 +162,42 @@ Page({
minute: this.data.reminder[mode] minute: this.data.reminder[mode]
}); });
}, },
onConfirm(e){ onConfirm(e) {
this.setData({ this.setData({
minuteVisible: false, minuteVisible: false,
[`reminder.${mode2}`]: e.detail.value, [`reminder.${mode2}`]: e.detail.value,
}); });
}, },
hidePickerMinute(){ hidePickerMinute() {
this.setData({ this.setData({
minuteVisible: false, minuteVisible: false,
}); });
}, },
onRadioChange(e){ onRadioChange(e) {
const { index } = e.currentTarget.dataset; const {
index
} = e.currentTarget.dataset;
this.setData({ this.setData({
[`detail[${index}].time`]: e.detail.value [`detail[${index}].time`]: e.detail.value
}) })
}, },
handleSuccess(e) { handleSuccess(e) {
console.log(e.detail) console.log(e.detail)
const { files } = e.detail; const {
files
} = e.detail;
this.setData({ this.setData({
originFiles: files, originFiles: files,
}); });
}, },
handleRemove(e) { handleRemove(e) {
console.log(e.detail.file); console.log(e.detail.file);
const { index } = e.detail; const {
const { originFiles } = this.data; index
} = e.detail;
const {
originFiles
} = this.data;
originFiles.splice(index, 1); originFiles.splice(index, 1);
this.setData({ this.setData({
originFiles, originFiles,
@ -190,46 +207,51 @@ Page({
console.log(e.detail.file); console.log(e.detail.file);
}, },
deleteItem(e){ deleteItem(e) {
const { index } = e.currentTarget.dataset; const {
index
} = e.currentTarget.dataset;
this.setData({ this.setData({
detail: this.data.detail.filter((item, i) => i !== index) detail: this.data.detail.filter((item, i) => i !== index)
}) })
}, },
onPickerCancel(){ onPickerCancel() {
this.setData({ this.setData({
selectVisible: false, selectVisible: false,
}); });
}, },
async addData(){ async addData() {
const res = await request('/api/v1/patient/add_therapeutic_regimen','post',{ const res = await request('/api/v1/patient/add_therapeutic_regimen', 'post', {
detail: JSON.stringify(this.data.detail), detail: JSON.stringify(this.data.detail),
reminder: JSON.stringify(this.data.reminder), reminder: JSON.stringify(this.data.reminder),
start_date: this.data.start_date, start_date: this.data.start_date,
end_date: this.data.end_date end_date: this.data.end_date
}) })
}, },
addItem(){ addItem() {
this.setData({ this.setData({
detail: [...this.data.detail, { detail: [...this.data.detail, {
"name": "", "name": "",
"dose": "", "dose": "",
"frequency": "", "frequency": "",
"time": "" "time": ""
}] }]
}) })
}, },
onInput(e){ onInput(e) {
const { mode, index } = e.currentTarget.dataset; const {
mode,
index
} = e.currentTarget.dataset;
this.setData({ this.setData({
[`detail[${index}].${mode}`]: e.detail.value, [`detail[${index}].${mode}`]: e.detail.value,
}) })
}, },
async saveData(){ async saveData() {
if(!this.data.start_date || !this.data.end_date){ if (!this.data.start_date || !this.data.end_date) {
wx.showToast({ wx.showToast({
title: '请选择用药周期', title: '请选择用药周期',
icon: 'none' icon: 'none'
@ -256,17 +278,17 @@ Page({
}); });
return; return;
} }
if(this.data.id){ if (this.data.id) {
const res = await request(`patient/medicine_scheme/${this.data.id}`,'put',{ const res = await request(`patient/medicine_scheme/${this.data.id}`, 'put', {
detail: JSON.stringify(this.data.detail), detail: JSON.stringify(this.data.detail),
reminder: JSON.stringify(this.data.reminder), reminder: JSON.stringify(this.data.reminder),
start_date: this.data.start_date, start_date: this.data.start_date,
end_date: this.data.end_date end_date: this.data.end_date
}) })
wx.navigateBack() wx.navigateBack()
}else{ } else {
const res = await request('patient/medicine_scheme','post',{ const res = await request('patient/medicine_scheme', 'post', {
detail: JSON.stringify(this.data.detail), detail: JSON.stringify(this.data.detail),
reminder: JSON.stringify(this.data.reminder), reminder: JSON.stringify(this.data.reminder),
start_date: this.data.start_date, start_date: this.data.start_date,
@ -279,8 +301,8 @@ Page({
* 生命周期函数--监听页面加载 * 生命周期函数--监听页面加载
*/ */
onLoad(options) { onLoad(options) {
console.log(options)
if(options.id){ if (options.id) {
this.setData({ this.setData({
id: options.id id: options.id
}) })
@ -288,48 +310,48 @@ Page({
} }
}, },
async getData(){ async getData() {
const obj = JSON.parse(await wx.getStorageSync('therapeuticRegimen')); const obj = JSON.parse(await wx.getStorageSync('therapeuticRegimen'));
this.setData({ this.setData({
detail: obj.detail, detail: obj.detail,
start_date: obj.start_date, start_date: obj.start_date,
end_date: obj.end_date, end_date: obj.end_date,
reminder: JSON.parse(obj.reminder), reminder: JSON.parse(obj.reminder),
}) })
}, },
//上传文件方法 //上传文件方法
async uploadFileToOSS(file, callback) { async uploadFileToOSS(file, callback) {
const policyData = await request('admin/policy_token', 'post') const policyData = await request('admin/policy_token', 'post')
const res = JSON.parse(policyData.token) const res = JSON.parse(policyData.token)
const fileNameWithExt = file.tempFilePath.split('/').pop(); // hello.png const fileName = file.tempFilePath.split('/').pop(); // hello.png
const fileName = fileNameWithExt.split('.').slice(0, -1).join('.'); // hello // const fileName = fileNameWithExt.split('.').slice(0, -1).join('.'); // hello
const formData = { const formData = {
key: 'upload_file/' + fileName, //上传文件名称 key: 'upload_file/' + fileName, //上传文件名称
policy: res.policy, //表单域 policy: res.policy, //表单域
'x-oss-signature-version': res.x_oss_signature_version, //指定签名的版本和算法 'x-oss-signature-version': res.x_oss_signature_version, //指定签名的版本和算法
'x-oss-credential': res.x_oss_credential, //指明派生密钥的参数集 'x-oss-credential': res.x_oss_credential, //指明派生密钥的参数集
'x-oss-date': res.x_oss_date, //请求的时间 'x-oss-date': res.x_oss_date, //请求的时间
'x-oss-signature': res.signature, //签名认证描述信息 'x-oss-signature': res.signature, //签名认证描述信息
'x-oss-security-token': res.security_token, //安全令牌 'x-oss-security-token': res.security_token, //安全令牌
success_action_status: "200" //上传成功后响应状态码 success_action_status: "200" //上传成功后响应状态码
}; };
// console.log(filePath) // console.log(filePath)
// return // return
// 发送请求上传文件 // 发送请求上传文件
wx.uploadFile({ wx.uploadFile({
url: 'https://image-fudan.oss-cn-beijing.aliyuncs.com/', url: 'https://image-fudan.oss-cn-beijing.aliyuncs.com/',
method: 'put', method: 'put',
filePath: file.tempFilePath, filePath: file.tempFilePath,
name: 'file', //固定值为file name: 'file', //固定值为file
formData: formData, formData: formData,
success(res) { success(res) {
console.log('上传响应:', res); console.log('上传响应:', res);
if (res.statusCode === 200) { 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 { } else {
console.error('上传失败,状态码:', res.statusCode); console.error('上传失败,状态码:', res.statusCode);
console.error('失败响应:', res); console.error('失败响应:', res);
@ -338,11 +360,14 @@ Page({
}, },
fail(err) { fail(err) {
console.error('上传失败:', err); // 输出错误信息 console.error('上传失败:', err); // 输出错误信息
wx.showToast({ title: '上传失败,请重试!', icon: 'none' }); wx.showToast({
title: '上传失败,请重试!',
icon: 'none'
});
callback(err); // 调用回调处理错误 callback(err); // 调用回调处理错误
} }
}); });
}, },
handleUpload(e) { handleUpload(e) {
wx.chooseMedia({ wx.chooseMedia({
@ -350,44 +375,91 @@ Page({
mediaType: ['image'], mediaType: ['image'],
sourceType: ['album', 'camera'], sourceType: ['album', 'camera'],
success: (res) => { success: (res) => {
wx.showToast({ title: '文件上传中,请稍等!', icon: 'none' }); wx.showToast({
title: '文件上传中,请稍等!',
icon: 'none'
});
console.log('选择的文件:', res.tempFiles); // 输出选择的文件信息 console.log('选择的文件:', res.tempFiles); // 输出选择的文件信息
if (res.tempFiles.length > 0) { if (res.tempFiles.length > 0) {
const tempFilePath = res.tempFiles[0]; const tempFilePath = res.tempFiles[0];
console.log('选择的文件路径:', tempFilePath); // 输出文件路径 console.log('选择的文件路径:', tempFilePath); // 输出文件路径
this.uploadFileToOSS(tempFilePath, (error, data) => { this.uploadFileToOSS(tempFilePath, (error, data) => {
if (error) { if (error) {
wx.showToast({ title: '上传失败!', icon: 'none' }); wx.showToast({
title: '上传失败!',
icon: 'none'
});
console.error('上传失败:', error); // 输出具体的错误信息 console.error('上传失败:', error); // 输出具体的错误信息
} else { } else {
wx.showToast({ title: '上传成功!', icon: 'success' }); wx.showToast({ title: '上传成功,正在识别内容!', icon: 'none' });
this.setData({ this.setData({
imageFile: data imageFile: data
}) })
getOcr(data).then(res => { getOcr(data).then(res => {
wx.showToast({ title: '识别完成!', icon: 'none' })
this.ocrAdditem(res)
}) })
console.log('上传成功:', data); // 输出上传成功后的数据 console.log('上传成功:', data); // 输出上传成功后的数据
} }
}); });
} else { } else {
wx.showToast({ title: '未选择文件!', icon: 'none' }); wx.showToast({
title: '未选择文件!',
icon: 'none'
});
} }
}, },
fail: (err) => { fail: (err) => {
wx.showToast({ title: '选择文件失败!', icon: 'none' }); wx.showToast({
title: '选择文件失败!',
icon: 'none'
});
console.error('选择文件失败:', err); // 输出选择文件的错误信息 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({ this.setData({
imageFile: '' imageFile: ''
}); });
}, },
handleImagePreview(e){ handleImagePreview(e) {
this.setData({ this.setData({
imageList: [this.data.imageFile], imageList: [this.data.imageFile],
imageIndex: 1, imageIndex: 1,

View File

@ -253,8 +253,8 @@ Page({
const policyData = await request('admin/policy_token', 'post') const policyData = await request('admin/policy_token', 'post')
const res = JSON.parse(policyData.token) const res = JSON.parse(policyData.token)
const fileNameWithExt = file.tempFilePath.split('/').pop(); // hello.png const fileName = file.tempFilePath.split('/').pop(); // hello.png
const fileName = fileNameWithExt.split('.').slice(0, -1).join('.'); // hello // const fileName = fileNameWithExt.split('.').slice(0, -1).join('.'); // hello
const formData = { const formData = {
key: 'upload_file/' + fileName, //上传文件名称 key: 'upload_file/' + fileName, //上传文件名称
@ -315,19 +315,23 @@ Page({
wx.showToast({ title: '上传失败!', icon: 'none' }); wx.showToast({ title: '上传失败!', icon: 'none' });
console.error('上传失败:', error); // 输出具体的错误信息 console.error('上传失败:', error); // 输出具体的错误信息
} else { } else {
// getOcr(data)
wx.showToast({ title: '上传成功,正在识别内容!', icon: 'success' });
const { mode } = e.currentTarget.dataset; const { mode } = e.currentTarget.dataset;
let arr = this.data.form[mode] let arr = this.data.form[mode]
arr.unshift(data) arr.unshift(data)
this.setData({ this.setData({
[`form.${mode}`]: arr [`form.${mode}`]: arr
}) })
getOcr(data).then(ocrRes => { if(mode != 'mdt_image'){
console.log(ocrRes) wx.showToast({ title: '上传成功,正在识别内容!', icon: 'none' });
wx.showToast({ title: '识别完成!', icon: 'success' }) getOcr(data).then(ocrRes => {
this.setFormData(ocrRes, mode) console.log(ocrRes)
}) wx.showToast({ title: '识别完成!', icon: 'none' })
this.setFormData(ocrRes, mode)
})
} else {
wx.showToast({ title: '上传成功!', icon: 'none' });
}
console.log('上传成功:', data); // 输出上传成功后的数据 console.log('上传成功:', data); // 输出上传成功后的数据
} }
}); });
@ -414,17 +418,17 @@ Page({
ocrs.forEach(ocr => { ocrs.forEach(ocr => {
// 血常规 // 血常规
if (mode == 'blood_routine_image') { if (mode == 'blood_routine_image') {
if (ocr.name == '血红蛋白') { if (ocr.name == '血红蛋白' || ocr.name == '血红蛋白检查') {
this.setData({ this.setData({
[`form.hemoglobin`]: ocr.value [`form.hemoglobin`]: ocr.value
}) })
} }
if (ocr.name == '血小板') { if (ocr.name == '血小板' || ocr.name == '血小板计数') {
this.setData({ this.setData({
[`form.platelets`]: ocr.value [`form.platelets`]: ocr.value
}) })
} }
if (ocr.name == '白细胞') { if (ocr.name == '白细胞' || ocr.name == '白细胞计数') {
this.setData({ this.setData({
[`form.white_blood_cells`]: ocr.value [`form.white_blood_cells`]: ocr.value
}) })

View File

@ -30,7 +30,9 @@ Page({
formKey: '', formKey: '',
getUserinfo(e) {
console.log(e)
},
//上传文件方法 //上传文件方法
async uploadFileToOSS(file, callback) { async uploadFileToOSS(file, callback) {
@ -38,8 +40,8 @@ async uploadFileToOSS(file, callback) {
const policyData = await request('admin/policy_token', 'post') const policyData = await request('admin/policy_token', 'post')
const res = JSON.parse(policyData.token) const res = JSON.parse(policyData.token)
const fileNameWithExt = file.tempFilePath.split('/').pop(); // hello.png const fileName = file.tempFilePath.split('/').pop(); // hello.png
const fileName = fileNameWithExt.split('.').slice(0, -1).join('.'); // hello // const fileName = fileNameWithExt.split('.').slice(0, -1).join('.'); // hello
const formData = { const formData = {
key: 'upload_file/' + fileName, //上传文件名称 key: 'upload_file/' + fileName, //上传文件名称

View File

@ -13,6 +13,7 @@
<t-input bindchange="onInput" data-mode="mobile" placeholder="请输入手机号" align="right" type="number" value="{{form.mobile}}" status="{{isMobile ? '' : 'error'}}" tips="{{isMobile ? '' : '请输入手机号'}}"> <t-input bindchange="onInput" data-mode="mobile" placeholder="请输入手机号" align="right" type="number" value="{{form.mobile}}" status="{{isMobile ? '' : 'error'}}" tips="{{isMobile ? '' : '请输入手机号'}}">
<view slot="label" class="custom-label">手机号</view> <view slot="label" class="custom-label">手机号</view>
</t-input> </t-input>
<!-- <button open-type="getUserInfo" bindgetuserinfo="getUserinfo">用户信息</button> -->
</view> </view>
<view class="follow-item"> <view class="follow-item">
<view class="custom-label">胆囊</view> <view class="custom-label">胆囊</view>

View File

@ -76,6 +76,13 @@
</view> </view>
<text class="iconfont icon-youjiantou thumbnail_3"></text> <text class="iconfont icon-youjiantou thumbnail_3"></text>
</view> </view>
<view class="block_7" data-url="/pages/mmp-7/index" bind:tap="toPath">
<view class="image-text_6">
<image src="https://image-fudan.oss-cn-beijing.aliyuncs.com/mini_images/my/jkjy.svg" class="thumbnail_4"></image>
<text lines="1" class="text-group_7">MMP-7</text>
</view>
<text class="iconfont icon-youjiantou thumbnail_3"></text>
</view>
</view> </view>
</view> </view>
</view> </view>

View File

@ -364,8 +364,8 @@ Page({
const policyData = await request('admin/policy_token', 'post') const policyData = await request('admin/policy_token', 'post')
const res = JSON.parse(policyData.token) const res = JSON.parse(policyData.token)
const fileNameWithExt = file.split('/').pop(); // hello.png const fileName = file.split('/').pop(); // hello.png
const fileName = fileNameWithExt.split('.').slice(0, -1).join('.'); // hello // const fileName = fileNameWithExt.split('.').slice(0, -1).join('.'); // hello
const formData = { const formData = {
key: 'upload_file/' + fileName, //上传文件名称 key: 'upload_file/' + fileName, //上传文件名称