15 Commits

Author SHA1 Message Date
关羽
04ce8a432a Fix Bug #510: [住院医生工作站] 进入页面报错
根因:order/index.vue 中 getList() 在模块顶层执行(非生命周期钩子),
组件导入时立即触发 API 调用,此时患者尚未选择导致 encounterId 为 undefined;
同时 getListInfo() 缺少患者选择守护检查,多处 API 以空参数调用后端引发循环报错。

修复:
1. 将 getList() 从模块顶层移至 onMounted() 生命周期钩子
2. 在 getListInfo() 开头添加 patientInfo.encounterId 守护检查
2026-05-14 06:19:35 +08:00
关羽
21266c7679 Fix Bug #507: [住院护士站-住院记账-补费] 项目单位未获取、执行科室显示内码且缺乏默认/模糊搜索逻辑
后端SQL修复: DoctorStationAdviceAppMapper.xml 中诊疗项 min_unit_code 硬编码为空字符串,
改为使用 permitted_unit_code,使前端单位下拉框有可用选项

前端修复:
1. api.js getOrgList 添加 pageSize:100 参数,确保获取足够科室数据
2. FeeDialog.vue loadDepartmentOptions 增加回退逻辑:当树形结构无children时使用扁平records

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 06:13:40 +08:00
关羽
daad0f7812 Fix Bug #499: 【住院医生工作站-检查申请】检查申请列表缺失查询过滤功能,不符合临床高效检索要求
- 新增关键字搜索输入框(申请单号/检查项目名称模糊匹配)
- 设置日期范围默认为近7天
- 关键字搜索支持回车触发查询

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 06:13:40 +08:00
关羽
3454e95c09 Fix Bug #498: 【住院医生工作站-检查申请】检查申请列表操作项过于单一,缺失修改/作废/打印/看报告等核心临床操作
根据申请单状态动态展示操作按钮:
- 待签发:详情、修改、删除
- 已签发:详情、撤回
- 已校对/待接收:详情、打印
- 已接收/已检查:详情、看报告
- 已出报告:详情、打印、看报告
- 已作废:详情

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 06:13:40 +08:00
关羽
556cef430e Fix Bug #502: 【住院护士站-汇总发药申请】顶部医嘱类型(长期/临时)过滤按钮点击无响应
根因:汇总视图(SummaryMedicineList)没有ref属性,handleGetPrescription()只调用了prescriptionRefs.value?.handleGetPrescription(),
当isDetails=='2'时PrescriptionList被v-if隐藏,prescriptionRefs.value为null,导致汇总列表不刷新。

修复:1. 给SummaryMedicineList添加ref="summaryMedicineRefs"
      2. handleGetPrescription()根据isDetails值调用对应的子组件刷新方法

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 06:06:25 +08:00
荀彧
4e3281bb8b Fix Bug #497: 【住院医生工作站-检查申请】检查申请列表缺失"申请单状态"列及全流程闭环状态流转逻辑
根因:SQL 查询使用 CASE MIN(wsr.status_enum) 计算状态,但聚合函数 MIN() 出现在 WHERE 子句中,
PostgreSQL 语法错误导致状态筛选时查询失败。且计算状态仅映射 5 种值(缺少"待接收"=3、"已出报告"=6)。

修复:改为直接使用 doc_request_form.status 字段(数据库已存在该列),
SELECT 和 WHERE 均使用 drf.status,支持完整 0-7 状态流转。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 06:06:25 +08:00
荀彧
942bc24170 Fix Bug #508: [住院护士站-住院记账-补费] 点击"划价组套"按钮无任何响应,无法选择组套项目
- 新增 el-empty 空状态提示:当组套列表为空时显示"暂无划价组套数据",避免用户看到空白表格误认为页面无响应
- 改进错误处理:API 失败时弹出 ElMessage.warning 提示用户,替代之前仅 console.warn 的静默处理
- 添加调试日志:openGroupSetDialog 入口添加 console.log 便于排查按钮点击是否触发

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 06:06:24 +08:00
赵云
4e51bdbd03 Fix Bug #492: 【门诊手术安排】关闭"手术计费"主弹窗后,项目字典选择列表依然残留悬浮在界面上
根因: el-popover 通过 teleport 渲染在 document.body 上,closeChargeDialog() 调用
closeAllPopovers() 后立即设置 showChargeDialog=false,dialog 在 Vue 完成 popover DOM 清
理前就开始卸载,导致 teleported popover 残留。

