feat(surgery): 完善手术管理功能模块

- 添加手术申请相关API接口,包括根据患者ID查询就诊列表功能
- 在医生工作站界面集成手术申请功能选项卡
- 实现手术管理页面的完整功能,包括手术申请的增删改查
- 添加手术排期、开始、完成等状态流转功能
- 优化手术管理页面表格展示,增加手术类型、等级、计划时间等字段
- 实现手术申请表单的完整编辑和查看模式
- 集成患者信息和就诊记录关联功能
- 添加手术室、医生、护士等资源选择功能
- 更新系统依赖配置,添加core-common模块
- 优化图标资源和manifest配置文件
- 调整患者档案和门诊记录相关状态枚举
This commit is contained in:
2026-01-06 16:23:15 +08:00
parent fa2884b320
commit b0850257c8
66 changed files with 7683 additions and 313 deletions

View File

@@ -0,0 +1,90 @@
import request from '@/utils/request'
/**
* 查询手术室列表
* @param {Object} query - 查询参数
* @returns {Promise} 请求结果
*/
export function listOperatingRoom(query) {
return request({
url: '/base-data-manage/operating-room/list',
method: 'get',
params: query
})
}
/**
* 查询手术室详细
* @param {Long} id - 手术室ID
* @returns {Promise} 请求结果
*/
export function getOperatingRoom(id) {
return request({
url: '/base-data-manage/operating-room/' + id,
method: 'get'
})
}
/**
* 新增手术室
* @param {Object} data - 手术室信息
* @returns {Promise} 请求结果
*/
export function addOperatingRoom(data) {
return request({
url: '/base-data-manage/operating-room',
method: 'post',
data: data
})
}
/**
* 修改手术室
* @param {Object} data - 手术室信息
* @returns {Promise} 请求结果
*/
export function updateOperatingRoom(data) {
return request({
url: '/base-data-manage/operating-room',
method: 'put',
data: data
})
}
/**
* 删除手术室
* @param {Long|Array} ids - 手术室ID或ID数组
* @returns {Promise} 请求结果
*/
export function deleteOperatingRoom(ids) {
return request({
url: '/base-data-manage/operating-room/' + ids,
method: 'delete'
})
}
/**
* 启用手术室
* @param {Array} ids - 手术室ID数组
* @returns {Promise} 请求结果
*/
export function enableOperatingRoom(ids) {
return request({
url: '/base-data-manage/operating-room/enable',
method: 'put',
data: ids
})
}
/**
* 停用手术室
* @param {Array} ids - 手术室ID数组
* @returns {Promise} 请求结果
*/
export function disableOperatingRoom(ids) {
return request({
url: '/base-data-manage/operating-room/disable',
method: 'put',
data: ids
})
}

View File

@@ -78,3 +78,16 @@ export function updateSurgeryStatus(id, statusEnum) {
params: { id, statusEnum }
})
}
/**
* 根据患者ID查询就诊列表
* @param patientId 患者ID
* @returns {AxiosPromise}
*/
export function getEncounterListByPatientId(patientId) {
return request({
url: '/clinical-manage/surgery/encounter-list',
method: 'get',
params: { patientId }
})
}

View File

@@ -1,4 +1,4 @@
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path d="M512 128c-70.69 0-128 57.31-128 128 0 70.69 57.31 128 128 70.69 0 128-57.31 128-128 0-70.69-57.31-128-128-128z m0 192c-35.34 0-64-28.66-64-64 0-35.34 28.66-64 64-64 35.34 0 64 28.66 64 64 0 35.34-28.66 64-64 64z" fill="currentColor"/>
<path d="M512 128c-70.69 0-128 57.31-128 128 0 70.69 57.31 128 128 128s128-57.31 128-128c0-70.69-57.31-128-128-128z m0 192c-35.34 0-64-28.66-64-64 0-35.34 28.66-64 64-64 35.34 0 64 28.66 64 64 0 35.34-28.66 64-64 64z" fill="currentColor"/>
<path d="M832 832c0-88.36-28.7-172.6-80.4-242.4C702 521 608 480 512 480s-190 41-239.6 109.6C221 659.4 192 743.64 192 832v64h640v-64z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 489 B

After

