606 门诊术中安排-医嘱】预览列表字段显示及逻辑异常(涉及单位、频次、执行时间)

This commit is contained in:
wangjian963
2026-06-15 11:44:39 +08:00
parent 9ae9fae2c8
commit dfce7d0332
3 changed files with 186 additions and 40 deletions

View File

@@ -260,7 +260,7 @@
>
<template #default="scope">
<span v-if="!scope.row.isEdit">
{{ scope.row.requesterId_dictText }}
{{ getSignField(scope.row, 'signDoctorName') }}
</span>
</template>
</vxe-column>
@@ -272,7 +272,7 @@
>
<template #default="scope">
<span v-if="!scope.row.isEdit">
{{ scope.row.requestTime }}
{{ getSignField(scope.row, 'signDate') }}
</span>
</template>
</vxe-column>
@@ -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;

View File

@@ -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 || '',

View File

@@ -198,7 +198,11 @@
field="unit"
min-width="80"
align="center"
/>
>
<template #default="{ row }">
{{ cleanUnitLabel(row.unit) || '-' }}
</template>
</vxe-column>
<vxe-column
title="用法"
field="usage"
@@ -214,13 +218,21 @@
field="frequency"
min-width="80"
align="center"
/>
>
<template #default>
立即
</template>
</vxe-column>
<vxe-column
title="执行时间"
field="executeTime"
min-width="160"
align="center"
/>
>
<template #default="{ row }">
{{ row.executeTime || '-' }}
</template>
</vxe-column>
<vxe-column
title="操作"
min-width="140"
@@ -511,9 +523,15 @@ const displayAdvicesList = computed(() => {
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,