Files
his/openhis-ui-vue3/src/views/inpatientDoctor/home/components/applicationShow/examineApplication.vue
关羽 3c3428e0b1 Fix Bug #499: 【住院医生工作站-检查申请】检查申请列表缺失查询过滤功能,不符合临床高效检索要求
- 新增关键字搜索输入框(申请单号/检查项目名称模糊匹配)
- 设置日期范围默认为近7天
- 关键字搜索支持回车触发查询

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 06:09:12 +08:00

630 lines
19 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: 220px"
@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 prop="name" label="申请单名称" width="140" />
<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="操作" width="280" align="center" fixed="right">
<template #default="scope">
<!-- 待签发详情修改删除 -->
<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>
</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="申请单名称">{{
currentDetail.name || '-'
}}</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)">
{{ transformField(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>
</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 {getDepartmentList} from '@/api/public.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([]);
// 获取近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: 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 labelMap = {
categoryType: '项目类别',
targetDepartment: '发往科室',
urgencyLevel: '紧急程度',
allergyHistory: '过敏史',
examinationPurpose: '检查目的',
expectedExaminationTime: '期望检查时间',
medicalHistorySummary: '病史摘要',
allergyConfirmed: '过敏确认',
symptom: '症状',
sign: '体征',
clinicalDiagnosis: '临床诊断',
otherDiagnosis: '其他诊断',
relatedResult: '相关结果',
attention: '注意事项',
};
// Fields that need value transformation before display
const transformField = (key, value) => {
if (key === 'urgencyLevel') {
return value === 'emergency' ? '急诊' : '普通';
}
if (key === 'allergyConfirmed') {
return value === true || value === 'true' ? '已口头确认' : '未确认';
}
return value;
};
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 = async () => {
try {
const res = await getDepartmentList();
orgOptions.value = Array.isArray(res.data) ? res.data : [];
} catch (e) {
console.warn('科室列表加载失败:', e.message);
orgOptions.value = [];
}
};
// 递归查找树形科室节点
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;
}
}
return null;
};
const handleViewDetail = async (row) => {
// 确保科室数据已加载,以便将 ID 解析为名称
if (!orgOptions.value || orgOptions.value.length === 0) {
await getLocationInfo();
}
currentDetail.value = row;
// 解析 descJson
if (row.descJson) {
try {
const obj = JSON.parse(row.descJson);
// 将发往科室 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);
descJsonData.value = null;
}
} else {
descJsonData.value = null;
}
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) => {
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>