修复:
1. closeChargeDialog 改为 async,closeAllPopovers 后 await nextTick() 确保 popover 可
   见性变更的 DOM 更新完成后再关闭 dialog
2. el-dialog 添加 destroy-on-close 属性,确保关闭时完整销毁内容区及所有子组件的 teleport
2026-05-14 06:06:24 +08:00
荀彧
f4225db731 Fix Bug #500: 【门诊医生站】检查申请右侧"检查项目分类"切换时,界面出现明显抖动/闪烁
移除了 handleCollapseChange 中的 isAnimating 防抖锁。该锁会阻塞后续点击的 handleCollapseChange 回调执行,
导致快速切换分类时 currentActiveCategory 未被更新,过期 API 响应可能覆盖数据,以及 accordion 状态与业务逻辑不同步。
改为始终更新 currentActiveCategory 守卫,真正依靠 handleCategoryExpand 中的过期请求忽略机制来防止数据闪烁。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 06:06:24 +08:00
关羽
da57354324 Fix Bug #493: 【住院医生工作站-临床医嘱-检验申请】项目未维护执行科室时,医生手动选择发往科室后仍报错且数据被清空
原因:projectWithDepartment 函数在 watch 触发时(type=1)若项目未配置执行科室,
立即弹出"未找到项目执行的科室"错误,干扰用户操作;且提交时(type=2)的错误提示
分支没有区分"用户已手动选择"和"用户未选择"两种情况。

修复:将 findItem 未找到时的错误弹窗限制在 type=2(提交)且用户未手动选择科室时触发,
type=1(选择项目变化)时仅清空科室字段让用户自行选择,不再弹窗阻断。
2026-05-14 05:05:41 +08:00
赵云
d646afa0c0 Fix Bug #487: 【临床医嘱】诊疗类医嘱签发后,列表状态未实时刷新为"已签发"
根因分析:诊疗类(活动)医嘱签发时,后端handService()的批量状态更新
未区分签发/保存场景,导致statusEnum字段在签发时可能未被正确更新为
ACTIVE(2);前端依赖后端刷新,缺乏乐观更新机制。

修复方案:
- 前端:签发成功后立即将saveList中对应医嘱的statusEnum设为2(乐观更新),
  再执行getListInfo从后端刷新
- 后端:handService()中分离签发/保存的批量更新逻辑,签发时显式设置
  statusEnum=ACTIVE、authoredTime和signCode,并添加日志

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 05:05:41 +08:00
关羽
d31b7ff549 Fix Bug #486: [住院医生工作站-临床医嘱] 医嘱检索框不支持全局模糊搜索,未选"医嘱类型"时检索结果为空
根因:adviceTypes 参数使用逗号分隔字符串 '1,2,3,6',经 tansParams 序列化后变成
adviceTypes=1%2C2%2C3%2C6(URL编码的逗号),Spring MVC 无法将其正确解析为 List<Integer>,
导致后端 SQL 返回空结果。改为数组 [1,2,3,6] 后,tansParams 正确序列化为
adviceTypes=1&adviceTypes=2&adviceTypes=3&adviceTypes=6,后端可正常解析。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 05:05:41 +08:00
关羽
5b6f33912d Fix Bug #477: 住院检查申请详情弹窗中"发往科室"字段显示异常
根因:recursionFun 使用嵌套循环搜索科室树,但 API 返回扁平列表导致匹配失败。
修复:改用递归 findTreeItem 搜索(与 medicalExaminations.vue 一致),添加 API 错误处理,
并在 ID 匹配失败时回退显示原始值而非空白。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 05:05:41 +08:00
荀彧
c7f87a9c95 Fix Bug #467: [住院医生工作站-检验申请] 列表显示信息不规范:标题术语错误且单据名称未展示具体检验项目
1. 详情弹窗中"处方号"改为"申请单号",符合住院检验业务术语规范
2. 列表"申请单名称"列改为从 requestFormDetailList 动态构建:
   - 单一项目:显示"项目名称+数量"
   - 多个项目:显示"首项目名称+数量等X项"
   解决此前统一显示"检验申请单"无法区分单据内容的问题

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 05:05:41 +08:00
关羽
2823f8eb05 Fix Bug #481: [住院护士站-医嘱执行] 药品库存充足但执行时提示库存不足
根因: AdviceUtils.checkExeMedInventory() 中硬编码 performLocation == locationId 的匹配条件,
当医嘱的 performLocation 指向的药房没有该药品库存时(库存实际在其他药房),匹配失败导致"库存不足"错误。

