完成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

@@ -2,11 +2,14 @@ package com.openhis.web.clinicalmanage.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.core.common.core.domain.R;
import com.core.common.core.domain.model.LoginUser;
import com.core.common.utils.SecurityUtils;
import com.openhis.web.clinicalmanage.appservice.ISurgicalScheduleAppService;
import com.openhis.web.clinicalmanage.dto.OpCreateScheduleDto;
import com.openhis.web.clinicalmanage.dto.OpScheduleDto;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.User;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
@@ -98,4 +101,29 @@ public class SurgicalScheduleController {
surgicalScheduleAppService.exportSurgerySchedule(opScheduleDto, response);
}
/**
* 验证密码
* @param password 密码
* @return 结果
*/
@PostMapping(value = "/checkPassword")
public R<?> checkPassword(@RequestParam String password) {
try {
//获取当前登录用户信息
LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser == null) {
return R.fail("用户未登录");
}
// 直接使用 SecurityUtils.matchesPassword 进行密码验证
boolean isMatch = SecurityUtils.matchesPassword(password, loginUser.getPassword());
if (isMatch) {
return R.ok(true);
} else {
return R.fail("密码错误");
}
} catch (Exception e) {
return R.fail("密码验证失败:" + e.getMessage());
}
}
}

View File

@@ -635,6 +635,10 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
} else {
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue());
}
// 来源业务单据号:如果前端指定了来源业务单据号,设置该字段
if (adviceSaveDto.getSourceBillNo() != null) {
chargeItem.setSourceBillNo(adviceSaveDto.getSourceBillNo());
}
chargeItem.setPrescriptionNo(adviceSaveDto.getPrescriptionNo()); // 处方号
chargeItem.setPatientId(adviceSaveDto.getPatientId()); // 患者
chargeItem.setContextEnum(adviceSaveDto.getAdviceType()); // 类型
@@ -652,10 +656,6 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
chargeItem.setConditionId(adviceSaveDto.getConditionId()); // 诊断id
chargeItem.setEncounterDiagnosisId(adviceSaveDto.getEncounterDiagnosisId()); // 就诊诊断id
chargeItem.setDispenseId(dispenseId); // 发放ID
// 来源业务单据号:如果前端指定了来源业务单据号,设置该字段
if (adviceSaveDto.getSourceBillNo() != null) {
chargeItem.setSourceBillNo(adviceSaveDto.getSourceBillNo());
}
chargeItem.setTenantId(tenantId); // 设置租户ID (修复本次报错)
chargeItem.setCreateBy(currentUsername); // 设置创建人
chargeItem.setCreateTime(curDate); // 设置创建时间
@@ -768,6 +768,16 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
// 保存耗材费用项
chargeItem = new ChargeItem();
// 生成来源:如果前端指定了生成来源,使用前端值;否则使用默认的医生开立
if (adviceSaveDto.getGenerateSourceEnum() != null) {
chargeItem.setGenerateSourceEnum(adviceSaveDto.getGenerateSourceEnum());
} else {
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue());
}
// 来源业务单据号:如果前端指定了来源业务单据号,设置该字段
if (adviceSaveDto.getSourceBillNo() != null) {
chargeItem.setSourceBillNo(adviceSaveDto.getSourceBillNo());
}
chargeItem.setId(adviceSaveDto.getChargeItemId()); // 费用项id
chargeItem.setTenantId(tenantId); // 补全租户ID
chargeItem.setCreateBy(currentUsername); // 补全创建人
@@ -909,6 +919,16 @@ public class DoctorStationAdviceAppServiceImpl implements IDoctorStationAdviceAp
// 保存时保存诊疗费用项
if (is_save) {
chargeItem = new ChargeItem();
// 生成来源:如果前端指定了生成来源,使用前端值;否则使用默认的医生开立
if (adviceSaveDto.getGenerateSourceEnum() != null) {
chargeItem.setGenerateSourceEnum(adviceSaveDto.getGenerateSourceEnum());
} else {
chargeItem.setGenerateSourceEnum(GenerateSource.DOCTOR_PRESCRIPTION.getValue());
}
// 来源业务单据号:如果前端指定了来源业务单据号,设置该字段
if (adviceSaveDto.getSourceBillNo() != null) {
chargeItem.setSourceBillNo(adviceSaveDto.getSourceBillNo());
}
chargeItem.setId(adviceSaveDto.getChargeItemId()); // 费用项id
chargeItem.setTenantId(tenantId); // 补全租户ID
chargeItem.setCreateBy(currentUsername); // 补全创建人

View File

@@ -59,4 +59,13 @@ export function exportSurgerySchedule(query) {
method: 'get',
params: query
})
}
//签名密码验证
export function checkPassword(params) {
return request({
url: '/clinical-manage/surgery-schedule/checkPassword',
method: 'POST',
params: params
})
}

