Files
his/openhis-ui-vue3/src/views/surgicalschedule/index.vue
wangjian963 b946a8a143 607 【门诊术中安排-医嘱】医师电子签名核验异常(签名医师显示账号名而非姓名、签名时间缺失)
606 门诊术中安排-医嘱】预览列表字段显示及逻辑异常(涉及单位、频次、执行时间)

605
【门诊手术安排-计费】新增计费项目保存后,明细列表的“总量”列单位错误显示为字典ID数字(如“瓶”显示为“8”
604 【门诊手术安排-医嘱】编辑临时医嘱保存后,需额外二次操作方能提交,与实际业务符合,交互体验不佳
2026-05-29 18:00:55 +08:00

3290 lines
110 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.

<template>
<div class="app-container">
<!-- 顶部筛选区 -->
<el-form
v-show="showSearch"
ref="queryRef"
:model="queryParams"
:inline="true"
class="query-form"
>
<el-form-item
label="手术单号"
prop="operCode"
>
<el-input
v-model="queryParams.operCode"
placeholder="请输入手术单号"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item
label="安排时间"
prop="scheduleDateRange"
>
<el-date-picker
v-model="queryParams.scheduleDateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
style="width: 240px"
/>
</el-form-item>
<el-form-item
label="卫生机构"
prop="tenantId"
>
<el-select
v-model="queryParams.tenantId"
placeholder="请选择卫生机构"
style="width: 200px"
>
<el-option
v-for="item in orgList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item
label="申请科室"
prop="applyDeptId"
>
<el-select
v-model="queryParams.applyDeptId"
placeholder="请选择申请科室"
style="width: 200px"
>
<el-option
v-for="item in deptList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item
label="姓名/拼音码"
prop="patientName"
>
<el-input
v-model="queryParams.patientName"
placeholder="请输入姓名/拼音码"
clearable
style="width: 200px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item class="search-buttons">
<el-button
type="primary"
icon="Search"
@click="handleQuery"
>
查询
</el-button>
<el-button
icon="Refresh"
@click="resetQuery"
>
重置
</el-button>
</el-form-item>
</el-form>
<el-row
:gutter="10"
class="mb8"
>
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
>
新增手术安排
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Money"
:disabled="!selectedRow"
@click="handleChargeCharge(selectedRow)"
>
计费
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Document"
:disabled="!selectedRow"
@click="handleMedicalAdvice(selectedRow)"
>
医嘱
</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Download"
@click="handleExport"
>
导出表格
</el-button>
</el-col>
<right-toolbar
v-model:show-search="showSearch"
@query-table="getList"
/>
</el-row>
<!-- 中部表格区 -->
<el-table
v-loading="loading"
:data="surgeryList"
row-key="scheduleId"
:row-class-name="getRowClassName"
@current-change="handleCurrentChange"
>
<el-table-column
label="手术单号"
align="center"
prop="operCode"
width="180"
show-overflow-tooltip
>
<template #default="scope">
<el-link
type="primary"
:underline="false"
@click="handleView(scope.row)"
>
{{ scope.row.operCode }}
</el-link>
</template>
</el-table-column>
<el-table-column
label="ID"
align="center"
width="80"
>
<template #default="{ $index }">
{{ (applyQueryParams.pageNo - 1) * applyQueryParams.pageSize + $index + 1 }}
</template>
</el-table-column>
<el-table-column
label="卫生机构"
align="center"
prop="orgName"
width="120"
show-overflow-tooltip
/>
<el-table-column
label="姓名"
align="center"
prop="patientName"
width="100"
/>
<el-table-column
label="就诊卡号"
align="center"
prop="identifierNo"
width="120"
/>
<el-table-column
label="手术名称"
align="center"
prop="operName"
min-width="140"
show-overflow-tooltip
/>
<el-table-column
label="申请科室"
align="center"
prop="applyDeptName"
width="100"
show-overflow-tooltip
>
<template #default="scope">
{{ scope.row.applyDeptName || '-' }}
</template>
</el-table-column>
<el-table-column
label="手术类型"
align="center"
width="100"
>
<template #default="scope">
{{ getSurgeryTypeName(scope.row.surgeryNature) }}
</template>
</el-table-column>
<el-table-column
label="手术性质"
align="center"
width="100"
>
<template #default="scope">
{{ getSurgeryTypeName(scope.row.surgeryNature) }}
</template>
</el-table-column>
<el-table-column
label="主刀医生"
align="center"
width="100"
prop="surgeonName"
/>
<el-table-column
label="麻醉方法"
align="center"
width="120"
>
<template #default="scope">
{{ getAnesthesiaName(scope.row.anesMethod) }}
</template>
</el-table-column>
<el-table-column
label="安排时间"
align="center"
prop="scheduleDate"
width="140"
>
<template #default="scope">
{{ parseTime(scope.row.scheduleDate, '{y}-{m}-{d} {h}:{i}:{s}') }}
</template>
</el-table-column>
<el-table-column
label="操作人"
align="center"
width="100"
prop="createByName"
/>
<el-table-column
label="操作"
align="center"
width="240"
fixed="right"
>
<template #default="scope">
<el-button
link
type="primary"
@click="handleView(scope.row)"
>
查看
</el-button>
<el-button
link
type="primary"
@click="handleEdit(scope.row)"
>
编辑
</el-button>
<el-button
v-hasPermi="['surgicalSchedule:delete']"
link
type="danger"
@click="handleDelete(scope.row)"
>
取消
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 底部分页区 -->
<div class="pagination-container">
<pagination
v-show="total > 0"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="getPageList"
/>
</div>
<!-- 手术安排弹窗 -->
<el-dialog
v-model="open"
:title="title"
width="1200px"
append-to-body
:close-on-click-modal="false"
@close="cancel"
>
<!-- 弹窗头部操作按钮 -->
<div
v-if="!isViewMode && !isEditMode"
class="dialog-header-buttons"
>
<el-button @click="handleFindApply">
查找
</el-button>
<el-button
class="refresh-btn"
@click="handleRefresh"
>
刷新
</el-button>
<el-button @click="cancel">
返回
</el-button>
<el-button
type="primary"
:disabled="isViewMode"
@click="submitForm"
>
保存
</el-button>
</div>
<!-- 表单内容区 -->
<el-form
ref="surgeryRef"
:model="form"
:rules="rules"
label-width="120px"
:disabled="isViewMode"
>
<!-- 病人基本信息组 -->
<el-divider content-position="left">
病人基本信息
</el-divider>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item
label="患者id"
prop="patientId"
>
<el-tooltip
:content="form.patientId"
placement="top"
:disabled="!form.patientId"
>
<el-input
v-model="form.patientId"
:disabled="true"
style="width: 100%"
/>
</el-tooltip>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="就诊卡号"
prop="identifierNo"
>
<el-tooltip
:content="form.identifierNo"
placement="top"
:disabled="!form.identifierNo"
>
<el-input
v-model="form.identifierNo"
:disabled="true"
style="width: 100%"
/>
</el-tooltip>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="姓名"
prop="patientName"
>
<el-tooltip
:content="form.patientName"
placement="top"
:disabled="!form.patientName"
>
<el-input
v-model="form.patientName"
:disabled="true"
style="width: 100%"
/>
</el-tooltip>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="性别"
width="80"
>
<el-tooltip
:content="form.gender == 0 ? '男' : form.gender == 1 ? '女' : ''"
placement="top"
:disabled="!form.gender"
>
<el-input
:value="form.gender == 0 ? '男' : form.gender == 1 ? '女' : form.gender=='男'?'男': form.gender=='女'?'女':''"
:disabled="true"
style="width: 100%"
/>
</el-tooltip>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item
label="年龄"
prop="age"
>
<el-tooltip
:content="form.age"
placement="top"
:disabled="!form.age"
>
<el-input
v-model="form.age"
:disabled="true"
style="width: 100%"
/>
</el-tooltip>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="病人体重"
prop="patientWeight"
>
<el-input
v-model="form.patientWeight"
placeholder="请输入病人体重kg"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="病人身高"
prop="patientHeight"
>
<el-input
v-model="form.patientHeight"
placeholder="请输入病人身高cm"
/>
</el-form-item>
</el-col>
</el-row>
<!-- 手术申请信息组 -->
<el-divider content-position="left">
手术申请信息
</el-divider>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item
label="手术单号"
prop="surgeryNo"
>
<el-tooltip
:content="form.surgeryNo"
placement="top"
:disabled="!form.surgeryNo"
>
<el-input
v-model="form.surgeryNo"
:disabled="true"
style="width: 100%"
/>
</el-tooltip>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="手术类型"
prop="surgeryType"
>
<el-select
v-model="form.surgeryType"
placeholder="请选择手术类型"
style="width: 100%"
>
<el-option
v-for="item in surgery_type"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="费用类别"
prop="feeType"
>
<el-tooltip
:content="form.feeType"
placement="top"
:disabled="!form.feeType"
>
<el-input
v-model="form.feeType"
:disabled="true"
style="width: 100%"
/>
</el-tooltip>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="申请时间"
prop="applyTime"
>
<el-tooltip
:content="formattedApplyTime"
placement="top"
:disabled="!form.applyTime"
>
<el-input
v-model="formattedApplyTime"
:disabled="true"
style="width: 100%"
/>
</el-tooltip>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="申请医生"
prop="applyDoctorName"
>
<el-tooltip
:content="form.applyDoctorName"
placement="top"
:disabled="!form.applyDoctorName"
>
<el-input
v-model="form.applyDoctorName"
:disabled="true"
style="width: 100%"
/>
</el-tooltip>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="手术名称"
prop="operName"
>
<el-tooltip
:content="form.operName"
placement="top"
:disabled="!form.operName"
>
<el-input
v-model="form.operName"
:disabled="true"
style="width: 100%"
/>
</el-tooltip>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="申请科室"
prop="applyDeptName"
>
<el-tooltip
:content="form.applyDeptName"
placement="top"
:disabled="!form.applyDeptName"
>
<el-input
v-model="form.applyDeptName"
:disabled="true"
style="width: 100%"
/>
</el-tooltip>
</el-form-item>
</el-col>
</el-row>
<!-- 手术安排组 -->
<el-divider content-position="left">
手术安排
</el-divider>
<el-row :gutter="20">
<el-col :span="10">
<el-form-item
label="安排时间"
prop="scheduleDate"
>
<el-date-picker
v-model="form.scheduleDate"
type="datetime"
placeholder="选择安排时间"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item
label="手术台次"
prop="sequenceNo"
>
<el-input
v-model.number="form.sequenceNo"
placeholder="请输入手术台次"
/>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item
label="手术间号"
prop="roomCode"
>
<el-select
v-model="form.roomCode"
placeholder="请选择手术间号"
style="width: 100%"
>
<el-option
v-for="item in operatingRoomList"
:key="item.roomCode"
:label="item.roomCode"
:value="item.roomCode"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="10">
<el-form-item
label="手术台"
prop="tableNo"
>
<el-input
v-model.number="form.tableNo"
placeholder="请输入手术台"
/>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item
label="是否首次手术"
prop="isFirstSurgery"
>
<el-radio-group v-model="form.isFirstSurgery">
<el-radio :value="1">
</el-radio>
<el-radio :value="0">
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<!-- 手术性质和部位 -->
<el-divider content-position="left">
手术性质和部位
</el-divider>
<el-row :gutter="20">
<el-col :span="10">
<el-form-item
label="手术性质"
prop="surgeryNature"
>
<el-select
v-model="form.surgeryNature"
placeholder="请选择手术性质"
style="width: 100%"
>
<el-option
v-for="item in surgery_type"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item
label="手术部位"
prop="surgerySite"
>
<el-select
v-model="form.surgerySite"
placeholder="请选择手术部位"
style="width: 100%"
>
<el-option
v-for="item in surgerySiteList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item
label="过敏药物"
prop="isAllergyMedication"
>
<el-radio-group v-model="form.isAllergyMedication">
<el-radio :value="1">
</el-radio>
<el-radio :value="0">
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item
label="过敏药物备注"
prop="allergyRemark"
>
<el-input
v-model="form.allergyRemark"
type="textarea"
placeholder="请输入过敏药物备注"
:rows="3"
/>
</el-form-item>
</el-col>
</el-row>
<!-- 医护人员组 -->
<el-divider content-position="left">
医护人员
</el-divider>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item
label="主刀医生"
prop="surgeonCode"
>
<el-tooltip
:content="form.surgeonName"
placement="top"
:disabled="!form.surgeonName"
>
<el-input
v-model="form.surgeonName"
:disabled="true"
style="width: 100%"
/>
</el-tooltip>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="助手1"
prop="assistant1Code"
>
<el-select
v-model="form.assistant1Code"
placeholder="请选择助手1"
filterable
style="width: 100%"
>
<el-option
v-for="item in doctorList"
:key="item.code"
:label="item.name"
:value="item.code"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="助手2"
prop="assistant2Code"
>
<el-select
v-model="form.assistant2Code"
placeholder="请选择助手2"
filterable
style="width: 100%"
>
<el-option
v-for="item in doctorList"
:key="item.code"
:label="item.name"
:value="item.code"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="助手3"
prop="assistant3Code"
>
<el-select
v-model="form.assistant3Code"
placeholder="请选择助手3"
filterable
style="width: 100%"
>
<el-option
v-for="item in doctorList"
:key="item.code"
:label="item.name"
:value="item.code"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item
label="洗手护士"
prop="scrubNurseCode"
>
<el-select
v-model="form.scrubNurseCode"
placeholder="请选择洗手护士"
filterable
style="width: 100%"
>
<el-option
v-for="item in nurseList"
:key="item.code"
:label="item.name"
:value="item.code"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="器械护士1"
prop="scrubNurse1Code"
>
<el-select
v-model="form.scrubNurse1Code"
placeholder="请选择器械护士1"
filterable
style="width: 100%"
>
<el-option
v-for="item in nurseList"
:key="item.code"
:label="item.name"
:value="item.code"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="器械护士2"
prop="scrubNurse2Code"
>
<el-select
v-model="form.scrubNurse2Code"
placeholder="请选择器械护士2"
filterable
style="width: 100%"
>
<el-option
v-for="item in nurseList"
:key="item.code"
:label="item.name"
:value="item.code"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="巡回护士1"
prop="circuNurse1Code"
>
<el-select
v-model="form.circuNurse1Code"
placeholder="请选择巡回护士1"
filterable
style="width: 100%"
>
<el-option
v-for="item in nurseList"
:key="item.code"
:label="item.name"
:value="item.code"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item
label="麻醉方法"
prop="anesMethod"
>
<el-select
v-model="form.anesMethod"
placeholder="请选择麻醉方法"
style="width: 100%"
>
<el-option
v-for="item in anesthesiaList"
:key="item.value"
:label="item.label"
:value="Number(item.value)"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="巡回护士2"
prop="circuNurse2Code"
>
<el-select
v-model="form.circuNurse2Code"
placeholder="请选择巡回护士2"
filterable
style="width: 100%"
>
<el-option
v-for="item in nurseList"
:key="item.code"
:label="item.name"
:value="item.code"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="外请专家"
prop="isExternalExpert"
>
<el-radio-group v-model="form.isExternalExpert">
<el-radio :value="1">
</el-radio>
<el-radio :value="0">
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="外请专家姓名"
prop="externalExpertName"
>
<el-input
v-model="form.externalExpertName"
placeholder="请输入外请专家姓名"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="10">
<el-form-item
label="麻醉医师1"
prop="anesDoctor1Code"
>
<el-select
v-model="form.anesDoctor1Code"
placeholder="请选择麻醉医师1"
filterable
style="width: 100%"
>
<el-option
v-for="item in doctorList"
:key="item.code"
:label="item.name"
:value="item.code"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item
label="麻醉医师2"
prop="anesDoctor2Code"
>
<el-select
v-model="form.anesDoctor2Code"
placeholder="请选择麻醉医师2"
filterable
style="width: 100%"
>
<el-option
v-for="item in doctorList"
:key="item.code"
:label="item.name"
:value="item.code"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item
label="麻醉医师3"
prop="anesDoctor3Code"
>
<el-select
v-model="form.anesDoctor3Code"
placeholder="请选择麻醉医师3"
filterable
style="width: 100%"
>
<el-option
v-for="item in doctorList"
:key="item.code"
:label="item.name"
:value="item.code"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item
label="术前诊断"
prop="preoperativeDiagnosis"
>
<el-input
v-model="form.preoperativeDiagnosis"
placeholder="请输入术前诊断"
/>
</el-form-item>
<el-form-item
label="术后诊断"
prop="postoperativeDiagnosis"
>
<el-input
v-model="form.postoperativeDiagnosis"
placeholder="请输入术后诊断"
/>
</el-form-item>
<!-- 手术过程组 -->
<el-divider content-position="left">
手术过程
</el-divider>
<el-row :gutter="20">
<el-col :span="10">
<el-form-item
label="入室时间"
prop="admissionTime"
>
<el-date-picker
v-model="form.admissionTime"
type="datetime"
placeholder="选择入室时间"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item
label="进室时间"
prop="entryTime"
>
<el-date-picker
v-model="form.entryTime"
type="datetime"
placeholder="选择进室时间"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item
label="麻醉开始时间"
prop="anesStart"
>
<el-date-picker
v-model="form.anesStart"
type="datetime"
placeholder="选择麻醉开始时间"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="10">
<el-form-item
label="切开时间"
prop="startTime"
>
<el-date-picker
v-model="form.startTime"
type="datetime"
placeholder="选择切开时间"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item
label="手术结束时间"
prop="endTime"
>
<el-date-picker
v-model="form.endTime"
type="datetime"
placeholder="选择手术结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="10">
<el-form-item
label="麻醉结束时间"
prop="anesEnd"
>
<el-date-picker
v-model="form.anesEnd"
type="datetime"
placeholder="选择麻醉结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item
label="切口类型"
prop="incisionType"
>
<el-select
v-model="form.incisionType"
placeholder="请选择切口类型"
style="width: 100%"
>
<el-option
v-for="item in incisionTypeList"
:key="item.value"
:label="item.label"
:value="Number(item.value)"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="感染诊断"
prop="infectionDiagnosis"
>
<el-input
v-model="form.infectionDiagnosis"
placeholder="请输入感染诊断"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="隔离种类"
prop="isolationType"
>
<el-input
v-model="form.isolationType"
placeholder="请输入隔离种类"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="是否植入标志"
prop="implantFlag"
>
<el-radio-group v-model="form.implantFlag">
<el-radio :value="1">
</el-radio>
<el-radio :value="0">
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="植入物序列号/批号"
prop="implantSerial"
>
<el-input
v-model="form.implantSerial"
placeholder="请输入植入物序列号/批号"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="出血量"
prop="bloodLoss"
>
<el-input
v-model="form.bloodLoss"
placeholder="请输入出血量"
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item
label="输血量"
prop="bloodTrans"
>
<el-input
v-model="form.bloodTrans"
placeholder="请输入输血量"
/>
</el-form-item>
</el-col>
</el-row>
<!-- 备注信息组 -->
<el-divider content-position="left">
备注信息
</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item
label="手术相关对话信息"
prop="communicationInfo"
>
<el-input
v-model="form.communicationInfo"
type="textarea"
placeholder="请输入手术相关对话信息"
:rows="3"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
label="手术相关备注"
prop="remark"
>
<el-input
v-model="form.remark"
type="textarea"
placeholder="请输入手术相关备注"
:rows="3"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<!-- 底部操作区 -->
<template #footer>
<div
v-if="!isViewMode"
class="dialog-footer"
>
<el-button @click="cancel">
取消
</el-button>
<el-button
type="primary"
@click="submitForm"
>
保存
</el-button>
</div>
</template>
</el-dialog>
<!-- 手术申请查询弹窗 -->
<el-dialog
v-model="showApplyDialog"
:title="'手术申请查询'"
width="1200px"
class="surgery-apply-dialog"
@close="cancelApplyDialog"
>
<!-- 查询条件区 -->
<el-form
ref="applyQueryRef"
:model="applyQueryParams"
:inline="true"
class="query-form"
>
<el-form-item
label="手术单号"
prop="surgeryNo"
>
<el-input
v-model="applyQueryParams.surgeryNo"
placeholder="请输入手术单号"
clearable
style="width: 200px"
@keyup.enter="handleApplyQuery"
/>
</el-form-item>
<el-form-item
label="申请时间范围"
prop="applyTimeRange"
>
<el-tooltip
:content="applyQueryParams.applyTimeRange ? applyQueryParams.applyTimeRange.join(' 至 ') : ''"
placement="top"
:disabled="!applyQueryParams.applyTimeRange || applyQueryParams.applyTimeRange.length === 0"
>
<el-date-picker
v-model="applyQueryParams.applyTimeRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
style="width: 240px"
/>
</el-tooltip>
</el-form-item>
<el-form-item
label="申请科室"
prop="applyDeptId"
>
<el-select
v-model="applyQueryParams.applyDeptId"
placeholder="请选择申请科室"
style="width: 150px"
>
<el-option
v-for="item in deptList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item
label="主刀医生"
prop="mainDoctorId"
>
<el-select
v-model="applyQueryParams.mainDoctorId"
placeholder="请选择主刀医生"
style="width: 150px"
>
<el-option
v-for="item in doctorList"
:key="item.code"
:label="item.name"
:value="item.code"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button
type="primary"
icon="Search"
@click="handleApplyQuery"
>
查询
</el-button>
<el-button
icon="Refresh"
@click="resetApplyQuery"
>
重置
</el-button>
</el-form-item>
</el-form>
<!-- 结果表格卡片 -->
<el-card
shadow="never"
class="apply-card"
>
<el-table
ref="applyTableRef"
v-loading="applyLoading"
:data="applyList"
row-key="surgeryNo"
:row-class-name="tableRowClassName"
style="width: 100%"
max-height="320"
@row-click="handleApplyRowClick"
>
<el-table-column
type="selection"
width="55"
:selectable="handleSelectable"
/>
<el-table-column
label="ID"
align="center"
width="80"
fixed
>
<template #default="{ $index }">
{{ (applyQueryParams.pageNo - 1) * applyQueryParams.pageSize + $index + 1 }}
</template>
</el-table-column>
<el-table-column
label="姓名"
align="center"
prop="name"
width="100"
/>
<el-table-column
label="手术单号"
align="center"
prop="surgeryNo"
width="120"
/>
<el-table-column
label="手术名称"
align="center"
prop="descJson.surgeryName"
min-width="140"
show-overflow-tooltip
/>
<el-table-column
label="申请科室"
align="center"
width="100"
prop="applyDeptName"
/>
<el-table-column
label="手术类型"
align="center"
width="90"
>
<template #default="scope">
{{ getSurgeryTypeName(scope.row.surgeryType) }}
</template>
</el-table-column>
<el-table-column
label="手术等级"
align="center"
width="90"
>
<template #default="scope">
{{ getSurgeryLevelName(scope.row.surgeryLevel || scope.row.descJson?.surgeryLevel) }}
</template>
</el-table-column>
<el-table-column
label="麻醉方式"
align="center"
width="90"
>
<template #default="scope">
{{ getAnesthesiaName(scope.row.anesthesiaTypeEnum) }}
</template>
</el-table-column>
<el-table-column
label="主刀医生"
align="center"
width="100"
prop="mainSurgeonName"
/>
</el-table>
<!-- 分页在卡片内部 -->
<div class="apply-pagination">
<pagination
v-show="applyTotal > 0"
:total="applyTotal"
:page="applyQueryParams.pageNo"
:limit="applyQueryParams.pageSize"
layout="total, sizes, prev, pager, next"
@update:page="val => applyQueryParams.pageNo = val"
@update:limit="val => applyQueryParams.pageSize = val"
@pagination="getSurgicalScheduleList"
/>
</div>
</el-card>
<!-- 底部操作区 -->
<template #footer>
<div
class="dialog-footer"
style="padding-top: 12px; border-top: 1px solid #ebeef5"
>
<el-button @click="cancelApplyDialog">
取消
</el-button>
<el-button
type="primary"
@click="confirmApply"
>
确认
</el-button>
</div>
</template>
</el-dialog>
<!-- 手术计费弹窗 -->
<el-dialog
v-model="showChargeDialog"
:title="chargeDialogTitle"
width="1400px"
append-to-body
destroy-on-close
@close="closeChargeDialog"
>
<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">
<el-descriptions :column="4">
<el-descriptions-item
label="患者信息:"
width="150"
>
{{
Object.keys(chargePatientInfo).length !== 0
? chargePatientInfo.patientName +
' / ' +
chargePatientInfo.age +
' / ' +
chargePatientInfo.genderEnum_enumText +
' / ' +
chargePatientInfo.typeCode_dictText
: '-'
}}
</el-descriptions-item>
<el-descriptions-item
label="安排时间"
width="150"
>
{{ Object.keys(chargePatientInfo).length !== 0 ? formatChargeDate(chargePatientInfo.registerTime) : '-' }}
</el-descriptions-item>
<el-descriptions-item
label="计费账号"
width="150"
>
{{ userStore.name }}
</el-descriptions-item>
<el-descriptions-item
label="手术名称"
width="150"
>
{{ chargeSurgeryInfo.surgeryName }}
</el-descriptions-item>
</el-descriptions>
</div>
<div style="padding: 10px">
<prescriptionlist
ref="prescriptionRef"
:patient-info="chargePatientInfo"
:generate-source-enum="chargePatientInfo.generateSourceEnum"
:source-bill-no="chargePatientInfo.sourceBillNo"
/>
<div
v-if="disabled"
class="overlay"
/>
</div>
</div>
</div>
</el-dialog>
<!-- 临时医嘱弹窗 -->
<el-dialog
v-model="showTemporaryMedical"
title=""
width="80%"
append-to-body
:close-on-click-modal="false"
>
<!-- 🔧 新增加载状态提示 -->
<div
v-if="temporaryMedicalLoading"
class="loading-container"
>
<el-icon class="is-loading">
<loading />
</el-icon>
<span>正在加载医嘱数据请稍候...</span>
</div>
<temporary-medical
v-else
v-model:temporary-advices="temporaryAdvices"
:patient-info="temporaryPatientInfo"
:billing-medicines="temporaryBillingMedicines"
:is-signed-prop="temporarySigned"
@submit="handleTemporaryMedicalSubmit"
@cancel="handleTemporaryMedicalCancel"
@refresh="handleTemporaryMedicalRefresh"
@quote-billing="handleQuoteBilling"
/>
</el-dialog>
</div>
</template>
<script setup name="SurgicalSchedule">
import { ref, reactive, onMounted, nextTick, computed, watch } from 'vue'
import { getCurrentInstance } from 'vue'
import { parseTime } from '@/utils/openhis'
import { useDict } from '@/utils/dict'
import download from '@/plugins/download'
import Prescriptionlist from '@/views/clinicmanagement/bargain/component/prescriptionlist.vue'
import useUserStore from '@/store/modules/user'
import { ElMessage } from 'element-plus'
import { Loading } from '@element-plus/icons-vue' // 🔧 新增:导入 Loading 图标
// 导入计费相关接口
import { getPrescriptionList } from '@/views/clinicmanagement/bargain/component/api'
// API 导入
import {
getSurgerySchedulePage,
addSurgerySchedule,
updateSurgerySchedule,
deleteSurgerySchedule,
getSurgeryScheduleDetail
} from '@/api/surgicalschedule'
import { getSurgeryPage} from '@/views/inpatientDoctor/home/components/applicationShow/api.js'
import { getContract } from '@/views/inpatientDoctor/home/components/api.js'
import request from '@/utils/request'
import SurgeryCharge from '../charge/surgerycharge/index.vue'
import TemporaryMedical from './temporaryMedical.vue'
// 静默获取字典列表(跳过拦截器错误提示,手术室护士等角色可能无此权限)
function getTenantPageSilent(query) {
return request({
url: '/system/tenant/page',
method: 'get',
params: query,
skipErrorMsg: true
})
}
// 静默获取科室树(跳过拦截器错误提示)
function deptTreeSelectSilent(params = {}) {
return request({
url: '/base-data-manage/organization/organization',
method: 'get',
params: { typeEnum: 2, ...params },
skipErrorMsg: true
})
}
// 静默获取用户列表(跳过拦截器错误提示)
function listUserSilent(query) {
return request({
url: '/base-data-manage/practitioner/user-practitioner-page',
method: 'get',
params: query,
skipErrorMsg: true
})
}
// 静默获取手术室列表(跳过拦截器错误提示)
function listOperatingRoomSilent(query) {
return request({
url: '/base-data-manage/operating-room/list',
method: 'get',
params: query,
skipErrorMsg: true
})
}
const { proxy } = getCurrentInstance()
const userStore = useUserStore()
const loading = ref(true)
const showSearch = ref(true)
const surgeryList = ref([])
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
scheduleDateRange: [],
scheduleDateStart: undefined,
scheduleDateEnd: undefined,
tenantId: undefined,
applyDeptId: undefined,
patientName: undefined,
operCode: undefined
})
const open = ref(false)
const isEditMode = ref(false)
const isViewMode = ref(false)
// 选中行状态管理
const selectedRow = ref(null)
const selectedRowIndex = ref(-1)
const form = reactive({
scheduleId:undefined,
applyId: undefined,
patientId: undefined,
visitId: undefined,
identifierNo: undefined,
operCode: undefined,
operName: undefined,
preoperativeDiagnosis: undefined,
postoperativeDiagnosis: undefined,
scheduleDate: undefined,
sequenceNo: undefined,
isFirstSurgery: 0,
isAllergyMedication: 0,
allergyRemark: undefined,
surgeryNature: undefined,
surgerySite: undefined,
incisionType: undefined,
surgeryLevel: undefined,
admissionTime: undefined,
entryTime: undefined,
roomCode: undefined,
tableNo: undefined,
anesMethod: undefined,
anesDoctor1Code: undefined,
anesDoctor2Code: undefined,
anesDoctor3Code: undefined,
scrubNurseCode: undefined,
circuNurse1Code: undefined,
circuNurse2Code: undefined,
scrubNurse1Code: undefined,
scrubNurse2Code: undefined,
surgeonCode: undefined,
assistant1Code: undefined,
assistant2Code: undefined,
assistant3Code: undefined,
startTime: undefined,
endTime: undefined,
anesStart: undefined,
anesEnd: undefined,
operStatus: 0,
implantFlag: 0,
implantSerial: undefined,
bloodLoss: undefined,
bloodTrans: undefined,
infectionDiagnosis: undefined,
isolationType: undefined,
patientWeight: undefined,
patientHeight: undefined,
communicationInfo: undefined,
remark: undefined,
createTime: undefined,
creatorId: undefined,
patientName: undefined,
gender: undefined,
birthDay: undefined,
orgName: undefined,
applyDeptName: undefined,
surgeonName: undefined,
isExternalExpert: 0,
externalExpertName: undefined,
feeType: undefined
})
const surgeryRef = ref()
const total = ref(0)
const title = ref('')
// 表单验证规则
const rules = reactive({
scheduleDate: [
{ required: true, message: '请选择安排时间', trigger: 'change' }
],
sequenceNo: [
{ required: true, message: '请输入手术台次', trigger: 'blur' },
{ type: 'number', message: '手术台次必须为数字', trigger: 'blur' }
],
roomCode: [
{ required: true, message: '请选择手术间号', trigger: 'change' }
],
tableNo: [
{ required: true, message: '请选择手术台', trigger: 'change' }
],
surgeryNature: [
{ required: true, message: '请选择手术性质', trigger: 'change' }
],
surgerySite: [
{ required: true, message: '请选择手术部位', trigger: 'change' }
],
anesMethod: [
{ required: true, message: '请选择麻醉方法', trigger: 'change' }
],
surgeonCode: [
{ required: true, message: '请选择主刀医生', trigger: 'change' }
]
})
// 手术申请查询弹窗
const showApplyDialog = ref(false)
const applyLoading = ref(false)
const applyList = ref([])
const applyTotal = ref(0)
const applyTableRef = ref()
// 手术计费弹窗
const showChargeDialog = ref(false)
const chargeDialogTitle = ref('')
const chargePatientInfo = ref({})
const chargeSurgeryInfo = ref({})
const prescriptionRef = ref()
// 临时医嘱弹窗
const showTemporaryMedical = ref(false)
const temporaryMedicalTitle = ref('门诊术中临时医嘱')
const temporaryPatientInfo = ref({})
const temporaryBillingMedicines = ref([])
const temporaryAdvices = ref([])
const temporaryMedicalLoading = ref(false) // 🔧 新增:临时医嘱加载状态
const temporarySigned = ref(false) // 🔧 新增:签名状态,用于保持按钮名称一致性
// 下拉列表数据
const orgList = ref([])
const deptList = ref([])
const doctorList = ref([])
const nurseList = ref([])
const operatingRoomList = ref([])
function flattenOrgTree(tree = []) {
const result = []
const stack = Array.isArray(tree) ? [...tree] : []
while (stack.length) {
const node = stack.shift()
if (!node) continue
const id = node.id ?? node.orgId ?? node.deptId
const name = node.name ?? node.label ?? node.deptName ?? node.orgName
if (id !== undefined && id !== null && name) {
result.push({ id, name })
}
const children = node.children || node.childList
if (Array.isArray(children) && children.length) {
stack.unshift(...children)
}
}
// 去重
const seen = new Set()
return result.filter(it => {
const key = String(it.id)
if (seen.has(key)) return false
seen.add(key)
return true
})
}
function mapPractitionerToOption(item) {
// user-practitioner-page 常见字段practitionerId / nickName / orgId 等
const code = item.code ?? item.practitionerId ?? item.userId ?? item.id
const name = item.name ?? item.nickName ?? item.userName ?? item.userNickName
return { code, name }
}
function mapOperatingRoomToOption(item) {
const roomCode = item.roomCode ?? item.code ?? item.no ?? item.name
return { ...item, roomCode }
}
// 字典数据
// 字典返回的字段名就是字典 key 本身(参照 surgerymanageconst { surgical_site } = useDict('surgical_site')
const {
surgical_site: surgerySiteList,
anesthesia_type: anesthesiaList,
incision_level: incisionTypeList,
isolation_type: isolationTypeList,
surgery_type,
surgery_level,
surgery_nature: surgeryNatureList,
method_code
} = useDict('surgical_site', 'anesthesia_type', 'incision_level', 'isolation_type', 'surgery_type', 'surgery_level', 'surgery_nature', 'method_code')
// Bug #433: 存储待转换的数据,等待字典加载后再设置类型
const pendingAnesData = ref(null)
// 监听麻醉字典加载,完成后立即设置表单值
let anesDataUnwatch = null
function setupAnesDataWatch() {
if (anesDataUnwatch) return // 防止重复设置
anesDataUnwatch = watch(
anesthesiaList,
(newList) => {
if (newList && newList.length > 0 && pendingAnesData.value) {
const data = pendingAnesData.value
if (data.anesMethod != null) form.anesMethod = Number(data.anesMethod)
if (data.incisionLevel != null) form.incisionType = Number(data.incisionLevel)
if (data.feeType != null) form.feeType = data.feeType
if (data.isExternalExpert != null) form.isExternalExpert = Number(data.isExternalExpert)
pendingAnesData.value = null
if (anesDataUnwatch) { anesDataUnwatch(); anesDataUnwatch = null }
}
},
{ immediate: true }
)
}
// 加载数据
onMounted(() => {
const anesthesiaType = sessionStorage.getItem('anesthesiaType')
if (anesthesiaType) {
form.anesMethod = anesthesiaType
sessionStorage.removeItem('anesthesiaType')
}
getPageList()
loadOrgList()
loadDeptList()
loadDoctorList()
loadNurseList()
loadOperatingRoomList()
})
// 加载卫生机构列表
function loadOrgList() {
getTenantPageSilent({ pageNo: 1, pageSize: 1000 })
.then(res => {
if (res.code === 200) {
const records = res.data?.records || res.data || []
orgList.value = records.map(item => ({ id: item.id, name: item.tenantName || item.name }))
} else {
// 权限不足时静默降级不弹窗阻断Bug #441
console.warn('卫生机构列表加载失败(可能无权限):', res.message || res.code)
orgList.value = []
}
})
.catch((err) => {
// 网络错误或权限拒绝:静默降级
console.warn('卫生机构列表加载失败:', err?.message || err)
orgList.value = []
})
}
// 加载科室列表
function loadDeptList() {
deptTreeSelectSilent()
.then(res => {
if (res.code === 200) {
const tree = res.data?.records || res.data || []
deptList.value = flattenOrgTree(tree)
} else {
// 权限不足时静默降级不弹窗阻断Bug #441
console.warn('科室列表加载失败(可能无权限):', res.message || res.code)
deptList.value = []
}
})
.catch(error => {
// 网络错误或权限拒绝:静默降级
console.warn('科室列表加载失败:', error?.message || error)
deptList.value = []
})
}
// 加载医生列表
function loadDoctorList() {
listUserSilent({ pageNo: 1, pageSize: 1000 })
.then(res => {
if (res.code === 200) {
const records = res.data?.records || []
doctorList.value = records.map(mapPractitionerToOption).filter(it => it.code && it.name)
} else {
// 权限不足时静默降级不弹窗阻断Bug #441
console.warn('医生列表加载失败(可能无权限):', res.message || res.code)
doctorList.value = []
}
})
.catch(error => {
// 网络错误或权限拒绝:静默降级
console.warn('医生列表加载失败:', error?.message || error)
doctorList.value = []
})
}
// 加载护士列表
function loadNurseList() {
listUserSilent({ pageNo: 1, pageSize: 1000 })
.then(res => {
if (res.code === 200) {
const records = res.data?.records || []
nurseList.value = records.map(mapPractitionerToOption).filter(it => it.code && it.name)
} else {
// 权限不足时静默降级不弹窗阻断Bug #441
console.warn('护士列表加载失败(可能无权限):', res.message || res.code)
nurseList.value = []
}
})
.catch(error => {
// 网络错误或权限拒绝:静默降级
console.warn('护士列表加载失败:', error?.message || error)
nurseList.value = []
})
}
// 加载手术室列表
function loadOperatingRoomList() {
listOperatingRoomSilent({ pageNo: 1, pageSize: 1000, statusEnum: 1 })
.then(res => {
if (res.code === 200) {
const records = res.data?.records || []
operatingRoomList.value = records.map(mapOperatingRoomToOption).filter(it => it.roomCode)
} else {
// 权限不足时静默降级不弹窗阻断Bug #441
console.warn('手术室列表加载失败(可能无权限):', res.message || res.code)
operatingRoomList.value = []
}
})
.catch(error => {
// 网络错误或权限拒绝:静默降级
console.warn('手术室列表加载失败:', error?.message || error)
operatingRoomList.value = []
})
}
// 获取手术安排列表
function getList() {
loading.value = true
// 处理日期范围
const params = { ...queryParams }
if (params.scheduleDateRange && params.scheduleDateRange.length === 2) {
params.scheduleDateStart = params.scheduleDateRange[0]
params.scheduleDateEnd = params.scheduleDateRange[1]
} else {
params.scheduleDateStart = undefined
params.scheduleDateEnd = undefined
}
delete params.scheduleDateRange
getSurgerySchedulePage(params).then((res) => {
surgeryList.value = res.data.records
total.value = res.data.total
}).catch(error => {
proxy.$modal.msgError('获取手术安排列表失败,请稍后重试')
surgeryList.value = []
total.value = 0
}).finally(() => {
loading.value = false
})
}
function getPageList() {
getList()
}
function handleQuery() {
queryParams.pageNo = 1
getList()
}
function resetQuery() {
proxy.resetForm('queryRef')
Object.assign(queryParams, {
pageNo: 1,
pageSize: 10,
scheduleDateRange: [],
scheduleDateStart: undefined,
scheduleDateEnd: undefined,
tenantId: undefined,
applyDeptId: undefined,
patientName: undefined,
operCode: undefined
})
getList()
}
// 新增手术安排
function handleAdd() {
title.value = '新增手术安排'
isEditMode.value = false
isViewMode.value = false
resetForm()
open.value = true
}
// 编辑手术安排
function handleEdit(row) {
title.value = '编辑手术安排'
isEditMode.value = true
isViewMode.value = false
resetForm()
getSurgeryScheduleDetail(row.scheduleId).then(res => {
if (res.code === 200) {
const data = res.data
Object.assign(form, data)
// Bug #433: 如果字典已加载则立即转换否则存入pending等待字典加载完成
if (anesthesiaList.value && anesthesiaList.value.length > 0) {
if (data.anesMethod != null) form.anesMethod = Number(data.anesMethod)
if (data.incisionLevel != null) form.incisionType = Number(data.incisionLevel)
if (data.feeType != null) form.feeType = data.feeType
if (data.isExternalExpert != null) form.isExternalExpert = Number(data.isExternalExpert)
} else {
pendingAnesData.value = data
setupAnesDataWatch()
}
} else {
proxy.$modal.msgError('获取手术安排详情失败')
}
}).catch(() => {
proxy.$modal.msgError('获取手术安排详情失败')
})
open.value = true
}
// 查看手术安排
function handleView(row) {
title.value = '查看手术安排'
isEditMode.value = false
isViewMode.value = true
resetForm()
getSurgeryScheduleDetail(row.scheduleId).then(res => {
if (res.code === 200) {
const data = res.data
Object.assign(form, data)
// Bug #433: 如果字典已加载则立即转换否则存入pending等待字典加载完成
if (anesthesiaList.value && anesthesiaList.value.length > 0) {
if (data.anesMethod != null) form.anesMethod = Number(data.anesMethod)
if (data.incisionLevel != null) form.incisionType = Number(data.incisionLevel)
if (data.feeType != null) form.feeType = data.feeType
if (data.isExternalExpert != null) form.isExternalExpert = Number(data.isExternalExpert)
} else {
pendingAnesData.value = data
setupAnesDataWatch()
}
} else {
proxy.$modal.msgError('获取手术安排详情失败')
}
}).catch(() => {
proxy.$modal.msgError('获取手术安排详情失败')
})
open.value = true
}
// 行选中事件处理
function handleCurrentChange(currentRow, oldRow) {
if (currentRow) {
selectedRow.value = currentRow
selectedRowIndex.value = surgeryList.value.findIndex(row => row.scheduleId === currentRow.scheduleId)
} else {
selectedRow.value = null
selectedRowIndex.value = -1
}
}
// 删除手术安排
function handleDelete(row) {
proxy.$modal.confirm('是否确认取消手术安排"' + row.operName + '"?').then(() => {
return deleteSurgerySchedule(row.scheduleId)
}).then(() => {
getPageList()
proxy.$modal.msgSuccess('手术安排已取消')
}).catch(() => {
return
})
}
// 手术计费
async function handleChargeCharge(row) {
// 如果没有传入行数据,使用选中的行
if (!row && selectedRow.value) {
row = selectedRow.value
}
// 如果还是没有行数据,显示提示
if (!row) {
proxy.$modal.msgWarning('请先选择要计费的手术安排')
return
}
// 调用接口获取账户信息
let accountId = null
try {
const contractResult = await getContract({ encounterId: row.visitId })
if (contractResult.code === 200 && contractResult.data && contractResult.data.length > 0) {
// 从返回数据中提取accountId - data是数组取第一个元素的accountId
accountId = contractResult.data[0].accountId || contractResult.data[0].id
} else {
proxy.$modal.msgError('获取账户信息失败')
}
} catch (error) {
return
}
// 设置计费弹窗数据 - 直接复制划价页面的逻辑
chargeDialogTitle.value = '手术计费 - ' + row.patientName + ' - ' + row.operName
// 构建患者信息传递encounterId和机构ID
chargePatientInfo.value = {
encounterId: row.visitId, // 就诊ID
patientId: row.patientId,
patientName: row.patientName,
genderEnum: row.gender,
age: row.age,
organizationName: row.applyDeptName,
registerTime: row.scheduleDate,
typeCode_dictText: row.applyDeptName,
genderEnum_enumText: row.gender === 0 ? '男' : row.gender === 1 ? '女' : '未知',
// 添加机构ID
orgId: userStore.organizationId || userStore.orgId || userStore.tenantId || 1,
// 添加账户ID
accountId: accountId,
// 添加手术单号用于关联对应的手术医嘱
sourceBillNo: row.operCode,
//添加计费标志手术计费
generateSourceEnum: 6
}
chargeSurgeryInfo.value = {
surgeryName: row.operName,
surgeryNo: row.operCode
}
// 打开计费弹窗
showChargeDialog.value = true
// 延迟加载处方列表,确保组件已经渲染
nextTick(() => {
if (prescriptionRef.value && prescriptionRef.value.getListInfo) {
prescriptionRef.value.getListInfo()
}
})
}
// 关闭计费弹窗
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 = {}
chargeSurgeryInfo.value = {}
}
// 处理医嘱按钮点击事件
function handleMedicalAdvice(row) {
// 如果没有传入行数据,使用选中的行
if (!row && selectedRow.value) {
row = selectedRow.value
}
// 如果还是没有行数据,显示提示
if (!row) {
proxy.$modal.msgWarning('请先选择要开具医嘱的手术安排')
return
}
// 设置临时医嘱弹窗数据
temporaryPatientInfo.value = {
patientName: row.patientName,
visitId: row.visitId,
operCode: row.operCode,
roomCode: row.roomCode,
doctorName: userStore.nickName,
role: userStore.roles[0],
effectiveOrgId : row.effectiveOrgId,
orgId: userStore.orgId,
positionId: userStore.orgId,
applyId: row.applyId // 手术申请单ID用于过滤关联医嘱
}
// 🔧 每次打开临时医嘱都重新拉取最新数据,确保计费弹窗签发后数据自动更新
// 🔧 修复 Bug #446: 先保存旧数据再清空,避免竟态条件
const prevAdvices = [...temporaryAdvices.value]
temporaryBillingMedicines.value = []
temporaryAdvices.value = []
// 🔧 修复 Bug #446: 如果是同一 encounter 且已有提交的医嘱(有 requestId保留签名状态
const hasSubmittedAdvices = prevAdvices.length > 0 &&
prevAdvices[0]?.originalMedicine?.encounterId === row.visitId &&
prevAdvices.some(a => a.originalMedicine?.requestId);
temporarySigned.value = hasSubmittedAdvices; // 修复:根据已有数据状态设置,而非盲目重置
temporaryMedicalLoading.value = true // 🔧 新增:开始加载
// 调用计费接口获取数据
getPrescriptionList(row.visitId, 6, row.operCode).then((res) => {
if (res.code === 200 && res.data) {
const seenIds = new Set();
const filteredItems = res.data.filter(item => {
// 匹配 encounterId
if (item.encounterId !== row.visitId) return false;
// 只保留药品(1)和耗材(2),屏蔽诊疗(3)和手术(6)
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;
// 根据药品请求ID去重避免重复显示
const itemId = item.requestId || item.id;
if (itemId && seenIds.has(itemId)) return false;
if (itemId) seenIds.add(itemId);
return true;
})
// 按 statusEnum 区分1=草稿(待生成)2=已签发(已生成)
const draftItems = filteredItems.filter(item => item.statusEnum === 1)
const activeItems = filteredItems.filter(item => item.statusEnum === 2)
if (activeItems.length > 0) {
temporarySigned.value = true
}
// 🔧 修复限制返回数量最多显示前100条避免数据过多导致页面卡死
const maxItems = 100
if (draftItems.length > maxItems) {
ElMessage.warning(`待签发医嘱数量过多(${draftItems.length}条),仅显示前${maxItems}`)
draftItems.length = maxItems
}
// === 待生成列表statusEnum=1 草稿状态的项目 ===
temporaryBillingMedicines.value = draftItems.map(item => {
try {
// 从 contentJson 或 content_json 中解析详细数据 - 兼容下划线和驼峰命名
const jsonContent = item.contentJson || item.content_json;
const contentData = jsonContent ? JSON.parse(jsonContent) : {};
return {
medicineName: contentData.adviceName || contentData.advice_name || item.adviceName || item.advice_name || item.chargeName || item.charge_name || contentData.itemName || contentData.item_name || '未知药品',
specification: contentData.volume || contentData.specification || item.volume || item.specification || '',
quantity: contentData.quantity || item.quantity || 0,
batchNumber: contentData.lotNumber || contentData.lot_number || item.lotNumber || item.lot_number || '',
unitPrice: contentData.unitPrice || contentData.unit_price || item.unitPrice || item.unit_price || 0,
subtotal: contentData.totalPrice || contentData.total_price || item.totalPrice || item.total_price ||
(contentData.unitPrice || contentData.unit_price || item.unitPrice || item.unit_price || 0) *
(contentData.quantity || item.quantity || 0),
insuranceType: (contentData.insuranceType || contentData.insurance_type) === 1 ? '医保' : (item.insuranceType === 1 || item.insurance_type === 1) ? '医保' : '自费',
// 添加医嘱定义ID和表名用于库存匹配
adviceDefinitionId: item.adviceDefinitionId || contentData.adviceDefinitionId || item.advice_definition_id || null,
adviceTableName: item.adviceTableName || contentData.adviceTableName || item.advice_table_name || null,
// 添加关键字段医嘱类型、费用项目ID、定价ID用于后端正确分类和保存
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) {
// 如果解析失败,使用顶层数据 - 兼容 snake_case 和 camelCase 以及后端不同字段名
return {
medicineName: item.adviceName || item.advice_name || item.chargeName || '',
specification: item.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 ||
(item.unitPrice || item.unit_price || 0) *
(item.quantity || item.quantity_value || item.quantityValue || 0),
insuranceType: (item.insuranceType || item.insurance_type) === 1 ? '医保' : '自费',
// 添加医嘱定义ID和表名用于库存匹配
adviceDefinitionId: item.adviceDefinitionId || item.advice_definition_id || null,
adviceTableName: item.adviceTableName || item.advice_table_name || null,
// 添加关键字段医嘱类型、费用项目ID、定价ID用于后端正确分类和保存
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
};
}
});
// === 已生成列表statusEnum=2 已签发状态的项目,直接转为医嘱格式 ===
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.]+)\s*([a-zA-Z一-龥]+)/)
const specValue = specMatch ? parseFloat(specMatch[1]) : 1
const specUnit = specMatch ? specMatch[2] : ''
const dosage = specValue * (contentData.quantity || item.quantity || 1)
let usageCode = contentData.methodCode || 'iv'
let usageLabel = getUsageLabel(usageCode)
if (usageCode === 'iv') {
if (medicineName.includes('注射液')) { usageCode = 'iv'; usageLabel = '静脉注射' }
} else if (usageCode === 'po') {
if (medicineName.includes('片') || medicineName.includes('胶囊')) { usageCode = 'po'; usageLabel = '口服' }
}
return {
id: index + 1,
adviceName: medicineName,
dosage,
unit: specUnit,
usage: usageCode,
usageLabel,
frequency: '立即',
executeTime: '',
originalMedicine: {
...item,
medicineName: medicineName,
specification: spec,
quantity: contentData.quantity || item.quantity || 1,
encounterId: row.visitId
}
}
} catch (e) {
return {
id: index + 1,
adviceName: item.adviceName || item.advice_name || '',
dosage: 1, unit: 'ml', usage: 'iv', usageLabel: '静脉注射',
frequency: '立即',
executeTime: '',
originalMedicine: {
...item,
medicineName: item.adviceName || item.advice_name || '',
specification: item.volume || item.specification || '',
quantity: item.quantity || 1,
encounterId: row.visitId
}
}
}
})
} else {
temporaryBillingMedicines.value = []
temporaryAdvices.value = []
}
// 打开临时医嘱弹窗
showTemporaryMedical.value = true
temporaryMedicalLoading.value = false // 🔧 新增:加载完成
}).catch((error) => {
temporaryBillingMedicines.value = []
temporaryAdvices.value = []
temporaryMedicalLoading.value = false // 🔧 新增:加载完成(即使失败也要关闭加载状态)
proxy.$modal.msgError('刷新数据失败,请重试')
console.error('Failed to refresh prescription list', error)
showTemporaryMedical.value = true
})
}
// 关闭临时医嘱弹窗
// 🔧 修复:清空数据,避免下次打开时使用缓存数据导致数据重复
function closeTemporaryMedical() {
showTemporaryMedical.value = false
// 🔧 修复:关闭弹窗时不清空数据,保留用户可能已经修改过的数据
// 只有当用户点击"取消"按钮时才应该清空数据
}
// 处理临时医嘱提交
// 🔧 修复:提交成功后,更新 temporaryAdvices 中的 requestId以便下次提交时执行更新操作
function handleTemporaryMedicalSubmit(data) {
// 🔧 Bug #445 修复:提交成功后重新拉取数据,确保"待生成"列表正确更新
if (data.patientInfo && data.patientInfo.visitId) {
const row = { visitId: data.patientInfo.visitId, operCode: data.patientInfo.operCode }
temporarySigned.value = true
ElMessage.success('临时医嘱已生成(已签发)')
// 重新拉取最新数据,后端已将 statusEnum 从 1(草稿) 更新为 2(已签发)
getPrescriptionList(row.visitId, 6, row.operCode).then((res) => {
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 {
medicineName: contentData.adviceName || contentData.advice_name || item.adviceName || item.advice_name || item.chargeName || item.charge_name || contentData.itemName || contentData.item_name || '未知药品',
specification: contentData.volume || contentData.specification || item.volume || item.specification || '',
quantity: contentData.quantity || item.quantity || 0,
batchNumber: contentData.lotNumber || contentData.lot_number || item.lotNumber || item.lot_number || '',
unitPrice: contentData.unitPrice || contentData.unit_price || item.unitPrice || item.unit_price || 0,
subtotal: contentData.totalPrice || contentData.total_price || item.totalPrice || item.total_price ||
(contentData.unitPrice || contentData.unit_price || item.unitPrice || item.unit_price || 0) *
(contentData.quantity || item.quantity || 0),
insuranceType: (contentData.insuranceType || contentData.insurance_type) === 1 ? '医保' : (item.insuranceType === 1 || item.insurance_type === 1) ? '医保' : '自费',
adviceDefinitionId: item.adviceDefinitionId || contentData.adviceDefinitionId || item.advice_definition_id || null,
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
}
}
})
// 更新已生成列表
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: '',
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: '',
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(
data.temporaryAdvices.map(a => {
const om = a.originalMedicine || {}
return `${om.medicineName || a.adviceName || ''}|||${om.specification || ''}|||${om.quantity || 0}`
}).filter(k => k !== '|||0')
)
if (submittedKeys.size > 0) {
temporaryBillingMedicines.value = (temporaryBillingMedicines.value || []).filter(m => {
return !submittedKeys.has(`${m.medicineName || ''}|||${m.specification || ''}|||${m.quantity || 0}`)
})
}
temporaryAdvices.value = data.temporaryAdvices
}
})
} else {
temporaryAdvices.value = []
temporaryBillingMedicines.value = []
}
}
function handleTemporaryMedicalCancel() {
// 🔧 修复:用户点击取消时才清空数据,因为用户可能要放弃修改
temporaryPatientInfo.value = {}
temporaryBillingMedicines.value = []
temporaryAdvices.value = []
temporarySigned.value = false // 🔧 重置签名状态
closeTemporaryMedical()
}
// 处理删除临时医嘱 - 将删除的医嘱放回已引用计费药品列表
function handleDeleteTemporaryAdvice(index) {
const deletedAdvice = temporaryAdvices.value[index]
// 如果有原始药品数据,放回到计费药品列表
if (deletedAdvice.originalMedicine) {
temporaryBillingMedicines.value.push(deletedAdvice.originalMedicine)
}
temporaryAdvices.value.splice(index, 1)
proxy.$modal.msgSuccess('临时医嘱已删除,已放回待生成列表')
}
// 处理刷新按钮点击
function handleTemporaryMedicalRefresh() {
// 重新拉取计费药品数据
if (temporaryPatientInfo.value.visitId) {
handleMedicalAdvice(temporaryPatientInfo.value)
} else {
proxy.$modal.msgWarning('患者信息不完整,请关闭弹窗重新打开')
}
}
// 处理引用计费按钮点击
function handleQuoteBilling() {
// 重新拉取计费药品数据
if (temporaryPatientInfo.value.visitId) {
// 🔧 修复 Bug #445: 在清空之前提取已提交项目的复合匹配键
// 原因:后续的 ID 匹配过滤依赖 temporaryAdvices但 temporaryAdvices 会被先清空
// 新医嘱没有 requestId/chargeItemId需用名称+规格+数量的复合键匹配
const submittedKeys = new Set(
(temporaryAdvices.value || [])
.map(a => {
const om = a.originalMedicine || {}
const name = om.medicineName || om.adviceName || a.adviceName || ''
const spec = om.specification || om.volume || ''
const qty = om.quantity ?? 0
return `${name}|||${spec}|||${qty}`
})
.filter(k => k !== '|||0')
)
temporaryMedicalLoading.value = true // 🔧 新增:开始加载
getPrescriptionList(temporaryPatientInfo.value.visitId, 6, temporaryPatientInfo.value.operCode).then((res) => {
if (res.code === 200 && res.data) {
// 🔧 修复:先清空旧数据,避免数据累积
temporaryBillingMedicines.value = []
temporaryAdvices.value = []
// 🔧 修复 Bug #445: 只保留药品类型adviceType=1过滤掉耗材(2)和诊疗项目(3/6)
// 同时过滤掉已有 requestId 的项目(已生成医嘱的不需要再次显示在"待生成"列表中)
// 先提取已签发项目(statusEnum=2)填充已生成列表
const activeItems = res.data.filter(item => {
if (item.encounterId !== temporaryPatientInfo.value.visitId) return false;
const at = Number(item.adviceType ?? item.advice_type);
if (at !== 1 && at !== 2) return false;
if (item.statusEnum !== 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;
return true;
})
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'
let usageLabel = getUsageLabel(usageCode)
if (usageCode === 'iv' && medicineName.includes('注射液')) { usageLabel = '静脉注射' }
else if (usageCode === 'po' && (medicineName.includes('片') || medicineName.includes('胶囊'))) { usageLabel = '口服' }
return {
id: index + 1, adviceName: medicineName, dosage, unit: specUnit,
usage: usageCode, usageLabel, frequency: '立即',
executeTime: '',
originalMedicine: {
...item,
medicineName: medicineName,
specification: spec,
quantity: contentData.quantity || item.quantity || 1,
encounterId: temporaryPatientInfo.value.visitId
}
}
} catch (e) {
return {
id: index + 1, adviceName: item.adviceName || item.advice_name || '',
dosage: 1, unit: 'ml', usage: 'iv', usageLabel: '静脉注射',
frequency: '立即', executeTime: '',
originalMedicine: {
...item,
medicineName: item.adviceName || item.advice_name || '',
specification: item.volume || item.specification || '',
quantity: item.quantity || 1,
encounterId: temporaryPatientInfo.value.visitId
}
}
}
})
// 再提取草稿项目(statusEnum=1)填充待生成列表
const filteredItems = res.data.filter(item => {
if (item.encounterId !== temporaryPatientInfo.value.visitId) return false;
const at = Number(item.adviceType ?? item.advice_type);
if (at !== 1 && at !== 2) return false;
if (item.statusEnum !== 1) 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;
return true;
})
// 🔧 修复限制返回数量最多显示前100条避免数据过多导致页面卡死
const maxItems = 100
if (filteredItems.length > maxItems) {
ElMessage.warning(`待签发医嘱数量过多(${filteredItems.length}条),仅显示前${maxItems}`)
filteredItems.length = maxItems
}
// 将过滤后的数据转换为临时医嘱需要的格式
temporaryBillingMedicines.value = filteredItems.map(item => {
try {
const jsonContent = item.contentJson || item.content_json;
const contentData = jsonContent ? JSON.parse(jsonContent) : {};
return {
medicineName: contentData.adviceName || contentData.advice_name || item.adviceName || item.advice_name || item.chargeName || '',
specification: contentData.volume || contentData.specification || item.volume || item.specification || '',
quantity: contentData.quantity || item.quantity || item.quantity_value || item.quantityValue || 0,
batchNumber: contentData.lotNumber || contentData.lot_number || item.lotNumber || item.lot_number || '',
unitPrice: contentData.unitPrice || contentData.unit_price || item.unitPrice || item.unit_price || 0,
subtotal: contentData.totalPrice || contentData.total_price || item.totalPrice || item.total_price ||
(contentData.unitPrice || contentData.unit_price || item.unitPrice || item.unit_price || 0) *
(contentData.quantity || item.quantity || item.quantity_value || item.quantityValue || 0),
insuranceType: (contentData.insuranceType || contentData.insurance_type) === 1 ? '医保' : (item.insuranceType === 1 || item.insurance_type === 1) ? '医保' : '自费',
orgId: contentData.orgId || item.orgId || contentData.positionId || item.positionId || userStore.orgId,
positionId: contentData.positionId || item.positionId || userStore.orgId,
definitionId: contentData.definitionId || item.definitionId,
definitionDetailId: contentData.definitionDetailId || item.definitionDetailId,
// 🔧 修复:传递 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 || '',
specification: item.specification || item.volume || '',
quantity: item.quantity || item.quantity_value || 0,
batchNumber: item.lotNumber || item.lot_number || '',
unitPrice: item.unitPrice || item.unit_price || 0,
subtotal: item.totalPrice || item.total_price ||
(item.unitPrice || item.unit_price || 0) *
(item.quantity || item.quantity_value || 0),
insuranceType: (item.insuranceType || item.insurance_type) === 1 ? '医保' : '自费',
orgId: item.orgId || item.positionId || userStore.orgId,
positionId: item.positionId || userStore.orgId,
definitionId: item.definitionId,
definitionDetailId: item.definitionDetailId,
// 🔧 修复:传递 requestId
requestId: item.requestId || null,
// 🔧 保留原始 contentJson临时医嘱签发时需要展开所有字段传给后端
contentJson: item.contentJson || item.content_json || null,
// 🔧 标记是否已签发,用于临时医嘱弹窗控制按钮状态
_signed: item.statusEnum === 2
}
}
})
temporaryMedicalLoading.value = false // 🔧 新增:加载完成
ElMessage.success('已成功引用最新计费药品信息!')
} else {
// 如果没有数据或接口调用失败,初始化空列表
temporaryBillingMedicines.value = []
temporaryAdvices.value = []
temporaryMedicalLoading.value = false // 🔧 新增:加载完成(即使失败也要关闭加载状态)
ElMessage.error('获取计费数据失败,请重试')
}
}).catch(() => {
temporaryBillingMedicines.value = []
temporaryAdvices.value = []
temporaryMedicalLoading.value = false // 🔧 新增:加载完成(即使失败也要关闭加载状态)
ElMessage.error('获取计费数据失败,请重试')
})
} else {
proxy.$modal.msgWarning('患者信息不完整,请关闭弹窗重新打开')
}
}
// 🔧 新增:根据用法编码获取对应的显示名称
function getUsageLabel(usageCode) {
if (!usageCode) return '-'
const dictItem = method_code.value?.find(item => item.value === usageCode)
return dictItem ? dictItem.label : usageCode
}
// 格式化计费弹窗中的日期
function formatChargeDate(date) {
if (!date) return '-'
return new Date(date).toLocaleString()
}
// 计算是否禁用
const disabled = computed(() => {
return Object.keys(chargePatientInfo.value).length === 0
})
// 重置表单
function resetForm() {
Object.assign(form, {
scheduleId: undefined,
applyId: undefined,
patientId: undefined,
visitId: undefined,
operCode: undefined,
operName: undefined,
preoperativeDiagnosis: undefined,
postoperativeDiagnosis: undefined,
scheduleDate: undefined,
sequenceNo: undefined,
isFirstSurgery: 0,
isAllergyMedication: 0,
allergyRemark: undefined,
surgeryNature: undefined,
surgerySite: undefined,
incisionType: undefined,
admissionTime: undefined,
entryTime: undefined,
roomCode: undefined,
tableNo: undefined,
anesMethod: undefined,
anesDoctor1Code: undefined,
anesDoctor2Code: undefined,
anesDoctor3Code: undefined,
scrubNurseCode: undefined,
circuNurse1Code: undefined,
circuNurse2Code: undefined,
scrubNurse1Code: undefined,
scrubNurse2Code: undefined,
surgeonCode: undefined,
assistant1Code: undefined,
assistant2Code: undefined,
assistant3Code: undefined,
startTime: undefined,
endTime: undefined,
anesStart: undefined,
anesEnd: undefined,
operStatus: 0,
implantFlag: 0,
implantSerial: undefined,
bloodLoss: undefined,
bloodTrans: undefined,
infectionDiagnosis: undefined,
isolationType: undefined,
patientWeight: undefined,
patientHeight: undefined,
communicationInfo: undefined,
remark: undefined,
patientName: undefined,
gender: undefined,
age: undefined,
orgName: undefined,
applyDeptName: undefined,
surgeonName: undefined,
isExternalExpert: 0,
externalExpertName: undefined,
feeType: undefined
})
if (surgeryRef.value) {
surgeryRef.value.resetFields()
}
}
// 提交表单
function submitForm() {
proxy.$refs['surgeryRef'].validate((valid) => {
if (valid) {
const submitData = {
...form,
orgId: userStore.orgId,
incisionLevel: form.incisionType
}
delete submitData.incisionType
if (!form.scheduleId) {
// 新增手术安排
addSurgerySchedule(submitData).then((res) => {
proxy.$modal.msgSuccess('新增成功')
queryParams.pageNo = 1
open.value = false
getPageList()
}).catch(() => {
proxy.$message.error('新增手术安排失败,请检查表单信息')
})
} else {
// 修改手术安排
updateSurgerySchedule(submitData).then((res) => {
proxy.$modal.msgSuccess('修改成功')
open.value = false
getPageList()
}).catch(() => {
proxy.$message.error('更新手术安排失败,请检查表单信息')
})
}
} else {
proxy.$message.error('请检查表单信息,标红字段为必填项')
}
})
}
// 取消
function cancel() {
open.value = false
isViewMode.value = false
resetForm()
}
// 刷新
function handleRefresh() {
resetForm()
}
// 查找手术申请
function handleFindApply() {
showApplyDialog.value = true
applyQueryParams.pageNo = 1
getSurgicalScheduleList()
}
// 获取手术申请列表(用于”查找”弹窗)
function getSurgicalScheduleList() {
applyLoading.value = true
const params = { ...applyQueryParams }
if (params.applyTimeRange && params.applyTimeRange.length === 2) {
params.applyTimeStart = params.applyTimeRange[0]
params.applyTimeEnd = params.applyTimeRange[1]
delete params.applyTimeRange
}
getSurgeryPage(params).then((res) => {
const responseData = res.data.data || res.data
applyList.value = responseData.records || []
applyTotal.value = responseData.total || 0
}).catch(() => {
proxy.$modal.msgError('获取手术申请列表失败,请稍后重试')
applyList.value = []
applyTotal.value = 0
})
.finally(() => {
applyLoading.value = false
})
}
const applyQueryParams = reactive({
pageNo: 1,
pageSize: 10,
applyTimeRange: undefined,
applyDeptId: undefined,
mainDoctorId: undefined,
})
// 手术申请查询
function handleApplyQuery() {
applyQueryParams.pageNo = 1
getSurgicalScheduleList()
}
// 重置手术申请查询条件
function resetApplyQuery() {
Object.assign(applyQueryParams, {
pageNo: 1,
pageSize: 10,
applyTimeRange: undefined,
applyDeptId: undefined,
mainDoctorId: undefined,
})
getSurgicalScheduleList()
}
// 取消手术申请查询
function cancelApplyDialog() {
showApplyDialog.value = false
Object.assign(applyQueryParams, {
pageNo: 1,
pageSize: 10,
applyTimeRange: undefined,
applyDeptId: undefined,
mainDoctorId: undefined,
})
applyList.value = []
applyTotal.value = 0
}
// 行点击事件处理
function handleApplyRowClick(row) {
const selectedRows = applyTableRef.value?.getSelectionRows ? applyTableRef.value.getSelectionRows() : []
// 如果已经有选中的行,先清除所有选择
if (selectedRows.length > 0) {
applyTableRef.value.clearSelection()
}
// 然后选择当前行
applyTableRef.value.toggleRowSelection(row)
}
// 表格行样式
function tableRowClassName({ row, rowIndex }) {
// 检查当前行是否被选中
const selectedRows = applyTableRef.value?.getSelectionRows ? applyTableRef.value.getSelectionRows() : []
const isSelected = selectedRows.some(selectedRow => selectedRow.surgeryNo === row.surgeryNo)
return isSelected ? 'selected-row' : ''
}
// 控制表格只能单选
function handleSelectable(row, rowIndex) {
const selectedRows = applyTableRef.value?.getSelectionRows ? applyTableRef.value.getSelectionRows() : []
// 如果还没有选中的行,或者当前行就是已经选中的行,则允许选择
return selectedRows.length === 0 || selectedRows.some(selectedRow => selectedRow.surgeryNo === row.surgeryNo)
}
// 获取手术类型名称
function getSurgeryTypeName(surgeryType) {
if (!surgeryType) return ''
const type = surgery_type.value.find(item => String(item.value) === String(surgeryType))
return type ? type.label : String(surgeryType)
}
// 获取手术等级名称
function getSurgeryLevelName(surgeryLevel) {
if (!surgeryLevel) return ''
const level = surgery_level.value.find(item => String(item.value) === String(surgeryLevel))
return level ? level.label : ''
}
// 获取麻醉方法名称
function getAnesthesiaName(anesMethod) {
if (!anesMethod) return ''
const anesthesia = anesthesiaList.value.find(item => String(item.value) === String(anesMethod))
return anesthesia ? anesthesia.label : ''
}
// 根据出生日期计算年龄
function calculateAge(birthDay) {
if (!birthDay) return ''
const birthDate = new Date(birthDay)
const today = new Date()
let age = today.getFullYear() - birthDate.getFullYear()
const monthDiff = today.getMonth() - birthDate.getMonth()
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
age--
}
return age
}
// 计算属性:格式化申请时间为年月日时分秒
const formattedApplyTime = computed(() => {
if (!form.applyTime) return ''
try {
const date = new Date(form.applyTime)
if (isNaN(date.getTime())) return form.applyTime
return parseTime(date, '{y}-{m}-{d} {h}:{i}:{s}')
} catch (error) {
return form.applyTime
}
})
// 确认手术申请
function confirmApply() {
const selectedRows = applyTableRef.value?.getSelectionRows ? applyTableRef.value.getSelectionRows() : []
if (!selectedRows || selectedRows.length === 0) {
proxy.$modal.msgWarning('请先选择一条手术申请记录')
return
}
const selectedRow = selectedRows[0]
// 填充手术申请信息到表单
form.surgeryNo = selectedRow.surgeryNo // 手术单号对应填入手术单号
form.applyId=selectedRow.applyId// 手术申请id
form.patientId = selectedRow.patientId// 患者id
form.visitId = selectedRow.encounterId // id对应填入就诊id
form.identifierNo = selectedRow.identifierNo || '' // 就诊卡号
form.operCode = selectedRow.surgeryNo // 手术单号作为手术编码
form.operName = selectedRow.descJson?.surgeryName//手术名称
form.preoperativeDiagnosis = selectedRow.preoperativeDiagnosis || selectedRow.descJson?.preoperativeDiagnosis
form.patientName = selectedRow.name// 患者姓名对应填入患者姓名
form.gender = selectedRow.gender//患者性别
form.birthDay = selectedRow.birthDay//患者出生日期
form.age = calculateAge(selectedRow.birthDay)//计算患者年龄
form.applyDeptName = selectedRow.applyDeptName//申请部门名称
form.applyDoctorName = selectedRow.applyDoctorName//申请医生
form.applyTime = selectedRow.applyTime//申请时间
form.surgeryType = selectedRow.surgeryTypeEnum//手术类型
form.surgeryNature = selectedRow.surgeryTypeEnum//手术性质
form.surgeonCode = selectedRow.mainSurgeonId//主刀医生id
form.surgeonName = selectedRow.mainSurgeonName//主刀医生姓名
form.feeType = selectedRow.feeType//费用类别
form.anesMethod = selectedRow.anesthesiaTypeEnum != null ? Number(selectedRow.anesthesiaTypeEnum) : undefined //麻醉方法
form.incisionType = selectedRow.incisionLevel != null ? Number(selectedRow.incisionLevel) : undefined //切口类型
form.surgeryLevel = selectedRow.surgeryLevel != null ? Number(selectedRow.surgeryLevel) : (selectedRow.descJson?.surgeryLevel != null ? Number(selectedRow.descJson.surgeryLevel) : undefined) //手术等级
form.surgerySite = selectedRow.descJson?.surgerySite //手术部位
form.isolationType = selectedRow.descJson?.isolationType //隔离类型
showApplyDialog.value = false
}
// 导出手术安排列表
function handleExport() {
proxy.$modal.confirm('确定要导出当前筛选条件下的手术安排列表吗?').then(() => {
download.downloadGet('/clinical-manage/surgery-schedule/export', queryParams, '手术安排列表.csv')
}).catch(() => {
// 取消导出,不做任何操作
})
}
// 获取行样式
function getRowClassName({ row, rowIndex }) {
if (selectedRow.value && row.scheduleId === selectedRow.value.scheduleId) {
return 'selected-row'
}
return ''
}
</script>
<style scoped>
/* 🔧 新增:临时医嘱加载状态样式 */
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100px 0;
color: #409eff;
font-size: 16px;
}
.loading-container .el-icon {
font-size: 32px;
margin-bottom: 16px;
}
.loading-container span {
font-weight: 500;
}
.app-container {
padding: 20px;
}
.query-form {
margin-bottom: 20px;
}
.search-buttons {
margin-left: 10px;
}
.mb8 {
margin-bottom: 8px;
}
.pagination-container {
margin-top: 20px;
text-align: right;
}
.dialog-header-buttons {
display: flex;
justify-content: flex-end;
margin-bottom: 20px;
}
.dialog-header-buttons .el-button {
margin-left: 10px;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
}
.dialog-footer .el-button {
margin-left: 10px;
}
/* 手术申请查询弹窗 — flex 布局确保分页不溢出 */
.surgery-apply-dialog :deep(.el-dialog__body) {
display: flex;
flex-direction: column;
padding-bottom: 16px;
overflow: hidden;
}
.surgery-apply-dialog :deep(.el-dialog__footer) {
padding-top: 0;
}
.surgery-apply-dialog :deep(.apply-card) {
flex: 1;
overflow: hidden;
min-height: 0;
}
.surgery-apply-dialog :deep(.apply-card .el-card__body) {
overflow-y: auto;
}
.surgery-apply-dialog :deep(.apply-pagination) {
display: flex;
justify-content: flex-end;
padding-top: 8px;
border-top: 1px solid #ebeef5;
}
.surgery-apply-dialog :deep(.apply-pagination .pagination-container) {
margin-top: 0;
}
.surgery-apply-dialog :deep(.apply-pagination .el-pagination) {
position: static;
}
/* 选中行样式 */
:deep(.el-table .selected-row) {
background-color: #ecf5ff !important;
}
:deep(.el-table .selected-row > td) {
border-bottom: 1px solid #d9ecff !important;
}
</style>
<style>
/* 手术申请查询弹窗 — 非 scoped 确保穿透 teleport */
.surgery-apply-dialog .el-dialog__body {
display: flex !important;
flex-direction: column !important;
padding-bottom: 16px !important;
overflow: hidden !important;
}
.surgery-apply-dialog .el-dialog__footer {
padding-top: 0 !important;
}
.surgery-apply-dialog .apply-card {
flex: 1 !important;
overflow: hidden !important;
min-height: 0 !important;
}
.surgery-apply-dialog .apply-card .el-card__body {
overflow-y: auto !important;
}
.surgery-apply-dialog .apply-pagination {
display: flex !important;
justify-content: flex-end !important;
padding-top: 8px !important;
border-top: 1px solid #ebeef5 !important;
}
.surgery-apply-dialog .apply-pagination .pagination-container {
margin-top: 0 !important;
}
.surgery-apply-dialog .apply-pagination .el-pagination {
position: static !important;
}
</style>