This commit is contained in:
左哥 2025-07-21 22:24:05 +08:00
parent 49c0136bff
commit 8ef6a6693e
4 changed files with 203 additions and 56 deletions

View File

@ -28,7 +28,7 @@ export const getOcr = (url) => {
}]
},
success(res) {
const data = parseMarkdownTable(res.data.choices[0].message.content)
const data = parseOcrResult(res.data.choices[0].message.content)
resolve(data);
},
fail(err) {
@ -63,3 +63,57 @@ function parseMarkdownTable(md) {
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);
}
// 其它情况返回空数组或原始内容
return [];
}

View File

@ -13,7 +13,7 @@ Page({
form: {
follow_name: '',
liver_function_image:[],
liver_function_image: [],
b_mode_image: [],
blood_routine_image: [],
coagulation_function_image: [],
@ -126,7 +126,7 @@ Page({
onInput(e) {
const { key, tips } = e.currentTarget.dataset;
if(e.detail.value){
if (e.detail.value) {
this.setData({
[tips]: true,
[`form.${key}`]: e.detail.value
@ -140,9 +140,9 @@ Page({
},
onInputTime(e){
onInputTime(e) {
const { tips } = e.currentTarget.dataset;
if(e.detail.value){
if (e.detail.value) {
this.setData({
[tips]: true,
});
@ -162,7 +162,7 @@ Page({
},
onPickerChange(e) {
if(e.detail.value){
if (e.detail.value) {
this.setData({
isTime: true,
['form.follow_date']: e.detail.value
@ -193,32 +193,32 @@ Page({
console.log(e.detail.file);
},
planId:'',
planId: '',
questionnaire_id: '',
async toQuestionnaire() {
let valid = true;
// 必填项校验
if(!this.data.form.follow_name){
if (!this.data.form.follow_name) {
this.setData({ isName: false });
valid = false;
}
if(!this.data.form.follow_date){
if (!this.data.form.follow_date) {
this.setData({ isTime: false });
valid = false;
}
if(!this.data.form.follow_hospital){
if (!this.data.form.follow_hospital) {
this.setData({ isDoctor: false });
valid = false;
}
if(!this.data.form.height){
if (!this.data.form.height) {
this.setData({ isHeight: false });
valid = false;
}
if(!this.data.form.weight){
if (!this.data.form.weight) {
this.setData({ isWeight: false });
valid = false;
}
if(!valid) return
if (!valid) return
const data = this.data.form
data.liver_function_image = data.liver_function_image.length > 0 ? data.liver_function_image.join(',') : ''
data.b_mode_image = data.b_mode_image.length > 0 ? data.b_mode_image.join(',') : '',
@ -228,7 +228,7 @@ Page({
data.nutritional_indicator_image = data.nutritional_indicator_image.length > 0 ? data.nutritional_indicator_image.join(',') : ''
data.is_have_cyst = data.is_have_cyst ? Number(data.is_have_cyst) : 0
data.is_have_ascites = data.is_have_ascites ? Number(data.is_have_ascites) : 0
await request('patient/follow_questionnaire', 'post',{plan_id: Number(this.planId), questionnaire_id: Number(this.questionnaire_id), ...data})
await request('patient/follow_questionnaire', 'post', { plan_id: Number(this.planId), questionnaire_id: Number(this.questionnaire_id), ...data })
wx.showToast({
title: '提交成功',
icon: 'success',
@ -278,7 +278,7 @@ Page({
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);
@ -295,7 +295,7 @@ Page({
},
handleUpload(e) {
const { mode } = e.currentTarget.dataset;
if(this.data.form[mode].length >= 9){
if (this.data.form[mode].length >= 9) {
wx.showToast({ title: '最多上传9张图片!', icon: 'none' });
return
}
@ -316,14 +316,18 @@ Page({
console.error('上传失败:', error); // 输出具体的错误信息
} else {
// getOcr(data)
wx.showToast({ title: '上传成功!', icon: 'success' });
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)
getOcr(data).then(ocrRes => {
console.log(ocrRes)
wx.showToast({ title: '识别完成!', icon: 'success' })
this.setFormData(ocrRes, mode)
})
console.log('上传成功:', data); // 输出上传成功后的数据
}
});
@ -338,8 +342,8 @@ Page({
});
},
imageKey:'',
handleImagePreview(e){
imageKey: '',
handleImagePreview(e) {
const { mode, index } = e.currentTarget.dataset;
this.imageKey = mode
console.log(this.data.form[mode])
@ -349,12 +353,12 @@ Page({
imageVisible: true
})
},
onClose(){
onClose() {
this.setData({
imageVisible: false
})
},
handleDelete(e){
handleDelete(e) {
const { mode, index } = e.currentTarget.dataset;
let arr = this.data.form[mode]
arr.splice(index, 1)
@ -363,7 +367,7 @@ Page({
})
},
onDelete(e){
onDelete(e) {
console.log(e)
let arr = this.data.form[this.imageKey]
arr.splice(e.detail.index, 1)
@ -388,13 +392,13 @@ Page({
['form.follow_date']: options.time
})
console.log(options)
if(options.questionnaire_id != 0){
if (options.questionnaire_id != 0) {
this.getDetail()
}
},
async getDetail(){
const res = await request('patient/questionnaire_info', 'post',{patient_id : 0, questionnaire_id: Number(this.questionnaire_id)})
async getDetail() {
const res = await request('patient/questionnaire_info', 'post', { patient_id: 0, questionnaire_id: Number(this.questionnaire_id) })
const data = res
data.liver_function_image = data.liver_function_image ? data.liver_function_image.split(',') : []
data.b_mode_image = data.b_mode_image ? data.b_mode_image.split(',') : []
@ -407,6 +411,71 @@ Page({
form: data
})
},
// 遍历识别结果,填充字段
setFormData(ocrs, mode) {
console.log(ocrs, mode)
ocrs.forEach(ocr => {
console.log(ocr['项目名称'], ocr['结果'])
// 血常规
if (mode == 'blood_routine_image') {
if (ocr['项目名称'] == '血红蛋白') {
this.setData({
[`form.hemoglobin`]: ocr['结果']
})
}
}
// 凝血功能
if (mode == 'coagulation_function_image') {
if (ocr['项目名称'] == '凝血酶原时间') {
this.setData({
[`form.pt`]: ocr['结果']
})
}
if (ocr['项目名称'] == '凝血酶原活动度') {
this.setData({
[`form.pta`]: ocr['结果']
})
}
if (ocr['项目名称'] == '国际标准化比值') {
this.setData({
[`form.inr`]: ocr['结果']
})
}
if (ocr['项目名称'] == '活化部分凝血活酶时间') {
this.setData({
[`form.aptt`]: ocr['结果']
})
}
if (ocr['项目名称'] == '凝血酶时间') {
this.setData({
[`form.tt`]: ocr['结果']
})
}
if (ocr['项目名称'] == '纤维蛋白原') {
this.setData({
[`form.fib`]: ocr['结果']
})
}
}
// 肝功能
if (mode == 'liver_function_image') {
}
// 营养指数
if (mode == 'nutritional_indicator_image') {
}
// B超
if (mode == 'b_mode_image') {
}
})
},
/**
* 生命周期函数--监听页面初次渲染完成
*/

View File

@ -91,7 +91,7 @@
<t-col span="12">
<view class="dark">
<view class="input-example">
<view class="input-example__label">总胆红素µmol/L)</view>
<view class="input-example__label">总胆红素µmol/L</view>
<t-input placeholder="请输入" size="small" bind:change="onInput" value="{{form.total_bilirubin}}" data-key="total_bilirubin" borderless="{{true}}" style="{{style}}"
type="digit" />
</view>
@ -100,7 +100,7 @@
<t-col span="12">
<view class="dark">
<view class="input-example">
<view class="input-example__label">直接胆红素(µmol/L)</view>
<view class="input-example__label">直接胆红素µmol/L</view>
<t-input placeholder="请输入" bind:change="onInput" value="{{form.direct_bilirubin}}" data-key="direct_bilirubin" borderless="{{true}}" style="{{style}}"
type="digit" />
</view>
@ -354,7 +354,7 @@
<t-col span="12">
<view class="dark">
<view class="input-example">
<view class="input-example__label">25OHD3 (ng/ml)</view>
<view class="input-example__label">25OHD3ng/ml</view>
<t-input type="digit" placeholder="请输入" bind:change="onInput" value="{{form.oh_d3}}" data-key="oh_d3" size="small" borderless="{{true}}" style="{{style}}" />
</view>
</view>
@ -362,7 +362,7 @@
<t-col span="12">
<view class="dark">
<view class="input-example">
<view class="input-example__label">25OHD2 (ng/ml)</view>
<view class="input-example__label">25OHD2ng/ml</view>
<t-input type="digit" placeholder="请输入" bind:change="onInput" value="{{form.oh_d2}}" data-key="oh_d2" size="small" borderless="{{true}}" style="{{style}}" />
</view>
</view>
@ -370,7 +370,7 @@
<t-col span="12">
<view class="dark">
<view class="input-example">
<view class="input-example__label">25OHD (ng/ml)</view>
<view class="input-example__label">25OHDng/ml</view>
<t-input type="digit" placeholder="请输入" bind:change="onInput" value="{{form.oh_d}}" data-key="oh_d" size="small" borderless="{{true}}" style="{{style}}" />
</view>
</view>
@ -378,7 +378,7 @@
<t-col span="12">
<view class="dark">
<view class="input-example">
<view class="input-example__label">维生素A (ng/ml)</view>
<view class="input-example__label">维生素Ang/ml</view>
<t-input type="digit" placeholder="请输入" bind:change="onInput" value="{{form.vitamin_a}}" data-key="vitamin_a" borderless="{{true}}" style="{{style}}" />
</view>
</view>
@ -386,7 +386,7 @@
<t-col span="12">
<view class="dark">
<view class="input-example">
<view class="input-example__label">维生素K (ng/ml)</view>
<view class="input-example__label">维生素Kng/ml</view>
<t-input type="digit" placeholder="请输入" bind:change="onInput" value="{{form.vitamin_k}}" data-key="vitamin_k" borderless="{{true}}" style="{{style}}" />
</view>
</view>
@ -394,7 +394,7 @@
<t-col span="12">
<view class="dark">
<view class="input-example">
<view class="input-example__label">维生素E (ng/ml)</view>
<view class="input-example__label">维生素Eng/ml</view>
<t-input type="digit" placeholder="请输入" bind:change="onInput" value="{{form.vitamin_e}}" data-key="vitamin_e" borderless="{{true}}" style="{{style}}" />
</view>
</view>
@ -433,7 +433,7 @@
<t-col span="12">
<view class="dark">
<view class="input-example">
<view class="input-example__label">肝肋下(mm)</view>
<view class="input-example__label">肝肋下mm</view>
<t-input type="digit" placeholder="请输入" bind:change="onInput" value="{{form.under_the_liver_rib}}" data-key="under_the_liver_rib" size="small" borderless="{{true}}" style="{{style}}" />
</view>
</view>
@ -441,7 +441,7 @@
<t-col span="12">
<view class="dark">
<view class="input-example">
<view class="input-example__label">肝剑突下(mm)</view>
<view class="input-example__label">肝剑突下mm</view>
<t-input type="digit" placeholder="请输入" bind:change="onInput" value="{{form.under_the_xiphoid_liver}}" data-key="under_the_xiphoid_liver" borderless="{{true}}" style="{{style}}" />
</view>
</view>
@ -449,7 +449,7 @@
<t-col span="12">
<view class="dark">
<view class="input-example">
<view class="input-example__label">脾肋下(mm)</view>
<view class="input-example__label">脾肋下mm</view>
<t-input type="digit" placeholder="请输入" bind:change="onInput" value="{{form.spleen_rib_area}}" data-key="spleen_rib_area" borderless="{{true}}" style="{{style}}" />
</view>
</view>
@ -457,7 +457,7 @@
<t-col span="12">
<view class="dark">
<view class="input-example">
<view class="input-example__label">门静脉主干内径(mm)</view>
<view class="input-example__label">门静脉主干内径mm</view>
<t-input placeholder="请输入" bind:change="onInput" value="{{form.main_portal_vein}}" data-key="main_portal_vein" borderless="{{true}}" style="{{style}}" />
</view>
</view>
@ -473,7 +473,7 @@
<t-col span="12">
<view class="dark">
<view class="input-example">
<view class="input-example__label">胆囊大小(mm)</view>
<view class="input-example__label">胆囊大小mm</view>
<t-input type="digit" placeholder="请输入" bind:change="onInput" value="{{form.gallbladder_size}}" data-key="gallbladder_size" size="small" borderless="{{true}}" style="{{style}}" />
</view>
</view>
@ -481,7 +481,7 @@
<t-col span="12">
<view class="dark">
<view class="input-example">
<view class="input-example__label">胆总管(mm)</view>
<view class="input-example__label">胆总管mm</view>
<t-input type="digit" placeholder="请输入" bind:change="onInput" value="{{form.common_bile_duct}}" data-key="common_bile_duct" size="small" borderless="{{true}}" style="{{style}}" />
</view>
</view>
@ -489,7 +489,7 @@
<t-col span="12">
<view class="dark">
<view class="input-example">
<view class="input-example__label">纤维块大小(mm)</view>
<view class="input-example__label">纤维块大小mm</view>
<t-input type="digit" placeholder="请输入" bind:change="onInput" value="{{form.fiber_block_size}}" data-key="fiber_block_size" size="small" borderless="{{true}}" style="{{style}}" />
</view>
</view>
@ -532,6 +532,30 @@
</view>
</view>
</t-col>
<t-col span="12">
<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_median}}" data-key="elastography_median" borderless="{{true}}" style="{{style}}" />
</view>
</view>
</t-col>
<t-col span="12">
<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_maximum}}" data-key="elastography_maximum" borderless="{{true}}" style="{{style}}" />
</view>
</view>
</t-col>
<t-col span="12">
<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_median}}" data-key="elastography_median" borderless="{{true}}" style="{{style}}" />
</view>
</view>
</t-col>
</t-row>
</view>
<view class="follow-item">

View File