Compare commits

...

12 Commits

Author SHA1 Message Date
Ranyunqiao
5132de3680 bug 445 497 565 2026-05-25 15:49:49 +08:00
232577caaa fix: #579 (codex) 2026-05-24 15:07:56 +08:00
926c1f68e3 fix: #568 (codex) 2026-05-24 15:03:14 +08:00
f11fa023c4 fix: #579 (codex) 2026-05-24 14:57:10 +08:00
1ac2252c34 fix: #568 (codex) 2026-05-24 14:55:18 +08:00
2b2fcc0f20 fix: #568 (codex) 2026-05-24 14:53:07 +08:00
72b0040921 fix: #568 (codex) 2026-05-24 14:45:15 +08:00
e439cf46cf update his-repo submodule 2026-05-24 14:40:20 +08:00
24ad69dfed fix: #568 门诊日结排版 #571 撤回流程 #579 报表格式 2026-05-24 14:37:31 +08:00
310847eae4 Fix Bug #571: 修复检验申请撤回时双重错误提示
根因:响应拦截器已对非200响应(code=500等)显示ElMessage错误提示,
但handleWithdraw的catch块再次调用proxy.$modal.msgError显示相同错误,
导致用户看到两个红色错误弹窗。

修复:将handleWithdraw和handleDelete的catch块改为静默处理,
与examineApplication.vue的handleRecall模式一致——响应拦截器已统一处理错误提示。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 12:37:27 +08:00
fd0fe29e54 Fix Bug #568: 根因+修复方案摘要 2026-05-22 12:36:30 +08:00
1406bbfcee Fix Bug #568: 修复门诊日结页面排版混乱 - 优化CSS Grid布局间距和字体
- 增大 grid gap 从 10px 16px 到 12px 24px,改善行列间距
- 为 .report-item 添加 gap: 8px,标签与数值间留白
- 统一 .label 字体大小为 14px,保持视觉一致
- 移除 .value 的 overflow:hidden,避免内容截断
- 调整费用性质下拉框宽度为 130px
2026-05-22 12:35:35 +08:00
32 changed files with 3143 additions and 458 deletions

Submodule his-repo updated: 5de8a22418...ea1271db8a

View File

