139 lines
5.2 KiB
JavaScript
139 lines
5.2 KiB
JavaScript
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": "请只返回提取好的检查项目及其结果,格式为 JSON 数组,每个元素包含 'name' 和 'value' 两个字段。例如:[{\"name\": \"白细胞\", \"value\": \"5.2\"}]。不要返回包含 rotate_rect、text 等字段的原始 OCR 结构化表格数据。"
|
||
}
|
||
]
|
||
}]
|
||
},
|
||
success(res) {
|
||
let data = parseJsonBlock(res.data.choices[0].message.content)
|
||
if(data.length == 0){
|
||
wx.showToast({
|
||
title: '识别失败,请选择带有符合表单字段的照片!',
|
||
icon: 'none',
|
||
duration: 5000
|
||
});
|
||
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: 5000
|
||
});
|
||
reject('识别失败!')
|
||
return
|
||
}
|
||
resolve(items);
|
||
return;
|
||
}
|
||
resolve(data);
|
||
|
||
},
|
||
fail(err) {
|
||
wx.showToast({
|
||
title: '识别失败,请选择带有符合表单字段的照片!',
|
||
icon: 'none',
|
||
duration: 5000
|
||
})
|
||
console.log(err)
|
||
// 断网、服务器挂了都会fail回调,直接reject即可
|
||
reject(err);
|
||
},
|
||
});
|
||
})
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* 解析类似 ```json ... ``` 格式的字符串,提取检测项目数组
|
||
* @param {string} str
|
||
* @returns {Array<Object>}
|
||
*/
|
||
function parseJsonBlock(str) {
|
||
// 匹配 ```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 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);
|
||
}
|
||
});
|
||
|
||
// 3. 每一行找 name/value
|
||
const result = [];
|
||
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;
|
||
} |