Width:  |  Height:  |  Size: 485 B

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1656035183065" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3395"
width="200" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
width="200" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("https://at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("https://at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("https://at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
</style></defs><path d="M958.88 730.06H65.12c-18.28 0-33.12-14.82-33.12-33.12V68.91c0-18.29 14.83-33.12 33.12-33.12h893.77c18.28 0 33.12 14.82 33.12 33.12v628.03c-0.01 18.3-14.84 33.12-33.13 33.12zM98.23 663.83h827.53v-561.8H98.23v561.8z" p-id="3396"></path><path d="M512 954.55c-18.28 0-33.12-14.82-33.12-33.12V733.92c0-18.29 14.83-33.12 33.12-33.12s33.12 14.82 33.12 33.12v187.51c0 18.3-14.84 33.12-33.12 33.12z" p-id="3397"></path><path d="M762.01 988.21H261.99c-18.28 0-33.12-14.82-33.12-33.12 0-18.29 14.83-33.12 33.12-33.12h500.03c18.28 0 33.12 14.82 33.12 33.12-0.01 18.29-14.84 33.12-33.13 33.12zM514.74 578.55c-21.63 0-43.31-3.87-64.21-11.65-45.95-17.13-82.49-51.13-102.86-95.74-5.07-11.08-0.19-24.19 10.89-29.26 11.08-5.09 24.19-0.18 29.26 10.91 15.5 33.88 43.25 59.7 78.14 72.71 34.93 12.99 72.79 11.64 106.66-3.85 33.22-15.17 58.8-42.26 72.03-76.3 4.42-11.37 17.21-17.01 28.57-12.58 11.36 4.42 16.99 17.22 12.57 28.58-17.42 44.82-51.1 80.5-94.82 100.47-24.34 11.12-50.25 16.71-76.23 16.71z" p-id="3398"></path><path d="M325.27 528.78c-1.66 0-3.34-0.18-5.02-0.57-11.88-2.77-19.28-14.63-16.49-26.51l18.84-81c1.34-5.82 5-10.84 10.13-13.92 5.09-3.09 11.3-3.96 17.03-2.41l80.51 21.43c11.79 3.14 18.8 15.23 15.67 27.02-3.15 11.79-15.42 18.75-27.02 15.65l-58.49-15.57-13.69 58.81c-2.37 10.2-11.45 17.07-21.47 17.07zM360.8 351.01c-2.65 0-5.37-0.49-8-1.51-11.36-4.41-16.99-17.21-12.59-28.57 17.4-44.79 51.06-80.47 94.8-100.48 92.15-42.06 201.25-1.39 243.31 90.68 5.07 11.08 0.19 24.19-10.89 29.26-11.13 5.07-24.19 0.17-29.26-10.91-31.97-69.91-114.9-100.82-184.79-68.86-33.22 15.19-58.8 42.28-71.99 76.29-3.41 8.74-11.75 14.1-20.59 14.1z" p-id="3399"></path><path d="M684.68 376.74c-1.47 0-2.95-0.15-4.42-0.44l-81.61-16.68c-11.94-2.45-19.64-14.11-17.21-26.06 2.44-11.96 14.1-19.64 26.04-17.22l59.29 12.12 10.23-59.5c2.05-12 13.52-20.19 25.48-18.01 12.03 2.06 20.09 13.48 18.02 25.5l-14.08 81.96a22.089 22.089 0 0 1-9.29 14.49c-3.7 2.51-8.03 3.84-12.45 3.84z" p-id="3400"></path></svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -91,28 +91,7 @@ export const constantRoutes = [
{
path: '/tpr',
component: () => import('@/views/inpatientNurse/tprsheet/index.vue'),
},
// {
// path: '/patientmanagement',
// component: Layout,
// redirect: '/patientmanagement/patientmanagement',
// name: 'PatientManagement',
// meta: { title: '患者管理', icon: 'patient' },
// children: [
// {
// path: 'patientmanagement',
// component: () => import('@/views/patientmanagement/patientmanagement/index.vue'),
// name: 'PatientManagementList',
// meta: { title: '患者档案管理', icon: 'patient' },
// },
// {
// path: 'outpatienrecords',
// component: () => import('@/views/patientmanagement/outpatienrecords/index.vue'),
// name: 'OutpatientRecords',
// meta: { title: '门诊就诊记录', icon: 'record' },
// },
// ],
// },
}
];
// 动态路由 - 基于用户权限动态加载的路由
@@ -192,58 +171,19 @@ export const dynamicRoutes = [
// meta: { title: '系统管理', icon: 'system' },
// children: [
// {
// path: 'user', // 用户管理路由
// component: () => import('@/views/system/user/index.vue'),
// name: 'User',
// meta: { title: '用户管理', icon: 'user', permissions: ['system:user:list'] }
// },
// {
// path: 'role', // 角色管理路由
// component: () => import('@/views/system/role/index.vue'),
// name: 'Role',
// meta: { title: '角色管理', icon: 'role', permissions: ['system:role:list'] }
// },
// {
// path: 'menu', // 菜单管理路由
// component: () => import('@/views/system/menu/index.vue'),
// name: 'Menu',
// meta: { title: '菜单管理', icon: 'menu', permissions: ['system:menu:list'] }
// },
// {
// path: 'dept', // 部门管理路由
// component: () => import('@/views/system/dept/index.vue'),
// name: 'Dept',
// meta: { title: '部门管理', icon: 'dept', permissions: ['system:dept:list'] }
// },
// {
// path: 'post', // 岗位管理路由
// component: () => import('@/views/system/post/index.vue'),
// name: 'Post',
// meta: { title: '岗位管理', icon: 'post', permissions: ['system:post:list'] }
// },
// {
// path: 'dict', // 字典管理路由
// component: () => import('@/views/system/dict/index.vue'),
// name: 'Dict',
// meta: { title: '字典管理', icon: 'dict', permissions: ['system:dict:list'] }
// },
// {
// path: 'config', // 参数配置路由
// component: () => import('@/views/system/config/index.vue'),
// name: 'Config',
// meta: { title: '参数配置', icon: 'config', permissions: ['system:config:list'] }
// },
// {
// path: 'notice', // 通知公告路由
// component: () => import('@/views/system/notice/index.vue'),
// name: 'Notice',
// meta: { title: '通知公告', icon: 'notice', permissions: ['system:notice:list'] }
// },
// {
// path: 'tenant', // 租户管理路由
// component: () => import('@/views/system/tenant/index.vue'),
// name: 'Tenant',
// meta: { title: '租户管理', icon: 'tenant', permissions: ['system:tenant:list'] }
// path: 'basicdata',
// component: Layout,
// redirect: '/system/basicdata/location',
// name: 'BasicData',
// meta: { title: '基础数据', icon: 'location' },
// children: [
// {
// path: 'operatingroom',
// component: () => import('@/views/operatingroom/index.vue'),
// name: 'OperatingRoomManage',
// meta: { title: '手术室管理' }
// }
// ]
// }
// ]
// },

View File

@@ -0,0 +1,715 @@
<!--
* @Description: 门诊手术申请
-->
<template>
<div class="surgery-application-container">
<!-- 顶部操作栏 -->
<el-row :gutter="10" class="mb8 top-operation-bar">
<el-col :span="1.5">
<el-button type="primary" icon="Plus" @click="handleAdd" class="add-button">新增手术申请</el-button>
</el-col>
<el-col :span="1.5">
<el-button icon="Refresh" @click="handleRefresh" class="refresh-button">刷新</el-button>
</el-col>
</el-row>
<!-- 手术申请记录表格 -->
<el-table
v-loading="loading"
:data="surgeryList"
border
row-key="id"
:row-class-name="getRowClassName"
height="calc(100vh - 250px)"
style="width: 100%"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<!-- 申请日期 -->
<el-table-column label="申请日期" align="center" prop="createTime" width="180">
<template #default="scope">
{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
</template>
</el-table-column>
<!-- 手术单号 -->
<el-table-column label="手术单号" align="center" prop="surgeryNo" width="150" show-overflow-tooltip />
<!-- 患者姓名 -->
<el-table-column label="患者姓名" align="center" prop="patientName" width="100" />
<!-- 申请医生 -->
<el-table-column label="申请医生" align="center" prop="applyDoctorName" width="100" />
<!-- 申请科室 -->
<el-table-column label="申请科室" align="center" prop="applyDeptName" width="120" show-overflow-tooltip />
<!-- 手术名称 -->
<el-table-column label="手术名称" align="center" prop="surgeryName" min-width="150" show-overflow-tooltip />
<!-- 手术等级 -->
<el-table-column label="手术等级" align="center" prop="surgeryLevel_dictText" width="100" />
<!-- 状态 -->
<el-table-column label="状态" align="center" prop="statusEnum_dictText" width="100">
<template #default="scope">
<el-tag :type="getStatusType(scope.row.statusEnum)">
{{ scope.row.statusEnum_dictText }}
</el-tag>
</template>
</el-table-column>
<!-- 操作 -->
<el-table-column label="操作" align="center" width="200" 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)" v-if="scope.row.statusEnum === 0">编辑</el-button>
<!-- 删除取消手术申请作废 -->
<el-button link type="danger" @click="handleDelete(scope.row)" v-if="scope.row.statusEnum === 0 || scope.row.statusEnum === 1">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 新增或修改手术申请对话框 -->
<el-dialog
:title="title"
v-model="open"
width="900px"
@close="cancel"
append-to-body
:close-on-click-modal="false"
>
<el-form ref="surgeryRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="id" prop="id" v-show="false">
<el-input v-model="form.id" />
</el-form-item>
<!-- 患者基本信息区 -->
<el-divider content-position="left">患者基本信息</el-divider>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="手术单号" prop="surgeryNo">
<el-input v-model="form.surgeryNo" disabled placeholder="系统自动生成" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="患者姓名" prop="patientName">
<el-input v-model="form.patientName" disabled placeholder="系统自动获取" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="就诊卡号" prop="encounterNo">
<el-input v-model="form.encounterNo" disabled placeholder="系统自动获取" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="性别" prop="patientGender">
<el-select v-model="form.patientGender" disabled placeholder="系统自动获取" style="width: 100%">
<el-option label="男" value="1" />
<el-option label="女" value="2" />
<el-option label="其他" value="9" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="年龄" prop="patientAge">
<el-input-number v-model="form.patientAge" disabled placeholder="系统自动获取" style="width: 100%" />
</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="surgeryTypeEnum">
<el-select v-model="form.surgeryTypeEnum" placeholder="请选择手术类型" style="width: 100%">
<el-option label="门诊手术" :value="1" />
<el-option label="日间手术" :value="2" />
<el-option label="急诊手术" :value="3" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="手术名称" prop="surgeryName">
<el-input v-model="form.surgeryName" placeholder="请选择手术名称">
<template #append>
<el-button icon="Search" @click="selectSurgeryName" />
</template>
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="拟实施手术日期" prop="plannedTime">
<el-date-picker
v-model="form.plannedTime"
type="datetime"
placeholder="选择日期时间"
value-format="YYYY-MM-DDTHH:mm:ss"
style="width: 100%"
/>
</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="surgeryLevel">
<el-select v-model="form.surgeryLevel" placeholder="请选择手术等级" style="width: 100%">
<el-option label="一级手术" :value="1" />
<el-option label="二级手术" :value="2" />
<el-option label="三级手术" :value="3" />
<el-option label="四级手术" :value="4" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="切口类型" prop="incisionLevel">
<el-select v-model="form.incisionLevel" placeholder="请选择切口类型" style="width: 100%">
<el-option label="I类切口" :value="1" />
<el-option label="II类切口" :value="2" />
<el-option label="III类切口" :value="3" />
<el-option label="IV类切口" :value="4" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="麻醉方式" prop="anesthesiaTypeEnum">
<el-select v-model="form.anesthesiaTypeEnum" placeholder="请选择麻醉方式" style="width: 100%">
<el-option label="局麻" :value="1" />
<el-option label="全麻" :value="3" />
</el-select>
</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="applyDoctorName">
<el-input v-model="form.applyDoctorName" disabled placeholder="系统自动获取" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="主刀医生" prop="mainSurgeonId">
<el-select v-model="form.mainSurgeonId" filterable placeholder="请选择主刀医生" style="width: 100%">
<el-option v-for="item in doctorList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="手术助手" prop="assistantId">
<el-select v-model="form.assistantId" filterable clearable placeholder="请选择手术助手" style="width: 100%">
<el-option v-for="item in doctorList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="申请科室" prop="applyDeptName">
<el-input v-model="form.applyDeptName" disabled placeholder="系统自动获取" />
</el-form-item>
</el-col>
</el-row>
<!-- 其他信息区 -->
<el-divider content-position="left">其他信息</el-divider>
<el-form-item label="术前诊断" prop="preoperativeDiagnosis">
<el-input v-model="form.preoperativeDiagnosis" disabled placeholder="自动获取门诊诊断的主要诊断名称" type="textarea" :rows="3" />
</el-form-item>
<el-form-item label="手术指征" prop="surgeryIndication">
<el-input v-model="form.surgeryIndication" placeholder="请输入手术指征" type="textarea" :rows="3" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancel"> </el-button>
<el-button type="primary" @click="submitForm">提交申请</el-button>
</div>
</template>
</el-dialog>
<!-- 查看手术详情对话框 -->
<el-dialog title="手术申请详情" v-model="viewOpen" width="900px" append-to-body>
<el-descriptions :column="2" border>
<el-descriptions-item label="手术单号">{{ viewData.surgeryNo }}</el-descriptions-item>
<el-descriptions-item label="申请日期">{{ parseTime(viewData.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</el-descriptions-item>
<el-descriptions-item label="患者姓名">{{ viewData.patientName }}</el-descriptions-item>
<el-descriptions-item label="患者信息">{{ viewData.patientGender }} / {{ viewData.patientAge }}</el-descriptions-item>
<el-descriptions-item label="就诊流水号">{{ viewData.encounterNo }}</el-descriptions-item>
<el-descriptions-item label="手术状态">
<el-tag :type="getStatusType(viewData.statusEnum)">{{ viewData.statusEnum_dictText }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="手术名称">{{ viewData.surgeryName }}</el-descriptions-item>
<el-descriptions-item label="手术类型">{{ viewData.surgeryTypeEnum_dictText }}</el-descriptions-item>
<el-descriptions-item label="手术等级">{{ viewData.surgeryLevel_dictText }}</el-descriptions-item>
<el-descriptions-item label="麻醉方式">{{ viewData.anesthesiaTypeEnum_dictText }}</el-descriptions-item>
<el-descriptions-item label="计划时间">{{ viewData.plannedTime }}</el-descriptions-item>
<el-descriptions-item label="主刀医生">{{ viewData.mainSurgeonName }}</el-descriptions-item>
<el-descriptions-item label="申请医生">{{ viewData.applyDoctorName }}</el-descriptions-item>
<el-descriptions-item label="申请科室">{{ viewData.applyDeptName }}</el-descriptions-item>
<el-descriptions-item label="术前诊断" :span="2">{{ viewData.preoperativeDiagnosis }}</el-descriptions-item>
<el-descriptions-item label="手术指征" :span="2">{{ viewData.surgeryIndication }}</el-descriptions-item>
</el-descriptions>
</el-dialog>
</div>
</template>
<script setup name="SurgeryApplication">
import { getCurrentInstance, ref, computed, watch } from 'vue'
import { getSurgeryPage, addSurgery, updateSurgery, deleteSurgery, getSurgeryDetail, updateSurgeryStatus } from '@/api/surgerymanage'
import { getEncounterDiagnosis } from '../api'
import { listUser } from '@/api/system/user'
import useUserStore from '@/store/modules/user'
const { proxy } = getCurrentInstance()
const userStore = useUserStore()
const props = defineProps({
patientInfo: {
type: Object,
default: () => ({})
},
activeTab: {
type: String,
default: ''
}
})
const loading = ref(true)
const surgeryList = ref([])
const open = ref(false)
const viewOpen = ref(false)
const isEditMode = ref(false)
const form = ref({
id: undefined,
patientId: undefined,
encounterId: undefined,
encounterNo: undefined,
patientName: undefined,
patientGender: undefined,
patientAge: undefined,
applyDoctorName: undefined,
applyDeptName: undefined,
surgeryNo: undefined,
surgeryName: undefined,
surgeryTypeEnum: undefined,
surgeryLevel: undefined,
plannedTime: undefined,
mainSurgeonId: undefined,
assistantId: undefined,
anesthesiaTypeEnum: undefined,
incisionLevel: undefined,
preoperativeDiagnosis: undefined,
surgeryIndication: undefined
})
const surgeryRef = ref()
const viewData = ref({})
const title = ref('')
const doctorList = ref([])
// 字典选项
const surgeryStatusOptions = ref([
{ value: 0, label: '新开' },
{ value: 1, label: '已安排' },
{ value: 2, label: '手术中' },
{ value: 3, label: '已完成' },
{ value: 4, label: '已取消' },
{ value: 5, label: '暂停' }
])
const surgeryTypeOptions = ref([
{ value: 1, label: '门诊手术' },
{ value: 2, label: '日间手术' },
{ value: 3, label: '急诊手术' }
])
const rules = ref({
surgeryName: [{ required: true, message: '请输入手术名称', trigger: 'blur' }],
surgeryTypeEnum: [{ required: true, message: '请选择手术类型', trigger: 'change' }],
surgeryLevel: [{ required: true, message: '请选择手术等级', trigger: 'change' }],
plannedTime: [{ required: true, message: '请选择计划手术时间', trigger: 'change' }],
mainSurgeonId: [{ required: true, message: '请选择主刀医生', trigger: 'change' }],
anesthesiaTypeEnum: [{ required: true, message: '请选择麻醉方式', trigger: 'change' }]
})
// 监听患者信息变化
watch(() => props.patientInfo, (newVal) => {
if (newVal && newVal.encounterId) {
getList()
loadDiagnosisInfo()
loadDoctorList()
}
}, { immediate: true })
// 获取手术申请列表
function getList() {
if (!props.patientInfo?.encounterId) {
surgeryList.value = []
loading.value = false
return
}
loading.value = true
getSurgeryPage({
pageNo: 1,
pageSize: 100,
encounterId: props.patientInfo.encounterId
}).then((res) => {
surgeryList.value = res.data.records || []
}).catch(error => {
console.error('获取手术列表失败:', error)
proxy.$message.error('数据加载失败,请稍后重试')
surgeryList.value = []
}).finally(() => {
loading.value = false
})
}
// 加载诊断信息
function loadDiagnosisInfo() {
if (!props.patientInfo?.encounterId) return
getEncounterDiagnosis(props.patientInfo.encounterId).then((res) => {
if (res.code == 200) {
const datas = res.data || []
const mainDiagnosis = datas.find(item => item?.maindiseFlag == 1)
if (mainDiagnosis) {
form.value.preoperativeDiagnosis = mainDiagnosis.name
}
}
}).catch(error => {
console.error('获取诊断信息失败:', error)
})
}
// 加载医生列表
function loadDoctorList() {
listUser({ pageNo: 1, pageSize: 1000 }).then(res => {
if (res.code === 200) {
doctorList.value = res.data.records || []
console.log('加载医生列表成功,数量:', doctorList.value.length)
} else {
proxy.$modal.msgError('获取医生列表失败')
doctorList.value = []
}
}).catch(error => {
console.error('加载医生列表失败:', error)
proxy.$modal.msgError('获取医生列表失败')
doctorList.value = []
})
}
// 新增
function handleAdd() {
if (!props.patientInfo?.encounterId) {
proxy.$message.warning('请先选择患者')
return
}
title.value = '新增手术申请'
open.value = true
reset()
// 自动填充患者信息
form.value.patientId = props.patientInfo.patientId
form.value.encounterId = props.patientInfo.encounterId
form.value.encounterNo = props.patientInfo.busNo
form.value.patientName = props.patientInfo.patientName
form.value.patientGender = props.patientInfo.genderEnum_enumText
form.value.patientAge = props.patientInfo.age
form.value.applyDoctorName = userStore.nickName
form.value.applyDeptName = props.patientInfo.deptName || ''
// 加载诊断信息
loadDiagnosisInfo()
}
// 编辑
function handleEdit(row) {
if (row.statusEnum !== 0) {
proxy.$modal.msgWarning('当前状态不允许编辑手术,仅新开状态可编辑')
return
}
title.value = '编辑手术申请'
open.value = true
isEditMode.value = true
getSurgeryDetail(row.id).then(res => {
if (res.code === 200) {
Object.assign(form.value, res.data)
}
}).catch(error => {
console.error('获取手术信息失败:', error)
proxy.$modal.msgError('获取手术信息失败')
})
}
// 查看
function handleView(row) {
viewOpen.value = true
getSurgeryDetail(row.id).then(res => {
if (res.code === 200) {
viewData.value = res.data
}
}).catch(error => {
console.error('获取手术信息失败:', error)
proxy.$modal.msgError('获取手术信息失败')
})
}
// 删除/取消
function handleDelete(row) {
if (row.statusEnum === 0) {
// 新开状态 - 直接删除
proxy.$modal.confirm('是否确认删除手术"' + row.surgeryName + '"?').then(() => {
return deleteSurgery(row.id)
}).then(() => {
getList()
proxy.$modal.msgSuccess('删除成功')
}).catch(error => {
console.error('删除手术失败:', error)
proxy.$modal.msgError('删除失败')
})
} else if (row.statusEnum === 1) {
// 已安排状态 - 更新为已取消
proxy.$modal.confirm('是否确认取消手术"' + row.surgeryName + '"?').then(() => {
return updateSurgeryStatus(row.id, 4) // 4 = 已取消
}).then(() => {
getList()
proxy.$modal.msgSuccess('手术已取消')
}).catch(error => {
console.error('取消手术失败:', error)
proxy.$modal.msgError('取消失败')
})
} else {
// 其他状态 - 不允许操作
proxy.$modal.msgWarning('当前状态不允许取消手术')
}
}
// 刷新
function handleRefresh() {
getList()
proxy.$modal.msgSuccess('刷新成功')
}
// 选择手术名称
function selectSurgeryName() {
proxy.$modal.msgInfo('请选择手术名称')
// TODO: 实现手术名称选择弹窗
}
// 提交表单
function submitForm() {
proxy.$refs['surgeryRef'].validate((valid) => {
if (valid) {
if (form.value.id == undefined) {
// 新增手术
addSurgery(form.value).then((res) => {
proxy.$modal.msgSuccess('新增成功')
open.value = false
getList()
}).catch(error => {
console.error('新增手术失败:', error)
proxy.$message.error('新增手术失败,请检查表单信息')
})
} else {
// 修改手术
updateSurgery(form.value).then((res) => {
proxy.$modal.msgSuccess('修改成功')
open.value = false
getList()
}).catch(error => {
console.error('更新手术失败:', error)
proxy.$message.error('更新手术失败,请检查表单信息')
})
}
} else {
proxy.$message.error('请检查表单信息,标红字段为必填项')
}
})
}
// 取消
function cancel() {
open.value = false
reset()
}
// 重置表单
function reset() {
form.value = {
id: undefined,
patientId: undefined,
encounterId: undefined,
encounterNo: undefined,
patientName: undefined,
patientGender: undefined,
patientAge: undefined,
applyDoctorName: undefined,
applyDeptName: undefined,
surgeryNo: undefined,
surgeryName: undefined,
surgeryTypeEnum: undefined,
surgeryLevel: undefined,
plannedTime: undefined,
mainSurgeonId: undefined,
assistantId: undefined,
anesthesiaTypeEnum: undefined,
incisionLevel: undefined,
preoperativeDiagnosis: undefined,
surgeryIndication: undefined
}
if (surgeryRef.value) {
surgeryRef.value.resetFields()
}
}
// 获取状态标签类型
function getStatusType(status) {
const typeMap = {
0: 'info',
1: 'warning',
2: 'primary',
3: 'success',
4: 'danger',
5: 'info'
}
return typeMap[status] || 'info'
}
// 获取表格行样式
function getRowClassName({ row }) {
return row.statusEnum === 4 ? 'cancelled-row' : ''
}
// 时间格式化函数
function parseTime(time, pattern) {
if (!time) return ''
const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
let date
if (typeof time === 'object') {
date = time
} else {
if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
time = parseInt(time)
} else if (typeof time === 'string') {
time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), '')
}
if ((typeof time === 'number') && (time.toString().length === 10)) {
time = time * 1000
}
date = new Date(time)
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
}
const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
const value = formatObj[key]
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
return value.toString().padStart(2, '0')
})
return time_str
}
defineExpose({
getList
})
</script>
<style scoped lang="scss">
.surgery-application-container {
height: 100%;
width: 100%;
padding: 10px;
/* 顶部操作栏样式 */
.top-operation-bar {
height: 60px;
display: flex;
align-items: center;
margin-bottom: 16px;
.add-button {
background-color: #5b8fb9;
color: white;
border-radius: 8px;
padding: 0 20px;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(91, 143, 185, 0.3);
}
}
.refresh-button {
background-color: transparent;
border: 1px solid #dcdfe6;
color: #606266;
border-radius: 8px;
padding: 0 20px;
&:hover {
background-color: #f5f7fa;
}
}
}
/* 表格样式 */
.el-table {
::v-deep(.cancelled-row) {
background-color: #f5f5f5;
color: #999;
text-decoration: line-through;
::v-deep(.cell) {
opacity: 0.6;
}
}
}
}
/* 对话框样式 */
.el-dialog {
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}
.dialog-footer {
text-align: center;
padding: 20px 0;
}
</style>

