diff --git a/healthlink-his-ui/src/views/clinicmanagement/bargain/component/prescriptionlist.vue b/healthlink-his-ui/src/views/clinicmanagement/bargain/component/prescriptionlist.vue index 766ba718f..16930cce7 100755 --- a/healthlink-his-ui/src/views/clinicmanagement/bargain/component/prescriptionlist.vue +++ b/healthlink-his-ui/src/views/clinicmanagement/bargain/component/prescriptionlist.vue @@ -260,7 +260,7 @@ > @@ -272,7 +272,7 @@ > @@ -437,6 +437,8 @@ import { } from './api'; import adviceBaseList from './adviceBaseList'; import {getCurrentInstance, nextTick, ref, watch, computed} from 'vue'; +import useUserStore from '@/store/modules/user' +import { parseTime } from '@/utils/his' const emit = defineEmits(['selectDiagnosis']); const prescriptionList = ref([]); @@ -496,6 +498,27 @@ const editingRowIndex = computed(() => { const stockList = ref([]); const groupList = ref([]) const { proxy } = getCurrentInstance(); +const userStore = useUserStore() + +/** + * 从 contentJson 中安全提取指定字段 + * @param {Object} row - 行数据 + * @param {string} field - 字段名,如 'signDate'、'signDoctorName' + * @returns {string} 字段值或空字符串 + */ +function getSignField(row, field) { + if (!row) return '' + // 🔧 优先读取顶层字段(计费签发 sign-advice 将签名存在顶层) + if (row[field]) return row[field] + // 回退到 contentJson 内部读取 + if (!row.contentJson) return '' + try { + const parsed = typeof row.contentJson === 'string' ? JSON.parse(row.contentJson) : row.contentJson + return parsed[field] || '' + } catch (e) { + return '' + } +} const inputRefs = ref({}); // 存储输入框实例 const requiredProps = ref([]); // 存储必填项 prop 顺序 const { method_code, unit_code, rate_code, distribution_category_code } = proxy.useDict( @@ -1208,10 +1231,16 @@ function handleSave() { return; } // 此处签发处方和单行保存处方传参相同,后台已经将传参存为JSON字符串,此处直接转换为JSON即可 + const signDoctor = userStore.name || userStore.nickName || '' + const signTime = parseTime(new Date()) let list = saveList.map((item) => { const parsedContent = item.contentJson ? JSON.parse(item.contentJson) : {}; return { ...parsedContent, + // 签发生效时写入签名信息到 contentJson(与临时医嘱弹窗 confirmSign 保持一致) + signDoctorName: signDoctor, + signDate: signTime, + executeTime: signTime, requestId: item.requestId, // 已有 requestId 的记录走 UPDATE 路径,新记录走 INSERT 路径 dbOpType: item.requestId ? '2' : '1', @@ -1242,6 +1271,25 @@ function handleSave() { }).then((res) => { if (res.code === 200) { proxy.$modal.msgSuccess('保存成功'); + // 乐观更新:签发成功后立即更新本地行数据,确保签发时间/签发人立即可见 + // 即使后端 API 刷新返回的数据结构有延迟,前端也能立刻回显 + saveList.forEach(signedItem => { + const row = prescriptionList.value.find(r => { + if (signedItem.requestId) return r.requestId === signedItem.requestId + return r.uniqueKey === signedItem.uniqueKey + }) + if (row) { + let contentJson = {} + try { + contentJson = typeof row.contentJson === 'string' ? JSON.parse(row.contentJson) : (row.contentJson || {}) + } catch (e) { /* ignore */ } + contentJson.signDoctorName = signDoctor + contentJson.signDate = signTime + contentJson.executeTime = signTime + row.contentJson = JSON.stringify(contentJson) + row.statusEnum = 2 + } + }) getListInfo(false); prescriptionList.value.map(k=>{ k.check = false @@ -1284,6 +1332,10 @@ function handleSaveSign(row, index) { row.encounterId = props.patientInfo.encounterId; row.accountId = props.patientInfo.accountId; const cleanRow = JSON.parse(JSON.stringify(row)); + // 保存为草稿时清除签名相关字段(签发时间/签发人仅在签发时才应写入 contentJson) + delete cleanRow.signDoctorName + delete cleanRow.signDate + delete cleanRow.executeTime cleanRow.contentJson = JSON.stringify(cleanRow); cleanRow.dbOpType = cleanRow.requestId ? '2' : '1'; cleanRow.minUnitQuantity = cleanRow.quantity * cleanRow.partPercent; diff --git a/healthlink-his-ui/src/views/surgicalschedule/index.vue b/healthlink-his-ui/src/views/surgicalschedule/index.vue index 64bd97c4b..3fc63d706 100755 --- a/healthlink-his-ui/src/views/surgicalschedule/index.vue +++ b/healthlink-his-ui/src/views/surgicalschedule/index.vue @@ -2436,18 +2436,24 @@ function handleMedicalAdvice(row) { const contentData = jsonContent ? JSON.parse(jsonContent) : {}; const medicineName = contentData.adviceName || contentData.advice_name || item.adviceName || item.advice_name || ''; const spec = contentData.volume || contentData.specification || item.volume || item.specification || ''; - const specMatch = spec.match(/([\d.]+)\s*([a-zA-Z一-龥]+)/) + // 🔧 BugFix: 提取并清洗单位。优先用正则从规格中解析,失败时回退到 contentData.doseUnitCode / unitCode + const specMatch = spec.match(/([\d.]+)\s*([a-zA-Z\u4e00-\u9fa5]+)/) + let specUnit = specMatch ? specMatch[2] : '' + if (!specUnit) { + specUnit = contentData.doseUnitCode || contentData.unitCode || contentData.unit || '' + } + // 清理多余符号:只保留字母、数字、中文 + specUnit = specUnit.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, '') const specValue = specMatch ? parseFloat(specMatch[1]) : 1 - const specUnit = specMatch ? specMatch[2] : '' const dosage = specValue * (contentData.quantity || item.quantity || 1) + // 🔧 用法编码归一化:旧格式(iv/po) → 字典兼容格式(404/1) + const oldToNewUsage = { 'iv': '404', 'po': '1', 'im': '403', 'sc': '401', 'ivgtt': '405', 'ih': '5', 'it': '610', 'ip': '604', 'top': '6', 'pr': '2' } let usageCode = contentData.methodCode || 'iv' - let usageLabel = getUsageLabel(usageCode) - if (usageCode === 'iv') { - if (medicineName.includes('注射液')) { usageCode = 'iv'; usageLabel = '静脉注射' } - } else if (usageCode === 'po') { - if (medicineName.includes('片') || medicineName.includes('胶囊')) { usageCode = 'po'; usageLabel = '口服' } + if (oldToNewUsage[usageCode]) { + usageCode = oldToNewUsage[usageCode] } + let usageLabel = getUsageLabel(usageCode) return { id: index + 1, @@ -2457,7 +2463,13 @@ function handleMedicalAdvice(row) { usage: usageCode, usageLabel, frequency: '立即', - executeTime: '', + // 🔧 BugFix: 执行时间优先从 contentJson 读取,兼容计费弹窗签发时签名字段在顶层的情况 + executeTime: contentData.executeTime || contentData.execute_time || contentData.signDate + || item.signDate || item.executeTime || item.execute_time || '', + // 🔧 签发时间:从 contentJson 中提取,供医嘱弹窗表格展示 + signDate: contentData.signDate || item.signDate || '', + // 🔧 签发人:从 contentJson 中提取,供医嘱弹窗表格展示 + signDoctorName: contentData.signDoctorName || item.signDoctorName || '', originalMedicine: { ...item, medicineName: medicineName, @@ -2473,6 +2485,8 @@ function handleMedicalAdvice(row) { dosage: 1, unit: 'ml', usage: 'iv', usageLabel: '静脉注射', frequency: '立即', executeTime: '', + signDate: '', + signDoctorName: '', originalMedicine: { ...item, medicineName: item.adviceName || item.advice_name || '', @@ -2599,21 +2613,31 @@ function handleTemporaryMedicalSubmit(data) { const contentData = jsonContent ? JSON.parse(jsonContent) : {} const medicineName = contentData.adviceName || contentData.advice_name || item.adviceName || item.advice_name || '' const spec = contentData.volume || contentData.specification || item.volume || item.specification || '' - const specMatch = spec.match(/(\d+)(\D+)/) - const specValue = specMatch ? parseInt(specMatch[1]) : 1 - const specUnit = specMatch ? specMatch[2] : 'ml' + const specMatch = spec.match(/([\d.]+)\s*([a-zA-Z\u4e00-\u9fa5]+)/) + let specUnit = specMatch ? specMatch[2] : '' + if (!specUnit) { + specUnit = contentData.doseUnitCode || contentData.unitCode || contentData.unit || 'ml' + } + specUnit = specUnit.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, '') + const specValue = specMatch ? parseFloat(specMatch[1]) : 1 const dosage = specValue * (contentData.quantity || item.quantity || 1) + const oldToNewUsage2 = { 'iv': '404', 'po': '1', 'im': '403', 'sc': '401', 'ivgtt': '405', 'ih': '5', 'it': '610', 'ip': '604', 'top': '6', 'pr': '2' } let usageCode = contentData.methodCode || 'iv' + if (oldToNewUsage2[usageCode]) { usageCode = oldToNewUsage2[usageCode] } return { id: index + 1, adviceName: medicineName, dosage, unit: specUnit, usage: usageCode, frequency: '立即', - executeTime: '', + executeTime: contentData.executeTime || contentData.execute_time || contentData.signDate + || item.signDate || item.executeTime || item.execute_time || '', + signDate: contentData.signDate || item.signDate || '', + signDoctorName: contentData.signDoctorName || item.signDoctorName || '', originalMedicine: { ...item, medicineName, specification: spec, quantity: contentData.quantity || item.quantity || 1, encounterId: row.visitId } } } catch (e) { return { id: index + 1, adviceName: item.adviceName || '', dosage: 1, unit: 'ml', usage: 'iv', frequency: '立即', executeTime: '', + signDate: '', signDoctorName: '', originalMedicine: { ...item, medicineName: item.adviceName || '', specification: item.volume || '', quantity: item.quantity || 1, encounterId: row.visitId } } } @@ -2717,18 +2741,25 @@ function handleQuoteBilling() { const contentData = jsonContent ? JSON.parse(jsonContent) : {}; const medicineName = contentData.adviceName || contentData.advice_name || item.adviceName || item.advice_name || ''; const spec = contentData.volume || contentData.specification || item.volume || item.specification || ''; - const specMatch = spec.match(/(\d+)(\D+)/) - const specValue = specMatch ? parseInt(specMatch[1]) : 1 - const specUnit = specMatch ? specMatch[2] : 'ml' + const specMatch = spec.match(/([\d.]+)\s*([a-zA-Z\u4e00-\u9fa5]+)/) + let specUnit = specMatch ? specMatch[2] : '' + if (!specUnit) { + specUnit = contentData.doseUnitCode || contentData.unitCode || contentData.unit || 'ml' + } + specUnit = specUnit.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, '') + const specValue = specMatch ? parseFloat(specMatch[1]) : 1 const dosage = specValue * (contentData.quantity || item.quantity || 1) + const oldToNewUsage3 = { 'iv': '404', 'po': '1', 'im': '403', 'sc': '401', 'ivgtt': '405', 'ih': '5', 'it': '610', 'ip': '604', 'top': '6', 'pr': '2' } let usageCode = contentData.methodCode || 'iv' + if (oldToNewUsage3[usageCode]) { usageCode = oldToNewUsage3[usageCode] } let usageLabel = getUsageLabel(usageCode) - if (usageCode === 'iv' && medicineName.includes('注射液')) { usageLabel = '静脉注射' } - else if (usageCode === 'po' && (medicineName.includes('片') || medicineName.includes('胶囊'))) { usageLabel = '口服' } return { id: index + 1, adviceName: medicineName, dosage, unit: specUnit, usage: usageCode, usageLabel, frequency: '立即', - executeTime: '', + executeTime: contentData.executeTime || contentData.execute_time || contentData.signDate + || item.signDate || item.executeTime || item.execute_time || '', + signDate: contentData.signDate || item.signDate || '', + signDoctorName: contentData.signDoctorName || item.signDoctorName || '', originalMedicine: { ...item, medicineName: medicineName, @@ -2742,6 +2773,7 @@ function handleQuoteBilling() { id: index + 1, adviceName: item.adviceName || item.advice_name || '', dosage: 1, unit: 'ml', usage: 'iv', usageLabel: '静脉注射', frequency: '立即', executeTime: '', + signDate: '', signDoctorName: '', originalMedicine: { ...item, medicineName: item.adviceName || item.advice_name || '', diff --git a/healthlink-his-ui/src/views/surgicalschedule/temporaryMedical.vue b/healthlink-his-ui/src/views/surgicalschedule/temporaryMedical.vue index b20710e45..507047088 100755 --- a/healthlink-his-ui/src/views/surgicalschedule/temporaryMedical.vue +++ b/healthlink-his-ui/src/views/surgicalschedule/temporaryMedical.vue @@ -198,7 +198,11 @@ field="unit" min-width="80" align="center" - /> + > + + + > + + + > + + { const isSigned = ref(false) -const signatureDoctor = ref(userStore.nickName || userStore.name || '未知用户') +const signatureDoctor = ref('') const signatureTime = ref('') +// 🔧 工具函数:清洗单位字符串,去除所有特殊符号,只保留字母、数字、中文 +const cleanUnitLabel = (raw) => { + if (!raw) return '' + return String(raw).replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, '') +} + const showSignDialog = ref(false) const signPassword = ref('') const showEditDialog = ref(false) @@ -523,7 +541,7 @@ const editForm = ref({ dosage: '', unit: '', usage: '', - frequency: '临时' + frequency: '立即' }) // 计算属性 @@ -552,9 +570,17 @@ const totalAmount = computed(() => { const convertedAdvices = computed(() => { return props.billingMedicines.map((medicine, index) => { // 解析规格中的数值和单位(支持小数,去除 ×、:、/、* 等多余字符) - const specMatch = medicine.specification ? medicine.specification.match(/([\d.]+)\s*([a-zA-Z一-龥]+)/) : null + const specMatch = medicine.specification ? medicine.specification.match(/([\d.]+)\s*([a-zA-Z\u4e00-\u9fa5]+)/) : null const specValue = specMatch ? parseFloat(specMatch[1]) : 1 - const specUnit = specMatch ? specMatch[2] : '' + // 🔧 BugFix: 清洗单位 — 优先从正则提取,失败时回退到 doseUnitCode/unitCode,再清洗多余符号 + let specUnit = specMatch ? specMatch[2] : '' + if (!specUnit && medicine.contentJson) { + try { + const cd = typeof medicine.contentJson === 'string' ? JSON.parse(medicine.contentJson) : medicine.contentJson + specUnit = cd.doseUnitCode || cd.unitCode || cd.unit || '' + } catch (e) { /* ignore */ } + } + specUnit = specUnit.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, '') // 计算剂量 = 规格数值 × 数量 const dosage = specValue * (medicine.quantity || 1) @@ -595,7 +621,16 @@ const displayAdvices = ref([]) const initDisplayAdvices = () => { // 区域二只显示已生成(已签发)的数据,没有时保持为空,不自动转换区域一的草稿 if (props.temporaryAdvices && props.temporaryAdvices.length > 0) { - displayAdvices.value = props.temporaryAdvices.map(mapUsageCode) + // 🔧 BugFix: 映射时同步清洗单位和频次(与 watch 保持一致) + displayAdvices.value = props.temporaryAdvices.map(advice => { + const mapped = { ...advice } + if (usageCodeMapping[advice.usage]) { + mapped.usage = usageCodeMapping[advice.usage] + } + mapped.unit = cleanUnitLabel(mapped.unit) + mapped.frequency = '立即' + return mapped + }) } else { displayAdvices.value = [] } @@ -616,8 +651,20 @@ watch(() => props.temporaryAdvices, (newVal, oldVal) => { // 如果 props 有新数据,更新 displayAdvices if (newVal && newVal.length > 0) { - // 🔧 修复:将旧编码映射到新编码 - displayAdvices.value = newVal.map(mapUsageCode) + // 🔧 修复:将旧编码映射到新编码,同时强制清洗单位和频次 + displayAdvices.value = newVal.map(advice => { + const mapped = { ...advice } + // 用法编码映射 + if (usageCodeMapping[advice.usage]) { + mapped.usage = usageCodeMapping[advice.usage] + } + // 🔧 BugFix: 清洗单位(去除 ×、:、/ 等多余字符) + mapped.unit = cleanUnitLabel(mapped.unit) + // 🔧 BugFix: 频次强制修正为"立即"(临床应用要求) + mapped.frequency = '立即' + // 🔧 BugFix: 执行时间不应为空时回退为空串(由父组件从 contentJson 中取持久化值) + return mapped + }) } }, { deep: true, immediate: true }) @@ -639,6 +686,14 @@ const handleSign = () => { // 点击已生成列表行 → 回显该行的签名信息 const handleAdviceRowClick = (row) => { + // 🔧 优先从映射后的顶层字段读取(index.vue 已将 contentJson 中的签名字段提取到顶层) + if (row.signDoctorName || row.signDate) { + if (row.signDoctorName) signatureDoctor.value = row.signDoctorName + if (row.signDate) signatureTime.value = row.signDate + isSigned.value = true + return + } + // 回退到 originalMedicine.contentJson const om = row?.originalMedicine if (!om) return const contentJson = om.contentJson || om.content_json @@ -669,9 +724,9 @@ const handleEditAdvice = (index) => { editForm.value = { adviceName: advice.adviceName, dosage: advice.dosage, - unit: advice.unit, + unit: cleanUnitLabel(advice.unit), usage: usageCode, // 使用映射后的正确编码 - frequency: advice.frequency + frequency: '立即' } showEditDialog.value = true } @@ -735,7 +790,7 @@ const handleSaveEdit = async () => { // 如果用户修改了剂量,重新计算数量 if (originalMedicine.specification) { - const specMatch = originalMedicine.specification.match(/([\d.]+)\s*([a-zA-Z一-龥]+)/) + const specMatch = originalMedicine.specification.match(/([\d.]+)\s*([a-zA-Z\u4e00-\u9fa5]+)/) const specValue = specMatch ? parseFloat(specMatch[1]) : 1 if (specValue > 0) { const newQuantity = editForm.value.dosage / specValue @@ -770,8 +825,11 @@ const handleSaveEdit = async () => { try { contentJsonData = JSON.parse(editMedicine.contentJson || '{}') } catch (e) {} const quantity = editMedicine.quantity || contentJsonData.quantity || 1 const unitPrice = editMedicine.unitPrice || contentJsonData.unitPrice || 0 + // 🔧 BugFix: 清理单位中的多余符号 + const cleanUnit = cleanUnitLabel(editForm.value.unit) + const cleanDoseUnitCode = cleanUnitLabel(editForm.value.unit) contentJsonData.dose = editForm.value.dosage - contentJsonData.doseUnitCode = editForm.value.unit + contentJsonData.doseUnitCode = cleanDoseUnitCode contentJsonData.methodCode = updatedAdvice.usage contentJsonData.quantity = quantity contentJsonData.totalPrice = unitPrice * quantity @@ -785,7 +843,7 @@ const handleSaveEdit = async () => { chargeItemId: editMedicine.chargeItemId, contentJson: JSON.stringify(contentJsonData), quantity, - unitCode: editMedicine.unitCode || editForm.value.unit, + unitCode: editMedicine.unitCode || cleanUnit, unitPrice, totalPrice: unitPrice * quantity, adviceName: updatedAdvice.adviceName, @@ -794,7 +852,7 @@ const handleSaveEdit = async () => { orgId: props.patientInfo.orgId, methodCode: updatedAdvice.usage, dose: editForm.value.dosage, - doseUnitCode: editForm.value.unit, + doseUnitCode: cleanDoseUnitCode, generateSourceEnum: 6, sourceBillNo: props.patientInfo?.operCode || '' } @@ -938,7 +996,9 @@ const handleSubmit = async () => { // ✅ 关键修复:把修改后的数据更新到contentJson中,后端会优先读取这里的值 contentJsonData.adviceName = advice.adviceName; contentJsonData.quantity = quantity; - contentJsonData.volume = advice.dosage + (advice.unit || ''); + // 🔧 BugFix: 清理单位中的多余符号,确保 volume 字段只包含纯单位名称 + const cleanUnit = cleanUnitLabel(advice.unit); + contentJsonData.volume = advice.dosage + cleanUnit; contentJsonData.totalPrice = totalPrice; contentJsonData.unitPrice = unitPrice; // 用法、剂量、单位也更新到contentJson @@ -957,10 +1017,12 @@ const handleSubmit = async () => { } contentJsonData.methodCode = methodCode; contentJsonData.dose = advice.dosage; - contentJsonData.doseUnitCode = advice.unit; + // 🔧 BugFix: 清理单位中的多余符号,确保保存的单位是纯单位名称 + contentJsonData.doseUnitCode = cleanUnitLabel(advice.unit); contentJsonData.rateCode = advice.frequency; contentJsonData.signDoctorName = signatureDoctor.value contentJsonData.signDate = signatureTime.value + contentJsonData.executeTime = signatureTime.value // 重新序列化contentJson const updatedContentJson = JSON.stringify(contentJsonData); @@ -981,7 +1043,7 @@ const handleSubmit = async () => { // 数量和单位:使用重新计算后的数量 quantity: quantity, - unitCode: contentJsonData.unitCode || originalMedicine?.unitCode || advice.unit, + unitCode: contentJsonData.unitCode || originalMedicine?.unitCode || cleanUnitLabel(advice.unit), unitPrice: unitPrice, totalPrice: totalPrice, definitionId: originalMedicine?.definitionId ? String(originalMedicine.definitionId) : contentJsonData.definitionId ? String(contentJsonData.definitionId) : advice.definitionId ? String(advice.definitionId) : null, @@ -1014,7 +1076,7 @@ const handleSubmit = async () => { methodCode: methodCode, rateCode: advice.frequency, dose: advice.dosage, - doseUnitCode: contentJsonData.doseUnitCode || originalMedicine?.doseUnitCode || advice.unit, + doseUnitCode: contentJsonData.doseUnitCode || originalMedicine?.doseUnitCode || cleanUnitLabel(advice.unit), skinTestFlag: originalMedicine?.skinTestFlag || contentJsonData.skinTestFlag, injectFlag: originalMedicine?.injectFlag || contentJsonData.injectFlag,