完成100需求,补充99需求追溯术中产生的费用新增费用项时向表中插入SourceBillNo和generate_source_enum字段值

This commit is contained in:
chenjinyang
2026-02-10 16:49:28 +08:00
parent f1bddf3fbe
commit b5b91d8971
5 changed files with 882 additions and 50 deletions

View File

@@ -0,0 +1,673 @@
<template>
<div class="temporary-medical-container">
<!-- 患者信息区域 -->
<div class="patient-info-section">
<div class="patient-info-header">
<h3>门诊术中临时医嘱</h3>
</div>
<div class="patient-info-content">
<el-descriptions :column="3" border>
<el-descriptions-item label="患者:">{{ patientInfo.patientName || '-' }}</el-descriptions-item>
<el-descriptions-item label="就诊卡号:">{{ patientInfo.visitId || '-' }}</el-descriptions-item>
<el-descriptions-item label="手术单号:">{{ patientInfo.operCode || '-' }}</el-descriptions-item>
<el-descriptions-item label="科室:">{{ patientInfo.roomCode || '-' }}</el-descriptions-item>
<el-descriptions-item label="医师:">{{ patientInfo.doctorName || '-' }}</el-descriptions-item>
<el-descriptions-item label="角色:">{{ patientInfo.role || '-' }}</el-descriptions-item>
</el-descriptions>
</div>
</div>
<!-- 已引用计费药品区域 -->
<div class="medicine-section">
<h4>已引用计费药品待生成医嘱</h4>
<el-table :data="billingMedicines" border style="width: 100%; margin-bottom: 16px" fit>
<el-table-column label="序号" type="index" width="60" align="center" />
<el-table-column label="药品名称" prop="medicineName" min-width="150" show-overflow-tooltip />
<el-table-column label="规格" prop="specification" min-width="100" align="center" />
<el-table-column label="数量" prop="quantity" width="80" align="center" />
<el-table-column label="批号" prop="batchNumber" min-width="120" align="center" />
<el-table-column label="单价" prop="unitPrice" min-width="80" align="center">
<template #default="{ row }">
{{ row.unitPrice ? `${row.unitPrice}` : '-' }}
</template>
</el-table-column>
<el-table-column label="小计" prop="subtotal" min-width="100" align="center">
<template #default="{ row }">
{{ row.subtotal ? `${row.subtotal}` : '-' }}
</template>
</el-table-column>
<el-table-column label="医保" prop="insuranceType" width="80" align="center" />
</el-table>
<div class="medicine-summary">
<div class="summary-item">医保内{{ insuranceAmount }}</div>
<div class="summary-item">自费{{ selfPayAmount }}</div>
<div class="summary-item total">总计{{ totalAmount }}</div>
</div>
</div>
<!-- 临时医嘱预览区域 -->
<div class="advice-section">
<h4>临时医嘱预览已生成</h4>
<el-table :data="displayAdvices" border style="width: 100%; margin-bottom: 16px" fit>
<el-table-column label="序号" type="index" width="60" align="center" />
<el-table-column label="医嘱名称" prop="adviceName" min-width="150" show-overflow-tooltip />
<el-table-column label="剂量" prop="dosage" min-width="80" align="center" />
<el-table-column label="单位" prop="unit" min-width="80" align="center" />
<el-table-column label="用法" prop="usage" min-width="100" show-overflow-tooltip />
<el-table-column label="频次" prop="frequency" min-width="80" align="center" />
<el-table-column label="执行时间" prop="executeTime" min-width="120" align="center" />
<el-table-column label="操作" min-width="120" align="center">
<template #default="{ $index }">
<el-button link type="primary" @click="handleEditAdvice($index)">编辑</el-button>
<el-button link type="danger" @click="handleDeleteAdvice($index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 医师电子签名区域 -->
<div class="signature-section">
<h4>医师电子签名</h4>
<div class="signature-content">
<div class="signature-info">
<span>签名医师</span>
<span :class="{ 'unsigned': !isSigned }">{{ isSigned ? currentUser.name : '未签名' }}</span>
</div>
<div class="signature-info">
<span>时间</span>
<span>{{ signatureTime || '-' }}</span>
</div>
</div>
</div>
<!-- 底部操作按钮 -->
<div class="footer-actions">
<el-button class="cancel-btn" @click="handleCancel">取消</el-button>
<el-button
class="sign-submit-btn"
:class="{ 'signed': isSigned }"
@click="handleSignAndSubmit"
>
{{ isSigned ? '提交' : '一键签名并生成医嘱' }}
</el-button>
</div>
<!-- 签名密码弹窗 -->
<el-dialog v-model="showSignDialog" title="弹窗-签名密码" width="400px" append-to-body>
<div class="sign-dialog-content">
<p>请输入账户密码</p>
<el-input
v-model="signPassword"
type="password"
placeholder="请输入密码"
style="margin-bottom: 16px"
@keyup.enter="confirmSign"
/>
<div class="dialog-actions">
<el-button @click="showSignDialog = false">取消</el-button>
<el-button type="primary" @click="confirmSign">确认</el-button>
</div>
</div>
</el-dialog>
<!-- 编辑医嘱弹窗 -->
<el-dialog v-model="showEditDialog" title="编辑医嘱" width="500px" append-to-body>
<div class="edit-dialog-content">
<el-form :model="editForm" label-width="80px">
<el-form-item label="药品名称">
<el-input v-model="editForm.adviceName" disabled />
</el-form-item>
<el-form-item label="剂量" required>
<el-input v-model.number="editForm.dosage" placeholder="请输入剂量" />
</el-form-item>
<el-form-item label="单位">
<el-input v-model="editForm.unit" placeholder="请输入单位" />
</el-form-item>
<el-form-item label="用法" required>
<el-select v-model="editForm.usage" placeholder="请选择用法" style="width: 100%">
<el-option
v-for="dict in method_code"
:key="dict.value"
:label="dict.label"
:value="dict.label"
/>
</el-select>
</el-form-item>
<el-form-item label="频次">
<el-input v-model="editForm.frequency" disabled />
</el-form-item>
</el-form>
<div class="dialog-actions">
<el-button @click="handleCancelEdit">取消</el-button>
<el-button type="primary" @click="handleSaveEdit">保存</el-button>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, computed, getCurrentInstance } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import useUserStore from '@/store/modules/user'
import { checkPassword } from '@/api/surgicalschedule'
import { savePrescription } from '@/views/clinicmanagement/bargain/component/api.js'
// 定义props
const props = defineProps({
// 患者和医生信息
patientInfo: {
type: Object,
default: () => ({
patientName: '',
visitId: '',
operCode: '',
roomCode: '',
doctorName: '',
role: ''
})
},
// 计费药品列表
billingMedicines: {
type: Array,
default: () => []
},
// 临时医嘱列表
temporaryAdvices: {
type: Array,
default: () => []
}
})
// 定义emit事件
const emit = defineEmits(['submit', 'cancel', 'delete-advice', 'update:temporaryAdvices'])
// 用户store
const userStore = useUserStore()
// 获取当前实例
const { proxy } = getCurrentInstance()
// 获取字典数据(与门诊医生站保持一致)
const { method_code } = proxy.useDict('method_code')
// 响应式数据
const isSigned = ref(false)
const signatureTime = ref('')
const showSignDialog = ref(false)
const signPassword = ref('')
const showEditDialog = ref(false)
const currentEditIndex = ref(-1)
const editForm = ref({
adviceName: '',
dosage: '',
unit: '',
usage: '',
frequency: '临时'
})
// 计算属性
const currentUser = computed(() => ({
name: userStore.name || '未知用户',
id: userStore.id
}))
const insuranceAmount = computed(() => {
return props.billingMedicines
.filter(med => med.insuranceType === '甲' || med.insuranceType === '乙')
.reduce((sum, med) => sum + (med.subtotal || 0), 0)
})
const selfPayAmount = computed(() => {
return props.billingMedicines
.filter(med => !med.insuranceType || med.insuranceType === '自费')
.reduce((sum, med) => sum + (med.subtotal || 0), 0)
})
const totalAmount = computed(() => {
return insuranceAmount.value + selfPayAmount.value
})
// 将计费药品转换为临时医嘱数据
const convertedAdvices = computed(() => {
return props.billingMedicines.map((medicine, index) => {
// 解析规格中的数值和单位
const specMatch = medicine.specification ? medicine.specification.match(/(\d+)(\D+)/) : null
const specValue = specMatch ? parseInt(specMatch[1]) : 1
const specUnit = specMatch ? specMatch[2] : 'ml'
// 计算剂量 = 规格数值 × 数量
const dosage = specValue * (medicine.quantity || 1)
// 根据药品名称判断用法
let usage = '静脉注射'
if (medicine.medicineName && medicine.medicineName.includes('注射液')) {
usage = '静脉注射'
} else if (medicine.medicineName && medicine.medicineName.includes('片')) {
usage = '口服'
} else if (medicine.medicineName && medicine.medicineName.includes('胶囊')) {
usage = '口服'
}
return {
id: index + 1,
adviceName: medicine.medicineName || '',
dosage: dosage,
unit: specUnit,
usage: usage,
frequency: '临时',
executeTime: new Date().toLocaleString('zh-CN'),
originalMedicine: medicine
}
})
})
// 使用转换后的数据或传入的临时医嘱数据
const displayAdvices = computed(() => {
return props.temporaryAdvices.length > 0 ? props.temporaryAdvices : convertedAdvices.value
})
// 方法
const handleSign = () => {
showSignDialog.value = true
}
// 编辑医嘱
const handleEditAdvice = (index) => {
const advice = displayAdvices.value[index]
currentEditIndex.value = index
editForm.value = {
adviceName: advice.adviceName,
dosage: advice.dosage,
unit: advice.unit,
usage: advice.usage,
frequency: advice.frequency
}
showEditDialog.value = true
}
// 保存编辑
const handleSaveEdit = () => {
if (!editForm.value.dosage || !editForm.value.usage) {
ElMessage.warning('请填写剂量和用法')
return
}
// 构建更新后的数据
const updatedAdvice = {
...displayAdvices.value[currentEditIndex.value],
...editForm.value
}
// 构建完整的更新后列表
const updatedAdvices = [...displayAdvices.value]
updatedAdvices[currentEditIndex.value] = updatedAdvice
// 通知父组件更新数据
emit('update:temporaryAdvices', updatedAdvices)
showEditDialog.value = false
ElMessage.success('编辑成功')
}
// 取消编辑
const handleCancelEdit = () => {
showEditDialog.value = false
currentEditIndex.value = -1
editForm.value = {
adviceName: '',
dosage: '',
unit: '',
usage: '',
frequency: '临时'
}
}
const confirmSign = async () => {
if (!signPassword.value) {
ElMessage.warning('请输入密码')
return
}
// 调用后端接口验证密码
try {
const response = await checkPassword({
password: signPassword.value
})
if (response.code === 200 && response.data) {
isSigned.value = true
signatureTime.value = new Date().toLocaleString('zh-CN')
showSignDialog.value = false
signPassword.value = ''
ElMessage.success('签名成功')
// 签名成功后自动提交
handleSubmit()
} else {
ElMessage.error('密码错误,请重新输入')
}
} catch (error) {
ElMessage.error('签名失败,请重试')
}
}
const handleSignAndSubmit = () => {
if (isSigned.value) {
// 如果已经签名,直接提交
handleSubmit()
} else {
// 如果未签名,打开签名弹窗
handleSign()
}
}
const handleDeleteAdvice = (index) => {
ElMessageBox.confirm('确定要删除这条医嘱吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 检查数据来源
if (props.temporaryAdvices.length > 0) {
// 如果使用的是传入的临时医嘱数据,通知父组件删除
emit('delete-advice', index)
} else {
// 如果使用的是转换的数据,需要构建新的临时医嘱数据并通知父组件
const updatedAdvices = [...displayAdvices.value]
updatedAdvices.splice(index, 1)
emit('update:temporaryAdvices', updatedAdvices)
}
ElMessage.success('删除成功')
}).catch(() => {
// 用户取消删除
})
}
const handleSubmit = async () => {
if (!isSigned.value) {
ElMessage.warning('请先进行电子签名')
return
}
// 检查是否有医嘱数据
if (displayAdvices.value.length === 0) {
ElMessage.warning('没有可保存的医嘱数据')
return
}
try {
// 构建保存医嘱的请求参数
const saveData = {
organizationId: props.patientInfo.organizationId || 1, // 默认科室ID
adviceSaveList: displayAdvices.value.map((advice, index) => {
// 获取原始药品数据
const originalMedicine = advice.originalMedicine
// 解析contentJson
let contentJsonData = {}
if (originalMedicine && originalMedicine.contentJson) {
try {
contentJsonData = JSON.parse(originalMedicine.contentJson)
} catch (e) {
return
}
}
// 构造请求参数(与门诊医生工作站完全一致)
return {
// 基础信息
dbOpType: originalMedicine?.requestId ? '2' : '1', // 有请求ID则为修改否则为新增
adviceType: originalMedicine?.adviceType || 1, // 使用原始类型或默认3
requestId: originalMedicine?.requestId,
chargeItemId: originalMedicine?.chargeItemId,
contentJson: originalMedicine?.contentJson,
categoryCode: contentJsonData.categoryCode || originalMedicine?.categoryCode,
positionId: originalMedicine?.positionId || contentJsonData.positionId,
pharmacologyCategoryCode: contentJsonData.pharmacologyCategoryCode || originalMedicine?.pharmacologyCategoryCode,
partPercent: originalMedicine?.partPercent || contentJsonData.partPercent || 1,
partAttributeEnum: originalMedicine?.partAttributeEnum || contentJsonData.partAttributeEnum,
executeNum: contentJsonData.executeNum || originalMedicine?.executeNum || 1,
prescriptionNo: originalMedicine?.prescriptionNo,
// 数量和单位
quantity: originalMedicine?.quantity || 1,
dispensePerDuration: originalMedicine?.dispensePerDuration || contentJsonData.dispensePerDuration,
unitCode: contentJsonData.unitCode || originalMedicine?.unitCode || advice.unit,
unitPrice: originalMedicine?.unitPrice || contentJsonData.unitPrice || 0,
totalPrice: originalMedicine?.totalPrice || contentJsonData.totalPrice || 0,
definitionId: contentJsonData.definitionId || originalMedicine?.definitionId,
definitionDetailId: contentJsonData.definitionDetailId || originalMedicine?.definitionDetailId,
lotNumber: originalMedicine?.lotNumber || contentJsonData.lotNumber,
// 状态和类型
statusEnum: originalMedicine?.statusEnum || 2, // 默认状态:已发送
categoryEnum: originalMedicine?.categoryEnum || contentJsonData.categoryEnum,
// 药品/诊疗信息
adviceDefinitionId: contentJsonData.adviceDefinitionId || originalMedicine?.adviceDefinitionId,
adviceTableName: contentJsonData.adviceTableName || originalMedicine?.adviceTableName,
adviceName: advice.adviceName,
minUnitQuantity: originalMedicine?.minUnitQuantity || contentJsonData.minUnitQuantity,
// 患者和就诊信息
patientId: props.patientInfo.patientId,
practitionerId: currentUser.value.id || originalMedicine?.practitionerId, // 开方医生
locationId: contentJsonData.locationId || originalMedicine?.locationId,
performLocation: originalMedicine?.performLocation || contentJsonData.performLocation,
founderOrgId: currentUser.value.id || originalMedicine?.founderOrgId, // 开方人科室
encounterId: props.patientInfo.visitId || contentJsonData.encounterId || originalMedicine?.encounterId,
accountId: contentJsonData.accountId || originalMedicine?.accountId,
conditionId: contentJsonData.conditionId || originalMedicine?.conditionId,
encounterDiagnosisId: originalMedicine?.encounterDiagnosisId || contentJsonData.encounterDiagnosisId,
conditionDefinitionId: originalMedicine?.conditionDefinitionId || contentJsonData.conditionDefinitionId,
// 治疗信息
therapyEnum: originalMedicine?.therapyEnum || contentJsonData.therapyEnum || 1, // 默认临时医嘱
methodCode: advice.usage, // 用法(使用转换后的中文名称)
rateCode: advice.frequency, // 频次
dose: advice.dosage,
firstDose: originalMedicine?.firstDose || contentJsonData.firstDose,
doseUnitCode: contentJsonData.doseUnitCode || originalMedicine?.doseUnitCode || advice.unit,
skinTestFlag: originalMedicine?.skinTestFlag || contentJsonData.skinTestFlag,
injectFlag: originalMedicine?.injectFlag || contentJsonData.injectFlag,
// 分组信息
groupId: originalMedicine?.groupId,
packageId: originalMedicine?.packageId || contentJsonData.packageId,
// 诊疗活动信息
activityId: contentJsonData.adviceDefinitionId || originalMedicine?.adviceDefinitionId, // 对于诊疗类型,使用 adviceDefinitionId 作为 activity_id
// 医保信息
ybClassEnum: originalMedicine?.ybClassEnum || contentJsonData.ybClassEnum,
// 中药信息
chineseHerbsDoseQuantity: originalMedicine?.chineseHerbsDoseQuantity || contentJsonData.chineseHerbsDoseQuantity || 1
}
})
}
// 调用保存医嘱接口
const response = await savePrescription(saveData)
if (response.code === 200) {
ElMessage.success('临时医嘱保存成功')
// 构建提交数据
const submitData = {
patientInfo: props.patientInfo,
billingMedicines: props.billingMedicines,
temporaryAdvices: displayAdvices.value,
signature: {
doctorName: currentUser.value.name,
signatureTime: signatureTime.value
}
}
// 通知父组件
emit('submit', submitData)
} else {
ElMessage.error('保存医嘱失败:' + (response.msg || response.message || '未知错误'))
}
} catch (error) {
ElMessage.error('保存医嘱失败,请重试')
}
}
const handleCancel = () => {
// 通知父组件关闭弹窗
emit('cancel')
}
</script>
<style scoped>
.temporary-medical-container {
padding: 20px;
max-height: 80vh;
overflow-y: auto;
}
.patient-info-section {
margin-bottom: 24px;
}
.patient-info-header h3 {
margin: 0 0 16px 0;
color: #333;
font-size: 18px;
}
.medicine-section,
.advice-section,
.signature-section {
margin-bottom: 24px;
}
.medicine-section h4,
.advice-section h4,
.signature-section h4 {
margin: 0 0 16px 0;
color: #666;
font-size: 16px;
}
.medicine-summary {
display: flex;
justify-content: flex-end;
gap: 20px;
padding: 12px;
background-color: #f5f7fa;
border-radius: 4px;
}
.summary-item {
font-size: 14px;
color: #666;
}
.summary-item.total {
font-weight: bold;
color: #409eff;
}
.signature-content {
padding: 16px;
background-color: #f5f7fa;
border-radius: 4px;
}
.signature-info {
margin-bottom: 12px;
font-size: 14px;
}
.signature-info span:first-child {
font-weight: bold;
margin-right: 8px;
}
.unsigned {
color: #f56c6c;
}
.signature-actions {
margin-top: 16px;
}
.footer-actions {
display: flex;
justify-content: center;
align-items: center;
gap: 30px;
margin-top: 24px;
padding-top: 16px;
border-top: 1px solid #e4e7ed;
}
.cancel-btn {
width: 120px;
height: 48px;
font-size: 16px;
border: 1px solid #dcdfe6;
transition: all 0.3s ease;
}
.cancel-btn:hover {
border-color: #409eff;
color: #409eff;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(64, 158, 255, 0.2);
}
.sign-submit-btn {
width: 200px;
height: 48px;
font-size: 16px;
font-weight: bold;
background: linear-gradient(135deg, #67c23a 0%, #85ce61 100%);
border: none;
color: white;
transition: all 0.3s ease;
box-shadow: 0 4px 8px rgba(103, 194, 58, 0.3);
}
.sign-submit-btn:hover {
transform: translateY(-3px);
box-shadow: 0 6px 12px rgba(103, 194, 58, 0.4);
}
.sign-submit-btn:active {
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(103, 194, 58, 0.4);
}
.sign-submit-btn.signed {
background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
box-shadow: 0 4px 8px rgba(64, 158, 255, 0.3);
}
.sign-submit-btn.signed:hover {
box-shadow: 0 6px 12px rgba(64, 158, 255, 0.4);
}
.sign-dialog-content p {
margin-bottom: 16px;
color: #666;
}
.dialog-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
}
:deep(.el-descriptions) {
margin-top: 8px;
}
:deep(.el-descriptions__label) {
font-weight: bold;
}
</style>