@@ -77,8 +77,10 @@ public class DoctorStationAdviceController {
*/ */
@PostMapping(value = "/save-advice") @PostMapping(value = "/save-advice")
@RepeatSubmit(interval = 5000, message = "请勿重复提交医嘱,请稍候再试") @RepeatSubmit(interval = 5000, message = "请勿重复提交医嘱,请稍候再试")
public R<?> saveAdvice(@RequestBody AdviceSaveParam adviceSaveParam) { public R<?> saveAdvice(@RequestBody AdviceSaveParam adviceSaveParam,
return iDoctorStationAdviceAppService.saveAdvice(adviceSaveParam, AdviceOpType.SAVE_ADVICE.getCode()); @RequestParam(required = false, defaultValue = "1") String adviceOpType) {
// 🔧 Bug #445 修复:使用前端传入的 adviceOpType 参数1=保存草稿2=签发),而非硬编码
return iDoctorStationAdviceAppService.saveAdvice(adviceSaveParam, adviceOpType);
} }
/** /**

View File

@@ -178,11 +178,24 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
inpatientAdviceParam.setEncounterIds(null); inpatientAdviceParam.setEncounterIds(null);
Integer exeStatus = inpatientAdviceParam.getExeStatus(); Integer exeStatus = inpatientAdviceParam.getExeStatus();
inpatientAdviceParam.setExeStatus(null); inpatientAdviceParam.setExeStatus(null);
// requestStatus由前端tab传入通过QueryWrapper自动添加到SQL外层WHERE过滤 // 提取requestStatus手动处理支持COMPLETED(3)和CHECK_VERIFIED(10)同时查询
Integer requestStatus = inpatientAdviceParam.getRequestStatus();
inpatientAdviceParam.setRequestStatus(null);
// 构建查询条件 // 构建查询条件
QueryWrapper<InpatientAdviceParam> queryWrapper QueryWrapper<InpatientAdviceParam> queryWrapper
= HisQueryUtils.buildQueryWrapper(inpatientAdviceParam, null, null, null); = HisQueryUtils.buildQueryWrapper(inpatientAdviceParam, null, null, null);
// 手动拼接requestStatus条件COMPLETED(3)时同时包含CHECK_VERIFIED(10)
// UNION查询外层列名为request_statusT1.status_enum AS request_status不是status_enum
if (requestStatus != null) {
if (RequestStatus.COMPLETED.getValue().equals(requestStatus)) {
queryWrapper.in("request_status",
RequestStatus.COMPLETED.getValue(), RequestStatus.CHECK_VERIFIED.getValue());
} else {
queryWrapper.eq("request_status", requestStatus);
}
}
// 手动拼接住院患者id条件 // 手动拼接住院患者id条件
if (encounterIds != null && !encounterIds.isEmpty()) { if (encounterIds != null && !encounterIds.isEmpty()) {
List<Long> encounterIdList List<Long> encounterIdList
@@ -315,19 +328,29 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
Long practitionerId = SecurityUtils.getLoginUser().getPractitionerId(); Long practitionerId = SecurityUtils.getLoginUser().getPractitionerId();
Date checkDate = new Date(); Date checkDate = new Date();
if (!serviceRequestList.isEmpty()) { if (!serviceRequestList.isEmpty()) {
// 更新服务请求状态已完成 List<Long> serviceReqIds = serviceRequestList.stream().map(PerformInfoDto::getRequestId).toList();
serviceRequestService.updateCompleteRequestStatus( // 先查询服务请求,按 categoryEnum 分流:检查类(23)走 CHECK_VERIFIED其余走 COMPLETED
serviceRequestList.stream().map(PerformInfoDto::getRequestId).toList(), practitionerId, checkDate); List<ServiceRequest> allServiceRequests = serviceRequestService.listByIds(serviceReqIds);
List<ServiceRequest> serviceRequests = serviceRequestService List<Long> checkReqIds = allServiceRequests.stream()
.listByIds(serviceRequestList.stream().map(PerformInfoDto::getRequestId).collect(Collectors.toList())); .filter(sr -> ActivityDefCategory.TEST.getValue().equals(sr.getCategoryEnum()))
for (ServiceRequest serviceRequest : serviceRequests) { .map(ServiceRequest::getId).toList();
// 判断医嘱类型 List<Long> otherReqIds = allServiceRequests.stream()
.filter(sr -> !ActivityDefCategory.TEST.getValue().equals(sr.getCategoryEnum()))
.map(ServiceRequest::getId).toList();
// 检查类 → 已校对CHECK_VERIFIED=10
if (!checkReqIds.isEmpty()) {
serviceRequestService.updateCheckVerifiedStatus(checkReqIds, practitionerId, checkDate);
}
// 其他类 → 已完成COMPLETED=3
if (!otherReqIds.isEmpty()) {
serviceRequestService.updateCompleteRequestStatus(otherReqIds, practitionerId, checkDate);
}
// 处理转科/出院等特殊医嘱
for (ServiceRequest serviceRequest : allServiceRequests) {
if (ActivityDefCategory.TRANSFER.getValue().equals(serviceRequest.getCategoryEnum())) { if (ActivityDefCategory.TRANSFER.getValue().equals(serviceRequest.getCategoryEnum())) {
// 更新患者状态 待转科
encounterService.updateEncounterStatus(serviceRequest.getEncounterId(), encounterService.updateEncounterStatus(serviceRequest.getEncounterId(),
EncounterZyStatus.PENDING_TRANSFER.getValue()); EncounterZyStatus.PENDING_TRANSFER.getValue());
} else if (ActivityDefCategory.DISCHARGE.getValue().equals(serviceRequest.getCategoryEnum())) { } else if (ActivityDefCategory.DISCHARGE.getValue().equals(serviceRequest.getCategoryEnum())) {
// 更新患者状态 待出院
encounterService.updateEncounterStatus(serviceRequest.getEncounterId(), encounterService.updateEncounterStatus(serviceRequest.getEncounterId(),
EncounterZyStatus.AWAITING_DISCHARGE.getValue()); EncounterZyStatus.AWAITING_DISCHARGE.getValue());
} }
@@ -442,6 +465,15 @@ public class AdviceProcessAppServiceImpl implements IAdviceProcessAppService {
List<ServiceRequestUseExe> actUseExeList = this.assemblyActivity(activityList); List<ServiceRequestUseExe> actUseExeList = this.assemblyActivity(activityList);
// 处理诊疗执行 // 处理诊疗执行
this.exeActivity(actUseExeList, exeDate); this.exeActivity(actUseExeList, exeDate);
// 检查类医嘱执行后,状态改为"待接收"PENDING_RECEIVE=11
List<Long> actReqIds = activityList.stream().map(AdviceExecuteDetailParam::getRequestId).toList();
List<ServiceRequest> executedReqs = serviceRequestService.listByIds(actReqIds);
List<Long> checkReqIds = executedReqs.stream()
.filter(sr -> ActivityDefCategory.TEST.getValue().equals(sr.getCategoryEnum()))
.map(ServiceRequest::getId).toList();
if (!checkReqIds.isEmpty()) {
serviceRequestService.updatePendingReceiveStatus(checkReqIds);
}
} }
return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[]{"医嘱执行"})); return R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, new Object[]{"医嘱执行"}));

View File

@@ -47,10 +47,19 @@
) THEN 5 ) THEN 5
WHEN EXISTS ( WHEN EXISTS (
SELECT 1 FROM wor_service_request ws SELECT 1 FROM wor_service_request ws
INNER JOIN lab_specimen ls ON ls.service_id = ws.id
WHERE ws.prescription_no = drf.prescription_no AND ws.delete_flag = '0' WHERE ws.prescription_no = drf.prescription_no AND ws.delete_flag = '0'
AND ls.collection_status_enum >= 1 AND ws.status_enum = 12
) THEN 4 ) THEN 4
WHEN EXISTS (
SELECT 1 FROM wor_service_request ws
WHERE ws.prescription_no = drf.prescription_no AND ws.delete_flag = '0'
AND ws.status_enum = 11
) THEN 3
WHEN EXISTS (
SELECT 1 FROM wor_service_request ws
WHERE ws.prescription_no = drf.prescription_no AND ws.delete_flag = '0'
AND ws.status_enum = 10
) THEN 2
WHEN EXISTS ( WHEN EXISTS (
SELECT 1 FROM wor_service_request ws SELECT 1 FROM wor_service_request ws
WHERE ws.prescription_no = drf.prescription_no AND ws.delete_flag = '0' WHERE ws.prescription_no = drf.prescription_no AND ws.delete_flag = '0'

View File

@@ -57,7 +57,22 @@ public enum RequestStatus implements HisEnumInterface {
/** /**
* 未知 * 未知
*/ */
UNKNOWN(9, "unknown", "未知"); UNKNOWN(9, "unknown", "未知"),
/**
* 已校对(检查申请:护士校对通过)
*/
CHECK_VERIFIED(10, "check_verified", "已校对"),
/**
* 待接收(检查申请:等待医技科室接单)
*/
PENDING_RECEIVE(11, "pending_receive", "待接收"),
/**
* 已接收(检查申请:医技科室已接单)
*/
CHECK_RECEIVED(12, "check_received", "已接收");
@EnumValue @EnumValue
private final Integer value; private final Integer value;

View File

@@ -39,6 +39,22 @@ public interface IServiceRequestService extends IService<ServiceRequest> {
*/ */
void updateCompleteRequestStatus(List<Long> serReqIdList, Long practitionerId, Date checkDate); void updateCompleteRequestStatus(List<Long> serReqIdList, Long practitionerId, Date checkDate);
/**
* 更新检查申请状态已校对护士校对检查申请后状态为CHECK_VERIFIED而非COMPLETED
*
* @param serReqIdList 服务请求id列表
* @param practitionerId 校对人
* @param checkDate 校对时间
*/
void updateCheckVerifiedStatus(List<Long> serReqIdList, Long practitionerId, Date checkDate);
/**
* 更新检查申请状态待接收护士执行检查申请后状态为PENDING_RECEIVE
*
* @param serReqIdList 服务请求id列表
*/
void updatePendingReceiveStatus(List<Long> serReqIdList);
/** /**
* 获取执行过的诊疗数据 * 获取执行过的诊疗数据
* *

View File

@@ -66,6 +66,31 @@ public class ServiceRequestServiceImpl extends ServiceImpl<ServiceRequestMapper,
.eq(ServiceRequest::getDeleteFlag, DelFlag.NO.getCode())); .eq(ServiceRequest::getDeleteFlag, DelFlag.NO.getCode()));
} }
/**
* 更新检查申请状态已校对护士校对检查申请后状态为CHECK_VERIFIED而非COMPLETED
*
* @param serReqIdList 服务请求id列表
*/
@Override
public void updateCheckVerifiedStatus(List<Long> serReqIdList, Long practitionerId, Date checkDate) {
baseMapper.update(new ServiceRequest().setStatusEnum(RequestStatus.CHECK_VERIFIED.getValue())
.setPerformerCheckId(SecurityUtils.getLoginUser().getPractitionerId()).setCheckTime(DateUtils.getNowDate()),
new LambdaUpdateWrapper<ServiceRequest>().in(ServiceRequest::getId, serReqIdList)
.eq(ServiceRequest::getDeleteFlag, DelFlag.NO.getCode()));
}
/**
* 更新检查申请状态待接收护士执行检查申请后状态为PENDING_RECEIVE
*
* @param serReqIdList 服务请求id列表
*/
@Override
public void updatePendingReceiveStatus(List<Long> serReqIdList) {
baseMapper.update(new ServiceRequest().setStatusEnum(RequestStatus.PENDING_RECEIVE.getValue()),
new LambdaUpdateWrapper<ServiceRequest>().in(ServiceRequest::getId, serReqIdList)
.eq(ServiceRequest::getDeleteFlag, DelFlag.NO.getCode()));
}
/** /**
* 获取执行过的诊疗数据 * 获取执行过的诊疗数据
* *

View File

@@ -34,6 +34,12 @@ export const RequestStatus = {
ENDED: 7, ENDED: 7,
/** 未知 */ /** 未知 */
UNKNOWN: 9, UNKNOWN: 9,
/** 已校对(检查申请:护士校对通过) */
CHECK_VERIFIED: 10,
/** 待接收(检查申请:等待医技科室接单) */
PENDING_RECEIVE: 11,
/** 已接收(检查申请:医技科室已接单) */
CHECK_RECEIVED: 12,
}; };
/** /**
@@ -48,6 +54,9 @@ export const RequestStatusDescriptions = {
6: '停嘱', 6: '停嘱',
7: '不执行', 7: '不执行',
9: '未知', 9: '未知',
10: '已校对',
11: '待接收',
12: '已接收',
}; };
/** /**

View File

@@ -25,9 +25,9 @@ export function getAdviceBaseInfo(queryParams) {
/** /**
* 保存处方(单条) * 保存处方(单条)
* @param {Object} data - 处方数据 * @param {Object} data - 处方数据
* @param {String} adviceOpType - 医嘱操作类型:'0'=保存草稿,'1'=签发医嘱 * @param {String} adviceOpType - 医嘱操作类型:'1'=保存草稿(SAVE_ADVICE)'2'=签发(SIGN_ADVICE)
*/ */
export function savePrescription(data, adviceOpType = '0') { export function savePrescription(data, adviceOpType = '1') {
return request({ return request({
url: '/doctor-station/advice/save-advice', url: '/doctor-station/advice/save-advice',
method: 'post', method: 'post',

View File

@@ -1174,7 +1174,9 @@ function handleSaveSign(row, index) {
cleanRow.sourceBillNo = props.patientInfo.sourceBillNo; cleanRow.sourceBillNo = props.patientInfo.sourceBillNo;
} }
// 🔧 门诊计费场景:保存为草稿,让药品出现在临时医嘱弹窗"已引用计费药品(待生成医嘱)"中 // 🔧 门诊计费场景:保存为草稿,让药品出现在临时医嘱弹窗"已引用计费药品(待生成医嘱)"中
const adviceOpType = props.patientInfo.sourceBillNo ? '0' : '1' // 🔧 修复:后端 AdviceOpType 枚举:'1'=SAVE_ADVICE(草稿), '2'=SIGN_ADVICE(签发)
// 之前传 '0' 后端不识别,导致直接创建了已签发(statusEnum=2)的记录
const adviceOpType = props.patientInfo.sourceBillNo ? '1' : '1'
savePrescription({ adviceSaveList: [cleanRow] }, adviceOpType).then((res) => { savePrescription({ adviceSaveList: [cleanRow] }, adviceOpType).then((res) => {
if (res.code === 200) { if (res.code === 200) {
proxy.$modal.msgSuccess('保存成功'); proxy.$modal.msgSuccess('保存成功');

View File

@@ -1,30 +1,30 @@
<template> <template>
<div class="app-container"> <div class="app-container">
<el-form <el-form
:model="queryParams"
ref="queryRef"
:inline="true"
v-show="showSearch" v-show="showSearch"
ref="queryRef"
:model="queryParams"
:inline="true"
label-width="90px" label-width="90px"
> >
<el-form-item label="查询日期"> <el-form-item label="查询日期">
<el-date-picker <el-date-picker
v-model="queryTime" v-model="queryTime"
type="daterange" type="daterange"
start-placeholder="开始日期" start-placeholder="开始日期"
end-placeholder="结束日期" end-placeholder="结束日期"
style="width: 300px; margin-right: 20px" style="width: 300px"
@change="getValue"
value-format="YYYY-MM-DD" value-format="YYYY-MM-DD"
@change="getValue"
/> />
</el-form-item> </el-form-item>
<el-form-item label="费用性质"> <el-form-item label="费用性质">
<el-select <el-select
v-model="contractNo" v-model="contractNo"
placeholder="费用性质" placeholder="费用性质"
clearable clearable
style="width: 160px"
@change="getValue" @change="getValue"
style="width: 150px; margin-right: 30px"
> >
<el-option <el-option
v-for="item in contractList" v-for="item in contractList"
@@ -33,88 +33,241 @@
:value="item.busNo" :value="item.busNo"
/> />
</el-select> </el-select>
<el-button type="primary" plain icon="Search" @click="getValue">查询</el-button> </el-form-item>
<el-button type="primary" plain icon="Printer" @click="print">打印</el-button> <el-form-item class="search-buttons">
<el-button
type="primary"
plain
icon="Search"
@click="getValue"
>
查询
</el-button>
<el-button
type="primary"
plain
icon="Printer"
@click="print"
>
打印
</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<div v-loading="loading" class="report-container"> <div
<div class="report-title">门诊收费日结单</div> v-loading="loading"
<div class="report-section"> class="report-container"
<div class="section-title">基本信息</div> >
<div class="report-row cols-4"> <div class="report-title">
<div class="report-item"><span class="label">经办人姓名</span><span class="value">{{ userStore.nickName }}</span></div> 门诊收费日结单
<div class="report-item"><span class="label">科室</span><span class="value">{{ userStore.orgName }}</span></div>
<div class="report-item span-2"><span class="label">时间</span><span class="value">{{ queryTime[0] + '~' + queryTime[1] }}</span></div>
</div> </div>
<el-row :gutter="20" class="info-row">
<el-col :xs="24" :sm="12" :md="6">
<div class="info-cell">
<span class="info-label">经办人姓名</span>
<span class="info-value">{{ userStore.nickName || '全部' }}</span>
</div> </div>
<div class="divider"></div> </el-col>
<div class="report-section"> <el-col :xs="24" :sm="12" :md="6">
<div class="section-title">收费汇总</div> <div class="info-cell">
<div class="report-row cols-4"> <span class="info-label">科室</span>
<div class="report-item"><span class="label">实际现金收入</span><span class="value">{{ formatValue(reportValue.cashSum) }}</span></div> <span class="info-value">{{ userStore.orgName || '-' }}</span>
<div class="report-item"><span class="label">现金</span><span class="value">{{ formatValue(reportValue.rmbCashSum) }}</span></div>
<div class="report-item"><span class="label">微信</span><span class="value">{{ formatValue(reportValue.vxCashSum) }}</span></div>
<div class="report-item"><span class="label">支付宝</span><span class="value">{{ formatValue(reportValue.aliCashSum) }}</span></div>
</div> </div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="info-cell">
<span class="info-label">机构</span>
<span class="info-value">{{ userStore.hospitalName || '-' }}</span>
</div> </div>
<div class="divider"></div> </el-col>
<div class="report-section"> <el-col :xs="24" :sm="12" :md="6">
<div class="info-cell">
<span class="info-label">时间</span>
<span class="info-value">{{ queryTime && queryTime.length === 2 ? queryTime[0] + ' ~ ' + queryTime[1] : '-' }}</span>
</div>
</el-col>
</el-row>
<el-divider />
<div class="section-title">收入汇总</div>
<el-row :gutter="16" class="data-row">
<el-col :xs="24" :sm="12" :md="6">
<div class="data-cell">
<span class="data-label">总收入</span>
<span class="data-value">{{ formatValue(reportValue.cashSum) }}</span>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="data-cell">
<span class="data-label">现金</span>
<span class="data-value">{{ formatValue(reportValue.rmbCashSum) }}</span>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="data-cell">
<span class="data-label">微信</span>
<span class="data-value">{{ formatValue(reportValue.vxCashSum) }}</span>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="data-cell">
<span class="data-label">支付宝</span>
<span class="data-value">{{ formatValue(reportValue.aliCashSum) }}</span>
</div>
</el-col>
</el-row>
<el-divider />
<div class="section-title">医保支付</div> <div class="section-title">医保支付</div>
<div class="report-row cols-4"> <el-row :gutter="16" class="data-row">
<div class="report-item"><span class="label">统筹支付</span><span class="value">{{ formatValue(reportValue.tcSum) }}</span></div> <el-col :xs="24" :sm="12" :md="6">
<div class="report-item"><span class="label">账户支付</span><span class="value">{{ formatValue(reportValue.zhSum) }}</span></div> <div class="data-cell">
<div class="report-item span-2"><span class="label">基金支付总额</span><span class="value">{{ formatValue(reportValue.fundSum) }}</span></div> <span class="data-label">统筹支付</span>
<span class="data-value">{{ formatValue(reportValue.tcSum) }}</span>
</div> </div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="data-cell">
<span class="data-label">账户支付</span>
<span class="data-value">{{ formatValue(reportValue.zhSum) }}</span>
</div> </div>
<div class="divider"></div> </el-col>
<div class="report-section"> <el-col :xs="24" :sm="12" :md="6">
<div class="data-cell">
<span class="data-label">基金支付总额</span>
<span class="data-value">{{ formatValue(reportValue.fundSum) }}</span>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="data-cell">
<span class="data-label">医保统筹+账户</span>
<span class="data-value">{{ formatValue(Number(reportValue.zhSum || 0) + Number(reportValue.fundSum || 0)) }}</span>
</div>
</el-col>
</el-row>
<el-divider />
<div class="section-title">费用明细</div> <div class="section-title">费用明细</div>
<div class="report-row cols-4"> <el-row :gutter="16" class="data-row">
<div class="report-item"><span class="label">诊查费</span><span class="value">{{ formatValue(reportValue.DIAGNOSTIC_FEE) }}</span></div> <el-col :xs="24" :sm="12" :md="6">
<div class="report-item"><span class="label">检查费</span><span class="value">{{ formatValue(reportValue.CHECK_FEE) }}</span></div> <div class="data-cell">
<div class="report-item"><span class="label">化验</span><span class="value">{{ formatValue(reportValue.DIAGNOSTIC_TEST_FEE) }}</span></div> <span class="data-label">诊查</span>
<div class="report-item"><span class="label">治疗费</span><span class="value">{{ formatValue(reportValue.MEDICAL_EXPENSE_FEE) }}</span></div> <span class="data-value">{{ formatValue(reportValue.DIAGNOSTIC_FEE) }}</span>
</div> </div>
<div class="report-row cols-4"> </el-col>
<div class="report-item"><span class="label">西药费</span><span class="value">{{ formatValue(reportValue.WEST_MEDICINE) }}</span></div> <el-col :xs="24" :sm="12" :md="6">
<div class="report-item"><span class="label">中药饮片费</span><span class="value">{{ formatValue(reportValue.CHINESE_MEDICINE_SLICES_FEE) }}</span></div> <div class="data-cell">
<div class="report-item"><span class="label">中成药</span><span class="value">{{ formatValue(reportValue.CHINESE_MEDICINE_FEE) }}</span></div> <span class="data-label">检查</span>
<div class="report-item"><span class="label">卫生材料费</span><span class="value">{{ formatValue(reportValue.SANITARY_MATERIALS_FEE) }}</span></div> <span class="data-value">{{ formatValue(reportValue.CHECK_FEE) }}</span>
</div> </div>
<div class="report-row cols-4"> </el-col>
<div class="report-item"><span class="label">诊疗费</span><span class="value">{{ formatValue(reportValue.GENERAL_CONSULTATION_FEE) }}</span></div> <el-col :xs="24" :sm="12" :md="6">
<div class="report-item"><span class="label">挂号费</span><span class="value">{{ formatValue(reportValue.REGISTRATION_FEE) }}</span></div> <div class="data-cell">
<div class="report-item span-2"><span class="label">其他费用</span><span class="value">{{ formatValue(reportValue.OTHER_FEE) }}</span></div> <span class="data-label">化验费</span>
<span class="data-value">{{ formatValue(reportValue.DIAGNOSTIC_TEST_FEE) }}</span>
</div> </div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="data-cell">
<span class="data-label">治疗费</span>
<span class="data-value">{{ formatValue(reportValue.MEDICAL_EXPENSE_FEE) }}</span>
</div> </div>
</el-col>
</el-row>
<el-row :gutter="16" class="data-row">
<el-col :xs="24" :sm="12" :md="6">
<div class="data-cell">
<span class="data-label">西药费</span>
<span class="data-value">{{ formatValue(reportValue.WEST_MEDICINE) }}</span>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="data-cell">
<span class="data-label">中药饮片费</span>
<span class="data-value">{{ formatValue(reportValue.CHINESE_MEDICINE_SLICES_FEE) }}</span>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="data-cell">
<span class="data-label">中成药费</span>
<span class="data-value">{{ formatValue(reportValue.CHINESE_MEDICINE_FEE) }}</span>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="data-cell">
<span class="data-label">卫生材料费</span>
<span class="data-value">{{ formatValue(reportValue.SANITARY_MATERIALS_FEE) }}</span>
</div>
</el-col>
</el-row>
<el-row :gutter="16" class="data-row">
<el-col :xs="24" :sm="12" :md="6">
<div class="data-cell">
<span class="data-label">普通挂号费</span>
<span class="data-value">{{ formatValue(reportValue.GENERAL_CONSULTATION_FEE) }}</span>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="data-cell">
<span class="data-label">挂号费</span>
<span class="data-value">{{ formatValue(reportValue.REGISTRATION_FEE) }}</span>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="data-cell">
<span class="data-label">其他费用</span>
<span class="data-value">{{ formatValue(reportValue.OTHER_FEE) }}</span>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="data-cell">
<span class="data-label">退费金额</span>
<span class="data-value">{{ formatValue(reportValue.returnFee) }}</span>
</div>
</el-col>
</el-row>
<el-row :gutter="16" class="data-row summary-row">
<el-col :xs="24" :sm="12" :md="6">
<div class="data-cell summary-cell">
<span class="data-label summary-label">费用总额</span>
<span class="data-value value-highlight">{{ totalFeeAmount }}</span>
</div>
</el-col>
<el-col :xs="24" :sm="12" :md="6">
<div class="data-cell summary-cell">
<span class="data-label summary-label">医保报销</span>
<span class="data-value value-highlight">{{ insuranceReimbursement }}</span>
</div>
</el-col>
</el-row>
</div> </div>
</div> </div>
</template> </template>
<script setup name="dayEnd"> <script setup name="DayEnd">
import {getContractList, getRreportReturnIssue, getTotal} from './component/api'; import { ref, reactive, toRefs, getCurrentInstance, computed } from 'vue';
import useUserStore from '@/store/modules/user';
import {formatDateStr} from '@/utils/index';
import Decimal from 'decimal.js'; import Decimal from 'decimal.js';
import { getTotal, getContractList, getRreportReturnIssue } from './component/api';
import useUserStore from '@/store/modules/user';
import { formatDateStr } from '@/utils/index';
const userStore = useUserStore(); const userStore = useUserStore();
const router = useRouter();
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();
const purchaseinventoryRef = ref(null);
const purchaseinventoryList = ref([]); const purchaseinventoryList = ref([]);
const open = ref(false); const reportValue = ref({});
const loading = ref(true); const total = ref(0);
const loading = ref(false);
const showSearch = ref(true); const showSearch = ref(true);
const ids = ref([]); const ids = ref([]);
const single = ref(true); const single = ref(true);
const multiple = ref(true); const multiple = ref(true);
const total = ref(0); const occurrenceTime = ref([]);
const title = ref(''); const contractList = ref([]);
const contractList = ref(undefined);
const reportValue = ref({});
const queryTime = ref([ const queryTime = ref([
formatDateStr(new Date(), 'YYYY-MM-DD'), formatDateStr(new Date(), 'YYYY-MM-DD'),
formatDateStr(new Date(), 'YYYY-MM-DD'), formatDateStr(new Date(), 'YYYY-MM-DD'),
@@ -260,93 +413,173 @@ function formatValue(value) {
return value == null || value == undefined ? '0.00 元' : value.toFixed(2) + ' 元'; return value == null || value == undefined ? '0.00 元' : value.toFixed(2) + ' 元';
} }
// 计算属性:费用总额
const totalFeeAmount = computed(() => {
const v = reportValue.value;
const sum =
Number(v.DIAGNOSTIC_FEE || 0) +
Number(v.CHECK_FEE || 0) +
Number(v.DIAGNOSTIC_TEST_FEE || 0) +
Number(v.MEDICAL_EXPENSE_FEE || 0) +
Number(v.WEST_MEDICINE || 0) +
Number(v.CHINESE_MEDICINE_SLICES_FEE || 0) +
Number(v.CHINESE_MEDICINE_FEE || 0) +
Number(v.GENERAL_CONSULTATION_FEE || 0) +
Number(v.REGISTRATION_FEE || 0) +
Number(v.OTHER_FEE || 0) +
Number(v.SANITARY_MATERIALS_FEE || 0);
return formatValue(sum);
});
// 计算属性:医保报销(统筹+账户)
const insuranceReimbursement = computed(() => {
const v = reportValue.value;
const sum = Number(v.tcSum || 0) + Number(v.zhSum || 0);
return formatValue(sum);
});
getList(); getList();
getPharmacyCabinetLists(); getPharmacyCabinetLists();
</script> </script>
<style scoped> <style scoped>
.report-container { .app-container {
width: 100%; padding: 16px;
max-width: 1200px;
margin: 0 auto;
box-sizing: border-box;
padding: 24px 32px;
background: #fff;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
} }
.report-container {
max-width: 1200px;
margin: 16px auto;
padding: 24px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.report-title { .report-title {
text-align: center; text-align: center;
font-size: 18px; font-size: 20px;
font-weight: bold; font-weight: 600;
margin: 16px 0; margin: 0 0 20px;
}
.report-section {
margin: 8px 0;
}
.section-title {
font-size: 15px;
font-weight: bold;
color: #303133; color: #303133;
margin: 8px 0;
padding-left: 8px;
border-left: 3px solid #409eff;
} }
.report-row {
display: grid; .info-row {
margin: 12px 0; padding: 12px 0;
gap: 10px 16px;
align-items: baseline;
} }
.cols-4 {
grid-template-columns: repeat(4, 1fr); .info-cell {
}
.span-2 {
grid-column: span 2;
}
.report-item {
display: flex; display: flex;
align-items: center; align-items: center;
box-sizing: border-box; padding: 6px 0;
min-width: 0; min-height: 32px;
} }
.label {
display: inline-block; .info-label {
width: 140px; color: #909399;
flex-shrink: 0; font-size: 13px;
color: #606266;
white-space: nowrap; white-space: nowrap;
min-width: 80px;
}
.info-value {
color: #303133;
font-size: 14px;
font-weight: 500;
flex: 1;
}
.data-row {
padding: 4px 0;
}
.data-cell {
display: flex;
align-items: center;
padding: 8px 12px;
margin-bottom: 4px;
background: #fafafa;
border-radius: 4px;
min-height: 40px;
}
.data-label {
color: #606266;
font-size: 13px;
white-space: nowrap;
min-width: 100px;
text-align: right;
padding-right: 8px;
}
.data-value {
color: #303133;
font-size: 14px;
font-weight: 500;
flex: 1;
text-align: right; text-align: right;
} }
.value {
color: #303133; .value-highlight {
font-weight: 500; color: #409eff;
white-space: nowrap; font-weight: 700;
flex: 1; font-size: 15px;
overflow: hidden;
text-overflow: ellipsis;
} }
.divider {
height: 1px; .summary-row {
background-color: #dcdfe6; margin-top: 8px;
margin: 16px 0;
} }
@media screen and (max-width: 1200px) {
.cols-4 { .summary-cell {
grid-template-columns: repeat(2, 1fr); background: #ecf5ff;
} border: 1px solid #d9ecff;
.span-2 {
grid-column: span 2;
}
} }
@media screen and (max-width: 768px) {
.cols-4 { .summary-label {
grid-template-columns: 1fr; font-weight: 600;
color: #409eff;
}
.section-title {
font-size: 15px;
font-weight: 600;
color: #409eff;
padding: 8px 0 8px 12px;
margin: 8px 0 4px;
border-left: 3px solid #409eff;
background: linear-gradient(90deg, rgba(64, 158, 255, 0.05) 0%, transparent 100%);
}
.search-buttons {
margin-bottom: 0;
}
.search-buttons .el-form-item__content {
justify-content: flex-start;
}
:deep(.el-divider--horizontal) {
margin: 12px 0;
}
.el-form--inline .el-form-item {
margin-bottom: 12px;
margin-right: 16px;
}
@media (max-width: 768px) {
.data-label {
min-width: 80px;
text-align: left;
padding-right: 4px;
} }
.span-2 {
grid-column: span 1; .data-value {
text-align: left;
} }
.label {
width: 100px; .info-label {
min-width: 70px;
} }
} }
</style> </style>

View File

@@ -0,0 +1,352 @@
<template>
<div class="app-container">
<el-form
:model="queryParams"
ref="queryRef"
:inline="true"
v-show="showSearch"
label-width="90px"
>
<el-form-item label="查询日期:">
<el-date-picker
v-model="queryTime"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
style="width: 300px; margin-right: 20px"
@change="getValue"
value-format="YYYY-MM-DD"
/>
</el-form-item>
<el-form-item label="费用性质:">
<el-select
v-model="contractNo"
placeholder="费用性质"
clearable
@change="getValue"
style="width: 150px; margin-right: 30px"
>
<el-option
v-for="item in contractList"
:key="item.busNo"
:label="item.contractName"
:value="item.busNo"
/>
</el-select>
<el-button type="primary" plain icon="Search" @click="getValue">查询</el-button>
<el-button type="primary" plain icon="Printer" @click="print">打印</el-button>
</el-form-item>
</el-form>
<div v-loading="loading" style="width: 1300px">
<div style="text-align: center">
<h2>门诊收费日结单</h2>
</div>
<el-row
:gutter="5"
style="margin: 20px 0; display: flex; align-items: center; justify-content: flex-start; padding: 0 20px"
>
<el-col :span="4">
<span class="label">经办人姓名</span>
<span class="value">{{ userStore.nickName }}</span>
</el-col>
<el-col :span="4">
<span class="label">科室</span>
<span class="value">{{ userStore.orgName }}</span>
</el-col>
<el-col :span="7">
<span class="label">时间</span>
<span class="value">{{ queryTime[0] + '~' + queryTime[1] }}</span>
</el-col>
</el-row>
<div class="divider"></div>
<el-row
:gutter="10"
style="margin: 20px 0; display: flex; align-items: center; justify-content: flex-start; padding: 0 20px"
>
<el-col :span="5">
<span class="label">总收入</span>
<span class="value">{{ formatValue(reportValue.cashSum) }}</span>
</el-col>
<el-col :span="5">
<span class="label">现金</span>
<span class="value">{{ formatValue(reportValue.rmbCashSum) }}</span>
</el-col>
<el-col :span="5">
<span class="label">微信</span>
<span class="value">{{ formatValue(reportValue.vxCashSum) }}</span>
</el-col>
<el-col :span="5">
<span class="label">支付宝</span>
<span class="value">{{ formatValue(reportValue.aliCashSum) }}</span>
</el-col>
</el-row>
<div class="divider"></div>
<el-row
:gutter="10"
style="margin: 20px 0; display: flex; align-items: center; justify-content: flex-start; padding: 0 20px"
>
<el-col :span="5">
<span class="label">统筹支付</span>
<span class="value">{{ formatValue(reportValue.tcSum) }}</span>
</el-col>
<el-col :span="5">
<span class="label">账户支付</span>
<span class="value">{{ formatValue(reportValue.zhSum) }}</span>
</el-col>
<el-col :span="5">
<span class="label">基金支付总额</span>
<span class="value">{{ formatValue(reportValue.fundSum) }}</span>
</el-col>
</el-row>
<div class="divider"></div>
<el-row
:gutter="10"
style="margin: 20px 0; display: flex; align-items: center; justify-content: flex-start; padding: 0 20px"
>
<el-col :span="5">
<span class="label">诊查费</span>
<span class="value">{{ formatValue(reportValue.DIAGNOSTIC_FEE) }}</span>
</el-col>
<el-col :span="5">
<span class="label">检查费</span>
<span class="value">{{ formatValue(reportValue.CHECK_FEE) }}</span>
</el-col>
<el-col :span="5">
<span class="label">化验费</span>
<span class="value">{{ formatValue(reportValue.DIAGNOSTIC_TEST_FEE) }}</span>
</el-col>
<el-col :span="5">
<span class="label">治疗费</span>
<span class="value">{{ formatValue(reportValue.MEDICAL_EXPENSE_FEE) }}</span>
</el-col>
</el-row>
<el-row
:gutter="10"
style="margin: 20px 0; display: flex; align-items: center; justify-content: flex-start; padding: 0 20px"
>
<el-col :span="5">
<span class="label">西药费</span>
<span class="value">{{ formatValue(reportValue.WEST_MEDICINE) }}</span>
</el-col>
<el-col :span="5">
<span class="label">中药饮片费</span>
<span class="value">{{ formatValue(reportValue.CHINESE_MEDICINE_SLICES_FEE) }}</span>
</el-col>
<el-col :span="5">
<span class="label">中成药费</span>
<span class="value">{{ formatValue(reportValue.CHINESE_MEDICINE_FEE) }}</span>
</el-col>
<el-col :span="5">
<span class="label">卫生材料费</span>
<span class="value">{{ formatValue(reportValue.SANITARY_MATERIALS_FEE) }}</span>
</el-col>
</el-row>
<el-row
:gutter="10"
style="margin: 20px 0; display: flex; align-items: center; justify-content: flex-start; padding: 0 20px"
>
<el-col :span="5">
<span class="label">诊疗费</span>
<span class="value">{{ formatValue(reportValue.GENERAL_CONSULTATION_FEE) }}</span>
</el-col>
<el-col :span="5">
<span class="label">挂号费</span>
<span class="value">{{ formatValue(reportValue.REGISTRATION_FEE) }}</span>
</el-col>
<el-col :span="5">
<span class="label">其他费用</span>
<span class="value">{{ formatValue(reportValue.OTHER_FEE) }}</span>
</el-col>
</el-row>
</div>
</div>
</template>
<script setup name="dayEnd">
import {getContractList, getRreportReturnIssue, getTotal} from './component/api';
import useUserStore from '@/store/modules/user';
import {formatDateStr} from '@/utils/index';
import Decimal from 'decimal.js';
const userStore = useUserStore();
const router = useRouter();
const { proxy } = getCurrentInstance();
const purchaseinventoryRef = ref(null);
const purchaseinventoryList = ref([]);
const open = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const title = ref('');
const contractList = ref(undefined);
const reportValue = ref({});
const queryTime = ref([
formatDateStr(new Date(), 'YYYY-MM-DD'),
formatDateStr(new Date(), 'YYYY-MM-DD'),
]);
const contractNo = ref('0000');
const data = reactive({
queryParams: {
form: {},
pageNo: 1,
pageSize: 10,
searchKey: undefined,
purposeLocationId: undefined,
sourceLocationId: undefined,
supplierId: undefined,
approvalTimeSTime: undefined,
approvalTimeETime: undefined,
},
rules: {},
});
const { queryParams, form, rules } = toRefs(data);
getValue();
function getValue() {
loading.value = true;
getTotal({
contractNo: contractNo.value,
startTime: queryTime.value[0] + ' 00:00:00',
endTime: queryTime.value[1] + ' 23:59:59',
entererId: userStore.practitionerId,
}).then((res) => {
loading.value = false;
reportValue.value = res.data;
});
}
getContract();
function getContract() {
getContractList().then((response) => {
contractList.value = response.data;
});
}
function getPharmacyCabinetLists() {
}
/** 查询调拨管理项目列表 */
function getList() {
loading.value = true;
getRreportReturnIssue(queryParams.value).then((res) => {
loading.value = false;
purchaseinventoryList.value = res.data.records;
total.value = res.data.total;
});
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.approvalTimeSTime =
occurrenceTime.value && occurrenceTime.value.length == 2
? occurrenceTime.value[0] + ' 00:00:00'
: '';
queryParams.value.approvalTimeETime =
occurrenceTime.value && occurrenceTime.value.length == 2
? occurrenceTime.value[1] + ' 23:59:59'
: '';
queryParams.value.pageNo = 1;
getList();
}
/** 清空条件按钮操作 */
function handleClear() {
queryParams.value.approvalTimeSTime = '';
queryParams.value.approvalTimeETime = '';
occurrenceTime.value = '';
proxy.resetForm('queryRef');
getList();
}
/** 选择条数 */
function handleSelectionChange(selection) {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
}
/** 打印门诊日结 */
async function print() {
console.log(reportValue.value, '==reportValue.value==');
const result = {
data: [
{
...reportValue.value,
nickName: userStore.nickName,
orgName: userStore.orgName,
fixmedinsName: userStore.hospitalName,
queryTime: queryTime.value[0] + '~' + queryTime.value[1],
zfAmount: new Decimal(reportValue.value.zhSum || 0).add(reportValue.value.fundSum || 0),
feeAmount: new Decimal(reportValue.value.DIAGNOSTIC_FEE || 0)
.add(reportValue.value.CHECK_FEE || 0)
.add(reportValue.value.DIAGNOSTIC_TEST_FEE || 0)
.add(reportValue.value.MEDICAL_EXPENSE_FEE || 0)
.add(reportValue.value.WEST_MEDICINE || 0)
.add(reportValue.value.CHINESE_MEDICINE_SLICES_FEE || 0)
.add(reportValue.value.CHINESE_MEDICINE_FEE || 0)
.add(reportValue.value.GENERAL_CONSULTATION_FEE || 0)
.add(reportValue.value.REGISTRATION_FEE || 0)
.add(reportValue.value.OTHER_FEE || 0)
.add(reportValue.value.SANITARY_MATERIALS_FEE || 0),
},
],
};
console.log(result, '==result.data==');
let jsonString = JSON.stringify(result, null, 2);
console.log(jsonString, 'jsonstring');
await CefSharp.BindObjectAsync('boundAsync');
await boundAsync
.printReport(getPrintFileName(contractNo.value), jsonString)
.then((response) => {
console.log(response, 'response');
var res = JSON.parse(response);
if (!res.IsSuccess) {
proxy.$modal.msgError('调用打印插件失败:' + res.ErrorMessage);
}
})
.catch((error) => {
proxy.$modal.msgError('调用打印插件失败:' + error);
});
}
function getPrintFileName(value) {
switch (value) {
case '0000':
return '门诊日结单(按登录角色查询)自费.grf';
case '229900':
return '门诊日结单(按登录角色查询)省医保.grf';
case '220100':
return '门诊日结单(按登录角色查询)市医保.grf';
}
}
function formatValue(value) {
return value == null || value == undefined ? '0.00 元' : value.toFixed(2) + ' 元';
}
getList();
getPharmacyCabinetLists();
</script>
<style scoped>
.label {
display: inline-block;
width: 120px !important;
}
.value {
float: right;
}
.el-col {
margin-right: 50px;
}
.divider {
height: 3px;
background-color: #000;
margin: 20px 0;
}
</style>

View File

@@ -615,8 +615,8 @@ const handleDelete = async (row) => {
} else { } else {
proxy.$modal?.msgError?.(res?.msg || '删除失败'); proxy.$modal?.msgError?.(res?.msg || '删除失败');
} }
} catch (e) { } catch {
proxy.$modal?.msgError?.(e.message || '删除异常'); // 响应拦截器已处理错误提示,此处静默
} }
}; };
@@ -640,8 +640,8 @@ const handleWithdraw = async (row) => {
} else { } else {
proxy.$modal?.msgError?.(res?.msg || '撤回失败'); proxy.$modal?.msgError?.(res?.msg || '撤回失败');
} }
} catch (e) { } catch {
proxy.$modal?.msgError?.(e.message || '撤回异常'); // 响应拦截器已处理错误提示,此处静默
} }
}; };

View File

@@ -539,7 +539,7 @@ const submit = () => {
requestFormId: requestFormId, requestFormId: requestFormId,
name: selectedNames, name: selectedNames,
descJson: JSON.stringify(submitForm), descJson: JSON.stringify(submitForm),
categoryEnum: '22', categoryEnum: '23',
}).then((res) => { }).then((res) => {
if (res.code === 200) { if (res.code === 200) {
ElMessage.success(res.msg || (props.isEditMode ? '修改成功' : '保存成功')); ElMessage.success(res.msg || (props.isEditMode ? '修改成功' : '保存成功'));

View File

@@ -230,6 +230,8 @@
待保存 待保存
</el-tag> </el-tag>
<el-tag v-else-if="scope.row.statusEnum == 1" type="primary">待签发</el-tag> <el-tag v-else-if="scope.row.statusEnum == 1" type="primary">待签发</el-tag>
<el-tag v-else-if="scope.row.statusEnum == 10" type="primary">已校对</el-tag>
<el-tag v-else-if="scope.row.statusEnum == 11" type="primary">待接收</el-tag>
<el-tag v-else-if="scope.row.statusEnum == 3" type="success">已完成</el-tag> <el-tag v-else-if="scope.row.statusEnum == 3" type="success">已完成</el-tag>
<el-tag v-else-if="scope.row.statusEnum == 6" type="error">停止</el-tag> <el-tag v-else-if="scope.row.statusEnum == 6" type="error">停止</el-tag>
<el-tag v-else type="info">{{ scope.row.chargeStatus_enumText }}</el-tag> <el-tag v-else type="info">{{ scope.row.chargeStatus_enumText }}</el-tag>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div busNo="app-container"> <div class="app-container">
<el-form <el-form
style="margin-top: 20px; margin-left: 20px" style="margin-top: 20px; margin-left: 20px"
:model="queryParams" :model="queryParams"
@@ -97,7 +97,7 @@
<el-row <el-row
:gutter="10" :gutter="10"
busNo="mb8" class="mb8"
style="margin-left: 20px; margin-right: 0px; margin-bottom: 5px" style="margin-left: 20px; margin-right: 0px; margin-bottom: 5px"
> >
<el-col :span="1.5"> <el-col :span="1.5">
@@ -268,7 +268,7 @@
/> />
<el-row <el-row
:gutter="10" :gutter="10"
busNo="mb8" class="mb8"
style=" style="
margin-top: 10px; margin-top: 10px;
display: flex; display: flex;
@@ -298,6 +298,7 @@ const { proxy } = getCurrentInstance();
const totalAmount = ref(0); const totalAmount = ref(0);
const purchaseinventoryListAll = ref([]); const purchaseinventoryListAll = ref([]);
const xiaojiTotal = ref([]); const xiaojiTotal = ref([]);
const rowSpanMap = ref({});
const rowSpan = ref(1); const rowSpan = ref(1);
const issuerOptions = ref([]); const issuerOptions = ref([]);
const payeeOptions = ref([]); const payeeOptions = ref([]);
@@ -548,93 +549,92 @@ function handleTotalAmount() {
}, 0); }, 0);
} }
// 门诊号合并行处理 // 表格合并行方法(纯函数,不修改数据)
function getTotals(row, i) {
let totalPriceSums = Number(purchaseinventoryList.value[i].totalPrice);
for (let j = 1; i - j >= 0; j++) {
if (purchaseinventoryList.value[i].busNo == purchaseinventoryList.value[i - j].busNo) {
totalPriceSums += Number(purchaseinventoryList.value[i - j].totalPrice);
}
}
xiaojiTotal.value.push({
inde: i + 1,
busNo: row.busNo,
genderEnum_enumText: row.genderEnum_enumText,
totalPrice: totalPriceSums.toFixed(4) || 0.0,
});
purchaseinventoryList.value.splice(i + 1, 0, {
busNo: row.busNo,
genderEnum_enumText: row.genderEnum_enumText,
departmentName: '小计',
totalPrice: totalPriceSums.toFixed(4) || 0.0,
});
}
// 表格合并行方法
const arraySpanMethod = ({ row, column, rowIndex, columnIndex }) => { const arraySpanMethod = ({ row, column, rowIndex, columnIndex }) => {
if (columnIndex === 1 && purchaseinventoryList.value.length > 0) { // 合并门诊号列columnIndex === 1
if ( if (columnIndex === 1) {
rowIndex === 0 || const spanInfo = rowSpanMap.value[rowIndex];
(rowIndex > 0 && row.busNo !== purchaseinventoryList.value[rowIndex - 1]?.busNo) if (spanInfo) {
) { return { rowspan: spanInfo.rowspan, colspan: spanInfo.colspan || 1 };
let rowspan = 1;
let totalPriceSum = 0;
for (let i = rowIndex + 1; i < purchaseinventoryList.value.length + 1; i++) {
if (purchaseinventoryList.value[i - 1].departmentName != '合计') {
if (
purchaseinventoryList.value[i] &&
purchaseinventoryList.value[i].busNo === row.busNo
) {
rowspan++;
totalPriceSum += Number(purchaseinventoryList.value[i].totalPrice);
if (i == purchaseinventoryList.value.length - 1) {
let findIndexTotal = xiaojiTotal.value.findIndex((k) => k.busNo == row.busNo);
if (findIndexTotal < 0) {
getTotals(row, i);
}
}
} else {
totalPriceSum += Number(row.totalPrice);
let findIndexTotal = xiaojiTotal.value.findIndex((k) => k.busNo == row.busNo);
if (findIndexTotal < 0) {
xiaojiTotal.value.push({
inde: i,
genderEnum_enumText: row.genderEnum_enumText,
busNo: row.busNo,
totalPrice: totalPriceSum,
});
purchaseinventoryList.value.splice(i, 0, {
busNo: row.busNo,
genderEnum_enumText: row.genderEnum_enumText,
departmentName: '小计',
totalPrice: totalPriceSum.toFixed(4) || 0.0,
});
}
break;
}
}
rowspan++;
}
return { rowspan, colspan: 1 };
} else { } else {
return { rowspan: 0, colspan: 0 }; return { rowspan: 0, colspan: 0 };
} }
} }
// 其他列不合并
return { rowspan: 1, colspan: 1 };
}; };
// 预处理列表数据:插入小计行、计算合并行信息
// 此函数替代了原来在 arraySpanMethod 中 splice 修改数据的做法
function processListWithSubtotals(list) {
rowSpanMap.value = {};
xiaojiTotal.value = [];
const result = [];
let i = 0;
while (i < list.length) {
const row = list[i];
// 跳过已有的合计行
if (row.departmentName === '合计') {
result.push(row);
rowSpanMap.value[result.length - 1] = { rowspan: 1, colspan: 1 };
i++;
continue;
}
const currentBusNo = row.busNo;
let rowspan = 0;
let totalPriceSum = 0;
let j = i;
// 计算相同门诊号的行数
while (j < list.length && list[j].busNo === currentBusNo && list[j].departmentName !== '合计') {
rowspan++;
totalPriceSum += Number(list[j].totalPrice) || 0;
j++;
}
// 设置第一行的 rowspan
const startRow = result.length;
rowSpanMap.value[startRow] = { rowspan, colspan: 1 };
// 添加数据行
for (let k = i; k < j; k++) {
result.push(list[k]);
}
// 添加小计行多于1行时才添加
if (rowspan > 1) {
const subtotalRow = {
...list[i],
departmentName: '小计',
totalPrice: totalPriceSum.toFixed(4),
};
// 小计行不合并
rowSpanMap.value[result.length] = { rowspan: 1, colspan: 1 };
result.push(subtotalRow);
xiaojiTotal.value.push({
inde: result.length,
busNo: currentBusNo,
genderEnum_enumText: list[i].genderEnum_enumText,
totalPrice: totalPriceSum.toFixed(4),
});
}
i = j;
}
return result;
}
// 统计类型变化处理 // 统计类型变化处理
function inventoryChange(val) { function inventoryChange(val) {
queryParams.value.statisticsType = val; queryParams.value.statisticsType = val;
xiaojiTotal.value = []; xiaojiTotal.value = [];
purchaseinventoryList.value = []; purchaseinventoryList.value = [];
rowSpanMap.value = {};
getList(); getList();
} }
@@ -694,8 +694,11 @@ function getList(type) {
: '0.0000' + (k.quantityUnit_dictText ? k.quantityUnit_dictText : ''); : '0.0000' + (k.quantityUnit_dictText ? k.quantityUnit_dictText : '');
}); });
// 处理搜索关键词时的合计 // 处理搜索关键词或单页数据
if (queryParams.value.searchKey) { if (queryParams.value.searchKey || (total.value && total.value <= queryParams.value.pageSize)) {
// 先处理小计行和合并信息
purchaseinventoryList.value = processListWithSubtotals(purchaseinventoryList.value);
purchaseinventoryList.value.forEach((k) => { purchaseinventoryList.value.forEach((k) => {
if (k.departmentName !== '小计' && k.departmentName !== '合计') { if (k.departmentName !== '小计' && k.departmentName !== '合计') {
totalPrice2 += Number(k.totalPrice) || 0; totalPrice2 += Number(k.totalPrice) || 0;
@@ -703,39 +706,24 @@ function getList(type) {
}); });
totalPrice2 = totalPrice2 ? totalPrice2.toFixed(4) : totalPrice2; totalPrice2 = totalPrice2 ? totalPrice2.toFixed(4) : totalPrice2;
purchaseinventoryList.value.push({ departmentName: '合计', totalPrice: totalPrice2 });
loading.value = false;
return;
}
// 处理分页数据
purchaseinventoryList.value.forEach((k) => {
if (total.value && total.value <= queryParams.value.pageSize) {
totalPrice2 += Number(k.totalPrice);
}
});
if (total.value <= res.data.size) {
loading.value = false;
}
// 单页数据合计
if (total.value && total.value <= queryParams.value.pageSize) {
totalPrice2 = totalPrice2 ? totalPrice2.toFixed(4) : totalPrice2;
let pageNoAll = total.value / queryParams.value.pageSize; let pageNoAll = total.value / queryParams.value.pageSize;
if (Math.ceil(pageNoAll) == queryParams.value.pageNo) { if (Math.ceil(pageNoAll) == queryParams.value.pageNo) {
purchaseinventoryList.value.push({ departmentName: '合计', totalPrice: totalPrice2 }); purchaseinventoryList.value.push({ departmentName: '合计', totalPrice: totalPrice2 });
} rowSpanMap.value[purchaseinventoryList.value.length - 1] = { rowspan: 1, colspan: 1 };
} }
// 多页数据处理 loading.value = false;
if (total.value && total.value > queryParams.value.pageSize && !queryParams.value.searchKey) { } else if (total.value && total.value > queryParams.value.pageSize && !queryParams.value.searchKey) {
// 多页数据先处理当前页数据确保rowSpanMap正确初始化避免表格格式错乱
purchaseinventoryList.value = processListWithSubtotals(purchaseinventoryList.value);
loading.value = false;
// 然后获取全部数据进行完整处理
let queryParamsValue = { ...queryParams.value }; let queryParamsValue = { ...queryParams.value };
queryParamsValue.pageSize = total.value; queryParamsValue.pageSize = total.value;
queryParamsValue.pageNo = 1; queryParamsValue.pageNo = 1;
// 移除空值参数
Object.keys(queryParamsValue).forEach(key => { Object.keys(queryParamsValue).forEach(key => {
if (queryParamsValue[key] === undefined || queryParamsValue[key] === null || queryParamsValue[key] === '') { if (queryParamsValue[key] === undefined || queryParamsValue[key] === null || queryParamsValue[key] === '') {
delete queryParamsValue[key]; delete queryParamsValue[key];
@@ -746,7 +734,7 @@ function getList(type) {
purchaseinventoryListAll.value = res.data.records || []; purchaseinventoryListAll.value = res.data.records || [];
if (purchaseinventoryListAll.value.length > 0) { if (purchaseinventoryListAll.value.length > 0) {
purchaseinventoryListAll.value.map((k, index) => { purchaseinventoryListAll.value.map((k) => {
k.totalPrice = k.totalPrice ? k.totalPrice.toFixed(4) : '0.0000'; k.totalPrice = k.totalPrice ? k.totalPrice.toFixed(4) : '0.0000';
k.price = k.price ? k.price.toFixed(4) : '0.0000'; k.price = k.price ? k.price.toFixed(4) : '0.0000';
k.number = k.number k.number = k.number
@@ -754,45 +742,16 @@ function getList(type) {
: '0.0000' + (k.quantityUnit_dictText ? k.quantityUnit_dictText : ''); : '0.0000' + (k.quantityUnit_dictText ? k.quantityUnit_dictText : '');
totalPrice2 += Number(k.totalPrice); totalPrice2 += Number(k.totalPrice);
// 处理跨页门诊号小计
for (let m = 1; m < index; m++) {
if (
queryParams.value.pageNo > 1 &&
index == queryParams.value.pageSize * (queryParams.value.pageNo - 1) &&
k.busNo == purchaseinventoryListAll.value[index - m]?.busNo
) {
let dispenseNoIndex1 = purchaseinventoryList.value.findIndex(
(o) => o.departmentName == '小计' && o.busNo == purchaseinventoryListAll.value[index - m].busNo
);
if (dispenseNoIndex1 > 0) {
purchaseinventoryList.value[dispenseNoIndex1].totalPrice =
(Number(purchaseinventoryList.value[dispenseNoIndex1].totalPrice) +
Number(purchaseinventoryListAll.value[index - m].totalPrice)).toFixed(4) || '0.0000';
}
}
if (
index + m == queryParams.value.pageSize * queryParams.value.pageNo &&
k.busNo == purchaseinventoryListAll.value[index + m]?.busNo
) {
if (
purchaseinventoryList.value[purchaseinventoryList.value.length - 1]
.departmentName == '小计'
) {
purchaseinventoryList.value.splice(purchaseinventoryList.value.length - 1, 1);
}
}
}
}); });
purchaseinventoryList.value = processListWithSubtotals(purchaseinventoryListAll.value);
totalPrice2 = totalPrice2 ? totalPrice2.toFixed(4) : totalPrice2; totalPrice2 = totalPrice2 ? totalPrice2.toFixed(4) : totalPrice2;
loading.value = false;
let pageNoAll = total.value / queryParams.value.pageSize; let pageNoAll = total.value / queryParams.value.pageSize;
if (Math.ceil(pageNoAll) == queryParams.value.pageNo) { if (Math.ceil(pageNoAll) == queryParams.value.pageNo) {
purchaseinventoryList.value.push({ departmentName: '合计', totalPrice: totalPrice2 }); purchaseinventoryList.value.push({ departmentName: '合计', totalPrice: totalPrice2 });
rowSpanMap.value[purchaseinventoryList.value.length - 1] = { rowspan: 1, colspan: 1 };
} }
} }
}); });

View File

@@ -1519,13 +1519,14 @@ function handleMedicalAdvice(row) {
} }
// 🔧 每次打开临时医嘱都重新拉取最新数据,确保计费弹窗签发后数据自动更新 // 🔧 每次打开临时医嘱都重新拉取最新数据,确保计费弹窗签发后数据自动更新
// 先清空旧数据 // 🔧 修复 Bug #446: 先保存旧数据再清空,避免竟态条件
const prevAdvices = [...temporaryAdvices.value]
temporaryBillingMedicines.value = [] temporaryBillingMedicines.value = []
temporaryAdvices.value = [] temporaryAdvices.value = []
// 🔧 修复 Bug #446: 如果是同一 encounter 且已有提交的医嘱(有 requestId保留签名状态 // 🔧 修复 Bug #446: 如果是同一 encounter 且已有提交的医嘱(有 requestId保留签名状态
const hasSubmittedAdvices = temporaryAdvices.value.length > 0 && const hasSubmittedAdvices = prevAdvices.length > 0 &&
temporaryAdvices.value[0]?.originalMedicine?.encounterId === row.visitId && prevAdvices[0]?.originalMedicine?.encounterId === row.visitId &&
temporaryAdvices.value.some(a => a.originalMedicine?.requestId); prevAdvices.some(a => a.originalMedicine?.requestId);
temporarySigned.value = hasSubmittedAdvices; // 修复:根据已有数据状态设置,而非盲目重置 temporarySigned.value = hasSubmittedAdvices; // 修复:根据已有数据状态设置,而非盲目重置
temporaryMedicalLoading.value = true // 🔧 新增:开始加载 temporaryMedicalLoading.value = true // 🔧 新增:开始加载
@@ -1585,7 +1586,13 @@ function handleMedicalAdvice(row) {
adviceType: item.adviceType || contentData.adviceType || item.advice_type || null, adviceType: item.adviceType || contentData.adviceType || item.advice_type || null,
chargeItemId: item.chargeItemId || contentData.chargeItemId || item.charge_item_id || null, chargeItemId: item.chargeItemId || contentData.chargeItemId || item.charge_item_id || null,
definitionId: item.definitionId || contentData.definitionId || item.definition_id || null, definitionId: item.definitionId || contentData.definitionId || item.definition_id || null,
definitionDetailId: item.definitionDetailId || contentData.definitionDetailId || item.definition_detail_id || null definitionDetailId: item.definitionDetailId || contentData.definitionDetailId || item.definition_detail_id || null,
// 🔧 修复:传递 requestId临时医嘱签署时需要用于更新已有记录而非新建
requestId: item.requestId || null,
// 🔧 保留原始 contentJson临时医嘱签发时需要展开所有字段传给后端
contentJson: item.contentJson || item.content_json || null,
// 🔧 标记是否已签发,用于临时医嘱弹窗控制按钮状态
_signed: item.statusEnum === 2
}; };
} catch (e) { } catch (e) {
// 如果解析失败,使用顶层数据 - 兼容 snake_case 和 camelCase 以及后端不同字段名 // 如果解析失败,使用顶层数据 - 兼容 snake_case 和 camelCase 以及后端不同字段名
@@ -1606,7 +1613,13 @@ function handleMedicalAdvice(row) {
adviceType: item.adviceType || item.advice_type || null, adviceType: item.adviceType || item.advice_type || null,
chargeItemId: item.chargeItemId || item.charge_item_id || null, chargeItemId: item.chargeItemId || item.charge_item_id || null,
definitionId: item.definitionId || item.definition_id || null, definitionId: item.definitionId || item.definition_id || null,
definitionDetailId: item.definitionDetailId || item.definition_detail_id || null definitionDetailId: item.definitionDetailId || item.definition_detail_id || null,
// 🔧 修复:传递 requestId临时医嘱签署时需要用于更新已有记录而非新建
requestId: item.requestId || null,
// 🔧 保留原始 contentJson临时医嘱签发时需要展开所有字段传给后端
contentJson: item.contentJson || item.content_json || null,
// 🔧 标记是否已签发,用于临时医嘱弹窗控制按钮状态
_signed: item.statusEnum === 2
}; };
} }
}); });
@@ -1694,73 +1707,134 @@ function closeTemporaryMedical() {
// 处理临时医嘱提交 // 处理临时医嘱提交
// 🔧 修复:提交成功后,更新 temporaryAdvices 中的 requestId以便下次提交时执行更新操作 // 🔧 修复:提交成功后,更新 temporaryAdvices 中的 requestId以便下次提交时执行更新操作
function handleTemporaryMedicalSubmit(data) { function handleTemporaryMedicalSubmit(data) {
// 🔧 修复:使用用户修改后的数据,而不是重新加载数据 // 🔧 Bug #445 修复:提交成功后重新拉取数据,确保"待生成"列表正确更新
// 这样可以确保用户修改的内容(如剂量)在保存后仍然正确显示 if (data.patientInfo && data.patientInfo.visitId) {
if (data.temporaryAdvices && data.temporaryAdvices.length > 0) { const row = { visitId: data.patientInfo.visitId, operCode: data.patientInfo.operCode }
// 🔧 关键修复:更新 temporaryAdvices 中的数据,保留用户的修改 temporarySigned.value = true
// 但是需要从后端返回的数据中获取 requestId以便下次提交时执行更新操作 ElMessage.success('临时医嘱已生成(已签发)')
// 如果后端返回了医嘱IDrequestId我们需要更新到 temporaryAdvices 中
temporaryAdvices.value = data.temporaryAdvices.map((advice, index) => {
const originalMedicine = advice.originalMedicine || {}
// 🔧 关键修复:从后端返回的数据中获取 requestId如果有 // 重新拉取最新数据,后端已将 statusEnum 从 1(草稿) 更新为 2(已签发)
// 后端返回的医嘱ID可能需要通过某个字段获取这里暂时假设后端会返回 getPrescriptionList(row.visitId, 6, row.operCode).then((res) => {
// 如果后端返回的 response 中包含医嘱ID需要更新到 originalMedicine 中 if (res.code === 200 && res.data) {
// 这里暂时保留原逻辑,等待用户提供后端返回的具体数据结构 const seenIds = new Set()
const filteredItems = res.data.filter(item => {
if (item.encounterId !== row.visitId) return false
const at = Number(item.adviceType ?? item.advice_type)
if (at !== 1 && at !== 2) return false
const medicineName = item.adviceName || item.advice_name
if (!medicineName || medicineName.trim() === '') return false
const excludedKeywords = ['术', '超声', '多普勒', '检查', '检验', '彩超', 'X线', 'CT', 'MRI', '扫描', '造影']
if (excludedKeywords.some(kw => medicineName.includes(kw))) return false
const itemId = item.requestId || item.id
if (itemId && seenIds.has(itemId)) return false
if (itemId) seenIds.add(itemId)
return true
})
const draftItems = filteredItems.filter(item => item.statusEnum === 1)
const activeItems = filteredItems.filter(item => item.statusEnum === 2)
// 更新待生成列表:只保留未生成的项目
temporaryBillingMedicines.value = draftItems.map(item => {
try {
const jsonContent = item.contentJson || item.content_json
const contentData = jsonContent ? JSON.parse(jsonContent) : {}
return { return {
...advice, medicineName: contentData.adviceName || contentData.advice_name || item.adviceName || item.advice_name || item.chargeName || item.charge_name || contentData.itemName || contentData.item_name || '未知药品',
// 确保 originalMedicine 包含所有必要字段 specification: contentData.volume || contentData.specification || item.volume || item.specification || '',
originalMedicine: { quantity: contentData.quantity || item.quantity || 0,
...originalMedicine, batchNumber: contentData.lotNumber || contentData.lot_number || item.lotNumber || item.lot_number || '',
// 保留用户的修改 unitPrice: contentData.unitPrice || contentData.unit_price || item.unitPrice || item.unit_price || 0,
contentJson: originalMedicine.contentJson || JSON.stringify({ subtotal: contentData.totalPrice || contentData.total_price || item.totalPrice || item.total_price ||
dose: advice.dosage, (contentData.unitPrice || contentData.unit_price || item.unitPrice || item.unit_price || 0) *
methodCode: advice.usage, (contentData.quantity || item.quantity || 0),
rateCode: advice.frequency, insuranceType: (contentData.insuranceType || contentData.insurance_type) === 1 ? '医保' : (item.insuranceType === 1 || item.insurance_type === 1) ? '医保' : '自费',
quantity: advice.quantity, adviceDefinitionId: item.adviceDefinitionId || contentData.adviceDefinitionId || item.advice_definition_id || null,
totalPrice: advice.totalPrice adviceTableName: item.adviceTableName || contentData.adviceTableName || item.advice_table_name || null,
}) adviceType: item.adviceType || contentData.adviceType || item.advice_type || null,
chargeItemId: item.chargeItemId || contentData.chargeItemId || item.charge_item_id || null,
definitionId: item.definitionId || contentData.definitionId || item.definition_id || null,
definitionDetailId: item.definitionDetailId || contentData.definitionDetailId || item.definition_detail_id || null,
// 🔧 修复:传递 requestId
requestId: item.requestId || null,
// 🔧 保留原始 contentJson临时医嘱签发时需要展开所有字段传给后端
contentJson: item.contentJson || item.content_json || null,
// 🔧 标记是否已签发,用于临时医嘱弹窗控制按钮状态
_signed: item.statusEnum === 2
}
} catch (e) {
return {
medicineName: item.adviceName || item.advice_name || item.chargeName || '',
specification: item.specification || item.volume || '',
quantity: item.quantity || item.quantity_value || item.quantityValue || 0,
batchNumber: item.lotNumber || item.lot_number || '',
unitPrice: item.unitPrice || item.unit_price || 0,
subtotal: item.totalPrice || item.total_price || 0,
insuranceType: (item.insuranceType || item.insurance_type) === 1 ? '医保' : '自费',
adviceDefinitionId: item.adviceDefinitionId || item.advice_definition_id || null,
adviceTableName: item.adviceTableName || item.advice_table_name || null,
adviceType: item.adviceType || item.advice_type || null,
chargeItemId: item.chargeItemId || item.charge_item_id || null,
definitionId: item.definitionId || item.definition_id || null,
definitionDetailId: item.definitionDetailId || item.definition_detail_id || null,
// 🔧 修复:传递 requestId
requestId: item.requestId || null,
// 🔧 保留原始 contentJson临时医嘱签发时需要展开所有字段传给后端
contentJson: item.contentJson || item.content_json || null,
// 🔧 标记是否已签发,用于临时医嘱弹窗控制按钮状态
_signed: item.statusEnum === 2
} }
} }
}) })
// 🔧 修复 Bug #445: 使用稳定的字段组合匹配已提交项目,而不是依赖可能为空的 requestId/chargeItemId // 更新已生成列表
// 构建已提交项目的匹配键集合(药品名称 + 规格 + 数量) temporaryAdvices.value = activeItems.map((item, index) => {
try {
const jsonContent = item.contentJson || item.content_json
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 dosage = specValue * (contentData.quantity || item.quantity || 1)
let usageCode = contentData.methodCode || 'iv'
return {
id: index + 1, adviceName: medicineName, dosage, unit: specUnit,
usage: usageCode, frequency: '临时',
executeTime: new Date().toLocaleString('zh-CN'),
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: new Date().toLocaleString('zh-CN'),
originalMedicine: { ...item, medicineName: item.adviceName || '', specification: item.volume || '', quantity: item.quantity || 1, encounterId: row.visitId }
}
}
})
}
}).catch(() => {
// 拉取失败时使用本地过滤作为兜底
if (data.temporaryAdvices && data.temporaryAdvices.length > 0) {
const submittedKeys = new Set( const submittedKeys = new Set(
(data.temporaryAdvices || []) data.temporaryAdvices.map(a => {
.map(a => {
const om = a.originalMedicine || {} const om = a.originalMedicine || {}
const name = om.medicineName || om.adviceName || om.advice_name || a.adviceName || '' return `${om.medicineName || a.adviceName || ''}|||${om.specification || ''}|||${om.quantity || 0}`
const spec = om.specification || om.volume || '' }).filter(k => k !== '|||0')
const qty = om.quantity || 0
return `${name}|||${spec}|||${qty}`
})
.filter(k => k !== '|||0') // 过滤掉空项
) )
if (submittedKeys.size > 0) { if (submittedKeys.size > 0) {
temporaryBillingMedicines.value = (temporaryBillingMedicines.value || []).filter(m => { temporaryBillingMedicines.value = (temporaryBillingMedicines.value || []).filter(m => {
const key = `${m.medicineName || ''}|||${m.specification || ''}|||${m.quantity || 0}` return !submittedKeys.has(`${m.medicineName || ''}|||${m.specification || ''}|||${m.quantity || 0}`)
return !submittedKeys.has(key) })
}
temporaryAdvices.value = data.temporaryAdvices
}
}) })
} else { } else {
// 如果没有任何匹配键,清空待生成列表(所有项目都已提交)
temporaryBillingMedicines.value = []
}
} else {
// 如果没有传递数据,则清空
temporaryAdvices.value = [] temporaryAdvices.value = []
temporaryBillingMedicines.value = [] temporaryBillingMedicines.value = []
} }
// 🔧 设置签名状态,保持按钮名称一致
temporarySigned.value = true
// 显示成功提示,不关闭弹窗,让用户可以查看已签发的医嘱状态
ElMessage.success('临时医嘱已生成(已签发),可继续查看或修改')
} }
function handleTemporaryMedicalCancel() { function handleTemporaryMedicalCancel() {
// 🔧 修复:用户点击取消时才清空数据,因为用户可能要放弃修改 // 🔧 修复:用户点击取消时才清空数据,因为用户可能要放弃修改
@@ -1911,7 +1985,13 @@ function handleQuoteBilling() {
orgId: contentData.orgId || item.orgId || contentData.positionId || item.positionId || userStore.orgId, orgId: contentData.orgId || item.orgId || contentData.positionId || item.positionId || userStore.orgId,
positionId: contentData.positionId || item.positionId || userStore.orgId, positionId: contentData.positionId || item.positionId || userStore.orgId,
definitionId: contentData.definitionId || item.definitionId, definitionId: contentData.definitionId || item.definitionId,
definitionDetailId: contentData.definitionDetailId || item.definitionDetailId definitionDetailId: contentData.definitionDetailId || item.definitionDetailId,
// 🔧 修复:传递 requestId
requestId: item.requestId || null,
// 🔧 保留原始 contentJson临时医嘱签发时需要展开所有字段传给后端
contentJson: item.contentJson || item.content_json || null,
// 🔧 标记是否已签发,用于临时医嘱弹窗控制按钮状态
_signed: item.statusEnum === 2
} }
} catch (e) { } catch (e) {
return { return {
@@ -1927,7 +2007,13 @@ function handleQuoteBilling() {
orgId: item.orgId || item.positionId || userStore.orgId, orgId: item.orgId || item.positionId || userStore.orgId,
positionId: item.positionId || userStore.orgId, positionId: item.positionId || userStore.orgId,
definitionId: item.definitionId, definitionId: item.definitionId,
definitionDetailId: item.definitionDetailId definitionDetailId: item.definitionDetailId,
// 🔧 修复:传递 requestId
requestId: item.requestId || null,
// 🔧 保留原始 contentJson临时医嘱签发时需要展开所有字段传给后端
contentJson: item.contentJson || item.content_json || null,
// 🔧 标记是否已签发,用于临时医嘱弹窗控制按钮状态
_signed: item.statusEnum === 2
} }
} }
}) })

