Files
his/openhis-ui-vue3/src/views/inpatientDoctor/home/components/applicationShow/examineApplication.vue
关羽 6725cef643 Fix Bug #477: 住院医生工作站-住院检查申请详情弹窗中"发往科室"字段显示为短横线(-),未正常获取数据
修复 examineApplication.vue 中 recursionFun 函数的空指针异常:
1. 增加 orgOptions.value 数组有效性校验,防止接口未返回数据时崩溃
2. 增加 obj.children 的 Array.isArray 检查,原代码直接访问 children.length 在 children 为 undefined 时抛 TypeError
3. 匹配成功后增加 break 提前退出循环
4. handleViewDetail 增加 targetDepartment 存在性检查,递归查找失败时回退显示原始 ID 值
2026-05-10 23:56:39 +08:00

843 lines
26 KiB
Vue
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!--
* @Author: sjjh
* @Date: 2025-09-05 21:16:06
* @Description: 检查申请
-->
<template>
<div class="report-container">
<div class="report-section">
<div class="report-title">
<span>检查申请</span>
<el-icon
class="report-refresh-icon"
:class="{ 'is-loading': loading }"
@click="handleRefresh"
>
<Refresh />
</el-icon>
</div>
<!-- 筛选表单 -->
<div class="filter-form">
<el-form :inline="true" :model="filterForm" class="filter-form-content">
<el-form-item label="申请日期">
<el-date-picker
v-model="filterForm.dateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
clearable
style="width: 240px"
/>
</el-form-item>
<el-form-item label="申请单状态">
<el-select
v-model="filterForm.status"
placeholder="请选择"
clearable
style="width: 150px"
>
<el-option label="全部" value="" />
<el-option label="待签发" value="0" />
<el-option label="已签发" value="1" />
<el-option label="已校对" value="2" />
<el-option label="待接收" value="3" />
<el-option label="已接收" value="4" />
<el-option label="已检查" value="5" />
<el-option label="已出报告" value="6" />
<el-option label="已作废" value="7" />
</el-select>
</el-form-item>
<el-form-item label="关键字">
<el-input
v-model="filterForm.keyword"
placeholder="申请单号/检查项目"
clearable
style="width: 200px"
@keyup.enter="handleSearch"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch" :loading="loading">
<el-icon><Search /></el-icon>
查询
</el-button>
<el-button @click="handleReset">
<el-icon><Refresh /></el-icon>
重置
</el-button>
</el-form-item>
</el-form>
</div>
<div class="report-table-wrapper">
<el-table
v-loading="loading"
:data="tableData"
border
size="small"
height="100%"
style="width: 100%"
>
<template #empty>
<div class="empty-data">
<el-empty description="暂无匹配记录" :image-size="80" />
</div>
</template>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="patientName" label="患者姓名" width="120" />
<el-table-column label="申请单名称" width="140">
<template #default="scope">
<span>{{ getItemName(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 prop="requesterId_dictText" label="申请者" width="120" />
<el-table-column label="申请单状态" width="120" align="center">
<template #default="scope">
<span>{{ parseStatus(scope.row.status) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" width="240">
<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="handleEdit(scope.row)">修改</el-button>
<el-popconfirm
title="确认删除该申请单?"
@confirm="handleDelete(scope.row)"
width="200"
>
<template #reference>
<el-button link type="danger">删除</el-button>
</template>
</el-popconfirm>
</template>
<!-- 已签发护士未校对撤回 -->
<template v-else-if="scope.row.status === '1' || scope.row.status === 1">
<el-popconfirm
title="确认撤回该申请单?撤回后将回滚至待签发状态"
@confirm="handleWithdraw(scope.row)"
width="260"
>
<template #reference>
<el-button link type="warning">撤回</el-button>
</template>
</el-popconfirm>
</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="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="handlePrint(scope.row)">打印</el-button>
<el-button link type="primary" @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="handlePrint(scope.row)">打印</el-button>
<el-button link type="success" @click="handleViewReport(scope.row)">看报告</el-button>
</template>
<!-- 已作废仅详情 -->
</template>
</el-table-column>
</el-table>
</div>
</div>
<!-- 详情弹窗 -->
<el-dialog
v-model="detailDialogVisible"
title="检查申请详情"
width="800px"
destroy-on-close
top="5vh"
:close-on-click-modal="false"
>
<div v-if="currentDetail" class="applicationShow-container">
<div class="applicationShow-container-content">
<el-descriptions title="基本信息" :column="2">
<el-descriptions-item label="患者姓名">{{
currentDetail.patientName || '-'
}}</el-descriptions-item>
<el-descriptions-item label="申请单名称">{{
getItemName(currentDetail)
}}</el-descriptions-item>
<el-descriptions-item label="申请单状态">{{
parseStatus(currentDetail.status)
}}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{
currentDetail.createTime || '-'
}}</el-descriptions-item>
<el-descriptions-item label="处方号">{{
currentDetail.prescriptionNo || '-'
}}</el-descriptions-item>
<el-descriptions-item label="申请者">{{
currentDetail.requesterId_dictText || '-'
}}</el-descriptions-item>
<el-descriptions-item label="就诊ID">{{
currentDetail.encounterId || '-'
}}</el-descriptions-item>
<el-descriptions-item label="申请单ID">{{
currentDetail.requestFormId || '-'
}}</el-descriptions-item>
</el-descriptions>
</div>
<div v-if="descJsonData && hasMatchedFields" class="applicationShow-container-content">
<el-descriptions title="申请单描述" :column="2">
<template v-for="(value, key) in descJsonData" :key="key">
<el-descriptions-item v-if="isFieldMatched(key)" :label="getFieldLabel(key)">
{{ value || '-' }}
</el-descriptions-item>
</template>
</el-descriptions>
</div>
<div
v-if="currentDetail.requestFormDetailList && currentDetail.requestFormDetailList.length"
class="applicationShow-container-table"
>
<el-table :data="currentDetail.requestFormDetailList" border>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="adviceName" label="医嘱名称" />
<el-table-column prop="quantity" label="数量" width="80" align="center" />
<el-table-column prop="unitCode_dictText" label="单位" width="100" />
<el-table-column prop="totalPrice" label="总价" width="100" align="right" />
</el-table>
</div>
</div>
<template #footer>
<el-button @click="detailDialogVisible = false">关闭</el-button>
</template>
</el-dialog>
<!-- 修改申请单弹窗 -->
<el-dialog
v-model="editDialogVisible"
title="修改检查申请"
width="800px"
destroy-on-close
top="5vh"
:close-on-click-modal="false"
>
<div v-if="editDetail" class="applicationShow-container">
<el-form :model="editForm" label-width="120px">
<el-form-item label="申请单名称">
<el-input v-model="editForm.name" />
</el-form-item>
<el-form-item label="临床诊断">
<el-input v-model="editForm.clinicalDiagnosis" type="textarea" :rows="2" />
</el-form-item>
<el-form-item label="注意事项">
<el-input v-model="editForm.attention" type="textarea" :rows="2" />
</el-form-item>
<div v-if="editDetail.requestFormDetailList && editDetail.requestFormDetailList.length">
<el-divider>申请项目</el-divider>
<el-table :data="editDetail.requestFormDetailList" border size="small">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="adviceName" label="医嘱名称" />
<el-table-column prop="quantity" label="数量" width="80" align="center" />
<el-table-column prop="unitCode_dictText" label="单位" width="100" />
<el-table-column prop="totalPrice" label="总价" width="100" align="right" />
</el-table>
</div>
</el-form>
</div>
<template #footer>
<el-button @click="editDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSaveEdit" :loading="saveLoading">保存</el-button>
</template>
</el-dialog>
<!-- 报告查看弹窗 -->
<el-dialog
v-model="reportDialogVisible"
title="检查报告"
width="900px"
destroy-on-close
top="5vh"
:close-on-click-modal="false"
>
<div v-if="reportUrl" style="min-height: 500px;">
<iframe :src="reportUrl" style="width: 100%; height: 500px; border: none;" />
</div>
<el-empty v-else description="暂无报告数据" />
<template #footer>
<el-button @click="reportDialogVisible = false">关闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import {computed, getCurrentInstance, ref, watch} from 'vue';
import {Refresh, Search} from '@element-plus/icons-vue';
import {patientInfo} from '../../store/patient.js';
import {getCheck, deleteRequestForm, withdrawRequestForm, getTestResult} from './api';
import {getOrgList} from '@/views/doctorstation/components/api.js';
const { proxy } = getCurrentInstance();
const tableData = ref([]);
const loading = ref(false);
const detailDialogVisible = ref(false);
const currentDetail = ref(null);
const descJsonData = ref(null);
const orgOptions = ref([]);
// 修改申请单相关
const editDialogVisible = ref(false);
const editDetail = ref(null);
const editForm = ref({});
const saveLoading = ref(false);
// 报告查看相关
const reportDialogVisible = ref(false);
const reportUrl = ref('');
// 筛选表单数据
const getDefaultDateRange = () => {
const now = new Date();
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
const formatDate = (d) => {
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
return [formatDate(weekAgo), formatDate(now)];
};
const filterForm = ref({
dateRange: getDefaultDateRange(), // 默认近一周
status: '', // 申请单状态
keyword: '', // 关键字搜索
});
const fetchData = async () => {
if (!patientInfo.value?.encounterId) {
tableData.value = [];
loading.value = false;
return;
}
loading.value = true;
try {
// 构建查询参数
const params = { encounterId: patientInfo.value.encounterId };
// 添加日期范围筛选
if (filterForm.value.dateRange && filterForm.value.dateRange.length === 2) {
params.startDate = filterForm.value.dateRange[0];
params.endDate = filterForm.value.dateRange[1];
}
// 添加状态筛选
if (filterForm.value.status !== '' && filterForm.value.status !== undefined) {
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;
const list = Array.isArray(raw) ? raw : [raw];
tableData.value = list.filter(Boolean);
} else {
tableData.value = [];
}
} catch (e) {
console.warn('查询检查申请失败:', e.message);
tableData.value = [];
} finally {
loading.value = false;
}
};
const handleRefresh = async () => {
if (loading.value || !patientInfo.value?.encounterId) return;
await fetchData();
};
/**
* 查询按钮处理
*/
const handleSearch = async () => {
if (!patientInfo.value?.encounterId) {
proxy.$modal?.msgWarning?.('请先选择患者');
return;
}
await fetchData();
};
/**
* 重置按钮处理
*/
const handleReset = () => {
filterForm.value.dateRange = getDefaultDateRange();
filterForm.value.status = '';
filterForm.value.keyword = '';
fetchData();
};
/**
* 解析申请单状态
* @param {string|number} status - 状态码
* @returns {string} 状态文本
*/
const parseStatus = (status) => {
const statusMap = {
'0': '待签发',
'1': '已签发',
'2': '已校对',
'3': '待接收',
'4': '已接收',
'5': '已检查',
'6': '已出报告',
'7': '已作废',
};
return statusMap[String(status)] || '-';
};
/**
* 获取申请单展示名称:优先使用具体医嘱名称列表
*/
const getItemName = (row) => {
const items = row?.requestFormDetailList;
if (items && items.length > 0) {
return items.map(item => item.adviceName).filter(Boolean).join('、') || row.name || '-';
}
return row.name || '-';
};
/**
* 构建打印用项目名称字符串(同步函数,用于模板字符串)
*/
const buildItemNames = (row) => {
const items = row?.requestFormDetailList;
if (items && items.length > 0) {
return items.map(item => item.adviceName).filter(Boolean).join('、') || row.name || '';
}
return row.name || '';
};
const labelMap = {
categoryType: '项目类别',
targetDepartment: '发往科室',
symptom: '症状',
sign: '体征',
clinicalDiagnosis: '临床诊断',
otherDiagnosis: '其他诊断',
relatedResult: '相关结果',
attention: '注意事项',
};
const isFieldMatched = (key) => {
return key in labelMap;
};
const getFieldLabel = (key) => {
return labelMap[key] || key;
};
const hasMatchedFields = computed(() => {
if (!descJsonData.value) return false;
return Object.keys(descJsonData.value).some((key) => isFieldMatched(key));
});
/** 查询科室 */
const getLocationInfo = () => {
getOrgList().then((res) => {
orgOptions.value = res.data.records;
});
};
const recursionFun = (targetDepartment) => {
if (!targetDepartment) return '';
if (!Array.isArray(orgOptions.value) || orgOptions.value.length === 0) 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 subObjArray = obj['children'];
if (Array.isArray(subObjArray)) {
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;
};
const handleViewDetail = (row) => {
console.log('targetDepartment========>', JSON.stringify(row));
currentDetail.value = row;
// 解析 descJson
if (row.descJson) {
try {
const obj = JSON.parse(row.descJson);
if (obj.targetDepartment) {
const deptName = recursionFun(obj.targetDepartment);
obj.targetDepartment = deptName || obj.targetDepartment;
}
descJsonData.value = obj;
} catch (e) {
console.error('解析 descJson 失败:', e);
descJsonData.value = null;
}
} else {
descJsonData.value = null;
}
detailDialogVisible.value = true;
};
/**
* 修改申请单
*/
const handleEdit = (row) => {
editDetail.value = row;
// 解析 descJson 为表单数据
const form = { name: row.name || '' };
if (row.descJson) {
try {
const desc = JSON.parse(row.descJson);
form.clinicalDiagnosis = desc.clinicalDiagnosis || '';
form.attention = desc.attention || '';
form.symptom = desc.symptom || '';
form.sign = desc.sign || '';
} catch (e) {
console.error('解析 descJson 失败:', e);
}
}
editForm.value = form;
editDialogVisible.value = true;
};
/**
* 保存修改
*/
const handleSaveEdit = async () => {
if (!editDetail.value?.requestFormId) {
proxy.$modal?.msgWarning?.('申请单ID不存在');
return;
}
saveLoading.value = true;
try {
// 复用 save-check 接口进行更新(传入 requestFormId 即为编辑场景)
const res = await proxy.$http?.post?.('/reg-doctorstation/request-form/save-check', {
requestFormId: editDetail.value.requestFormId,
encounterId: editDetail.value.encounterId,
patientId: editDetail.value.patientId,
name: editForm.value.name,
organizationId: editDetail.value.inHospitalOrgId || patientInfo.value?.inHospitalOrgId,
descJson: JSON.stringify({
clinicalDiagnosis: editForm.value.clinicalDiagnosis,
attention: editForm.value.attention,
symptom: editForm.value.symptom,
sign: editForm.value.sign,
}),
activityList: [], // 修改场景仅更新描述信息,不修改项目
});
if (res?.code === 200) {
proxy.$modal?.msgSuccess?.('修改成功');
editDialogVisible.value = false;
await fetchData();
} else {
proxy.$modal?.msgError?.(res?.msg || '修改失败');
}
} catch (e) {
console.warn('修改申请单失败:', e.message);
proxy.$modal?.msgError?.('修改失败: ' + (e.message || '网络异常'));
} finally {
saveLoading.value = false;
}
};
/**
* 删除申请单(仅待签发状态可删除)
*/
const handleDelete = async (row) => {
if (!row.requestFormId) {
proxy.$modal?.msgWarning?.('申请单ID不存在');
return;
}
try {
const res = await deleteRequestForm({ requestFormId: row.requestFormId });
if (res?.code === 200) {
proxy.$modal?.msgSuccess?.('删除成功');
await fetchData();
} else {
proxy.$modal?.msgError?.(res?.msg || '删除失败');
}
} catch (e) {
console.warn('删除申请单失败:', e.message);
proxy.$modal?.msgError?.('删除失败: ' + (e.message || '网络异常'));
}
};
/**
* 撤回申请单(已签发状态撤回至待签发)
*/
const handleWithdraw = async (row) => {
if (!row.requestFormId) {
proxy.$modal?.msgWarning?.('申请单ID不存在');
return;
}
try {
const res = await withdrawRequestForm({ requestFormId: row.requestFormId });
if (res?.code === 200) {
proxy.$modal?.msgSuccess?.('撤回成功');
await fetchData();
} else {
proxy.$modal?.msgError?.(res?.msg || '撤回失败');
}
} catch (e) {
console.warn('撤回申请单失败:', e.message);
proxy.$modal?.msgError?.('撤回失败: ' + (e.message || '网络异常'));
}
};
/**
* 打印申请单
*/
const handlePrint = (row) => {
// 打开新窗口进行打印
const printWindow = window.open('', '_blank');
if (!printWindow) {
proxy.$modal?.msgWarning?.('请允许浏览器弹出窗口以进行打印');
return;
}
const content = buildPrintContent(row);
printWindow.document.write(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>检查申请单打印</title>
<style>
body { font-family: "Microsoft YaHei", sans-serif; padding: 20px; font-size: 14px; }
h2 { text-align: center; margin-bottom: 20px; }
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background: #f5f5f5; }
.info-row { margin: 8px 0; }
.label { font-weight: bold; display: inline-block; width: 100px; }
@media print { .no-print { display: none; } }
</style>
</head>
<body>
${content}
<div class="no-print" style="text-align: center; margin-top: 20px;">
<button onclick="window.print()">打印</button>
<button onclick="window.close()">关闭</button>
</div>
</body>
</html>
`);
printWindow.document.close();
};
/**
* 构建打印内容
*/
const buildPrintContent = (row) => {
let descHtml = '';
if (row.descJson) {
try {
const desc = JSON.parse(row.descJson);
descHtml = Object.entries(labelMap)
.filter(([key]) => desc[key])
.map(([key, label]) => `<div class="info-row"><span class="label">${label}:</span>${desc[key]}</div>`)
.join('');
} catch (e) {
console.error('解析 descJson 失败:', e);
}
}
let itemsHtml = '';
if (row.requestFormDetailList && row.requestFormDetailList.length) {
itemsHtml = `
<h3>申请项目</h3>
<table>
<tr><th>序号</th><th>医嘱名称</th><th>数量</th><th>单位</th><th>总价</th></tr>
${row.requestFormDetailList.map((item, i) => `
<tr>
<td>${i + 1}</td>
<td>${item.adviceName || '-'}</td>
<td>${item.quantity || '-'}</td>
<td>${item.unitCode_dictText || '-'}</td>
<td>${item.totalPrice || '-'}</td>
</tr>
`).join('')}
</table>
`;
}
return `
<h2>检查申请单</h2>
<div class="info-row"><span class="label">患者姓名:</span>${row.patientName || '-'}</div>
<div class="info-row"><span class="label">申请单名称:</span>${buildItemNames(row) || '-'}</div>
<div class="info-row"><span class="label">创建时间:</span>${row.createTime || '-'}</div>
<div class="info-row"><span class="label">处方号:</span>${row.prescriptionNo || '-'}</div>
<div class="info-row"><span class="label">申请者:</span>${row.requesterId_dictText || '-'}</div>
<div class="info-row"><span class="label">申请单状态:</span>${parseStatus(row.status)}</div>
${descHtml ? `<h3>申请单描述</h3>${descHtml}` : ''}
${itemsHtml}
`;
};
/**
* 查看报告
*/
const handleViewReport = async (row) => {
reportUrl.value = '';
reportDialogVisible.value = true;
try {
const res = await getTestResult({ encounterId: row.encounterId, requestFormId: row.requestFormId });
if (res?.code === 200 && res?.data) {
reportUrl.value = res.data;
} else {
console.warn('获取检查报告失败:', res?.msg || '无数据');
}
} catch (e) {
console.warn('获取检查报告异常:', e.message);
}
};
watch(
() => patientInfo.value?.encounterId,
(val) => {
if (val) {
fetchData();
getLocationInfo();
} else {
tableData.value = [];
filterForm.value.dateRange = getDefaultDateRange();
filterForm.value.status = '';
filterForm.value.keyword = '';
}
},
{ immediate: true }
);
defineExpose({
refresh: fetchData,
});
</script>
<style scoped lang="scss">
.report-container {
display: flex;
flex-direction: column;
gap: 12px;
padding: 8px 0;
height: 100%;
}
.report-section {
background: #fff;
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
.report-title {
font-weight: 600;
margin-bottom: 8px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 8px;
}
.filter-form {
padding: 12px 8px;
border-bottom: 1px solid #ebeef5;
background-color: #fafafa;
}
.filter-form-content {
:deep(.el-form-item) {
margin-bottom: 0;
margin-right: 16px;
}
:deep(.el-form-item__label) {
font-weight: 500;
}
}
.report-table-wrapper {
flex: 1;
min-height: 0;
overflow: auto;
padding: 0 8px;
}
.empty-data {
padding: 40px 0;
display: flex;
justify-content: center;
align-items: center;
}
.report-refresh-icon {
cursor: pointer;
color: #909399;
transition: color 0.2s;
font-size: 18px;
}
.report-refresh-icon:hover {
color: #409eff;
}
.report-refresh-icon.is-loading {
animation: rotating 2s linear infinite;
}
@keyframes rotating {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
:deep(.el-dialog__body) {
padding-top: 0 !important;
}
.applicationShow-container {
display: flex;
flex-direction: column;
max-height: 70vh;
width: 100%;
overflow-y: auto;
.applicationShow-container-content {
flex-shrink: 0;
margin-bottom: 0px;
}
.applicationShow-container-table {
flex-shrink: 0;
max-height: 3000px;
overflow: auto;
}
}
</style>