View File

@@ -55,6 +55,9 @@
<el-col :span="1.5">
<el-button type="success" plain icon="Money" @click="handleChargeCharge(selectedRow)" :disabled="!selectedRow"> 计费 </el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Document" @click="handleMedicalAdvice(selectedRow)" :disabled="!selectedRow"> 医嘱 </el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Download" @click="handleExport">导出表格</el-button>
</el-col>
@@ -62,7 +65,7 @@
</el-row>
<!-- 中部表格区 -->
<el-table v-loading="loading" :data="surgeryList" row-key="scheduleId" :row-class-name="getRowClassName" highlight-current-row @current-change="handleCurrentChange">
<el-table v-loading="loading" :data="surgeryList" row-key="scheduleId" :row-class-name="getRowClassName" @current-change="handleCurrentChange">
<el-table-column label="ID" align="center" width="80">
<template #default="{ $index }">
{{ (applyQueryParams.pageNo - 1) * applyQueryParams.pageSize + $index + 1 }}
@@ -799,6 +802,18 @@
</div>
</div>
</el-dialog>
<!-- 临时医嘱弹窗 -->
<el-dialog :title="temporaryMedicalTitle" v-model="showTemporaryMedical" width="80%" append-to-body :close-on-click-modal="false">
<temporary-medical
:patient-info="temporaryPatientInfo"
:billing-medicines="temporaryBillingMedicines"
v-model:temporary-advices="temporaryAdvices"
@submit="handleTemporaryMedicalSubmit"
@cancel="handleTemporaryMedicalCancel"
@delete-advice="handleDeleteTemporaryAdvice"
/>
</el-dialog>
</div>
</template>
@@ -810,6 +825,9 @@ import { useDict } from '@/utils/dict'
import download from '@/plugins/download'
import Prescriptionlist from '@/views/clinicmanagement/bargain/component/prescriptionlist.vue'
import useUserStore from '@/store/modules/user'
import { ElMessage } from 'element-plus'
// 导入计费相关接口
import { getPrescriptionList } from '@/views/clinicmanagement/bargain/component/api'
// API 导入
import { getSurgerySchedulePage, addSurgerySchedule, updateSurgerySchedule, deleteSurgerySchedule, getSurgeryScheduleDetail } from '@/api/surgicalschedule'
@@ -820,6 +838,7 @@ import { getTestResultPage} from '@/views/inpatientDoctor/home/components/applic
import { getTenantPage } from '@/api/system/tenant'
import { getContract } from '@/views/inpatientDoctor/home/components/api.js'
import SurgeryCharge from '../charge/surgerycharge/index.vue'
import TemporaryMedical from './temporaryMedical.vue'
const { proxy } = getCurrentInstance()
const userStore = useUserStore()
@@ -947,6 +966,13 @@ const chargePatientInfo = ref({})
const chargeSurgeryInfo = ref({})
const prescriptionRef = ref()
// 临时医嘱弹窗
const showTemporaryMedical = ref(false)
const temporaryMedicalTitle = ref('门诊术中临时医嘱')
const temporaryPatientInfo = ref({})
const temporaryBillingMedicines = ref([])
const temporaryAdvices = ref([])
// 下拉列表数据
const orgList = ref([])
const deptList = ref([])
@@ -1026,8 +1052,7 @@ function loadOrgList() {
orgList.value = []
}
})
.catch(error => {
console.error('加载卫生机构列表失败:', error)
.catch(() => {
proxy.$modal.msgError('获取卫生机构列表失败')
orgList.value = []
})
@@ -1046,7 +1071,6 @@ function loadDeptList() {
}
})
.catch(error => {
console.error('加载科室列表失败:', error)
proxy.$modal.msgError('获取科室列表失败')
deptList.value = []
})
@@ -1065,7 +1089,6 @@ function loadDoctorList() {
}
})
.catch(error => {
console.error('加载医生列表失败:', error)
proxy.$modal.msgError('获取医生列表失败')
doctorList.value = []
})
@@ -1084,7 +1107,6 @@ function loadNurseList() {
}
})
.catch(error => {
console.error('加载护士列表失败:', error)
proxy.$modal.msgError('获取护士列表失败')
nurseList.value = []
})
@@ -1103,7 +1125,6 @@ function loadOperatingRoomList() {
}
})
.catch(error => {
console.error('加载手术室列表失败:', error)
proxy.$modal.msgError('获取手术室列表失败')
operatingRoomList.value = []
})
@@ -1116,7 +1137,6 @@ function getList() {
surgeryList.value = res.data.records
total.value = res.data.total
}).catch(error => {
console.error('获取手术安排列表失败:', error)
proxy.$modal.msgError('获取手术安排列表失败,请稍后重试')
surgeryList.value = []
total.value = 0
@@ -1170,8 +1190,7 @@ function handleEdit(row) {
} else {
proxy.$modal.msgError('获取手术安排详情失败')
}
}).catch(error => {
console.error('获取手术安排详情失败:', error)
}).catch(() => {
proxy.$modal.msgError('获取手术安排详情失败')
})
open.value = true
@@ -1190,8 +1209,7 @@ function handleView(row) {
} else {
proxy.$modal.msgError('获取手术安排详情失败')
}
}).catch(error => {
console.error('获取手术安排详情失败:', error)
}).catch(() => {
proxy.$modal.msgError('获取手术安排详情失败')
})
open.value = true
@@ -1215,7 +1233,8 @@ function handleDelete(row) {
}).then(() => {
getPageList()
proxy.$modal.msgSuccess('手术安排已取消')
}).catch(error => {
}).catch(() => {
return
})
}
@@ -1232,32 +1251,18 @@ async function handleChargeCharge(row) {
return
}
console.log('计费按钮被点击,行数据:', row)
// 检查用户信息中的机构信息
console.log('完整的用户信息:', JSON.stringify(userStore, null, 2))
console.log('用户机构信息:', {
organizationId: userStore.organizationId,
orgId: userStore.orgId,
tenantId: userStore.tenantId,
userInfo: userStore.userInfo
})
// 调用接口获取账户信息
let accountId = null
try {
const contractResult = await getContract({ encounterId: row.visitId })
console.log('账户信息接口返回:', contractResult)
if (contractResult.code === 200 && contractResult.data && contractResult.data.length > 0) {
// 从返回数据中提取accountId - data是数组取第一个元素的accountId
accountId = contractResult.data[0].accountId || contractResult.data[0].id
console.log('获取到的accountId:', accountId)
} else {
console.warn('获取账户信息失败:', contractResult.msg)
proxy.$modal.msgError('获取账户信息失败')
}
} catch (error) {
console.error('调用账户信息接口出错:', error)
return
}
// 设置计费弹窗数据 - 直接复制划价页面的逻辑
@@ -1279,7 +1284,9 @@ async function handleChargeCharge(row) {
// 添加账户ID
accountId: accountId,
// 添加手术申请单号用于追溯
sourceBillNo: row.applyId || row.operCode
sourceBillNo: row.applyId,
//添加计费标志手术计费
generateSourceEnum: 6
}
chargeSurgeryInfo.value = {
@@ -1287,21 +1294,12 @@ async function handleChargeCharge(row) {
surgeryNo: row.operCode
}
console.log('计费弹窗数据设置完成:', {
chargeDialogTitle: chargeDialogTitle.value,
chargePatientInfo: chargePatientInfo.value,
chargeSurgeryInfo: chargeSurgeryInfo.value
})
// 打开计费弹窗
showChargeDialog.value = true
console.log('计费弹窗状态:', showChargeDialog.value)
// 延迟加载处方列表,确保组件已经渲染
nextTick(() => {
console.log('nextTick执行prescriptionRef:', prescriptionRef.value)
if (prescriptionRef.value && prescriptionRef.value.getListInfo) {
console.log('调用prescriptionRef.getListInfo()')
prescriptionRef.value.getListInfo()
}
})
@@ -1314,6 +1312,114 @@ function closeChargeDialog() {
chargeSurgeryInfo.value = {}
}
// 处理医嘱按钮点击事件
function handleMedicalAdvice(row) {
// 如果没有传入行数据,使用选中的行
if (!row && selectedRow.value) {
row = selectedRow.value
}
// 如果还是没有行数据,显示提示
if (!row) {
proxy.$modal.msgWarning('请先选择要开具医嘱的手术安排')
return
}
// 设置临时医嘱弹窗数据
temporaryPatientInfo.value = {
patientName: row.patientName,
visitId: row.visitId,
operCode: row.operCode,
roomCode: row.roomCode,
doctorName: userStore.nickName,
role: userStore.roles[0],
}
// 初始化临时医嘱列表
temporaryAdvices.value = []
// 调用计费接口获取数据
getPrescriptionList(row.visitId).then((res) => {
if (res.code === 200 && res.data) {
// 过滤数据,只保留 adviceType 值为 1 的药品
const filteredItems = res.data.filter(item => {
try {
// 尝试从 contentJson 中解析数据
const contentData = JSON.parse(item.contentJson)
const adviceType = Number(contentData.adviceType)
return adviceType === 3
} catch (e) {
// 如果解析失败,尝试使用顶层的 adviceType
const adviceType = Number(item.adviceType)
return adviceType === 3
}
})
// 将过滤后的数据转换为临时医嘱需要的格式
temporaryBillingMedicines.value = filteredItems.map(item => {
try {
// 从 contentJson 中解析详细数据
const contentData = JSON.parse(item.contentJson)
return {
medicineName: contentData.adviceName || item.adviceName || '',
specification: contentData.volume || contentData.specification || '',
quantity: contentData.quantity || item.quantity || 0,
batchNumber: contentData.lotNumber || item.lotNumber || '',
unitPrice: contentData.unitPrice || item.unitPrice || 0,
subtotal: contentData.totalPrice || item.totalPrice || 0,
insuranceType: contentData.insuranceType === 1 ? '医保' : '自费'
}
} catch (e) {
// 如果解析失败,使用顶层数据
return {
medicineName: item.adviceName || '',
specification: item.specification || '',
quantity: item.quantity || 0,
batchNumber: item.lotNumber || '',
unitPrice: item.unitPrice || 0,
subtotal: item.totalPrice || 0,
insuranceType: item.insuranceType === 1 ? '医保' : '自费'
}
}
})
} else {
// 如果没有数据或接口调用失败,初始化空列表
temporaryBillingMedicines.value = []
}
}).catch(() => {
temporaryBillingMedicines.value = []
})
// 打开临时医嘱弹窗
showTemporaryMedical.value = true
}
// 关闭临时医嘱弹窗
function closeTemporaryMedical() {
showTemporaryMedical.value = false
temporaryPatientInfo.value = {}
temporaryBillingMedicines.value = []
temporaryAdvices.value = []
}
// 处理临时医嘱提交
function handleTemporaryMedicalSubmit(data) {
// 这里可以调用后端API保存临时医嘱数据
proxy.$modal.msgSuccess('临时医嘱提交成功')
closeTemporaryMedical()
}
// 处理临时医嘱取消
function handleTemporaryMedicalCancel() {
closeTemporaryMedical()
}
// 处理删除临时医嘱
function handleDeleteTemporaryAdvice(index) {
temporaryAdvices.value.splice(index, 1)
proxy.$modal.msgSuccess('临时医嘱已删除')
}
// 格式化计费弹窗中的日期
function formatChargeDate(date) {
if (!date) return '-'
@@ -1394,15 +1500,13 @@ function submitForm() {
proxy.$refs['surgeryRef'].validate((valid) => {
if (valid) {
const submitData = { ...form }
console.log("scheduleId:", form.scheduleId)
if (!form.scheduleId) {
// 新增手术安排
addSurgerySchedule(submitData).then((res) => {
proxy.$modal.msgSuccess('新增成功')
open.value = false
getPageList()
}).catch(error => {
console.error('新增手术安排失败:', error)
}).catch(() => {
proxy.$message.error('新增手术安排失败,请检查表单信息')
})
} else {
@@ -1411,8 +1515,7 @@ function submitForm() {
proxy.$modal.msgSuccess('修改成功')
open.value = false
getPageList()
}).catch(error => {
console.error('更新手术安排失败:', error)
}).catch(() => {
proxy.$message.error('更新手术安排失败,请检查表单信息')
})
}
@@ -1455,8 +1558,7 @@ function getSurgicalScheduleList() {
const responseData = res.data.data || res.data
applyList.value = responseData.records || []
applyTotal.value = responseData.total || 0
}).catch(error => {
console.error('获取手术申请列表失败:', error)
}).catch(() => {
proxy.$modal.msgError('获取手术申请列表失败,请稍后重试')
applyList.value = []
applyTotal.value = 0

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>