View File

@@ -317,10 +317,11 @@ const getMethodCodeDict = computed(() => {
return dict return dict
}) })
// 🔧 修复 Bug #446: 检查计费药品是否已全部提交(有 requestId用于区分"首次签名"和"已提交重开" // 🔧 检查是否已全部签发statusEnum=2用于控制"一键签名"按钮是否禁用
// 注意:不能依赖 requestId因为草稿记录也有 requestId
const allItemsSubmitted = computed(() => { const allItemsSubmitted = computed(() => {
const meds = props.billingMedicines || [] const meds = props.billingMedicines || []
return meds.length > 0 && meds.every(m => m.requestId) return meds.length > 0 && meds.every(m => m._signed)
}) })
// 展开/收起控制 // 展开/收起控制
@@ -425,32 +426,20 @@ const displayAdvices = ref([])
// 初始化 displayAdvices // 初始化 displayAdvices
const initDisplayAdvices = () => { const initDisplayAdvices = () => {
// 只要有用户修改过的数据,就优先使用用户修改后的数据 // 区域二只显示已生成(已签发)的数据,没有时保持为空,不自动转换区域一的草稿
// 避免自动转换覆盖用户的修改
if (props.temporaryAdvices && props.temporaryAdvices.length > 0) { if (props.temporaryAdvices && props.temporaryAdvices.length > 0) {
// 🔧 修复:将旧编码映射到新编码
displayAdvices.value = props.temporaryAdvices.map(mapUsageCode) displayAdvices.value = props.temporaryAdvices.map(mapUsageCode)
} else { } else {
// 否则自动转换第一区域的已引用计费药品 displayAdvices.value = []
displayAdvices.value = convertedAdvices.value
} }
} }
// 初始化 // 初始化
initDisplayAdvices() initDisplayAdvices()
// 组件挂载时,如果没有传入临时医嘱数据,将自动转换后的数据同步到父组件
// 确保修改可以被父组件保存,下次打开仍然能看到
onMounted(() => { onMounted(() => {
// 初始化 displayAdvices
initDisplayAdvices() initDisplayAdvices()
// 🔧 修复:不再自动把区域一草稿转为区域二数据
if ((!props.temporaryAdvices || props.temporaryAdvices.length === 0) && convertedAdvices.value.length > 0) {
console.log('=== onMounted 触发 emit ===')
emit('update:temporary-advices', convertedAdvices.value)
}
}) })
// 🔧 新增:监听 temporary-advices prop 的变化,同步更新 displayAdvices // 🔧 新增:监听 temporary-advices prop 的变化,同步更新 displayAdvices
@@ -465,23 +454,9 @@ watch(() => props.temporaryAdvices, (newVal, oldVal) => {
} }
}, { deep: true, immediate: true }) }, { deep: true, immediate: true })
// 监听第一区域的计费药品变化,如果第一区域更新了,并且用户还没修改过第二区域数据,自动更新第二区域 // 🔧 修复:不再自动把区域一草稿转为区域二数据,区域二只显示已签发的记录
watch(() => props.billingMedicines, (newVal) => { watch(() => props.billingMedicines, (newVal) => {
console.log('=== watch billingMedicines 被调用 ===') // 空 watcher仅保留结构避免破坏其他逻辑
console.log('=== newVal ===', newVal)
console.log('=== props.temporaryAdvices ===', props.temporaryAdvices)
console.log('=== props.temporaryAdvices.length ===', props.temporaryAdvices?.length)
// 🔧 修复:只有当 temporary-advices 为空时才自动更新,避免覆盖用户的修改
if (!props.temporaryAdvices || props.temporaryAdvices.length === 0) {
if (newVal.length > 0) {
console.log('=== watch 触发 emit ===')
displayAdvices.value = convertedAdvices.value
emit('update:temporary-advices', convertedAdvices.value)
}
} else {
console.log('=== watch 不触发 emit因为 props.temporaryAdvices 已有数据 ===')
}
}, { deep: true }) }, { deep: true })
// 🔧 新增:监听字典加载,确保字典加载后编辑弹窗能正确显示 // 🔧 新增:监听字典加载,确保字典加载后编辑弹窗能正确显示
@@ -697,7 +672,9 @@ const handleSubmit = async () => {
} }
// 检查是否有医嘱数据 // 检查是否有医嘱数据
if (displayAdvices.value.length === 0) { // 🔧 修复签发应该处理区域一中所有待签发的药品billingMedicines而非区域二中已有数据displayAdvices
const itemsToSign = convertedAdvices.value
if (itemsToSign.length === 0) {
ElMessage.warning('没有可保存的医嘱数据') ElMessage.warning('没有可保存的医嘱数据')
return return
} }
@@ -709,7 +686,7 @@ const handleSubmit = async () => {
// 构建保存医嘱的请求参数 // 构建保存医嘱的请求参数
const saveData = { const saveData = {
organizationId: props.patientInfo.orgId || props.patientInfo.organizationId || 1, // 使用计费时的orgId organizationId: props.patientInfo.orgId || props.patientInfo.organizationId || 1, // 使用计费时的orgId
adviceSaveList: displayAdvices.value.map((advice, index) => { adviceSaveList: itemsToSign.map((advice, index) => {
// 获取原始药品数据 // 获取原始药品数据
const originalMedicine = advice.originalMedicine const originalMedicine = advice.originalMedicine
@@ -764,53 +741,44 @@ const handleSubmit = async () => {
// 重新序列化contentJson // 重新序列化contentJson
const updatedContentJson = JSON.stringify(contentJsonData); const updatedContentJson = JSON.stringify(contentJsonData);
// 构造请求参数(与门诊医生工作站完全一致 // 🔧 构造请求参数:与计费弹窗 handleSave 保持完全一致的模式
// 先展开原始 contentJson 的所有字段,然后用当前值覆盖(保证不丢字段)
return { return {
...contentJsonData,
// 基础信息 // 基础信息
// 🔧 修复dbOpType 的判断逻辑 - 如果有 requestId 则为修改,否则为新增
// 但对于从计费药品转换来的数据,即使没有 requestId也应该先更新 chargeItemId
dbOpType: originalMedicine?.requestId ? '2' : (originalMedicine?.chargeItemId ? '2' : '1'), dbOpType: originalMedicine?.requestId ? '2' : (originalMedicine?.chargeItemId ? '2' : '1'),
adviceType: originalMedicine?.adviceType || 1, // 使用原始类型或默认1 adviceType: originalMedicine?.adviceType || 1,
requestId: originalMedicine?.requestId, requestId: originalMedicine?.requestId,
chargeItemId: originalMedicine?.chargeItemId, chargeItemId: originalMedicine?.chargeItemId,
contentJson: updatedContentJson, contentJson: updatedContentJson,
categoryCode: contentJsonData.categoryCode || originalMedicine?.categoryCode, categoryCode: contentJsonData.categoryCode || originalMedicine?.categoryCode,
pharmacologyCategoryCode: contentJsonData.pharmacologyCategoryCode || originalMedicine?.pharmacologyCategoryCode,
partPercent: originalMedicine?.partPercent || contentJsonData.partPercent || 1, partPercent: originalMedicine?.partPercent || contentJsonData.partPercent || 1,
partAttributeEnum: originalMedicine?.partAttributeEnum || contentJsonData.partAttributeEnum,
executeNum: contentJsonData.executeNum || originalMedicine?.executeNum || 1, executeNum: contentJsonData.executeNum || originalMedicine?.executeNum || 1,
prescriptionNo: originalMedicine?.prescriptionNo,
// 数量和单位:使用重新计算后的数量 // 数量和单位:使用重新计算后的数量
quantity: quantity, quantity: quantity,
dispensePerDuration: originalMedicine?.dispensePerDuration || contentJsonData.dispensePerDuration,
unitCode: contentJsonData.unitCode || originalMedicine?.unitCode || advice.unit, unitCode: contentJsonData.unitCode || originalMedicine?.unitCode || advice.unit,
unitPrice: unitPrice, unitPrice: unitPrice,
// 总价根据新的数量重新计算
totalPrice: totalPrice, totalPrice: totalPrice,
definitionId: originalMedicine?.definitionId ? String(originalMedicine.definitionId) : contentJsonData.definitionId ? String(contentJsonData.definitionId) : advice.definitionId ? String(advice.definitionId) : null, definitionId: originalMedicine?.definitionId ? String(originalMedicine.definitionId) : contentJsonData.definitionId ? String(contentJsonData.definitionId) : advice.definitionId ? String(advice.definitionId) : null,
definitionDetailId: originalMedicine?.definitionDetailId ? String(originalMedicine.definitionDetailId) : contentJsonData.definitionDetailId ? String(contentJsonData.definitionDetailId) : advice.definitionDetailId ? String(advice.definitionDetailId) : null, definitionDetailId: originalMedicine?.definitionDetailId ? String(originalMedicine.definitionDetailId) : contentJsonData.definitionDetailId ? String(contentJsonData.definitionDetailId) : advice.definitionDetailId ? String(advice.definitionDetailId) : null,
lotNumber: originalMedicine?.lotNumber || contentJsonData.lotNumber, lotNumber: originalMedicine?.lotNumber || contentJsonData.lotNumber,
// 状态和类型 // 状态和类型
statusEnum: originalMedicine?.statusEnum || 2, // 默认状态:已发送
categoryEnum: originalMedicine?.categoryEnum || contentJsonData.categoryEnum || 1, categoryEnum: originalMedicine?.categoryEnum || contentJsonData.categoryEnum || 1,
// 药品/诊疗信息 - 🔧 修复优先使用originalMedicine中的adviceDefinitionId和adviceTableName // 药品/诊疗信息
// 🔧 关键修复确保adviceDefinitionId不为null使用definitionId作为后备 adviceDefinitionId: originalMedicine?.adviceDefinitionId || contentJsonData.adviceDefinitionId || advice.definitionId || contentJsonData.definitionId,
adviceDefinitionId: originalMedicine?.adviceDefinitionId || contentJsonData.adviceDefinitionId || advice.definitionId || contentJsonData.definitionId || definitionId,
adviceTableName: originalMedicine?.adviceTableName || contentJsonData.adviceTableName || 'med_medication_definition', adviceTableName: originalMedicine?.adviceTableName || contentJsonData.adviceTableName || 'med_medication_definition',
adviceName: advice.adviceName, adviceName: advice.adviceName,
minUnitQuantity: originalMedicine?.minUnitQuantity || contentJsonData.minUnitQuantity,
// 患者和就诊信息 // 患者和就诊信息
patientId: props.patientInfo.patientId, patientId: props.patientInfo.patientId,
practitionerId: currentUser.value.id || originalMedicine?.practitionerId, // 开方医生 practitionerId: currentUser.value.id || originalMedicine?.practitionerId,
locationId: originalMedicine?.positionId || contentJsonData.locationId || props.patientInfo.orgId || props.patientInfo.positionId, locationId: originalMedicine?.positionId || contentJsonData.locationId || props.patientInfo.orgId || props.patientInfo.positionId,
positionId: originalMedicine?.positionId || contentJsonData.positionId, positionId: originalMedicine?.positionId || contentJsonData.positionId,
orgId: originalMedicine?.orgId || props.patientInfo.orgId || props.patientInfo.positionId, orgId: originalMedicine?.orgId || props.patientInfo.orgId || props.patientInfo.positionId,
performLocation: originalMedicine?.performLocation || contentJsonData.performLocation, founderOrgId: currentUser.value.id || originalMedicine?.founderOrgId,
founderOrgId: currentUser.value.id || originalMedicine?.founderOrgId, // 开方人科室
encounterId: props.patientInfo.visitId || contentJsonData.encounterId || originalMedicine?.encounterId, encounterId: props.patientInfo.visitId || contentJsonData.encounterId || originalMedicine?.encounterId,
accountId: contentJsonData.accountId || originalMedicine?.accountId, accountId: contentJsonData.accountId || originalMedicine?.accountId,
conditionId: contentJsonData.conditionId || originalMedicine?.conditionId, conditionId: contentJsonData.conditionId || originalMedicine?.conditionId,
@@ -818,12 +786,10 @@ const handleSubmit = async () => {
conditionDefinitionId: originalMedicine?.conditionDefinitionId || contentJsonData.conditionDefinitionId, conditionDefinitionId: originalMedicine?.conditionDefinitionId || contentJsonData.conditionDefinitionId,
// 治疗信息 // 治疗信息
therapyEnum: originalMedicine?.therapyEnum || contentJsonData.therapyEnum || 1, // 默认临时医嘱 therapyEnum: originalMedicine?.therapyEnum || contentJsonData.therapyEnum || 1,
// 🔧 修复:methodCode 使用编码,而不是中文名称 methodCode: methodCode,
methodCode: methodCode, // 用法(使用编码) rateCode: advice.frequency,
rateCode: advice.frequency, // 频次
dose: advice.dosage, dose: advice.dosage,
firstDose: originalMedicine?.firstDose || contentJsonData.firstDose,
doseUnitCode: contentJsonData.doseUnitCode || originalMedicine?.doseUnitCode || advice.unit, doseUnitCode: contentJsonData.doseUnitCode || originalMedicine?.doseUnitCode || advice.unit,
skinTestFlag: originalMedicine?.skinTestFlag || contentJsonData.skinTestFlag, skinTestFlag: originalMedicine?.skinTestFlag || contentJsonData.skinTestFlag,
injectFlag: originalMedicine?.injectFlag || contentJsonData.injectFlag, injectFlag: originalMedicine?.injectFlag || contentJsonData.injectFlag,
@@ -832,55 +798,30 @@ const handleSubmit = async () => {
groupId: originalMedicine?.groupId, groupId: originalMedicine?.groupId,
packageId: originalMedicine?.packageId || contentJsonData.packageId, packageId: originalMedicine?.packageId || contentJsonData.packageId,
// 诊疗活动信息
activityId: contentJsonData.adviceDefinitionId || originalMedicine?.adviceDefinitionId, // 对于诊疗类型,使用 adviceDefinitionId 作为 activity_id
// 医保信息 // 医保信息
ybClassEnum: originalMedicine?.ybClassEnum || contentJsonData.ybClassEnum, ybClassEnum: originalMedicine?.ybClassEnum || contentJsonData.ybClassEnum,
// 中药信息 // 🔧 补充:手术计费上下文
chineseHerbsDoseQuantity: originalMedicine?.chineseHerbsDoseQuantity || contentJsonData.chineseHerbsDoseQuantity || 1 generateSourceEnum: 6,
sourceBillNo: props.patientInfo?.operCode || originalMedicine?.sourceBillNo || ''
} }
}) })
} }
// 调用保存医嘱接口 // 🔧 修复:使用 savePrescription + adviceOpType='2'(后端 AdviceOpType: '2'=SIGN_ADVICE 签发)
// 如果已签名,则执行签发操作('1'),否则执行保存操作('0' const response = await savePrescription(saveData, '2')
const adviceOpType = isSigned.value ? '1' : '0'
const response = await savePrescription(saveData, adviceOpType)
if (response.code === 200) { if (response.code === 200) {
ElMessage.success('临时医嘱保存成功') ElMessage.success('临时医嘱保存成功')
// 🔧 关键修复:处理后端返回的医嘱 ID // 🔧 签名成功,后端已更新已有记录的 statusEnum 为 2
// 后端返回的 data 数组只包含新创建的医嘱记录的 ID不包含已更新的记录 // 不需要手动更新 requestId签名操作是 UPDATE不是 INSERT后端返回新 ID
// 我们需要正确地映射这些 ID 到对应的临时医嘱数据
if (response.data && Array.isArray(response.data) && response.data.length > 0) {
// 创建一个索引,记录哪些记录是新创建的
let newDataIndex = 0
displayAdvices.value.forEach((advice, index) => { // 🔧 修复:将保存后的医嘱数据传递给父组件
const originalMedicine = advice.originalMedicine || {}
// 如果这个记录没有 requestId说明是新创建的
if (!originalMedicine.requestId) {
if (newDataIndex < response.data.length) {
// 更新 originalMedicine 中的 requestId
if (!displayAdvices.value[index].originalMedicine) {
displayAdvices.value[index].originalMedicine = {}
}
displayAdvices.value[index].originalMedicine.requestId = response.data[newDataIndex]
newDataIndex++
}
}
})
}
// 🔧 修复:将保存后的医嘱数据(包含用户的修改和后端返回的 requestId传递给父组件
// 这样父组件可以使用用户修改后的数据,而不是重新加载数据
const submitData = { const submitData = {
patientInfo: props.patientInfo, patientInfo: props.patientInfo,
billingMedicines: props.billingMedicines, billingMedicines: props.billingMedicines,
temporaryAdvices: displayAdvices.value, // 使用用户修改后的数据,包含后端返回的 requestId temporaryAdvices: itemsToSign,
signature: { signature: {
doctorName: currentUser.value.name, doctorName: currentUser.value.name,
signatureTime: signatureTime.value signatureTime: signatureTime.value

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long