This commit is contained in:
左哥 2025-07-27 17:55:11 +08:00
parent bd38cb6cfb
commit 75bc3d6d94
7 changed files with 167 additions and 11 deletions

141
api/BOcr.js Normal file
View File

@ -0,0 +1,141 @@
export const getBOcr = (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) {
console.log(res.data)
return
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);
},
});
})
}
/**
* 解析类似 ```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;
}

View File

@ -31,9 +31,9 @@ export const getOcr = (url) => {
let data = parseJsonBlock(res.data.choices[0].message.content)
if(data.length == 0){
wx.showToast({
title: '识别失败,请重新选择照片!',
title: '识别失败,请选择带有符合表单字段的照片!',
icon: 'none',
duration: 2000
duration: 5000
});
reject('识别失败!')
return
@ -43,9 +43,9 @@ export const getOcr = (url) => {
let items = extractCheckItemsFromOcrTable(data);
if(items.length==0){
wx.showToast({
title: '识别失败,请重新选择照片!',
title: '识别失败,请选择带有符合表单字段的照片!',
icon: 'none',
duration: 2000
duration: 5000
});
reject('识别失败!')
return
@ -58,9 +58,9 @@ export const getOcr = (url) => {
},
fail(err) {
wx.showToast({
title: '识别失败,请重新选择照片!',
title: '识别失败,请选择带有符合表单字段的照片!',
icon: 'none',
duration: 2000
duration: 5000
})
console.log(err)
// 断网、服务器挂了都会fail回调直接reject即可

View File

@ -1,6 +1,7 @@
import request from '~/api/request';
import { getOcr } from '~/api/ocr';
import { getBOcr } from '~/api/BOcr';
Page({
/**

View File

@ -536,7 +536,7 @@
<view class="dark">
<view class="input-example">
<view class="input-example__label">弹性成像最小值kPa</view>
<t-input type="digit" placeholder="请输入" bind:change="onInput" value="{{form.elastography_minimum}}" data-key="elastography_median" borderless="{{true}}" style="{{style}}" />
<t-input type="digit" placeholder="请输入" bind:change="onInput" value="{{form.elastography_minimum}}" data-key="elastography_minimum" borderless="{{true}}" style="{{style}}" />
</view>
</view>
</t-col>

View File

@ -166,6 +166,14 @@ Page({
},
async getPersonalInfo() {
// const token = await wx.getStorageSync('access_token');
// if(!token){
// wx.navigateTo({
// url: '/pages/login/login'
// })
// return
// }
const user_info = await wx.getStorageSync('user_info')
console.log(user_info)
if (user_info) {
@ -178,9 +186,12 @@ Page({
}
this.getChartData()
} else {
wx.navigateTo({
url: '/pages/login/login'
})
const res = await request('patient/basic/0')
await wx.setStorageSync('user_info', res)
this.getChartData()
// wx.navigateTo({
// url: '/pages/login/login'
// })
}
},

View File

@ -276,6 +276,7 @@
// width: 188rpx;
// margin: 8rpx 0 8rpx 0;
margin-top: 4rpx;
white-space: nowrap;
}
.thumbnail_1 {
color: rgba(212,48,48,1);

View File

@ -41,7 +41,9 @@
<view class="text-wrapper_2" wx:if="{{personalInfo.risk_type == 3}}">
<text lines="1" class="text_6">高危</text>
</view>
<!-- <view class="text-wrapper_2" >
<text lines="1" class="text_6">高危</text>
</view> -->
</view>
</view>
<text class="iconfont icon-youjiantou label_4"></text>