View File

@@ -0,0 +1,633 @@
<template>
<div class="today-outpatient-patient-list">
<!-- 搜索过滤区域 -->
<div class="filter-section">
<el-form :model="queryParams" ref="queryRef" :inline="true">
<el-form-item label="搜索" prop="searchKey">
<el-input
v-model="queryParams.searchKey"
placeholder="姓名/身份证/手机号/就诊号"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="statusEnum">
<el-select
v-model="queryParams.statusEnum"
placeholder="全部状态"
clearable
style="width: 120px"
>
<el-option label="待就诊" :value="1" />
<el-option label="就诊中" :value="2" />
<el-option label="已完成" :value="3" />
<el-option label="已取消" :value="4" />
</el-select>
</el-form-item>
<el-form-item label="患者类型" prop="typeCode">
<el-select
v-model="queryParams.typeCode"
placeholder="全部类型"
clearable
style="width: 120px"
>
<el-option label="普通" :value="1" />
<el-option label="急诊" :value="2" />
<el-option label="VIP" :value="3" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
<el-button type="warning" icon="Download" @click="exportData">导出</el-button>
</el-form-item>
</el-form>
</div>
<!-- 患者列表 -->
<div class="table-section">
<el-table
:data="patientList"
border
style="width: 100%"
v-loading="loading"
:header-cell-style="{ background: '#f5f7fa', fontWeight: 'bold' }"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="patientName" label="患者" min-width="100">
<template #default="scope">
<div class="patient-name-cell">
<span class="name">{{ scope.row.patientName }}</span>
<el-tag
v-if="scope.row.importantFlag"
size="small"
type="danger"
class="important-tag"
>
重点
</el-tag>
</div>
</template>
</el-table-column>
<el-table-column prop="genderEnumEnumText" label="性别" width="80" align="center" />
<el-table-column prop="age" label="年龄" width="80" align="center" />
<el-table-column prop="phone" label="联系电话" width="120" />
<el-table-column prop="encounterBusNo" label="就诊号" width="120" align="center" />
<el-table-column prop="registerTime" label="挂号时间" width="160" sortable />
<el-table-column prop="waitingDuration" label="候诊时长" width="100" align="center">
<template #default="scope">
<span :class="getWaitingDurationClass(scope.row.waitingDuration)">
{{ scope.row.waitingDuration || 0 }} 分钟
</span>
</template>
</el-table-column>
<el-table-column prop="statusEnumEnumText" label="就诊状态" width="100" align="center">
<template #default="scope">
<el-tag
:type="getStatusTagType(scope.row.statusEnum)"
size="small"
class="status-tag"
>
{{ scope.row.statusEnumEnumText }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="scope">
<div class="action-buttons">
<el-button
v-if="scope.row.statusEnum === 1"
type="primary"
size="small"
@click="handleReceive(scope.row.encounterId)"
class="action-button"
>
<el-icon><VideoPlay /></el-icon>
接诊
</el-button>
<el-button
v-if="scope.row.statusEnum === 2"
type="success"
size="small"
@click="handleComplete(scope.row.encounterId)"
class="action-button"
>
<el-icon><CircleCheck /></el-icon>
完成
</el-button>
<el-button
v-if="scope.row.statusEnum !== 4"
type="warning"
size="small"
@click="handleCancel(scope.row.encounterId)"
class="action-button"
>
<el-icon><Close /></el-icon>
取消
</el-button>
<el-button
type="info"
size="small"
@click="handleViewDetail(scope.row)"
class="action-button"
>
<el-icon><View /></el-icon>
详情
</el-button>
</div>
</template>
</el-table-column>
</el-table>
</div>
<!-- 分页 -->
<div class="pagination-section">
<el-pagination
v-model:current-page="queryParams.pageNo"
v-model:page-size="queryParams.pageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
<!-- 批量操作 -->
<div class="batch-section" v-if="selectedPatients.length > 0">
<el-card shadow="always" class="batch-card">
<div class="batch-content">
<el-space>
<el-text>已选择 {{ selectedPatients.length }} 个患者</el-text>
<el-button-group>
<el-button
v-if="selectedPatients.some(p => p.statusEnum === 1)"
type="primary"
size="small"
@click="batchReceive"
>
<el-icon><VideoPlay /></el-icon>
批量接诊
</el-button>
<el-button
v-if="selectedPatients.some(p => p.statusEnum === 2)"
type="success"
size="small"
@click="batchComplete"
>
<el-icon><CircleCheck /></el-icon>
批量完成
</el-button>
<el-button
type="warning"
size="small"
@click="batchCancel"
>
<el-icon><Close /></el-icon>
批量取消
</el-button>
<el-button
type="info"
size="small"
@click="clearSelection"
>
<el-icon><Delete /></el-icon>
清空选择
</el-button>
</el-button-group>
</el-space>
</div>
</el-card>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, defineEmits, defineExpose } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
VideoPlay, CircleCheck, Close, View, Delete
} from '@element-plus/icons-vue'
import {
getTodayOutpatientPatients,
receivePatient,
completeVisit,
cancelVisit,
batchUpdatePatientStatus
} from './api.js'
// 数据
const patientList = ref([])
const total = ref(0)
const loading = ref(false)
const selectedPatients = ref([])
// 查询参数
const queryParams = reactive({
searchKey: '',
statusEnum: null,
typeCode: null,
importantFlag: null,
hasPrescription: null,
hasExamination: null,
hasLaboratory: null,
pageNo: 1,
pageSize: 10,
sortField: 1,
sortOrder: 2
})
// 定义事件
const emit = defineEmits(['refresh'])
// 暴露方法给父组件
defineExpose({
refreshList
})
// 页面加载
onMounted(() => {
loadPatients()
})
// 加载患者列表
const loadPatients = () => {
loading.value = true
getTodayOutpatientPatients(queryParams)
.then(res => {
if (res.code === 200) {
patientList.value = res.data?.records || []
total.value = res.data?.total || 0
}
})
.finally(() => {
loading.value = false
})
}
// 刷新列表
function refreshList() {
loadPatients()
emit('refresh')
}
// 搜索
const handleQuery = () => {
queryParams.pageNo = 1
loadPatients()
}
// 重置搜索
const resetQuery = () => {
queryParams.searchKey = ''
queryParams.statusEnum = null
queryParams.typeCode = null
queryParams.importantFlag = null
queryParams.hasPrescription = null
queryParams.hasExamination = null
queryParams.hasLaboratory = null
handleQuery()
}
// 分页大小变化
const handleSizeChange = (val) => {
queryParams.pageSize = val
loadPatients()
}
// 当前页变化
const handleCurrentChange = (val) => {
queryParams.pageNo = val
loadPatients()
}
// 选择变化
const handleSelectionChange = (val) => {
selectedPatients.value = val
}
// 清空选择
const clearSelection = () => {
selectedPatients.value = []
}
// 接诊患者
const handleReceive = (encounterId) => {
ElMessageBox.confirm('确定接诊该患者吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
receivePatient(encounterId).then(res => {
if (res.code === 200) {
ElMessage.success('接诊成功')
refreshList()
} else {
ElMessage.error(res.msg || '接诊失败')
}
})
})
}
// 完成就诊
const handleComplete = (encounterId) => {
ElMessageBox.confirm('确定完成该患者的就诊吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
completeVisit(encounterId).then(res => {
if (res.code === 200) {
ElMessage.success('就诊完成')
refreshList()
} else {
ElMessage.error(res.msg || '操作失败')
}
})
})
}
// 取消就诊
const handleCancel = (encounterId) => {
ElMessageBox.prompt('请输入取消原因', '取消就诊', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /^.{1,200}$/,
inputErrorMessage: '取消原因长度在1到200个字符之间'
}).then(({ value }) => {
cancelVisit(encounterId, value).then(res => {
if (res.code === 200) {
ElMessage.success('就诊已取消')
refreshList()
} else {
ElMessage.error(res.msg || '操作失败')
}
})
})
}
// 查看详情
const handleViewDetail = (patient) => {
// 在实际应用中,可以打开详细对话框或跳转到详情页面
ElMessage.info(`查看患者 ${patient.patientName} 的详情`)
}
// 批量接诊
const batchReceive = () => {
const waitingIds = selectedPatients.value
.filter(p => p.statusEnum === 1)
.map(p => p.encounterId)
if (waitingIds.length === 0) {
ElMessage.warning('请选择待就诊的患者')
return
}
ElMessageBox.confirm(`确定批量接诊 ${waitingIds.length} 个患者吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
batchUpdatePatientStatus(waitingIds, 2).then(res => {
if (res.code === 200) {
ElMessage.success(`成功接诊 ${waitingIds.length} 个患者`)
refreshList()
selectedPatients.value = []
} else {
ElMessage.error(res.msg || '批量接诊失败')
}
})
})
}
// 批量完成
const batchComplete = () => {
const inProgressIds = selectedPatients.value
.filter(p => p.statusEnum === 2)
.map(p => p.encounterId)
if (inProgressIds.length === 0) {
ElMessage.warning('请选择就诊中的患者')
return
}
ElMessageBox.confirm(`确定批量完成 ${inProgressIds.length} 个患者的就诊吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
batchUpdatePatientStatus(inProgressIds, 3).then(res => {
if (res.code === 200) {
ElMessage.success(`成功完成 ${inProgressIds.length} 个患者的就诊`)
refreshList()
selectedPatients.value = []
} else {
ElMessage.error(res.msg || '批量完成失败')
}
})
})
}
// 批量取消
const batchCancel = () => {
ElMessageBox.prompt('请输入批量取消的原因', '批量取消就诊', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /^.{1,200}$/,
inputErrorMessage: '取消原因长度在1到200个字符之间'
}).then(({ value }) => {
const cancelIds = selectedPatients.value
.filter(p => p.statusEnum !== 4)
.map(p => p.encounterId)
if (cancelIds.length === 0) {
ElMessage.warning('没有符合条件的患者可以取消')
return
}
batchUpdatePatientStatus(cancelIds, 4).then(res => {
if (res.code === 200) {
ElMessage.success(`成功取消 ${cancelIds.length} 个患者的就诊`)
refreshList()
selectedPatients.value = []
} else {
ElMessage.error(res.msg || '批量取消失败')
}
})
})
}
// 导出数据
const exportData = () => {
ElMessage.info('导出功能开发中...')
}
// 获取状态标签类型
const getStatusTagType = (status) => {
switch (status) {
case 1: // 待就诊
return 'warning'
case 2: // 就诊中
return 'primary'
case 3: // 已完成
return 'success'
case 4: // 已取消
return 'info'
default:
return ''
}
}
// 获取候诊时长样式
const getWaitingDurationClass = (duration) => {
if (!duration) return ''
if (duration > 60) return 'waiting-long' // 超过1小时
if (duration > 30) return 'waiting-medium' // 超过30分钟
return 'waiting-short' // 30分钟以内
}
</script>
<style lang="scss" scoped>
.today-outpatient-patient-list {
.filter-section {
margin-bottom: 20px;
padding: 16px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.05);
}
.table-section {
margin-bottom: 20px;
.patient-name-cell {
display: flex;
align-items: center;
gap: 8px;
.name {
font-weight: 500;
}
.important-tag {
font-size: 10px;
padding: 2px 6px;
}
}
.status-tag {
font-weight: bold;
}
.action-buttons {
display: flex;
flex-wrap: wrap;
gap: 4px;
.action-button {
display: flex;
align-items: center;
gap: 4px;
.el-icon {
font-size: 12px;
}
}
}
// 候诊时长颜色
.waiting-short {
color: #67c23a;
font-weight: 500;
}
.waiting-medium {
color: #e6a23c;
font-weight: 500;
}
.waiting-long {
color: #f56c6c;
font-weight: 500;
}
}
.pagination-section {
display: flex;
justify-content: flex-end;
margin-bottom: 20px;
}
.batch-section {
position: sticky;
bottom: 20px;
z-index: 1000;
.batch-card {
background: linear-gradient(135deg, #f6f9fc, #ffffff);
border: 1px solid #e4e7ed;
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
.batch-content {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
.el-text {
font-weight: bold;
color: #333;
}
.el-button-group {
gap: 8px;
.el-button {
display: flex;
align-items: center;
gap: 4px;
.el-icon {
font-size: 12px;
}
}
}
}
}
}
}
// 响应式设计
@media (max-width: 768px) {
.today-outpatient-patient-list {
.filter-section {
padding: 12px;
.el-form-item {
margin-bottom: 12px;
width: 100%;
.el-input,
.el-select {
width: 100%;
}
}
}
.table-section {
overflow-x: auto;
.el-table {
min-width: 800px;
}
}
.pagination-section {
overflow-x: auto;
}
}
}
</style>

