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 @@
>
- {{ scope.row.requesterId_dictText }}
+ {{ getSignField(scope.row, 'signDoctorName') }}
@@ -272,7 +272,7 @@
>
- {{ scope.row.requestTime }}
+ {{ getSignField(scope.row, 'signDate') }}
@@ -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"
- />
+ >
+
+ {{ cleanUnitLabel(row.unit) || '-' }}
+
+
+ >
+
+ 立即
+
+
+ >
+
+ {{ row.executeTime || '-' }}
+
+
{
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,