根因: - Bug #请修复 Bug #581 存在的问题 修复: - 变更摘要 - ### 修改文件 - 1. `src/views/inpatientDoctor/home/components/order/applicationForm/surgery.vue`** - 在"发往科室"字段之后,依次新增了以下 9 个业务字段: - | 字段 | 控件类型 | 必填 | 数据来源 | - |---|---|---|---| - | 手术等级 | `el-select` 下拉 | ✅ | 字典 `surgery_level` | - | 麻醉方式 | `el-select` 下拉 | ✅ | 字典 `anesthesia_type` | - | 手术部位 | `el-select` 下拉 | ✅ | 字典 `surgery_site` | - | 切口类别 | `el-select` 下拉 | ❌ | 字典 `incision_level` | - | 手术性质 | `el-select` 下拉 | ❌ | 字典 `surgery_type` | - | 主刀医生 | `el-select` 可搜索 | ✅ | `listUser` API,默认当前登录医生 | - | 第一助手 | `el-select` 可搜索 | ❌ | `listUser` API | - | 第二助手 | `el-select` 可搜索 | ❌ | `listUser` API | - | 预定手术时间 | `el-date-picker` datetime | ✅ | 无默认值 | - 新增逻辑: - `loadDictOptions()`** — 并行加载 5 个字典选项 - `loadDoctorOptions()`** — 加载医生列表,自动设当前登录用户为主刀医生默认值 - `submit()` 新增强拦截校验** — 手术等级、麻醉方式、手术部位、主刀医生、预定手术时间为必填,为空时阻断提交并提示 - 2. `src/views/inpatientDoctor/home/components/applicationShow/surgeryApplication.vue`** - `labelMap` 新增 9 条标签映射,确保详情弹窗能正确显示新字段的中文标签。 - ### 全链路完整性 - 录入 ✅ 前端弹窗增加输入控件 - 保存 ✅ 通过 `descJson: JSON.stringify(form)` 序列化,后端无需改动 - 查询 ✅ 详情展示组件新增 labelMap 映射 - 修改 ⏸ 申请单编辑功能不在本轮范围(后续迭代可复用 submit 逻辑) - 删除 ✅ 不影响 - 关联 ✅ 门诊手术申请走独立 API,不共享 descJson,无需修改 - ### 验证 - `npm run lint` — ✅ 通过,无错误
603 lines
15 KiB
Vue
Executable File
603 lines
15 KiB
Vue
Executable File
<!--
|
||
* @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="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"
|
||
:loading="loading"
|
||
@click="handleSearch"
|
||
>
|
||
<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%"
|
||
>
|
||
<el-table-column
|
||
type="index"
|
||
label="序号"
|
||
width="60"
|
||
align="center"
|
||
/>
|
||
<el-table-column
|
||
label="手术单号"
|
||
width="160"
|
||
align="center"
|
||
>
|
||
<template #default="scope">
|
||
<el-link
|
||
type="primary"
|
||
@click="handleViewDetail(scope.row)"
|
||
>
|
||
{{ scope.row.prescriptionNo || '-' }}
|
||
</el-link>
|
||
</template>
|
||
</el-table-column>
|
||
<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="requesterId_dictText"
|
||
label="申请者"
|
||
width="120"
|
||
/>
|
||
<el-table-column
|
||
label="操作"
|
||
align="center"
|
||
fixed="right"
|
||
>
|
||
<template #default="scope">
|
||
<el-button
|
||
link
|
||
type="primary"
|
||
icon="View"
|
||
@click="handleViewDetail(scope.row)"
|
||
>
|
||
详情
|
||
</el-button>
|
||
</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="创建时间">
|
||
{{
|
||
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
|
||
icon="Close"
|
||
@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 {getSurgery} 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 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();
|
||
};
|
||
|
||
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 getSurgery(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) {
|
||
proxy.$modal?.msgError?.(e.message || '查询手术申请失败');
|
||
tableData.value = [];
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
};
|
||
|
||
const handleRefresh = async () => {
|
||
if (loading.value || !patientInfo.value?.encounterId) return;
|
||
await fetchData();
|
||
};
|
||
|
||
const labelMap = {
|
||
categoryType: '项目类别',
|
||
targetDepartment: '发往科室',
|
||
surgeryLevel: '手术等级',
|
||
anesthesiaType: '麻醉方式',
|
||
surgerySite: '手术部位',
|
||
incisionLevel: '切口类别',
|
||
surgeryNature: '手术性质',
|
||
mainSurgeonId: '主刀医生',
|
||
assistant1Id: '第一助手',
|
||
assistant2Id: '第二助手',
|
||
plannedTime: '预定手术时间',
|
||
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 = async () => {
|
||
const res = await getDepartmentList();
|
||
orgOptions.value = res.data || [];
|
||
};
|
||
|
||
const recursionFun = (targetDepartment) => {
|
||
if (!targetDepartment || !orgOptions.value || orgOptions.value.length === 0) {
|
||
return '';
|
||
}
|
||
let name = '';
|
||
// 统一处理:扁平列表和树形结构都适用
|
||
const findInList = (list) => {
|
||
for (const node of list) {
|
||
if (String(node.id) === String(targetDepartment)) {
|
||
name = node.name;
|
||
return true;
|
||
}
|
||
// 树形结构:递归查找 children
|
||
if (node.children && node.children.length > 0) {
|
||
if (findInList(node.children)) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
};
|
||
findInList(orgOptions.value);
|
||
return name;
|
||
};
|
||
|
||
const handleViewDetail = async (row) => {
|
||
// 确保科室数据已加载,以便将 ID 解析为名称
|
||
if (!orgOptions.value || orgOptions.value.length === 0) {
|
||
await getLocationInfo();
|
||
}
|
||
|
||
currentDetail.value = row;
|
||
// 解析 descJson
|
||
if (row.descJson) {
|
||
try {
|
||
// descJsonData.value = JSON.parse(row.descJson);
|
||
const obj = JSON.parse(row.descJson);
|
||
obj.targetDepartment = recursionFun(obj.targetDepartment);
|
||
descJsonData.value = obj;
|
||
} catch (e) {
|
||
console.error('解析 descJson 失败:', e);
|
||
descJsonData.value = null;
|
||
}
|
||
} else {
|
||
descJsonData.value = null;
|
||
}
|
||
detailDialogVisible.value = true;
|
||
};
|
||
|
||
watch(
|
||
() => patientInfo.value?.encounterId,
|
||
async (val) => {
|
||
if (val) {
|
||
await Promise.all([fetchData(), getLocationInfo()]);
|
||
} else {
|
||
tableData.value = [];
|
||
}
|
||
},
|
||
{ 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: 0 8px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.filter-form-content {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
gap: 0;
|
||
}
|
||
|
||
:deep(.filter-form-content .el-form-item) {
|
||
margin-bottom: 0;
|
||
margin-right: 16px;
|
||
}
|
||
|
||
.report-table-wrapper {
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow: auto;
|
||
padding: 0 8px;
|
||
}
|
||
|
||
.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: 300px;
|
||
overflow: auto;
|
||
}
|
||
}
|
||
</style>
|