View File

@@ -0,0 +1,303 @@
<template>
<div class="today-outpatient-stats">
<el-row :gutter="20">
<el-col :xs="12" :sm="6" :md="6" :lg="6">
<div class="stats-card total-registered">
<div class="stats-icon">
<el-icon><User /></el-icon>
</div>
<div class="stats-info">
<div class="stats-value">{{ stats.totalRegistered || 0 }}</div>
<div class="stats-label">今日总挂号</div>
</div>
</div>
</el-col>
<el-col :xs="12" :sm="6" :md="6" :lg="6">
<div class="stats-card waiting">
<div class="stats-icon">
<el-icon><Clock /></el-icon>
</div>
<div class="stats-info">
<div class="stats-value">{{ stats.waitingCount || 0 }}</div>
<div class="stats-label">待就诊</div>
</div>
</div>
</el-col>
<el-col :xs="12" :sm="6" :md="6" :lg="6">
<div class="stats-card in-progress">
<div class="stats-icon">
<el-icon><VideoPlay /></el-icon>
</div>
<div class="stats-info">
<div class="stats-value">{{ stats.inProgressCount || 0 }}</div>
<div class="stats-label">就诊中</div>
</div>
</div>
</el-col>
<el-col :xs="12" :sm="6" :md="6" :lg="6">
<div class="stats-card completed">
<div class="stats-icon">
<el-icon><CircleCheck /></el-icon>
</div>
<div class="stats-info">
<div class="stats-value">{{ stats.completedCount || 0 }}</div>
<div class="stats-label">已完成</div>
</div>
</div>
</el-col>
</el-row>
<!-- 时间统计 -->
<div class="time-stats">
<el-row :gutter="20">
<el-col :xs="12" :sm="12" :md="12" :lg="12">
<div class="time-card waiting-time">
<el-icon><Timer /></el-icon>
<div class="time-info">
<div class="time-value">{{ stats.averageWaitingTime || 0 }} 分钟</div>
<div class="time-label">平均候诊时间</div>
</div>
</div>
</el-col>
<el-col :xs="12" :sm="12" :md="12" :lg="12">
<div class="time-card visit-time">
<el-icon><Watch /></el-icon>
<div class="time-info">
<div class="time-value">{{ stats.averageVisitTime || 0 }} 分钟</div>
<div class="time-label">平均就诊时间</div>
</div>
</div>
</el-col>
</el-row>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, defineEmits, defineExpose } from 'vue'
import { User, Clock, VideoPlay, CircleCheck, Timer, Watch } from '@element-plus/icons-vue'
import { getTodayOutpatientStats } from './api.js'
// 数据
const stats = ref({})
// 定义事件
const emit = defineEmits(['refresh'])
// 暴露方法给父组件
defineExpose({
refreshStats
})
// 页面加载
onMounted(() => {
loadStats()
})
// 加载统计信息
const loadStats = () => {
getTodayOutpatientStats().then(res => {
if (res.code === 200) {
stats.value = res.data || {}
}
})
}
// 刷新统计信息
function refreshStats() {
loadStats()
emit('refresh')
}
</script>
<style lang="scss" scoped>
.today-outpatient-stats {
.stats-card {
background: white;
border-radius: 8px;
padding: 20px;
display: flex;
align-items: center;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.15);
}
.stats-icon {
width: 48px;
height: 48px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
.el-icon {
color: white;
font-size: 24px;
}
}
.stats-info {
flex: 1;
.stats-value {
font-size: 24px;
font-weight: bold;
color: #333;
line-height: 1.2;
}
.stats-label {
font-size: 14px;
color: #666;
margin-top: 4px;
}
}
// 不同统计卡片的颜色
&.total-registered {
.stats-icon {
background: linear-gradient(135deg, #409eff, #79bbff);
}
}
&.waiting {
.stats-icon {
background: linear-gradient(135deg, #e6a23c, #fab85c);
}
}
&.in-progress {
.stats-icon {
background: linear-gradient(135deg, #67c23a, #95d475);
}
}
&.completed {
.stats-icon {
background: linear-gradient(135deg, #909399, #b1b3b8);
}
}
}
.time-stats {
margin-top: 20px;
.time-card {
background: white;
border-radius: 8px;
padding: 20px;
display: flex;
align-items: center;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.15);
}
.el-icon {
font-size: 32px;
margin-right: 16px;
color: #409eff;
}
.time-info {
.time-value {
font-size: 20px;
font-weight: bold;
color: #333;
line-height: 1.2;
}
.time-label {
font-size: 14px;
color: #666;
margin-top: 4px;
}
}
// 不同时间统计卡片的颜色
&.waiting-time {
.el-icon {
color: #e6a23c;
}
}
&.visit-time {
.el-icon {
color: #67c23a;
}
}
}
}
// 响应式间距
.el-row {
margin-bottom: 0;
}
.el-col {
margin-bottom: 20px;
}
}
// 响应式设计
@media (max-width: 768px) {
.today-outpatient-stats {
.stats-card {
padding: 16px;
.stats-icon {
width: 40px;
height: 40px;
margin-right: 10px;
.el-icon {
font-size: 20px;
}
}
.stats-info {
.stats-value {
font-size: 20px;
}
.stats-label {
font-size: 12px;
}
}
}
.time-stats {
.time-card {
padding: 16px;
.el-icon {
font-size: 24px;
margin-right: 12px;
}
.time-info {
.time-value {
font-size: 18px;
}
.time-label {
font-size: 12px;
}
}
}
}
}
}
</style>