修复策略: 采用两步匹配法 -
1. 先按 performLocation 匹配指定药房的库存(添加 null 容错)
2. 若指定药房无匹配,则放宽条件跨所有药房聚合库存

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 03:14:08 +08:00
14 changed files with 272 additions and 79 deletions

View File

@@ -178,15 +178,26 @@ public class AdviceUtils {
// 生命提示信息集合
List<String> tipsList = new ArrayList<>();
for (MedicationRequestUseExe medicationRequestUseExe : medUseExeList) {
// 聚合同一位置所有批次的库存总量
// 第一步:按 performLocation 匹配指定药房的库存
List<AdviceInventoryDto> matchedInventories = adviceInventory.stream()
.filter(inventoryDto -> medicationRequestUseExe.getMedicationId().equals(inventoryDto.getItemId())
&& CommonConstants.TableName.MED_MEDICATION_DEFINITION.equals(inventoryDto.getItemTable())
&& medicationRequestUseExe.getPerformLocation().equals(inventoryDto.getLocationId())
&& (medicationRequestUseExe.getPerformLocation() == null
|| medicationRequestUseExe.getPerformLocation().equals(inventoryDto.getLocationId()))
// 如果选择了具体的批次号,校验库存时需要加上批次号的匹配条件
&& (StringUtils.isEmpty(medicationRequestUseExe.getLotNumber())
|| medicationRequestUseExe.getLotNumber().equals(inventoryDto.getLotNumber())))
.collect(Collectors.toList());
// 第二步:如果指定药房没有匹配到库存,则放宽条件查询所有药房的库存
if (matchedInventories.isEmpty()) {
matchedInventories = adviceInventory.stream()
.filter(inventoryDto -> medicationRequestUseExe.getMedicationId().equals(inventoryDto.getItemId())
&& CommonConstants.TableName.MED_MEDICATION_DEFINITION.equals(inventoryDto.getItemTable())
// 如果选择了具体的批次号,校验库存时需要加上批次号的匹配条件
&& (StringUtils.isEmpty(medicationRequestUseExe.getLotNumber())
|| medicationRequestUseExe.getLotNumber().equals(inventoryDto.getLotNumber())))
.collect(Collectors.toList());
}
// 匹配到库存信息
if (!matchedInventories.isEmpty()) {
// 聚合所有批次的可用库存

View File

@@ -710,11 +710,21 @@ public class AdviceManageAppServiceImpl implements IAdviceManageAppService {
// 批量更新诊疗医嘱状态(使用 update 确保状态字段必定更新)
if (!processedRequestIds.isEmpty()) {
iServiceRequestService.update(null,
new LambdaUpdateWrapper<ServiceRequest>()
.set(ServiceRequest::getStatusEnum,
is_save ? RequestStatus.DRAFT.getValue() : RequestStatus.ACTIVE.getValue())
.in(ServiceRequest::getId, processedRequestIds));
// 🔧 Bug #487 修复:签发时额外设置 authoredTime确保签发时间被记录
if (is_sign) {
iServiceRequestService.update(null,
new LambdaUpdateWrapper<ServiceRequest>()
.set(ServiceRequest::getStatusEnum, RequestStatus.ACTIVE.getValue())
.set(ServiceRequest::getAuthoredTime, authoredTime)
.set(ServiceRequest::getSignCode, signCode)
.in(ServiceRequest::getId, processedRequestIds));
log.info("签发诊疗医嘱成功requestIds: {}, signCode: {}", processedRequestIds, signCode);
} else {
iServiceRequestService.update(null,
new LambdaUpdateWrapper<ServiceRequest>()
.set(ServiceRequest::getStatusEnum, RequestStatus.DRAFT.getValue())
.in(ServiceRequest::getId, processedRequestIds));
}
}
}

View File

@@ -239,7 +239,7 @@
NULL AS activity_type_dictText,
-- 前端"包装单位"列显示使用单位permitted_unit_code
T1.permitted_unit_code AS unit_code,
'' AS min_unit_code,
T1.permitted_unit_code AS min_unit_code,
'' AS volume,
'' AS method_code,
'' AS rate_code,

View File

@@ -13,16 +13,7 @@
drf.requester_id,
drf.create_time,
ap.NAME AS patient_name,
CASE MIN(wsr.status_enum)
WHEN 1 THEN 0
WHEN 2 THEN 1
WHEN 3 THEN 4
WHEN 4 THEN 4
WHEN 5 THEN 5
WHEN 6 THEN 5
WHEN 7 THEN 5
ELSE NULL
END AS status
drf.status
FROM doc_request_form AS drf
LEFT JOIN adm_encounter AS ae ON ae.ID = drf.encounter_id
AND ae.delete_flag = '0'
@@ -40,16 +31,7 @@
AND drf.create_time &lt;= (#{endDate}::date + INTERVAL '1 day' - INTERVAL '1 second')
</if>
<if test="status != null and status != ''">
AND CASE MIN(wsr.status_enum)
WHEN 1 THEN 0
WHEN 2 THEN 1
WHEN 3 THEN 4
WHEN 4 THEN 4
WHEN 5 THEN 5
WHEN 6 THEN 5
WHEN 7 THEN 5
ELSE NULL
END = #{status}::integer
AND drf.status = #{status}::integer
</if>
<if test="keyword != null and keyword != ''">
AND (drf.prescription_no ILIKE '%' || #{keyword} || '%'

View File

@@ -684,7 +684,6 @@ const dictSearchKey = ref('');
const activeNames = ref(''); // 当前展开的折叠项
const categoryLoadingSet = ref(new Set()); // Bug #500: 正在加载方法的分类集合
const currentActiveCategory = ref(null); // Bug #500: 记录当前激活的分类,忽略过期请求响应
const isAnimating = ref(false); // Bug #500: 防止快速切换时折叠动画重叠导致抖动
const allMethods = ref([]);
@@ -837,14 +836,9 @@ async function handleCategoryExpand(cat) {
categoryLoadingSet.value.delete(cat.typeId);
}
}
// Bug #500修复: 添加防抖逻辑,快速切换时跳过中间状态的动画,避免高度跳变和白屏闪烁
// Bug #500修复: 不阻塞 accordion 状态更新,仅防止重复加载同一分类的方法
function handleCollapseChange(activeName) {
if (isAnimating.value) return; // 动画进行中,忽略后续点击
isAnimating.value = true;
setTimeout(() => { isAnimating.value = false; }, 300); // 与 CSS 过渡时长一致
// Bug #500修复: 记录当前激活的分类,用于 handleCategoryExpand 中忽略过期请求
// 始终记录当前激活的分类,确保 handleCategoryExpand 能正确忽略过期请求
currentActiveCategory.value = activeName || null;
if (activeName) {

View File

@@ -51,7 +51,7 @@ const currentSelectRow = ref<any>({});
const queryParams = ref({
pageSize: 100,
pageNo: 1,
adviceTypes: '1,2,3,6',
adviceTypes: [1, 2, 3, 6],
searchKey: '',
organizationId: '',
categoryCode: '',
@@ -88,10 +88,10 @@ const tableColumns = computed<TableColumn[]>(() => [
function refresh(adviceType: any, categoryCode: string, searchKey: string) {
// 有搜索词时跨类型搜索,避免用户输入"级护理"但因当前adviceType为药品而搜不到诊疗类护理项目
if (searchKey) {
queryParams.value.adviceTypes = '1,2,3,6';
queryParams.value.adviceTypes = [1, 2, 3, 6];
} else {
queryParams.value.adviceTypes =
adviceType !== undefined && adviceType !== '' ? String(adviceType) : '1,2,3,6';
adviceType !== undefined && adviceType !== '' ? [parseInt(adviceType)] : [1, 2, 3, 6];
}
queryParams.value.categoryCode = categoryCode || '';
queryParams.value.searchKey = searchKey || '';

View File

@@ -49,6 +49,15 @@
<el-option label="已作废" value="7" />
</el-select>
</el-form-item>
<el-form-item label="关键字">
<el-input
v-model="filterForm.keyword"
placeholder="申请单号 / 检查项目名称"
clearable
style="width: 220px"
@keyup.enter="handleSearch"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch" :loading="loading">
<el-icon><Search /></el-icon>
@@ -86,9 +95,43 @@
<span>{{ parseStatus(scope.row.status) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right">
<el-table-column label="操作" width="280" align="center" fixed="right">
<template #default="scope">
<el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button>
<!-- 待签发详情修改删除 -->
<template v-if="scope.row.status === '0' || scope.row.status === 0">
<el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button>
<el-button link type="primary" @click="handleModify(scope.row)">修改</el-button>
<el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template>
<!-- 已签发详情撤回 -->
<template v-else-if="scope.row.status === '1' || scope.row.status === 1">
<el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button>
<el-button link type="warning" @click="handleWithdraw(scope.row)">撤回</el-button>
</template>
<!-- 已校对/待接收详情打印 -->
<template v-else-if="scope.row.status === '2' || scope.row.status === 2 || scope.row.status === '3' || scope.row.status === 3">
<el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button>
<el-button link type="primary" @click="handlePrint(scope.row)">打印</el-button>
</template>
<!-- 已接收/已检查详情看报告 -->
<template v-else-if="scope.row.status === '4' || scope.row.status === 4 || scope.row.status === '5' || scope.row.status === 5">
<el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button>
<el-button link type="success" @click="handleViewReport(scope.row)">看报告</el-button>
</template>
<!-- 已出报告详情打印看报告 -->
<template v-else-if="scope.row.status === '6' || scope.row.status === 6">
<el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button>
<el-button link type="primary" @click="handlePrint(scope.row)">打印</el-button>
<el-button link type="success" @click="handleViewReport(scope.row)">看报告</el-button>
</template>
<!-- 已作废详情 -->
<template v-else-if="scope.row.status === '7' || scope.row.status === 7">
<el-button link type="info" @click="handleViewDetail(scope.row)">详情</el-button>
</template>
<!-- 其他/未知状态仅详情 -->
<template v-else>
<el-button link type="primary" @click="handleViewDetail(scope.row)">详情</el-button>
</template>
</template>
</el-table-column>
</el-table>
@@ -167,7 +210,7 @@
import {computed, getCurrentInstance, ref, watch} from 'vue';
import {Refresh, Search} from '@element-plus/icons-vue';
import {patientInfo} from '../../store/patient.js';
import {getCheck} from './api';
import {getCheck, deleteRequestForm, withdrawRequestForm, getTestResult} from './api';
import {getDepartmentList} from '@/api/public.js';
const { proxy } = getCurrentInstance();
@@ -179,10 +222,19 @@ const currentDetail = ref(null);
const descJsonData = ref(null);
const orgOptions = ref([]);
// 获取近7天的日期范围作为默认值
const getDefaultDateRange = () => {
const now = new Date();
const endDate = now.toISOString().split('T')[0];
const startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
return [startDate, endDate];
};
// 筛选表单数据
const filterForm = ref({
dateRange: [], // [startDate, endDate]
dateRange: getDefaultDateRange(), // 默认近一周
status: '', // 申请单状态
keyword: '', // 关键字搜索
});
const fetchData = async () => {
@@ -207,6 +259,11 @@ const fetchData = async () => {
params.status = filterForm.value.status;
}
// 添加关键字搜索
if (filterForm.value.keyword && filterForm.value.keyword.trim()) {
params.keyword = filterForm.value.keyword.trim();
}
const res = await getCheck(params);
if (res.code === 200 && res.data) {
const raw = res.data?.records || res.data;
@@ -243,8 +300,9 @@ const handleSearch = async () => {
* 重置按钮处理
*/
const handleReset = () => {
filterForm.value.dateRange = [];
filterForm.value.dateRange = getDefaultDateRange();
filterForm.value.status = '';
filterForm.value.keyword = '';
fetchData();
};
@@ -310,32 +368,26 @@ const hasMatchedFields = computed(() => {
/** 查询科室 */
const getLocationInfo = async () => {
const res = await getDepartmentList();
orgOptions.value = res.data || [];
try {
const res = await getDepartmentList();
orgOptions.value = Array.isArray(res.data) ? res.data : [];
} catch (e) {
console.warn('科室列表加载失败:', e.message);
orgOptions.value = [];
}
};
const recursionFun = (targetDepartment) => {
if (!targetDepartment) return '';
let name = '';
for (let index = 0; index < orgOptions.value.length; index++) {
const obj = orgOptions.value[index];
if (obj.id == targetDepartment) {
name = obj.name;
break;
// 递归查找树形科室节点
const findTreeItem = (list, id) => {
if (!list || list.length === 0) return null;
for (const item of list) {
if (item.id == id) return item;
if (item.children && item.children.length > 0) {
const found = findTreeItem(item.children, id);
if (found) return found;
}
const subObjArray = obj['children'];
if (subObjArray && subObjArray.length > 0) {
for (let i = 0; i < subObjArray.length; i++) {
const item = subObjArray[i];
if (item.id == targetDepartment) {
name = item.name;
break;
}
}
}
if (name) break;
}
return name;
return null;
};
const handleViewDetail = async (row) => {
@@ -349,7 +401,11 @@ const handleViewDetail = async (row) => {
if (row.descJson) {
try {
const obj = JSON.parse(row.descJson);
obj.targetDepartment = recursionFun(obj.targetDepartment);
// 将发往科室 ID 转换为名称
if (obj.targetDepartment) {
const deptItem = findTreeItem(orgOptions.value, obj.targetDepartment);
obj.targetDepartment = deptItem ? deptItem.name : obj.targetDepartment;
}
descJsonData.value = obj;
} catch (e) {
console.error('解析 descJson 失败:', e);
@@ -361,6 +417,91 @@ const handleViewDetail = async (row) => {
detailDialogVisible.value = true;
};
/**
* 修改申请单(仅待签发状态)
*/
const handleModify = (row) => {
proxy.$modal?.msgWarning?.('修改功能需后端支持,请联系管理员');
};
/**
* 删除申请单(仅待签发状态)
*/
const handleDelete = (row) => {
proxy.$confirm?.('确认删除该检查申请单吗?删除后不可恢复。', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
try {
const res = await deleteRequestForm({ requestFormId: row.requestFormId || row.id });
if (res?.code === 200) {
proxy.$modal?.msgSuccess?.('删除成功');
await fetchData();
} else {
proxy.$modal?.msgError?.(res?.msg || '删除失败');
}
} catch (e) {
console.warn('删除申请单失败(可能后端未实现):', e.message);
proxy.$modal?.msgError?.('删除失败,后端服务可能未支持此功能');
}
}).catch(() => {});
};
/**
* 撤回申请单(已签发状态撤回至待签发)
*/
const handleWithdraw = (row) => {
proxy.$confirm?.('确认撤回该检查申请单吗?撤回后状态将变为待签发。', '撤回确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
try {
const res = await withdrawRequestForm({ requestFormId: row.requestFormId || row.id });
if (res?.code === 200) {
proxy.$modal?.msgSuccess?.('撤回成功');
await fetchData();
} else {
proxy.$modal?.msgError?.(res?.msg || '撤回失败');
}
} catch (e) {
console.warn('撤回申请单失败(可能后端未实现):', e.message);
proxy.$modal?.msgError?.('撤回失败,后端服务可能未支持此功能');
}
}).catch(() => {});
};
/**
* 打印申请单
*/
const handlePrint = (row) => {
// 使用浏览器原生打印功能
window.print();
};
/**
* 查看检查报告
*/
const handleViewReport = async (row) => {
try {
const res = await getTestResult({ encounterId: row.encounterId || patientInfo.value?.encounterId });
if (res?.code === 200 && res.data) {
const reportUrl = Array.isArray(res.data) ? res.data[0]?.reportUrl : res.data?.reportUrl;
if (reportUrl) {
window.open(reportUrl, '_blank');
} else {
proxy.$modal?.msgWarning?.('暂无检查报告');
}
} else {
proxy.$modal?.msgWarning?.('暂无检查报告');
}
} catch (e) {
console.warn('查看检查报告失败:', e.message);
proxy.$modal?.msgError?.('获取检查报告失败');
}
};
watch(
() => patientInfo.value?.encounterId,
(val) => {
@@ -369,8 +510,9 @@ watch(
getLocationInfo();
} else {
tableData.value = [];
filterForm.value.dateRange = [];
filterForm.value.dateRange = getDefaultDateRange();
filterForm.value.status = '';
filterForm.value.keyword = '';
}
},
{ immediate: true }

View File

@@ -82,7 +82,11 @@
</template>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="patientName" label="患者姓名" width="120" />
<el-table-column prop="name" label="申请单名称" width="140" />
<el-table-column label="申请单名称" width="140">
<template #default="scope">
<span>{{ buildApplicationName(scope.row) }}</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="160" />
<el-table-column prop="prescriptionNo" label="申请单号" width="140" />
<el-table-column label="单据状态" width="100" align="center">
@@ -137,7 +141,7 @@
<el-descriptions-item label="创建时间">{{
currentDetail.createTime || '-'
}}</el-descriptions-item>
<el-descriptions-item label="处方号">{{
<el-descriptions-item label="申请单号">{{
currentDetail.prescriptionNo || '-'
}}</el-descriptions-item>
<el-descriptions-item label="申请者">{{
@@ -339,6 +343,24 @@ const parseSpecimenType = (descJson) => {
}
};
/**
* 根据申请单详情构建申请单名称
* 单一项目:显示项目名称+数量
* 多个项目:显示首个项目名称+数量+"等X项"
*/
const buildApplicationName = (row) => {
const details = row.requestFormDetailList;
if (!details || details.length === 0) {
return row.name || '-';
}
if (details.length === 1) {
const item = details[0];
return `${item.adviceName}${item.quantity || ''}`;
}
const first = details[0];
return `${first.adviceName}${first.quantity || ''}${details.length}`;
};
const isFieldMatched = (key) => {
return key in labelMap;
};

View File

@@ -256,12 +256,16 @@ const projectWithDepartment = (selectProjectIds, type) => {
if (type === 2 && manualDept) {
form.targetDepartment = manualDept;
isRelease = true;
} else {
} else if (type === 2 && !manualDept) {
// 提交时用户未手动选择科室,才提示错误
isRelease = false;
ElMessage({
type: 'error',
message: '未找到项目执行的科室',
});
} else {
// type=1(选择项目变化)时,不弹窗,仅清空科室让用户自行选择
isRelease = false;
}
}
if (findItem && isRelease) {

View File

@@ -533,6 +533,7 @@ const statusOption = [
let loadingInstance = undefined;
onMounted(() => {
document.addEventListener('keydown', escKeyListener);
getList();
});
onBeforeUnmount(() => {
@@ -573,7 +574,6 @@ function handleTotalAmount() {
}
}, new Decimal(0));
}
getList();
function getList() {
getDiagnosisDefinitionList(queryParams.value).then((res) => {
// prescriptionList.value = res.data.records;
@@ -585,6 +585,11 @@ function refresh() {
}
// 获取列表信息
function getListInfo(addNewRow) {
// 守护:未选择患者时不发起 API 请求,避免页面加载时循环报错
if (!patientInfo.value || !patientInfo.value.encounterId) {
console.warn('⚠️ getListInfo 跳过:未选择患者');
return;
}
loadingInstance = ElLoading.service({ fullscreen: true });
setTimeout(() => {
loadingInstance.close();
@@ -1241,6 +1246,13 @@ function handleSave() {
if (res.code === 200) {
proxy.$modal.msgSuccess('签发成功');
isSaving.value = false;
// 乐观更新:立即将已签发医嘱的状态设为"已签发",确保列表实时刷新
saveList.forEach((item) => {
const row = prescriptionList.value.find((r) => r.requestId && r.requestId === item.requestId);
if (row) {
row.statusEnum = 2;
}
});
getListInfo(false);
bindMethod.value = {};
nextId.value = 1;

View File

@@ -296,6 +296,7 @@
</template>
</el-table-column>
</el-table>
<el-empty v-if="!groupSetLoading && groupSetList.length === 0" description="暂无划价组套数据" :image-size="80" />
<div style="margin-top: 15px; text-align: right">
<el-button @click="groupSetDialogVisible = false">取消</el-button>
<el-button type="primary" @click="applyGroupSet" :disabled="!selectedGroupSet">应用</el-button>
@@ -523,7 +524,11 @@ function loadDepartmentOptions() {
getOrgList()
.then((res) => {
if (res.data && res.data.records && res.data.records.length > 0) {
departmentOptions.value = res.data.records[0].children || [];
const firstRecord = res.data.records[0];
// 优先使用 children树形结构回退到 records 本身(扁平结构)
departmentOptions.value = (firstRecord.children && firstRecord.children.length > 0)
? firstRecord.children
: res.data.records;
}
})
.catch(() => {
@@ -798,6 +803,7 @@ function resetData() {
// 划价组套相关功能
function openGroupSetDialog() {
console.log('openGroupSetDialog called');
groupSetDialogVisible.value = true;
groupSetSearchText.value = '';
selectedGroupSet.value = null;
@@ -834,8 +840,9 @@ function loadGroupSets() {
groupSetList.value = rawList;
}
})
.catch(() => {
console.warn('组套列表加载失败(可能无权限)');
.catch((err) => {
console.warn('组套列表加载失败(可能无权限):', err);
ElMessage.warning('组套列表加载失败,当前暂无可用组套');
groupSetList.value = [];
})
.finally(() => {

View File

@@ -28,6 +28,7 @@ export function getOrgList() {
return request({
url: '/base-data-manage/organization/organization',
method: 'get',
params: { pageSize: 100, pageNum: 1 },
});
}
/**

View File

@@ -85,7 +85,7 @@
:deadline="deadline"
:therapyEnum="therapyEnum"
/>
<SummaryMedicineList v-else :therapyEnum="therapyEnum" />
<SummaryMedicineList v-else ref="summaryMedicineRefs" :therapyEnum="therapyEnum" />
<!-- <el-tabs v-model="activeName" class="demo-tabs centered-tabs" @tab-change="handleClick">
<el-tab-pane
v-for="tab in prescriptionTabs"
@@ -129,6 +129,7 @@ const therapyEnum = ref(undefined);
// 存储子组件引用的对象
const prescriptionRefs = ref();
const summaryMedicineRefs = ref();
const navigationButtons = inpatientNurseNavs;
@@ -165,7 +166,11 @@ function handleClick(tabName) {
function handleGetPrescription() {
chooseAll.value = false;
prescriptionRefs.value?.handleGetPrescription();
if (isDetails.value == '1') {
prescriptionRefs.value?.handleGetPrescription();
} else {
summaryMedicineRefs.value?.handleGetPrescription();
}
}
function handelSwicthChange(value) {

View File

@@ -803,7 +803,7 @@
</el-dialog>
<!-- 手术计费弹窗 -->
<el-dialog :title="chargeDialogTitle" v-model="showChargeDialog" width="1400px" @close="closeChargeDialog" append-to-body>
<el-dialog :title="chargeDialogTitle" v-model="showChargeDialog" width="1400px" @close="closeChargeDialog" append-to-body destroy-on-close>
<div style="display: flex; justify-content: space-between; height: 80vh">
<div style="width: 100%; border: 1px solid #eee; position: relative">
<div style="padding: 10px; border: 1px solid #eee; height: 50px; border-left: 0">
@@ -1456,11 +1456,14 @@ async function handleChargeCharge(row) {
}
// 关闭计费弹窗
function closeChargeDialog() {
async function closeChargeDialog() {
// 先关闭 prescriptionlist 内所有已打开的项目字典 popover
if (prescriptionRef.value && prescriptionRef.value.closeAllPopovers) {
prescriptionRef.value.closeAllPopovers()
}
// 等待 Vue 完成 popover 可见性更新的 DOM 操作,
// 因为 el-popover 通过 teleport 渲染在 body 上,需要在 dialog 卸载前完成清理
await nextTick()
// 清空数据,避免下次打开时使用缓存
showChargeDialog.value = false
chargePatientInfo.value = {}