| | |
| | | </view> |
| | | </view> |
| | | </uni-popup> |
| | | |
| | | <!-- 拍照识别弹窗 --> |
| | | <uni-popup ref="photoOCRPopup" type="bottom" :safe-area="true"> |
| | | <view class="photo-ocr-popup"> |
| | | <view class="popup-header"> |
| | | <view class="popup-title">拍照识别</view> |
| | | <view class="popup-close" @click="closePhotoOCRPopup"> |
| | | <uni-icons type="closeempty" size="24" color="#333"></uni-icons> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="ocr-content"> |
| | | <view class="ocr-tip"> |
| | | <uni-icons type="info" size="18" color="#007AFF"></uni-icons> |
| | | <text>拍照或选择图片,自动识别转运单信息</text> |
| | | </view> |
| | | |
| | | <view class="image-preview" v-if="ocrImage"> |
| | | <image :src="ocrImage" mode="aspectFit" style="width: 100%; height: 300rpx; border-radius: 10rpx;"></image> |
| | | </view> |
| | | |
| | | <view class="ocr-actions"> |
| | | <button class="select-btn" @click="selectImage">选择图片</button> |
| | | <button class="capture-btn" @click="captureImage">拍照</button> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="popup-footer"> |
| | | <button class="cancel-btn" @click="closePhotoOCRPopup">取消</button> |
| | | <button class="confirm-btn" @click="performOCR" :disabled="ocrLoading"> |
| | | {{ ocrLoading ? '识别中...' : '开始识别' }} |
| | | </button> |
| | | </view> |
| | | </view> |
| | | </uni-popup> |
| | | </scroll-view> |
| | | </template> |
| | | |
| | |
| | | // 智能识别相关 |
| | | rawText: '', |
| | | parseLoading: false, |
| | | // 拍照识别相关 |
| | | ocrImage: '', |
| | | ocrLoading: false, |
| | | // 多图片拍照识别相关 |
| | | multiOcrImages: [], |
| | | multiOcrLoading: false, |
| | |
| | | |
| | | // ==================== 拍照识别相关方法 ==================== |
| | | |
| | | // 显示拍照识别弹窗 |
| | | showPhotoOCRPopup() { |
| | | this.ocrImage = '' |
| | | this.$refs.photoOCRPopup.open() |
| | | }, |
| | | |
| | | // 显示多图拍照识别弹窗 |
| | | showMultiPhotoOCRPopup() { |
| | | this.page1Image = '' |
| | |
| | | fail: (err) => { |
| | | console.error('选择图片失败:', err) |
| | | this.$modal.showToast('选择图片失败') |
| | | } |
| | | }) |
| | | }, |
| | | |
| | | // 选择图片 |
| | | selectImage() { |
| | | uni.chooseImage({ |
| | | count: 1, |
| | | sizeType: ['compressed'], |
| | | sourceType: ['album'], |
| | | success: (res) => { |
| | | this.ocrImage = res.tempFilePaths[0] |
| | | }, |
| | | fail: (err) => { |
| | | console.error('选择图片失败:', err) |
| | | this.$modal.showToast('选择图片失败') |
| | | } |
| | | }) |
| | | }, |
| | | |
| | | // 拍照 |
| | | captureImage() { |
| | | uni.chooseImage({ |
| | | count: 1, |
| | | sizeType: ['compressed'], |
| | | sourceType: ['camera'], |
| | | success: (res) => { |
| | | this.ocrImage = res.tempFilePaths[0] |
| | | }, |
| | | fail: (err) => { |
| | | console.error('拍照失败:', err) |
| | | this.$modal.showToast('拍照失败') |
| | | } |
| | | }) |
| | | }, |
| | |
| | | } |
| | | } |
| | | |
| | | // 提取诊断(病情) |
| | | // 提取诊断(病情)(限制10个字符以内,避免误识别) |
| | | if (fields['诊断']) { |
| | | this.taskForm.patient.condition = fields['诊断'].trim() |
| | | const diagnosis = fields['诊断'].trim() |
| | | // 只有当10个字符以内的诊断信息才填入表单 |
| | | if (diagnosis.length <= 10) { |
| | | this.taskForm.patient.condition = diagnosis |
| | | } else { |
| | | console.log('诊断信息过长,可能为误识别,已忽略:', diagnosis) |
| | | } |
| | | } |
| | | |
| | | // 提取行程(转出医院和转入医院) |
| | | if (fields['行程']) { |
| | | const route = fields['行程'].trim() |
| | | // 按"-"或"—"分割行程 |
| | | const hospitals = route.split(/[-—]/).map(h => h.trim()) |
| | | console.log('【行程】OCR识别的行程信息:', route) |
| | | |
| | | // 按多种分隔符分割行程:-、—、---、-->、→、空格等 |
| | | const hospitals = route.split(/[-—]{1,3}|-->|→|\s{2,}/).map(h => h.trim()).filter(h => h.length > 0) |
| | | console.log('【行程】分割后的医院列表:', hospitals) |
| | | |
| | | if (hospitals.length >= 2) { |
| | | // 第一个是转出医院 |
| | | this.taskForm.hospitalOut.name = hospitals[0] |
| | | // 第二个是转入医院 |
| | | this.taskForm.hospitalIn.name = hospitals[1] |
| | | const outHospital = hospitals[0] |
| | | this.taskForm.hospitalOut.name = outHospital |
| | | |
| | | // 尝试从医院库中匹配并补全地址 |
| | | // 如枟转出医院是“家中”,提取后面的详细地址 |
| | | if (outHospital === '家中' && hospitals.length >= 3) { |
| | | // 将剩余部分作为详细地址 |
| | | const detailAddress = hospitals.slice(1, hospitals.length - 1).join(' ') |
| | | if (detailAddress) { |
| | | this.taskForm.hospitalOut.address = detailAddress |
| | | console.log('【行程】转出家中地址:', detailAddress) |
| | | } |
| | | // 最后一个是转入医院 |
| | | this.taskForm.hospitalIn.name = hospitals[hospitals.length - 1] |
| | | console.log('【行程】转入医院:', hospitals[hospitals.length - 1]) |
| | | } else { |
| | | // 第二个是转入医院 |
| | | const inHospital = hospitals[1] |
| | | this.taskForm.hospitalIn.name = inHospital |
| | | |
| | | // 如果转入医院是“家中”,提取后面的详细地址 |
| | | if (inHospital === '家中' && hospitals.length >= 3) { |
| | | // 将剩余部分作为详细地址 |
| | | const detailAddress = hospitals.slice(2).join(' ') |
| | | if (detailAddress) { |
| | | this.taskForm.hospitalIn.address = detailAddress |
| | | console.log('【行程】转入家中地址:', detailAddress) |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 尝试从医院库中匹配并补全地址(只在非“家中”时) |
| | | const outHospitalName = this.taskForm.hospitalOut.name |
| | | const inHospitalName = this.taskForm.hospitalIn.name |
| | | |
| | | Promise.all([ |
| | | this.findHospitalByName(hospitals[0], 'out', false), |
| | | this.findHospitalByName(hospitals[1], 'in', false) |
| | | outHospitalName !== '家中' ? this.findHospitalByName(outHospitalName, 'out', false) : Promise.resolve(null), |
| | | inHospitalName !== '家中' ? this.findHospitalByName(inHospitalName, 'in', false) : Promise.resolve(null) |
| | | ]).then(([outHosp, inHosp]) => { |
| | | // 处理转出医院 |
| | | if (outHospitalName !== '家中') { |
| | | if (outHosp) { |
| | | // 找到医院,使用医院库中的信息 |
| | | this.taskForm.hospitalOut.id = outHosp.hospId |
| | | this.taskForm.hospitalOut.name = outHosp.hospName |
| | | if (outHosp.hospName !== '家中') { |
| | | this.taskForm.hospitalOut.address = this.buildFullAddress(outHosp) |
| | | this.taskForm.hospitalOut.city = outHosp.hopsCity || '' |
| | | console.log('【行程】转出医院匹配成功:', outHosp.hospName) |
| | | } else { |
| | | // 找不到医院,默认设置为"家中",原医院名称作为地址 |
| | | console.log('【行程】转出医院未找到,默认设置为家中,地址为:', outHospitalName) |
| | | this.taskForm.hospitalOut.name = '家中' |
| | | this.taskForm.hospitalOut.address = outHospitalName |
| | | this.taskForm.hospitalOut.id = null |
| | | this.taskForm.hospitalOut.city = '' |
| | | } |
| | | } |
| | | |
| | | // 处理转入医院 |
| | | if (inHospitalName !== '家中') { |
| | | if (inHosp) { |
| | | // 找到医院,使用医院库中的信息 |
| | | this.taskForm.hospitalIn.id = inHosp.hospId |
| | | this.taskForm.hospitalIn.name = inHosp.hospName |
| | | if (inHosp.hospName !== '家中') { |
| | | this.taskForm.hospitalIn.address = this.buildFullAddress(inHosp) |
| | | this.taskForm.hospitalIn.city = inHosp.hopsCity || '' |
| | | console.log('【行程】转入医院匹配成功:', inHosp.hospName) |
| | | } else { |
| | | // 找不到医院,默认设置为"家中",原医院名称作为地址 |
| | | console.log('【行程】转入医院未找到,默认设置为家中,地址为:', inHospitalName) |
| | | this.taskForm.hospitalIn.name = '家中' |
| | | this.taskForm.hospitalIn.address = inHospitalName |
| | | this.taskForm.hospitalIn.id = null |
| | | this.taskForm.hospitalIn.city = '' |
| | | } |
| | | } |
| | | |
| | |
| | | console.log('多图OCR结果处理完成,表单数据更新') |
| | | }, |
| | | |
| | | // 执行OCR识别 |
| | | performOCR() { |
| | | if (!this.ocrImage) { |
| | | this.$modal.showToast('请先选择或拍摄图片') |
| | | return |
| | | } |
| | | |
| | | this.ocrLoading = true |
| | | |
| | | // 使用OCR API进行识别 |
| | | recognizeImage(this.ocrImage, 'HandWriting', 'tencent', DEFAULT_TRANSFER_ITEM_NAMES) |
| | | .then(response => { |
| | | const ocrResult = response.data.ocrResult |
| | | this.processOCRResult(ocrResult) |
| | | this.$modal.showToast('OCR识别成功') |
| | | }) |
| | | .catch(error => { |
| | | console.error('OCR识别失败:', error) |
| | | this.$modal.showToast(`OCR识别失败: ${error.msg || '未知错误'}`) |
| | | }) |
| | | .finally(() => { |
| | | this.ocrLoading = false |
| | | this.closePhotoOCRPopup() |
| | | }) |
| | | }, |
| | | |
| | | // 处理OCR识别结果 |
| | | processOCRResult(ocrResult) { |
| | | if (!ocrResult || !ocrResult.content) { |
| | | console.log('OCR识别结果为空') |
| | | return |
| | | } |
| | | |
| | | const content = ocrResult.content |
| | | console.log('OCR识别内容:', content) |
| | | |
| | | // 提取患者姓名 |
| | | const patientNameMatch = content.match(/患者姓名[::]?\s*([^\n,,。;;]+)/) |
| | | if (patientNameMatch && patientNameMatch[1]) { |
| | | this.taskForm.patient.name = patientNameMatch[1].trim() |
| | | } |
| | | |
| | | // 提取性别 |
| | | const genderMatch = content.match(/性别[::]?\s*([^\n,,。;;]+)/) |
| | | if (genderMatch && genderMatch[1]) { |
| | | const gender = genderMatch[1].trim() |
| | | if (gender.includes('男')) { |
| | | this.taskForm.patient.gender = 'male' |
| | | } else if (gender.includes('女')) { |
| | | this.taskForm.patient.gender = 'female' |
| | | } |
| | | } |
| | | |
| | | // 提取身份证号 |
| | | const idCardMatch = content.match(/身份证号[::]?\s*([^\n,,。;;]+)/) |
| | | const signerIdMatch = content.match(/签字人身份证号码[::]?\s*([^\n,,。;;]+)/) |
| | | if (idCardMatch && idCardMatch[1]) { |
| | | this.taskForm.patient.idCard = idCardMatch[1].trim() |
| | | } else if (signerIdMatch && signerIdMatch[1]) { |
| | | this.taskForm.patient.idCard = signerIdMatch[1].trim() |
| | | } |
| | | |
| | | // 提取联系电话 |
| | | const phoneMatch = content.match(/联系电话[::]?\s*([^\n,,。;;]+)/) |
| | | if (phoneMatch && phoneMatch[1]) { |
| | | this.taskForm.patient.phone = phoneMatch[1].trim() |
| | | } |
| | | |
| | | // 提取诊断信息 |
| | | const diagnosisMatch = content.match(/诊断[::]?\s*([^\n,,。;;]+)/) |
| | | if (diagnosisMatch && diagnosisMatch[1]) { |
| | | this.taskForm.patient.condition = diagnosisMatch[1].trim() |
| | | } |
| | | |
| | | // 提取需支付转运费用(成交价) |
| | | const priceMatch = content.match(/需支付转运费用[::]?\s*([^\n,,。;;]+)/) |
| | | if (priceMatch && priceMatch[1]) { |
| | | // 提取数字金额 |
| | | const priceNumber = priceMatch[1].match(/\d+(?:\.\d{1,2})?/) |
| | | if (priceNumber) { |
| | | this.taskForm.price = parseFloat(priceNumber[0]).toFixed(2) |
| | | } |
| | | } |
| | | |
| | | // 提取日期 |
| | | const dateMatch = content.match(/日期[::]?\s*([^\n,,。;;]+)/) |
| | | if (dateMatch && dateMatch[1]) { |
| | | const dateString = dateMatch[1].trim() |
| | | // 尝试解析日期格式 |
| | | const dateFormatted = this.formatDateString(dateString) |
| | | if (dateFormatted) { |
| | | this.taskForm.transferTime = dateFormatted |
| | | } |
| | | } |
| | | |
| | | // 提取行程(转出医院和转入医院) |
| | | const routeMatch = content.match(/行程[::]?\s*([^\n]+)/) |
| | | if (routeMatch && routeMatch[1]) { |
| | | const route = routeMatch[1].trim() |
| | | // 按"-"分割行程,获取转出和转入医院 |
| | | const hospitals = route.split(/[-—]/).map(h => h.trim()) |
| | | if (hospitals.length >= 2) { |
| | | // 第一个是转出医院 |
| | | this.taskForm.hospitalOut.name = hospitals[0] |
| | | // 第二个是转入医院 |
| | | this.taskForm.hospitalIn.name = hospitals[1] |
| | | } |
| | | } |
| | | |
| | | // 提取家属签名或本人作为联系人 |
| | | const familyContactMatch = content.match(/(?:家属签名|本人)[::]?\s*([^\n,,。;;]+)/) |
| | | if (familyContactMatch && familyContactMatch[1]) { |
| | | this.taskForm.patient.contact = familyContactMatch[1].trim() |
| | | } |
| | | |
| | | // 提取患者签名(手印)作为联系人 |
| | | const patientSignatureMatch = content.match(/患者签名(手印)[::]?\s*([^\n,,。;;]+)/) |
| | | if (patientSignatureMatch && patientSignatureMatch[1]) { |
| | | if (!this.taskForm.patient.contact) { |
| | | this.taskForm.patient.contact = patientSignatureMatch[1].trim() |
| | | } |
| | | } |
| | | |
| | | console.log('OCR结果处理完成,表单数据更新') |
| | | }, |
| | | |
| | | // 格式化日期字符串(返回 yyyy-MM-dd HH:mm:ss 格式) |
| | | formatDateString(dateStr) { |
| | | // 尝试不同的日期格式 |
| | | let cleaned = dateStr.replace(/[年月]/g, '-').replace(/[日号]/g, '') |
| | | |
| | | // 支持格式: |
| | | // - yyyy年MM月dd日HH时mm分 |
| | | // - yyyy年MM月dd日 |
| | | // - YYYYMMDD |
| | | // - yyyy-MM-dd HH:mm:ss |
| | | console.log('尝试格式化日期字符串:', dateStr) |
| | | let cleaned = dateStr |
| | | .replace(/[年月]/g, '-') |
| | | .replace(/[日号]/g, ' ') // 日/号 → 空格,保留日期和时间的分隔 |
| | | .replace(/时/g, ':') |
| | | .replace(/分/g, ':') |
| | | .replace(/秒/g, '') |
| | | .replace(/\s+/g, ' ') // 多个空格合并为一个 |
| | | .trim() |
| | | console.log('清理后的日期字符串:', cleaned) |
| | | let dateResult = '' |
| | | |
| | | // 如果是YYMMDD格式 |
| | |
| | | const year = '20' + cleaned.substring(0, 2) |
| | | const month = cleaned.substring(2, 4) |
| | | const day = cleaned.substring(4, 6) |
| | | dateResult = `${year}-${month}-${day}` |
| | | dateResult = `${year}-${month}-${day}`; |
| | | } |
| | | // 如果是YYYYMMDD格式 |
| | | else if (/^\d{8}$/.test(cleaned)) { |
| | |
| | | else if (cleaned.match(/^\d{4}[-/]\d{1,2}[-/]\d{1,2}\s+\d{1,2}:\d{1,2}:\d{1,2}$/)) { |
| | | return cleaned.replace(/[//]/g, '-') |
| | | } |
| | | // 如果包含时分但缺少秒(yyyy-MM-dd HH:mm:) |
| | | else if (cleaned.match(/^\d{4}[-/]\d{1,2}[-/]\d{1,2}\s+\d{1,2}:\d{1,2}:$/)) { |
| | | // 去掉末尾的冒号,补上秒数00 |
| | | return cleaned.replace(/[//]/g, '-').replace(/:$/, '') + ':00' |
| | | } |
| | | // 如果只包含时分(yyyy-MM-dd HH:mm) |
| | | else if (cleaned.match(/^\d{4}[-/]\d{1,2}[-/]\d{1,2}\s+\d{1,2}:\d{1,2}$/)) { |
| | | return cleaned.replace(/[//]/g, '-') + ':00' |
| | | } |
| | | else { |
| | | dateResult = dateStr |
| | | } |