feat(surgery): 完善手术管理功能模块
- 添加手术申请相关API接口,包括根据患者ID查询就诊列表功能 - 在医生工作站界面集成手术申请功能选项卡 - 实现手术管理页面的完整功能,包括手术申请的增删改查 - 添加手术排期、开始、完成等状态流转功能 - 优化手术管理页面表格展示,增加手术类型、等级、计划时间等字段 - 实现手术申请表单的完整编辑和查看模式 - 集成患者信息和就诊记录关联功能 - 添加手术室、医生、护士等资源选择功能 - 更新系统依赖配置,添加core-common模块 - 优化图标资源和manifest配置文件 - 调整患者档案和门诊记录相关状态枚举
This commit is contained in:
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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'
|
||||
})
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user