View File

@@ -0,0 +1,95 @@
import request from '@/utils/request'
// 获取今日门诊统计信息
export function getTodayOutpatientStats() {
return request({
url: '/today-outpatient/stats',
method: 'get'
})
}
// 分页查询今日门诊患者列表
export function getTodayOutpatientPatients(queryParams) {
return request({
url: '/today-outpatient/patients',
method: 'get',
params: queryParams
})
}
// 获取今日待就诊患者队列
export function getWaitingPatients() {
return request({
url: '/today-outpatient/patients/waiting',
method: 'get'
})
}
// 获取今日就诊中患者列表
export function getInProgressPatients() {
return request({
url: '/today-outpatient/patients/in-progress',
method: 'get'
})
}
// 获取今日已完成就诊患者列表
export function getCompletedPatients() {
return request({
url: '/today-outpatient/patients/completed',
method: 'get'
})
}
// 获取患者就诊详情
export function getPatientDetail(encounterId) {
return request({
url: `/today-outpatient/patients/${encounterId}`,
method: 'get'
})
}
// 批量更新患者状态
export function batchUpdatePatientStatus(encounterIds, targetStatus) {
return request({
url: '/today-outpatient/patients/batch-update-status',
method: 'post',
params: {
encounterIds: encounterIds.join(','),
targetStatus
}
})
}
// 接诊患者
export function receivePatient(encounterId) {
return request({
url: `/today-outpatient/patients/${encounterId}/receive`,
method: 'post'
})
}
// 完成就诊
export function completeVisit(encounterId) {
return request({
url: `/today-outpatient/patients/${encounterId}/complete`,
method: 'post'
})
}
// 取消就诊
export function cancelVisit(encounterId, reason) {
return request({
url: `/today-outpatient/patients/${encounterId}/cancel`,
method: 'post',
params: { reason }
})
}
// 快速接诊
export function quickReceivePatient(encounterId) {
return request({
url: `/today-outpatient/quick-receive/${encounterId}`,
method: 'post'
})
}

View File

@@ -0,0 +1,753 @@
<template>
<div class="today-outpatient-container">
<!-- 统计卡片区域 -->
<div class="stats-cards">
<el-row :gutter="20">
<el-col :span="6">
<el-card class="stats-card" shadow="hover">
<div class="stats-content">
<div class="stats-icon" style="background-color: #409eff;">
<el-icon><User /></el-icon>
</div>
<div class="stats-info">
<div class="stats-label">今日总挂号</div>
<div class="stats-value">{{ stats.totalRegistered || 0 }}</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="stats-card" shadow="hover">
<div class="stats-content">
<div class="stats-icon" style="background-color: #e6a23c;">
<el-icon><Clock /></el-icon>
</div>
<div class="stats-info">
<div class="stats-label">待就诊</div>
<div class="stats-value">{{ stats.waitingCount || 0 }}</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="stats-card" shadow="hover">
<div class="stats-content">
<div class="stats-icon" style="background-color: #67c23a;">
<el-icon><VideoPlay /></el-icon>
</div>
<div class="stats-info">
<div class="stats-label">就诊中</div>
<div class="stats-value">{{ stats.inProgressCount || 0 }}</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="stats-card" shadow="hover">
<div class="stats-content">
<div class="stats-icon" style="background-color: #909399;">
<el-icon><CircleCheck /></el-icon>
</div>
<div class="stats-info">
<div class="stats-label">已完成</div>
<div class="stats-value">{{ stats.completedCount || 0 }}</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 时间统计 -->
<el-row :gutter="20" class="time-stats">
<el-col :span="12">
<el-card class="time-card" shadow="hover">
<div class="time-content">
<el-icon class="time-icon"><Timer /></el-icon>
<div class="time-info">
<div class="time-label">平均候诊时间</div>
<div class="time-value">{{ stats.averageWaitingTime || 0 }} 分钟</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="12">
<el-card class="time-card" shadow="hover">
<div class="time-content">
<el-icon class="time-icon"><Watch /></el-icon>
<div class="time-info">
<div class="time-label">平均就诊时间</div>
<div class="time-value">{{ stats.averageVisitTime || 0 }} 分钟</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
<!-- 搜索和过滤区域 -->
<div class="search-filter">
<el-form :model="queryParams" ref="queryRef" :inline="true">
<el-form-item label="搜索" prop="searchKey">
<el-input
v-model="queryParams.searchKey"
placeholder="姓名/身份证/手机号/就诊号"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="statusEnum">
<el-select
v-model="queryParams.statusEnum"
placeholder="全部状态"
clearable
style="width: 120px"
>
<el-option label="待就诊" :value="1" />
<el-option label="就诊中" :value="2" />
<el-option label="已完成" :value="3" />
<el-option label="已取消" :value="4" />
</el-select>
</el-form-item>
<el-form-item label="患者类型" prop="typeCode">
<el-select
v-model="queryParams.typeCode"
placeholder="全部类型"
clearable
style="width: 120px"
>
<el-option label="普通" :value="1" />
<el-option label="急诊" :value="2" />
<el-option label="VIP" :value="3" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
<!-- 患者列表 -->
<div class="patient-list">
<el-table
:data="patientList"
border
style="width: 100%"
v-loading="loading"
:header-cell-style="{ background: '#f5f7fa', fontWeight: 'bold' }"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="patientName" label="患者" min-width="100" />
<el-table-column prop="genderEnumEnumText" label="性别" width="80" align="center" />
<el-table-column prop="age" label="年龄" width="80" align="center" />
<el-table-column prop="phone" label="联系电话" width="120" />
<el-table-column prop="encounterBusNo" label="就诊号" width="120" align="center" />
<el-table-column prop="registerTime" label="挂号时间" width="160" sortable />
<el-table-column prop="waitingDuration" label="候诊时长" width="100" align="center">
<template #default="scope">
{{ scope.row.waitingDuration || 0 }} 分钟
</template>
</el-table-column>
<el-table-column prop="statusEnumEnumText" label="就诊状态" width="100" align="center">
<template #default="scope">
<el-tag
:type="getStatusTagType(scope.row.statusEnum)"
size="small"
>
{{ scope.row.statusEnumEnumText }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="subjectStatusEnumEnumText" label="就诊对象状态" width="120" align="center" />
<el-table-column label="操作" width="180" fixed="right">
<template #default="scope">
<el-button
v-if="scope.row.statusEnum === 1"
type="primary"
size="small"
@click="handleReceive(scope.row.encounterId)"
>
接诊
</el-button>
<el-button
v-if="scope.row.statusEnum === 2"
type="success"
size="small"
@click="handleComplete(scope.row.encounterId)"
>
完成
</el-button>
<el-button
v-if="scope.row.statusEnum !== 4"
type="warning"
size="small"
@click="handleCancel(scope.row.encounterId)"
>
取消
</el-button>
<el-button
type="info"
size="small"
@click="handleViewDetail(scope.row.encounterId)"
>
详情
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-wrapper">
<el-pagination
v-model:current-page="queryParams.pageNo"
v-model:page-size="queryParams.pageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
<!-- 批量操作 -->
<div class="batch-actions" v-if="selectedPatients.length > 0">
<el-space>
<el-text>已选择 {{ selectedPatients.length }} 个患者</el-text>
<el-button
v-if="selectedPatients.some(p => p.statusEnum === 1)"
type="primary"
size="small"
@click="batchReceive"
>
批量接诊
</el-button>
<el-button
v-if="selectedPatients.some(p => p.statusEnum === 2)"
type="success"
size="small"
@click="batchComplete"
>
批量完成
</el-button>
<el-button
type="warning"
size="small"
@click="batchCancel"
>
批量取消
</el-button>
</el-space>
</div>
<!-- 患者详情对话框 -->
<el-dialog
v-model="detailDialogVisible"
title="患者就诊详情"
width="800px"
:before-close="handleDetailDialogClose"
>
<div v-loading="detailLoading">
<el-descriptions v-if="patientDetail" :column="2" border>
<el-descriptions-item label="患者姓名">{{ patientDetail.patientName }}</el-descriptions-item>
<el-descriptions-item label="性别">{{ patientDetail.genderEnumEnumText }}</el-descriptions-item>
<el-descriptions-item label="年龄">{{ patientDetail.age }}</el-descriptions-item>
<el-descriptions-item label="身份证号">{{ patientDetail.idCard }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ patientDetail.phone }}</el-descriptions-item>
<el-descriptions-item label="就诊号">{{ patientDetail.encounterBusNo }}</el-descriptions-item>
<el-descriptions-item label="挂号时间">{{ patientDetail.registerTime }}</el-descriptions-item>
<el-descriptions-item label="接诊时间">{{ patientDetail.receptionTime || '未接诊' }}</el-descriptions-item>
<el-descriptions-item label="就诊状态">
<el-tag :type="getStatusTagType(patientDetail.statusEnum)" size="small">
{{ patientDetail.statusEnumEnumText }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="就诊对象状态">{{ patientDetail.subjectStatusEnumEnumText }}</el-descriptions-item>
<el-descriptions-item label="候诊时长">{{ patientDetail.waitingDuration || 0 }} 分钟</el-descriptions-item>
<el-descriptions-item label="就诊时长">{{ patientDetail.visitDuration || 0 }} 分钟</el-descriptions-item>
<el-descriptions-item label="是否重点患者">
<el-tag :type="patientDetail.importantFlag ? 'danger' : 'info'" size="small">
{{ patientDetail.importantFlag ? '是' : '否' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="是否已开药">
<el-tag :type="patientDetail.hasPrescription ? 'success' : 'info'" size="small">
{{ patientDetail.hasPrescription ? '是' : '否' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="是否已检查">
<el-tag :type="patientDetail.hasExamination ? 'success' : 'info'" size="small">
{{ patientDetail.hasExamination ? '是' : '否' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="是否已检验">
<el-tag :type="patientDetail.hasLaboratory ? 'success' : 'info'" size="small">
{{ patientDetail.hasLaboratory ? '是' : '否' }}
</el-tag>
</el-descriptions-item>
</el-descriptions>
<el-empty v-else description="暂无患者详情" />
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="detailDialogVisible = false">关闭</el-button>
<el-button
v-if="patientDetail && patientDetail.statusEnum === 1"
type="primary"
@click="handleReceive(patientDetail.encounterId)"
>
接诊患者
</el-button>
<el-button
v-if="patientDetail && patientDetail.statusEnum === 2"
type="success"
@click="handleComplete(patientDetail.encounterId)"
>
完成就诊
</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
User, Clock, VideoPlay, CircleCheck, Timer, Watch
} from '@element-plus/icons-vue'
import {
getTodayOutpatientStats,
getTodayOutpatientPatients,
receivePatient,
completeVisit,
cancelVisit,
batchUpdatePatientStatus
} from './api.js'
// 数据
const stats = ref({})
const patientList = ref([])
const total = ref(0)
const loading = ref(false)
const detailLoading = ref(false)
const selectedPatients = ref([])
const patientDetail = ref(null)
// 对话框控制
const detailDialogVisible = ref(false)
// 查询参数
const queryParams = reactive({
searchKey: '',
statusEnum: null,
typeCode: null,
importantFlag: null,
hasPrescription: null,
hasExamination: null,
hasLaboratory: null,
doctorId: null,
departmentId: null,
queryDate: null,
sortField: 1,
sortOrder: 2,
pageNo: 1,
pageSize: 10
})
// 页面加载
onMounted(() => {
loadStats()
loadPatients()
})
// 加载统计信息
const loadStats = () => {
getTodayOutpatientStats().then(res => {
if (res.code === 200) {
stats.value = res.data || {}
}
})
}
// 加载患者列表
const loadPatients = () => {
loading.value = true
getTodayOutpatientPatients(queryParams)
.then(res => {
if (res.code === 200) {
patientList.value = res.data?.records || []
total.value = res.data?.total || 0
}
})
.finally(() => {
loading.value = false
})
}
// 搜索
const handleQuery = () => {
queryParams.pageNo = 1
loadPatients()
}
// 重置搜索
const resetQuery = () => {
queryParams.searchKey = ''
queryParams.statusEnum = null
queryParams.typeCode = null
queryParams.importantFlag = null
queryParams.hasPrescription = null
queryParams.hasExamination = null
queryParams.hasLaboratory = null
queryParams.sortField = 1
queryParams.sortOrder = 2
handleQuery()
}
// 分页大小变化
const handleSizeChange = (val) => {
queryParams.pageSize = val
loadPatients()
}
// 当前页变化
const handleCurrentChange = (val) => {
queryParams.pageNo = val
loadPatients()
}
// 选择变化
const handleSelectionChange = (val) => {
selectedPatients.value = val
}
// 接诊患者
const handleReceive = (encounterId) => {
ElMessageBox.confirm('确定接诊该患者吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
receivePatient(encounterId).then(res => {
if (res.code === 200) {
ElMessage.success('接诊成功')
loadStats()
loadPatients()
if (detailDialogVisible.value) {
handleViewDetail(encounterId)
}
} else {
ElMessage.error(res.msg || '接诊失败')
}
})
})
}
// 完成就诊
const handleComplete = (encounterId) => {
ElMessageBox.confirm('确定完成该患者的就诊吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
completeVisit(encounterId).then(res => {
if (res.code === 200) {
ElMessage.success('就诊完成')
loadStats()
loadPatients()
if (detailDialogVisible.value) {
handleViewDetail(encounterId)
}
} else {
ElMessage.error(res.msg || '操作失败')
}
})
})
}
// 取消就诊
const handleCancel = (encounterId) => {
ElMessageBox.prompt('请输入取消原因', '取消就诊', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /^.{1,200}$/,
inputErrorMessage: '取消原因长度在1到200个字符之间'
}).then(({ value }) => {
cancelVisit(encounterId, value).then(res => {
if (res.code === 200) {
ElMessage.success('就诊已取消')
loadStats()
loadPatients()
if (detailDialogVisible.value) {
handleViewDetail(encounterId)
}
} else {
ElMessage.error(res.msg || '操作失败')
}
})
})
}
// 查看详情
const handleViewDetail = (encounterId) => {
detailDialogVisible.value = true
detailLoading.value = true
// 模拟获取详情数据实际应该调用API
setTimeout(() => {
const patient = patientList.value.find(p => p.encounterId === encounterId)
if (patient) {
patientDetail.value = { ...patient }
}
detailLoading.value = false
}, 500)
}
// 关闭详情对话框
const handleDetailDialogClose = (done) => {
patientDetail.value = null
done()
}
// 批量接诊
const batchReceive = () => {
const waitingIds = selectedPatients.value
.filter(p => p.statusEnum === 1)
.map(p => p.encounterId)
if (waitingIds.length === 0) {
ElMessage.warning('请选择待就诊的患者')
return
}
ElMessageBox.confirm(`确定批量接诊 ${waitingIds.length} 个患者吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
batchUpdatePatientStatus(waitingIds, 2).then(res => {
if (res.code === 200) {
ElMessage.success(`成功接诊 ${waitingIds.length} 个患者`)
loadStats()
loadPatients()
selectedPatients.value = []
} else {
ElMessage.error(res.msg || '批量接诊失败')
}
})
})
}
// 批量完成
const batchComplete = () => {
const inProgressIds = selectedPatients.value
.filter(p => p.statusEnum === 2)
.map(p => p.encounterId)
if (inProgressIds.length === 0) {
ElMessage.warning('请选择就诊中的患者')
return
}
ElMessageBox.confirm(`确定批量完成 ${inProgressIds.length} 个患者的就诊吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
batchUpdatePatientStatus(inProgressIds, 3).then(res => {
if (res.code === 200) {
ElMessage.success(`成功完成 ${inProgressIds.length} 个患者的就诊`)
loadStats()
loadPatients()
selectedPatients.value = []
} else {
ElMessage.error(res.msg || '批量完成失败')
}
})
})
}
// 批量取消
const batchCancel = () => {
ElMessageBox.prompt('请输入批量取消的原因', '批量取消就诊', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /^.{1,200}$/,
inputErrorMessage: '取消原因长度在1到200个字符之间'
}).then(({ value }) => {
const cancelIds = selectedPatients.value
.filter(p => p.statusEnum !== 4)
.map(p => p.encounterId)
if (cancelIds.length === 0) {
ElMessage.warning('没有符合条件的患者可以取消')
return
}
batchUpdatePatientStatus(cancelIds, 4).then(res => {
if (res.code === 200) {
ElMessage.success(`成功取消 ${cancelIds.length} 个患者的就诊`)
loadStats()
loadPatients()
selectedPatients.value = []
} else {
ElMessage.error(res.msg || '批量取消失败')
}
})
})
}
// 获取状态标签类型
const getStatusTagType = (status) => {
switch (status) {
case 1: // 待就诊
return 'warning'
case 2: // 就诊中
return 'primary'
case 3: // 已完成
return 'success'
case 4: // 已取消
return 'info'
default:
return ''
}
}
</script>
<style lang="scss" scoped>
.today-outpatient-container {
padding: 20px;
background-color: #f5f7fa;
min-height: 100vh;
.stats-cards {
margin-bottom: 20px;
.stats-card {
border-radius: 8px;
.stats-content {
display: flex;
align-items: center;
.stats-icon {
width: 48px;
height: 48px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
.el-icon {
color: white;
font-size: 24px;
}
}
.stats-info {
.stats-label {
font-size: 14px;
color: #666;
margin-bottom: 4px;
}
.stats-value {
font-size: 24px;
font-weight: bold;
color: #333;
}
}
}
}
.time-stats {
margin-top: 20px;
.time-card {
border-radius: 8px;
.time-content {
display: flex;
align-items: center;
.time-icon {
font-size: 32px;
color: #409eff;
margin-right: 12px;
}
.time-info {
.time-label {
font-size: 14px;
color: #666;
margin-bottom: 4px;
}
.time-value {
font-size: 20px;
font-weight: bold;
color: #333;
}
}
}
}
}
}
.search-filter {
background: white;
padding: 16px;
border-radius: 8px;
margin-bottom: 20px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.patient-list {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
.pagination-wrapper {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
}
.batch-actions {
position: fixed;
bottom: 20px;
right: 20px;
background: white;
padding: 12px 16px;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.2);
z-index: 1000;
}
}
// 响应式设计
@media (max-width: 768px) {
.today-outpatient-container {
padding: 10px;
.stats-cards {
.el-col {
margin-bottom: 10px;
}
}
.search-filter {
.el-form-item {
margin-bottom: 10px;
}
}
}
}
</style>

View File

@@ -164,6 +164,9 @@
<el-tab-pane label="检验" name="inspection">
<inspectionApplication :patientInfo="patientInfo" :activeTab="activeTab" ref="inspectionRef" />
</el-tab-pane>
<el-tab-pane label="手术申请" name="surgery">
<surgeryApplication :patientInfo="patientInfo" :activeTab="activeTab" ref="surgeryRef" />
</el-tab-pane>
<el-tab-pane label="电子处方" name="eprescription">
<eprescriptionlist :patientInfo="patientInfo" ref="eprescriptionRef" />
</el-tab-pane>
@@ -217,6 +220,7 @@ import eprescriptionlist from './components/eprescriptionlist.vue';
import HospitalizationDialog from './components/hospitalizationDialog.vue';
import tcmAdvice from './components/tcm/tcmAdvice.vue';
import inspectionApplication from './components/inspection/inspectionApplication.vue';
import surgeryApplication from './components/surgery/surgeryApplication.vue';
import {formatDate, formatDateStr} from '@/utils/index';
import useUserStore from '@/store/modules/user';
import {nextTick} from 'vue';
@@ -266,6 +270,7 @@ const patientDrawerRef = ref();
const prescriptionRef = ref();
const tcmRef = ref();
const inspectionRef = ref();
const surgeryRef = ref();
const emrRef = ref();
const diagnosisRef = ref();
const waitCount = ref(0);
@@ -402,6 +407,9 @@ function handleClick(tab) {
case 'inspection':
// 检验tab点击处理逻辑可以在这里添加
break;
case 'surgery':
surgeryRef.value.getList();
break;
case 'eprescription':
eprescriptionRef.value.getList();
break;
@@ -460,6 +468,7 @@ function handleCardClick(item, index) {
prescriptionRef.value.getListInfo();
tcmRef.value.getListInfo();
inspectionRef.value.getList();
surgeryRef.value.getList();
diagnosisRef.value.getList();
eprescriptionRef.value.getList();
// emrRef.value.getDetail(item.encounterId);

View File

@@ -0,0 +1,372 @@
<template>
<div :class="['today-outpatient-page', { 'fullscreen-mode': isFullscreen }]">
<!-- 页面头部工具栏 -->
<div class="page-header-toolbar">
<el-row :gutter="10">
<el-col :span="18">
<div class="toolbar-left">
<h2 style="margin: 0; line-height: 32px;">
<i class="el-icon-calendar" style="margin-right: 8px;"></i>
今日门诊
<span v-if="isFullscreen" style="margin-left: 12px; font-size: 14px; color: #666;">
(全屏模式)
</span>
</h2>
</div>
</el-col>
<el-col :span="6">
<div class="toolbar-right">
<el-button-group>
<el-tooltip :content="isFullscreen ? '退出全屏模式' : '进入全屏模式'" placement="top">
<el-button
:type="isFullscreen ? 'warning' : 'primary'"
:icon="isFullscreen ? 'CloseBold' : 'FullScreen'"
@click="toggleFullscreen"
:loading="refreshing"
>
{{ isFullscreen ? '退出全屏' : '全屏' }}
</el-button>
</el-tooltip>
<el-tooltip content="刷新数据" placement="top">
<el-button
type="primary"
icon="Refresh"
@click="refreshData"
:loading="refreshing"
>
刷新
</el-button>
</el-tooltip>
<el-tooltip content="页面设置" placement="top">
<el-button
type="info"
icon="Setting"
@click="showSettings = true"
>
设置
</el-button>
</el-tooltip>
</el-button-group>
</div>
</el-col>
</el-row>
</div>
<!-- 内容区域 -->
<div class="content-area">
<!-- 统计卡片 -->
<div class="stats-section">
<today-outpatient-stats />
</div>
<!-- 患者列表 -->
<div class="patient-list-section">
<today-outpatient-patient-list />
</div>
</div>
<!-- 设置对话框 -->
<el-dialog
v-model="showSettings"
title="今日门诊设置"
width="500px"
>
<el-form :model="settingsForm" label-width="100px">
<el-form-item label="默认显示">
<el-select v-model="settingsForm.defaultView" placeholder="请选择默认视图">
<el-option label="待就诊" value="waiting" />
<el-option label="就诊中" value="inProgress" />
<el-option label="已完成" value="completed" />
<el-option label="全部" value="all" />
</el-select>
</el-form-item>
<el-form-item label="每页显示">
<el-input-number
v-model="settingsForm.pageSize"
:min="5"
:max="100"
:step="5"
/>
</el-form-item>
<el-form-item label="自动刷新">
<el-switch v-model="settingsForm.autoRefresh" />
<div style="margin-top: 8px; font-size: 12px; color: #999;">
启用后每30秒自动刷新数据
</div>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="showSettings = false">取消</el-button>
<el-button type="primary" @click="saveSettings">保存</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, computed } from 'vue'
import { ElMessage } from 'element-plus'
import TodayOutpatientStats from './components/todayOutpatient/TodayOutpatientStats.vue'
import TodayOutpatientPatientList from './components/todayOutpatient/TodayOutpatientPatientList.vue'
// 数据
const showSettings = ref(false)
const refreshTimer = ref(null)
const refreshing = ref(false)
const isFullscreen = ref(false)
const settingsForm = ref({
defaultView: 'waiting',
pageSize: 10,
autoRefresh: false
})
// 页面加载
onMounted(() => {
loadSettings()
})
// 页面卸载
onUnmounted(() => {
if (refreshTimer.value) {
clearInterval(refreshTimer.value)
}
})
// 加载设置
const loadSettings = () => {
const savedSettings = localStorage.getItem('todayOutpatientSettings')
if (savedSettings) {
settingsForm.value = JSON.parse(savedSettings)
}
// 启动自动刷新
if (settingsForm.value.autoRefresh) {
startAutoRefresh()
}
}
// 保存设置
const saveSettings = () => {
localStorage.setItem('todayOutpatientSettings', JSON.stringify(settingsForm.value))
// 更新自动刷新
if (settingsForm.value.autoRefresh) {
startAutoRefresh()
} else {
stopAutoRefresh()
}
ElMessage.success('设置保存成功')
showSettings.value = false
}
// 开始自动刷新
const startAutoRefresh = () => {
stopAutoRefresh() // 先停止现有的定时器
refreshTimer.value = setInterval(() => {
refreshData()
}, 30000) // 30秒刷新一次
}
// 停止自动刷新
const stopAutoRefresh = () => {
if (refreshTimer.value) {
clearInterval(refreshTimer.value)
refreshTimer.value = null
}
}
// 刷新数据
const refreshData = () => {
refreshing.value = true
// 触发子组件的刷新方法
// 在实际应用中可以通过事件总线或provide/inject传递刷新方法
setTimeout(() => {
refreshing.value = false
ElMessage.success('数据已刷新')
}, 500)
}
// 全屏切换功能
const toggleFullscreen = () => {
if (!isFullscreen.value) {
// 进入全屏模式
const elem = document.documentElement
if (elem.requestFullscreen) {
elem.requestFullscreen()
} else if (elem.webkitRequestFullscreen) { /* Safari */
elem.webkitRequestFullscreen()
} else if (elem.msRequestFullscreen) { /* IE11 */
elem.msRequestFullscreen()
}
isFullscreen.value = true
ElMessage.success('已进入全屏模式')
} else {
// 退出全屏模式
if (document.exitFullscreen) {
document.exitFullscreen()
} else if (document.webkitExitFullscreen) { /* Safari */
document.webkitExitFullscreen()
} else if (document.msExitFullscreen) { /* IE11 */
document.msExitFullscreen()
}
isFullscreen.value = false
ElMessage.success('已退出全屏模式')
}
}
// 监听全屏状态变化
const handleFullscreenChange = () => {
isFullscreen.value = !!(
document.fullscreenElement ||
document.webkitFullscreenElement ||
document.msFullscreenElement
)
}
// 监听键盘事件ESC退出全屏
const handleKeyDown = (e) => {
if (e.key === 'Escape' && isFullscreen.value) {
toggleFullscreen()
}
}
// 页面加载时添加监听器
onMounted(() => {
loadSettings()
document.addEventListener('fullscreenchange', handleFullscreenChange)
document.addEventListener('webkitfullscreenchange', handleFullscreenChange)
document.addEventListener('msfullscreenchange', handleFullscreenChange)
document.addEventListener('keydown', handleKeyDown)
})
// 页面卸载时移除监听器
onUnmounted(() => {
if (refreshTimer.value) {
clearInterval(refreshTimer.value)
}
document.removeEventListener('fullscreenchange', handleFullscreenChange)
document.removeEventListener('webkitfullscreenchange', handleFullscreenChange)
document.removeEventListener('msfullscreenchange', handleFullscreenChange)
document.removeEventListener('keydown', handleKeyDown)
})
</script>
<style lang="scss" scoped>
.today-outpatient-page {
height: 100%;
display: flex;
flex-direction: column;
background-color: #f5f7fa;
// 全屏模式样式
&.fullscreen-mode {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
background: white;
padding: 0 !important;
.page-header-toolbar {
background: #fff !important;
border-bottom: 1px solid #dcdfe6 !important;
}
}
.page-header-toolbar {
background: #f5f7fa;
padding: 16px 20px;
border-bottom: 1px solid #ebeef5;
margin-bottom: 20px;
flex-shrink: 0;
.toolbar-left {
display: flex;
align-items: center;
h2 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #333;
.el-icon-calendar {
color: #409eff;
}
}
}
.toolbar-right {
display: flex;
justify-content: flex-end;
align-items: center;
.el-button-group {
display: flex;
gap: 8px;
}
}
}
.content-area {
flex: 1;
overflow: hidden;
padding: 0 20px 20px 20px;
display: flex;
flex-direction: column;
}
.stats-section {
margin-bottom: 20px;
flex-shrink: 0;
}
.patient-list-section {
flex: 1;
min-height: 0;
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
overflow: auto;
}
}
// 全屏模式下的特定样式
:fullscreen .today-outpatient-page,
:-webkit-full-screen .today-outpatient-page,
:-moz-full-screen .today-outpatient-page,
:-ms-fullscreen .today-outpatient-page {
&.fullscreen-mode {
.content-area {
padding: 0 20px;
}
}
}
// 响应式设计
@media (max-width: 768px) {
.today-outpatient-page {
.page-header-toolbar {
padding: 12px;
.toolbar-left h2 {
font-size: 16px;
}
}
.content-area {
padding: 0 12px 12px 12px;
}
.patient-list-section {
padding: 12px;
}
}
}
</style>

View File

@@ -409,9 +409,12 @@ const handleStatClick = (stat) => {
} else if (stat.key === 'todayRevenue' || stat.key === 'todayPayments') {
// 跳转到收费页面
router.push('/charge/cliniccharge')
} else if (stat.key === 'appointments' || stat.key === 'todayAppointments') {
} else if (stat.key === 'appointments') {
// 跳转到预约管理页面
router.push('/appoinmentmanage')
} else if (stat.key === 'todayAppointments') {
// 跳转到今日门诊模块
router.push('/doctorstation/today-outpatient')
} else if (stat.key === 'pendingApprovals' || stat.key === 'pendingReview') {
// 跳转到待审核页面
router.push('/clinicmanagement/ePrescribing')

View File

@@ -0,0 +1,501 @@
<template>
<div class="app-container">
<!-- 查询表单 -->
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" class="query-form">
<el-form-item label="手术室名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入手术室名称"
clearable
@keyup.enter="handleQuery"
style="width: 200px"
/>
</el-form-item>
<el-form-item label="状态" prop="statusEnum">
<el-select v-model="queryParams.statusEnum" placeholder="请选择状态" clearable style="width: 150px">
<el-option
v-for="item in statusOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<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="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete">
批量删除
</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<!-- 数据表格 -->
<el-table
v-loading="loading"
:data="operatingRoomList"
@selection-change="handleSelectionChange"
row-key="id"
:row-class-name="getRowClassName"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" type="index" align="center" width="60" />
<el-table-column label="手术室编码" align="center" prop="busNo" width="120" show-overflow-tooltip />
<el-table-column label="手术室名称" align="center" prop="name" min-width="150" show-overflow-tooltip />
<el-table-column label="位置描述" align="center" prop="locationDescription" min-width="150" show-overflow-tooltip />
<el-table-column label="设备配置" align="center" prop="equipmentConfig" min-width="200" show-overflow-tooltip />
<el-table-column label="容纳人数" align="center" prop="capacity" width="100" />
<el-table-column label="状态" align="center" prop="statusEnum" width="100">
<template #default="scope">
<el-tag :type="getStatusType(scope.row.statusEnum)">
{{ scope.row.statusEnum === 1 ? '启用' : '停用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="显示顺序" align="center" prop="displayOrder" width="100" />
<el-table-column label="操作" align="center" width="180" fixed="right">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleEdit(scope.row)">编辑</el-button>
<el-button link type="primary" icon="View" @click="handleView(scope.row)">查看</el-button>
<el-button
link
type="danger"
icon="Delete"
@click="handleDelete(scope.row)"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 新增或修改手术室对话框 -->
<el-dialog :title="title" v-model="open" width="700px" append-to-body>
<el-form ref="operatingRoomRef" :model="form" :rules="rules" label-width="120px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="手术室名称" prop="name">
<el-input v-model="form.name" placeholder="请输入手术室名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="容纳人数" prop="capacity">
<el-input-number
v-model="form.capacity"
:min="1"
:max="20"
style="width: 100%"
placeholder="请输入容纳人数"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="所属科室" prop="organizationId">
<el-select
v-model="form.organizationId"
filterable
clearable
placeholder="请选择所属科室"
:loading="deptLoading"
style="width: 100%"
>
<el-option
v-for="item in flatDeptOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态" prop="statusEnum">
<el-select v-model="form.statusEnum" placeholder="请选择状态" style="width: 100%">
<el-option
v-for="item in statusOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="显示顺序" prop="displayOrder">
<el-input-number
v-model="form.displayOrder"
:min="0"
:max="999"
style="width: 100%"
placeholder="请输入显示顺序"
/>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="位置描述" prop="locationDescription">
<el-input
v-model="form.locationDescription"
type="textarea"
placeholder="请输入位置描述"
:rows="2"
/>
</el-form-item>
<el-form-item label="设备配置" prop="equipmentConfig">
<el-input
v-model="form.equipmentConfig"
type="textarea"
placeholder="请输入设备配置,如:麻醉机、监护仪、手术台等"
:rows="3"
/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="form.remark"
type="textarea"
placeholder="请输入备注"
:rows="2"
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<!-- 查看手术室详情对话框 -->
<el-dialog title="手术室详情" v-model="viewOpen" width="700px" append-to-body>
<el-descriptions :column="2" border>
<el-descriptions-item label="手术室编码">{{ viewData.busNo }}</el-descriptions-item>
<el-descriptions-item label="手术室名称">{{ viewData.name }}</el-descriptions-item>
<el-descriptions-item label="位置描述">{{ viewData.locationDescription }}</el-descriptions-item>
<el-descriptions-item label="容纳人数">{{ viewData.capacity }}</el-descriptions-item>
<el-descriptions-item label="所属科室">{{ viewData.organizationName }}</el-descriptions-item>
<el-descriptions-item label="显示顺序">{{ viewData.displayOrder }}</el-descriptions-item>
<el-descriptions-item label="设备配置" :span="2">{{ viewData.equipmentConfig }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="getStatusType(viewData.statusEnum)">
{{ viewData.statusEnum === 1 ? '启用' : '停用' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ viewData.remark }}</el-descriptions-item>
</el-descriptions>
</el-dialog>
</div>
</template>
<script setup name="OperatingRoomManage">
import { listOperatingRoom, getOperatingRoom, addOperatingRoom, updateOperatingRoom, deleteOperatingRoom } from '@/api/operatingroom'
import { deptTreeSelect } from '@/api/system/user'
import { computed } from 'vue'
const { proxy } = getCurrentInstance()
const loading = ref(true)
const showSearch = ref(true)
const operatingRoomList = ref([])
const ids = ref([])
const single = ref(true)
const multiple = ref(true)
const total = ref(0)
const title = ref('')
const open = ref(false)
const viewOpen = ref(false)
const deptOptions = ref([]) // 科室选项,始终是数组
const deptLoading = ref(false) // 科室数据加载状态
// 将树形科室数据扁平化为一维数组,用于可搜索的下拉框
const flatDeptOptions = computed(() => {
const flatten = (nodes) => {
const result = []
nodes.forEach(node => {
result.push({
id: node.id,
name: node.name
})
if (node.children && node.children.length > 0) {
result.push(...flatten(node.children))
}
})
return result
}
return flatten(deptOptions.value)
})
const queryParams = ref({
pageNo: 1,
pageSize: 10,
name: undefined,
statusEnum: undefined
})
const form = ref({
id: undefined,
busNo: undefined,
name: undefined,
organizationId: undefined,
locationDescription: undefined,
equipmentConfig: undefined,
capacity: 1, // 默认值为1而不是undefined
statusEnum: 1,
displayOrder: 0, // 默认值为0
remark: undefined
})
const operatingRoomRef = ref()
const viewData = ref({})
const statusOptions = ref([
{ value: 1, label: '启用' },
{ value: 0, label: '停用' }
])
const rules = ref({
name: [{ required: true, message: '手术室名称不能为空', trigger: 'blur' }],
capacity: [
{ required: true, message: '容纳人数不能为空', trigger: 'blur' },
{ type: 'number', message: '容纳人数必须为数字', trigger: 'blur' }
],
organizationId: [{ required: true, message: '请选择所属科室', trigger: 'change' }]
})
/** 查询手术室列表 */
function getList() {
loading.value = true
const params = { ...queryParams.value }
listOperatingRoom(params).then(res => {
if (res.code === 200) {
operatingRoomList.value = res.data.records || []
total.value = res.data.total || 0
} else {
proxy.$modal.msgError(res.msg || '获取手术室列表失败')
}
}).catch(error => {
console.error('获取手术室列表失败:', error)
proxy.$modal.msgError('获取手术室列表失败,请稍后重试')
}).finally(() => {
loading.value = false
})
}
/** 查询科室树 */
function getDeptTree() {
deptLoading.value = true
deptTreeSelect().then(res => {
if (res.code === 200) {
// 后端返回的是分页对象,需要从 records 中获取实际数据
const data = res.data
if (data && Array.isArray(data.records)) {
deptOptions.value = data.records
} else if (Array.isArray(data)) {
deptOptions.value = data
} else {
deptOptions.value = []
console.warn('科室数据格式不正确:', data)
}
} else {
deptOptions.value = []
console.warn('获取科室树失败:', res.msg)
}
}).catch(error => {
console.error('获取科室树失败:', error)
deptOptions.value = []
}).finally(() => {
deptLoading.value = false
})
}
/** 取消按钮 */
function cancel() {
open.value = false
reset()
}
/** 表单重置 */
function reset() {
form.value = {
id: undefined,
busNo: undefined,
name: undefined,
organizationId: undefined,
locationDescription: undefined,
equipmentConfig: undefined,
capacity: 1, // 默认值为1
statusEnum: 1,
displayOrder: 0, // 默认值为0
remark: undefined
}
proxy.resetForm('operatingRoomRef')
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNo = 1
getList()
}
/** 重置按钮操作 */
function resetQuery() {
proxy.resetForm('queryRef')
queryParams.value = {
pageNo: 1,
pageSize: 10,
name: undefined,
statusEnum: undefined
}
handleQuery()
}
/** 多选框选中数据 */
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.id)
single.value = selection.length !== 1
multiple.value = !selection.length
}
/** 新增按钮操作 */
function handleAdd() {
reset()
open.value = true
title.value = '新增手术室'
}
/** 修改按钮操作 */
function handleEdit(row) {
reset()
const id = row.id
getOperatingRoom(id).then(res => {
if (res.code === 200) {
form.value = res.data
open.value = true
title.value = '编辑手术室'
}
})
}
/** 查看按钮操作 */
function handleView(row) {
const id = row.id
listOperatingRoom({ id }).then(res => {
if (res.code === 200 && res.data.records.length > 0) {
viewData.value = res.data.records[0]
viewOpen.value = true
}
})
}
/** 提交按钮 */
function submitForm() {
proxy.$refs['operatingRoomRef'].validate(valid => {
if (valid) {
// 确保所有必填字段都有值
const submitData = {
...form.value,
name: form.value.name?.trim() || '',
organizationId: form.value.organizationId || null,
capacity: form.value.capacity || 1,
displayOrder: form.value.displayOrder || 0
}
if (form.value.id) {
updateOperatingRoom(submitData).then(res => {
if (res.code === 200) {
proxy.$modal.msgSuccess('修改成功')
open.value = false
getList()
} else {
proxy.$modal.msgError(res.msg || '修改失败')
}
}).catch(error => {
console.error('修改手术室失败:', error)
proxy.$modal.msgError('修改失败,请稍后重试')
})
} else {
addOperatingRoom(submitData).then(res => {
if (res.code === 200) {
proxy.$modal.msgSuccess('新增成功')
open.value = false
getList()
} else {
proxy.$modal.msgError(res.msg || '新增失败')
}
}).catch(error => {
console.error('新增手术室失败:', error)
proxy.$modal.msgError('新增失败,请稍后重试')
})
}
} else {
proxy.$modal.msgError('请完善表单信息')
}
})
}
/** 删除按钮操作 */
function handleDelete(row) {
const ids = row.id || ids.value
proxy.$modal
.confirm('是否确认删除选中的手术室?')
.then(function () {
return deleteOperatingRoom(ids)
})
.then(() => {
getList()
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
}
/** 获取状态标签类型 */
function getStatusType(status) {
const typeMap = {
1: 'success',
0: 'info'
}
return typeMap[status] || 'info'
}
/** 获取行样式 */
function getRowClassName({ row }) {
if (row.statusEnum === 0) {
return 'disabled-row'
}
return ''
}
getDeptTree()
getList()
</script>
<style scoped>
.disabled-row {
background-color: #f5f5f5;
color: #999;
}
</style>

View File

@@ -49,10 +49,11 @@
clearable
style="width: 120px"
>
<el-option label="待就诊" :value="1" />
<el-option label="就诊中" :value="2" />
<el-option label="已完成" :value="3" />
<el-option label="已取消" :value="4" />
<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-select>
</el-form-item>
<el-form-item label="医生" prop="doctorName">
@@ -181,16 +182,18 @@ function getList() {
/** 根据状态获取标签类型 */
function getStatusTagType(status) {
// 假设状态值1-待就诊2-就诊中3-已完成4-已取消
// 状态值对应后端 EncounterSubjectStatus 枚举1-已到达2-已分诊3-已看诊4-已离开5-已完成
switch (status) {
case 1:
return 'warning'; // 待就诊 - 黄色
return 'warning'; // 已到达 - 黄色
case 2:
return 'primary'; // 就诊中 - 蓝色
return 'primary'; // 已分诊 - 蓝色
case 3:
return 'success'; // 已完成 - 绿色
return 'success'; // 已看诊 - 绿色
case 4:
return 'info'; // 已取消 - 灰色
return 'info'; // 已离开 - 灰色
case 5:
return 'success'; // 已完成 - 绿色
default:
return '';
}

File diff suppressed because it is too large Load Diff