Files
his/openhis-ui-vue3/src/views/appoinmentmanage/deptManage/index.vue

2179 lines
69 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="appoinmentmanage-wrapper">
<div class="appoinmentmanage-container">
<div class="appoinmentmanage-header">
<h2 class="appoinmentmanage-title">科室排班管理</h2>
</div>
<div class="appoinmentmanage-content">
<!-- 查询条件 -->
<div class="query-condition">
<el-select v-model="queryParams.orgName" placeholder="全部机构" class="query-select" clearable @change="handleOrgChange">
<el-option label="全部机构" value=""></el-option>
<el-option v-for="org in organizationOptions" :key="org.id || org.code" :label="org.name || org.orgName" :value="org.name || org.orgName"></el-option>
</el-select>
<el-select v-model="queryParams.deptName" placeholder="全部科室" class="query-select">
<el-option label="全部科室" value=""></el-option>
<el-option v-for="dept in departmentOptions" :key="dept.id || dept.code" :label="dept.name || dept.deptName" :value="dept.name || dept.deptName"></el-option>
</el-select>
<el-button type="primary" @click="handleQuery" class="query-button">查询</el-button>
<el-button @click="handleReset" class="reset-button">重置</el-button>
<el-button type="success" @click="handleAppointmentSetting" class="appointment-setting-button">预约设置</el-button>
</div>
<!-- 科室列表 -->
<div class="dept-table-container">
<el-table :data="deptList" border style="width: 100%" class="centered-table">
<!-- 添加空状态提示 -->
<template #empty>
<div style="padding: 20px 0;">
<el-icon><DocumentRemove /></el-icon>
<span style="margin-left: 8px;">暂无数据</span>
</div>
</template>
<!-- 适配常见的后端字段名 -->
<el-table-column prop="id" label="ID" width="150"></el-table-column>
<el-table-column prop="orgName" label="卫生机构" width="350">
<template #default="scope">
{{ scope.row.orgName || scope.row.organizationName || scope.row.org || '' }}
</template>
</el-table-column>
<el-table-column prop="deptName" label="科室名称" width="350">
<template #default="scope">
{{ scope.row.deptName || scope.row.departmentName || scope.row.name || '' }}
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" width="400">
<template #default="scope">
{{ scope.row.remark || scope.row.description || scope.row.note || '' }}
</template>
</el-table-column>
<el-table-column prop="status" label="作废标志">
<template #default="scope">
<el-tag :type="(scope.row.status === 1 || scope.row.status === true || scope.row.status === '1') ? 'success' : 'danger'">
{{ (scope.row.status === 1 || scope.row.status === true || scope.row.status === '1') ? '有效' : '无效' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="350" fixed="right">
<template #default="scope">
<el-button type="primary" size="small" @click="handleEdit(scope.row)">
<el-icon><EditPen /></el-icon>
</el-button>
<el-button type="info" size="small" @click="handleView(scope.row)">
<el-icon><View /></el-icon>
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
v-model:current-page="pagination.currentPage"
v-model:page-size="pagination.pageSize"
:page-sizes="[10, 20, 30, 50]"
layout="total, sizes, prev, pager, next, jumper"
:total="pagination.total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</div>
</div>
<!-- 预约设置弹窗 -->
<el-dialog
v-model="appointmentSettingDialog"
title="预约设置"
width="400px"
center
:close-on-click-modal="false"
:close-on-press-escape="false"
top="50%"
:before-close="handleAppointmentSettingCancel"
>
<el-form label-position="top" :model="appointmentSettingForm">
<el-form-item label="取消预约时间类型">
<el-select v-model="appointmentSettingForm.cancelAppointmentType" placeholder="请选择" style="width: 200px">
<el-option label="年" value="YEAR"></el-option>
<el-option label="月" value="MONTH"></el-option>
<el-option label="日" value="DAY"></el-option>
</el-select>
</el-form-item>
<el-form-item label="取消预约次数">
<el-input-number
v-model="appointmentSettingForm.cancelAppointmentCount"
:min="0"
:step="1"
placeholder="请输入"
></el-input-number>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleAppointmentSettingCancel">取消</el-button>
<el-button type="primary" @click="handleAppointmentSettingConfirm">确定</el-button>
</div>
</template>
</el-dialog>
<!-- 医生排班弹窗 -->
<el-dialog
v-model="scheduleDialogVisible"
:title="scheduleDialogTitle"
width="90%"
:close-on-click-modal="false"
top="20px"
fullscreen
>
<div class="doctorschedule-content">
<!-- 筛选条件 -->
<div class="filter-condition">
<span class="filter-label">卫生机构</span>
<el-input v-model="displayedInstitutionName" class="filter-select" disabled></el-input>
<span class="filter-label">科室名称</span>
<el-select v-model="filterParams.deptName" class="filter-select" disabled>
<el-option :label="filterParams.deptName" :value="filterParams.deptName"></el-option>
</el-select>
<span class="filter-label">开始日期</span>
<el-date-picker
v-model="filterParams.startDate"
type="date"
placeholder="选择日期"
class="filter-date-picker"
/>
<span class="filter-label">排班类型</span>
<div class="radio-group">
<el-radio v-model="filterParams.appointmentType" label="普通" @change="handleAppointmentTypeChange">普通</el-radio>
<el-radio v-model="filterParams.appointmentType" label="专家" @change="handleAppointmentTypeChange">专家</el-radio>
</div>
</div>
<!-- 排班表格 -->
<div class="schedule-table-container">
<!-- 按日期分组显示排班 -->
<div v-for="(dateGroup, index) in groupedSchedule" :key="index" class="daily-schedule">
<div class="daily-header">
<span class="date-text">{{ dateGroup.date }}</span>
<span class="weekday-text">{{ dateGroup.weekday }}</span>
</div>
<el-table :data="dateGroup.items" border style="width: 100%" class="schedule-table">
<el-table-column label="时段" width="100">
<template #default="scope">
<el-select
v-model="scope.row.timeSlot"
placeholder="请选择"
:disabled="!isEditMode"
@change="(val) => handleTimeSlotChange(val, scope.row)"
>
<el-option label="上午" value="上午"></el-option>
<el-option label="下午" value="下午"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column prop="doctorName" :label="filterParams.appointmentType === '专家' ? '专家' : '医生'" width="150">
<template #default="scope">
<el-select
v-model="scope.row.doctorId"
placeholder="请选"
class="inline-select"
:disabled="!isEditMode"
:key="`doctor-${filterParams.appointmentType}-${scope.row.id}`"
@change="(selectedId) => handleDoctorChange(selectedId, scope.row)"
>
<el-option
v-for="doctor in getDoctorOptions(filterParams.appointmentType)"
:key="doctor.id"
:label="doctor.label"
:value="String(doctor.id)"
></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column prop="room" label="诊室" width="100">
<template #default="scope">
<el-select
v-model="scope.row.room"
placeholder="请选择"
filterable
:remote-method="searchClinicRooms"
class="inline-select"
:disabled="!isEditMode"
>
<el-option
v-for="room in filteredClinicRoomOptions"
:key="room.id"
:label="room.label"
:value="room.value"
></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column prop="startTime" label="开始时间" width="120">
<template #default="scope">
<el-time-picker
v-model="scope.row.startTime"
type="time"
format="HH:mm"
value-format="HH:mm"
placeholder="选择开始时间"
:disabled="!isEditMode"
class="time-picker"
@change="(val) => handleStartTimeChange(val, scope.row)"
/>
</template>
</el-table-column>
<el-table-column prop="endTime" label="结束时间" width="120">
<template #default="scope">
<el-time-picker
v-model="scope.row.endTime"
type="time"
format="HH:mm"
value-format="HH:mm"
placeholder="选择结束时间"
:disabled="!isEditMode"
class="time-picker"
/>
</template>
</el-table-column>
<el-table-column prop="maxNumber" label="限号数量" width="80">
<template #default="scope">
<el-input
v-model="scope.row.maxNumber"
type="number"
:disabled="!isEditMode"
/>
</template>
</el-table-column>
<el-table-column prop="record" label="号源记录" width="80">
<template #default="scope">
<el-icon
@click="handleViewRecord(scope.row)"
class="record-icon"
title="查看号源记录"
>
<View />
</el-icon>
</template>
</el-table-column>
<el-table-column prop="appointmentItem" label="挂号项目" width="120">
<template #default="scope">
<el-select
v-model="scope.row.appointmentItem"
placeholder="请选"
class="inline-select"
:disabled="!isEditMode"
@change="handleAppointmentItemChange(scope.row)"
>
<el-option
v-for="item in registrationItemOptions"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column prop="registrationFee" label="挂号费(元)" width="100">
<template #default="scope">
<span>{{ scope.row.registrationFee || '0' }}</span>
</template>
</el-table-column>
<el-table-column prop="clinicItem" label="诊查项目" width="150">
<template #default="scope">
<el-select
v-model="scope.row.clinicItem"
placeholder="请选择诊查项目"
class="inline-select"
:disabled="!isEditMode"
@change="handleClinicItemChange(scope.row)"
>
<el-option
v-for="item in clinicItemOptions"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column prop="treatmentFee" label="诊疗费(元)" width="100">
<template #default="scope">
<span>{{ scope.row.treatmentFee || '0' }}</span>
</template>
</el-table-column>
<el-table-column prop="online" label="线上" width="60">
<template #default="scope">
<el-checkbox v-model="scope.row.online" :disabled="!isEditMode"></el-checkbox>
</template>
</el-table-column>
<el-table-column prop="stopClinic" label="停诊" width="60">
<template #default="scope">
<el-checkbox v-model="scope.row.stopClinic" :disabled="!isEditMode"></el-checkbox>
</template>
</el-table-column>
<el-table-column prop="stopReason" label="停诊原因" width="150">
<template #default="scope">
<el-input
v-model="scope.row.stopReason"
:placeholder="scope.row.stopClinic ? '请输入停诊原因' : ''"
class="inline-input"
:disabled="!isEditMode || !scope.row.stopClinic"
></el-input>
</template>
</el-table-column>
<el-table-column label="操作" width="150" fixed="right">
<template #default="scope">
<el-button
type="primary"
size="small"
@click="handleAddSchedule(scope.row)"
:disabled="!isEditMode"
>
添加
</el-button>
<el-button
type="danger"
size="small"
@click="handleDeleteSchedule(scope.row)"
:disabled="!isEditMode || isLastDraftRowInSlot(scope.row)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
<!-- 底部操作按钮 -->
<div class="bottom-buttons">
<el-button type="primary" @click="handleSave" :disabled="!isEditMode">确定</el-button>
<el-button @click="handleCancel">取消</el-button>
</div>
</div>
<!-- 号源记录对话框 -->
<el-dialog
v-model="recordDialogVisible"
title="号源记录"
width="30%"
:close-on-click-modal="true"
>
<div class="appointment-records">
<div class="record-item" v-for="record in appointmentRecords" :key="record.index">
<span class="record-time">{{ record.time }}</span>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="recordDialogVisible = false">确定</el-button>
</div>
</template>
</el-dialog>
</el-dialog>
</div>
</template>
<script setup name="DeptManage">
import {computed, onMounted, ref, watch} from 'vue'
import { getDeptAppthoursList } from '@/api/appoinmentmanage/deptappthoursManage'
import {useRouter} from 'vue-router'
import {ElDialog, ElForm, ElFormItem, ElInput, ElMessage, ElMessageBox, ElOption, ElSelect} from 'element-plus'
import {DocumentRemove, EditPen, View, Delete} from '@element-plus/icons-vue'
import {listDept, searchDept} from '@/api/appoinmentmanage/dept'
import {getAppointmentConfig, saveAppointmentConfig} from '@/api/appoinmentmanage/appointmentConfig'
import {getLocationTree, getPractitionerMetadata, getHealthcareMetadata} from '@/views/charge/outpatientregistration/components/outpatientregistration'
import {addDoctorSchedule, addDoctorScheduleWithDate, updateDoctorSchedule, deleteDoctorSchedule, getRegisterOrganizations, getDoctorScheduleListByDeptId, getDoctorScheduleListByDeptIdAndDateRange} from './api'
import {getClinicRoomList} from '@/api/appoinmentmanage/clinicRoom'
import {getInitOption, getRegistrationItems, getClinicItems} from '@/views/basicservices/registrationfee/components/registrationfee'
import { deptTreeSelect } from '@/api/system/user'
// 查询参数
const queryParams = ref({
orgName: '',
deptName: ''
})
// 当前视图
const currentView = ref('list')
// 机构选项列表
const organizationOptions = ref([])
// 科室选项列表
const departmentOptions = ref([])
// 科室列表
const deptList = ref([])
// 分页参数
const pagination = ref({
currentPage: 1,
pageSize: 10,
total: 0
})
// 预约设置弹窗
const appointmentSettingDialog = ref(false)
// 预约设置表单数据
const appointmentSettingForm = ref({
cancelAppointmentType: '年',
cancelAppointmentCount: 0
})
// 医生排班相关数据
// 排班弹窗可见性
const scheduleDialogVisible = ref(false)
// 排班弹窗标题
const scheduleDialogTitle = ref('')
// 当前操作模式edit/view
const currentMode = ref('edit')
// 当前选中的科室
const currentDept = ref(null)
// 筛选参数
const filterParams = ref({
orgName: '',
deptName: '测试内科',
clinicRoomName: '', // 添加诊室名称过滤字段
startDate: new Date(),
appointmentType: '普通'
})
// 医生列表数据 - 初始化为空,将在获取科室信息后填充
const doctorOptions = ref({
'普通': [],
'专家': []
})
// 诊室选项
const clinicRoomOptions = ref([])
// 挂号项目选项
const registrationItemOptions = ref([])
// 诊查项目选项
const clinicItemOptions = ref([])
// 科室工作时间配置
const workTimeConfig = ref(null)
// 显示的机构名称
const displayedInstitutionName = ref('');
// 获取医生列表
const fetchDoctorList = async (orgId) => {
try {
console.log('正在获取科室ID为:', orgId, '的医生列表')
// 重要不要将ID转换为数字以避免精度丢失
// 直接使用原始ID值字符串或数字
if (!orgId) {
console.error('无效的科室ID:', orgId)
doctorOptions.value = {
'普通': [],
'专家': []
}
return
}
// 后端查询需要orgId和roleCode两个参数
// 根据后端代码分析roleCode通常为'doctor'
// 使用原始ID值避免精度丢失
const response = await getPractitionerMetadata({
orgId: orgId, // 直接使用原始ID值不进行类型转换
roleCode: 'doctor' // 添加roleCode参数确保查询的是医生角色
})
console.log('获取医生列表响应:', response)
if (response.code === 200) {
// 适配不同的后端数据结构
let actualData = response.data
if (actualData && actualData.code === 200 && actualData.msg) {
actualData = actualData.data
}
// 确保数据是数组格式
let doctorList = []
if (Array.isArray(actualData)) {
doctorList = actualData
} else if (actualData && actualData.records) {
doctorList = actualData.records
} else if (actualData && actualData.content) {
doctorList = actualData.content
} else if (actualData && actualData.list) {
doctorList = actualData.list
}
console.log('原始医生数据:', doctorList)
// 医生下拉统一展示:普通/专家都显示同一批医生
// 排班回显仍按 schedule.regType 过滤,不受这里影响
const allDoctors = []
doctorList.forEach(doctor => {
// 不再单独检查医生的org_id是否与当前科室匹配
// 因为我们已经通过orgId参数向后端请求特定科室的医生
// 直接添加医生到列表中
const doctorInfo = {
label: doctor.name || doctor.practitionerName,
value: doctor.name || doctor.practitionerName,
id: String(doctor.id),
orgId: doctor.orgId
}
allDoctors.push(doctorInfo)
})
const uniqueDoctors = Array.from(
new Map(allDoctors.map(doc => [doc.id, doc])).values()
)
// 更新医生选项
doctorOptions.value['普通'] = uniqueDoctors
doctorOptions.value['专家'] = uniqueDoctors
} else {
doctorOptions.value = {
'普通': [],
'专家': []
}
}
} catch (error) {
doctorOptions.value = {
'普通': [],
'专家': []
}
}
}
// 获取指定科室下的诊室列表
const fetchClinicRoomsByDept = async (deptName) => {
try {
// 调用后端API获取诊室列表可能包含所有科室的诊室
const response = await getClinicRoomList({
pageNum: 1,
pageSize: 100
});
if (response.code === 200) {
let clinicRooms = [];
// 适配不同的后端数据结构
if (response.data && response.data.records) {
clinicRooms = response.data.records;
} else if (Array.isArray(response.data)) {
clinicRooms = response.data;
} else if (response.data && response.data.content) {
clinicRooms = response.data.content;
} else if (response.data && response.data.list) {
clinicRooms = response.data.list;
}
// 过滤出属于指定科室的诊室(前后端双重保险)
const filteredClinicRooms = clinicRooms.filter(room =>
room.department &&
(room.department.toLowerCase().includes(deptName.toLowerCase()) ||
(room.deptName && room.deptName.toLowerCase().includes(deptName.toLowerCase())))
);
// 将诊室数据转换为下拉选项格式
const options = filteredClinicRooms.map(room => ({
label: room.roomName || room.name || room.room_no,
value: room.roomName || room.name || room.room_no,
id: room.id
}));
// 更新诊室选项
clinicRoomOptions.value = options;
return options;
}
clinicRoomOptions.value = [];
return [];
} catch (error) {
console.error('获取诊室列表失败:', error);
clinicRoomOptions.value = [];
return [];
}
};
// 获取挂号项目选项
const fetchRegistrationItems = async (deptId = null) => {
try {
// 调用后端API获取挂号项目列表 - 使用专门的挂号项目API
// 如果提供了科室ID则按科室过滤挂号项目
const params = deptId ? { deptId } : {};
const response = await getRegistrationItems(params);
if (response.code === 200) {
let healthcareItems = [];
// 适配不同的后端数据结构
if (response.data && response.data.records) {
healthcareItems = response.data.records;
} else if (Array.isArray(response.data)) {
healthcareItems = response.data;
} else if (response.data && response.data.content) {
healthcareItems = response.data.content;
} else if (response.data && response.data.list) {
healthcareItems = response.data.list;
}
// 由于后端已经按ybType='13'过滤,这里不再需要前端过滤
const registrationItems = healthcareItems;
// 将挂号项目数据转换为下拉选项格式
const options = registrationItems.map(item => ({
label: `${item.name} (${item.retailPrice || '0'}元)`,
value: item.name,
price: item.retailPrice || 0
}));
// 更新挂号项目选项
registrationItemOptions.value = options;
return options;
} else {
console.error('获取挂号项目列表失败:', response.msg || response.message);
registrationItemOptions.value = [];
return [];
}
} catch (error) {
console.error('获取挂号项目列表失败:', error);
registrationItemOptions.value = [];
return [];
}
};
// 获取诊查项目选项
const fetchClinicItems = async (deptId = null) => {
try {
// 调用后端API获取诊查项目列表 - 传递科室ID进行过滤
const params = deptId ? { orgId: deptId } : {};
const response = await getClinicItems(params);
if (response.code === 200) {
let healthcareItems = [];
// 适配不同的后端数据结构
if (response.data && response.data.records) {
healthcareItems = response.data.records;
} else if (Array.isArray(response.data)) {
healthcareItems = response.data;
} else if (response.data && response.data.content) {
healthcareItems = response.data.content;
} else if (response.data && response.data.list) {
healthcareItems = response.data.list;
}
// 将诊查项目数据转换为下拉选项格式 [编号 项目名称 单价]
const options = healthcareItems.map(item => ({
label: `${item.busNo} ${item.name} ${item.retailPrice || '0'}`,
value: item.name,
price: item.retailPrice || 0,
busNo: item.busNo
}));
// 更新诊查项目选项
clinicItemOptions.value = options;
return options;
} else {
console.error('获取诊查项目列表失败:', response.msg || response.message);
clinicItemOptions.value = [];
return [];
}
} catch (error) {
console.error('获取诊查项目列表失败:', error);
clinicItemOptions.value = [];
return [];
}
};
// 根据排班类型获取医生选项
const getDoctorOptions = (appointmentType) => {
return doctorOptions.value[appointmentType] || []
}
// 排班列表数据
const scheduleList = ref([])
// 号源记录对话框相关
const recordDialogVisible = ref(false)
const currentRow = ref(null)
const appointmentRecords = ref([])
// 计算属性 - 是否为编辑模式
const isEditMode = computed(() => {
return currentMode.value === 'edit'
})
// 按日期分组的排班数据
const groupedSchedule = computed(() => {
// 按日期分组
const groups = {}
scheduleList.value.forEach(item => {
if (!groups[item.date]) {
groups[item.date] = {
date: item.date,
weekday: item.weekday,
items: []
}
}
groups[item.date].items.push(item)
})
// 转换为数组
return Object.values(groups)
})
// 过滤后的诊室选项
const filteredClinicRoomOptions = computed(() => {
if (!filterParams.value.clinicRoomName) {
return clinicRoomOptions.value
}
return clinicRoomOptions.value.filter(option =>
option.label.toLowerCase().includes(filterParams.value.clinicRoomName.toLowerCase())
)
})
// 获取卫生机构列表
const fetchOrganizationList = async () => {
try {
// 从科室列表中提取唯一的机构名称
const response = await getRegisterOrganizations({})
if (response.code === 200) {
let actualData = response.data
// 处理嵌套data结构
if (actualData && actualData.code === 200 && actualData.msg) {
actualData = actualData.data
}
// 确保数据是数组格式
let deptList = []
if (Array.isArray(actualData)) {
deptList = actualData
} else if (actualData && actualData.records) {
deptList = actualData.records
} else if (actualData && actualData.content) {
deptList = actualData.content
} else if (actualData && actualData.list) {
deptList = actualData.list
}
// 提取唯一的机构名称
const uniqueOrgNames = new Set();
deptList.forEach(dept => {
const orgName = dept.orgName || dept.organizationName || dept.org || dept.name;
if (orgName) {
uniqueOrgNames.add(orgName);
}
});
// 将机构名称转换为下拉选项格式
organizationOptions.value = Array.from(uniqueOrgNames).map((orgName, index) => ({
id: index, // 使用索引作为ID因为我们只需要名称
name: orgName,
orgName: orgName
}));
} else {
organizationOptions.value = []
}
} catch (error) {
organizationOptions.value = []
}
}
// 获取科室列表(通用方法)
const fetchDeptList = async (apiFunction) => {
try {
// 统一查询参数口径:后端使用 name 作为科室名称条件
const params = {
pageNum: pagination.value.currentPage,
pageSize: pagination.value.pageSize
};
if (queryParams.value.orgName) {
params.orgName = queryParams.value.orgName;
}
if (queryParams.value.deptName) {
params.name = queryParams.value.deptName;
}
// 同时获取配置数据,分页大小与主列表保持一致
// 这样既解决了 9999 导致的性能问题,又保证了当前显示行的数据完整性
const [deptRes, configRes] = await Promise.all([
apiFunction(params),
getDeptAppthoursList({
pageNum: pagination.value.currentPage,
pageSize: pagination.value.pageSize
})
]);
if (deptRes.code === 200) {
let dataList = [];
let totalCount = 0;
let actualData = deptRes.data;
if (actualData && actualData.code === 200 && actualData.msg) {
actualData = actualData.data;
}
// 提取主数据列表
if (actualData && actualData.records) {
dataList = actualData.records;
totalCount = actualData.total || 0;
} else if (actualData && actualData.content) {
dataList = actualData.content;
totalCount = actualData.totalElements || 0;
} else if (Array.isArray(actualData)) {
dataList = actualData;
totalCount = actualData.length;
} else if (actualData && actualData.list) {
dataList = actualData.list;
totalCount = actualData.total || actualData.list.length;
} else {
dataList = actualData || [];
totalCount = Array.isArray(actualData) ? actualData.length : 0;
}
// 提取并映射配置数据
const configs = configRes?.data?.records || configRes?.rows || configRes?.data || [];
const configMap = new Map();
if (Array.isArray(configs)) {
configs.forEach(c => {
// 假设通过机构+科室名进行匹配或者通过ID取决于后端模型
const key = `${c.institution}-${c.department}`;
configMap.set(key, c);
});
}
if (apiFunction === getRegisterOrganizations) {
deptList.value = dataList.map(org => {
const key = `${org.orgName || org.organizationName || org.org || ''}-${org.name}`;
const config = configMap.get(key);
return {
id: org.id,
orgName: org.orgName || org.organizationName || org.name,
deptName: org.name,
name: org.name,
remark: org.remark || org.intro,
// 优先使用配置中的状态,作为补充
status: config ? config.activeFlag !== 0 : (org.activeFlag === 1),
registerFlag: config ? config.registerFlag : org.registerFlag
};
});
} else {
deptList.value = dataList;
}
pagination.value.total = totalCount;
} else {
ElMessage.error(deptRes.msg || '获取科室列表失败');
}
} catch (error) {
console.error('获取科室列表失败:', error);
ElMessage.error('获取科室列表失败');
}
}
// 获取科室列表(默认列表)
const getDeptList = async () => {
// 在获取科室列表后,也获取挂号项目选项
// 优化:挂号项目已移至排班弹窗按需加载,此处不再提前加载。
await fetchDeptList(getRegisterOrganizations); // 使用挂号科室API
}
// 查询
const handleQuery = async () => {
pagination.value.currentPage = 1;
await fetchDeptList(getRegisterOrganizations); // 使用挂号科室API
}
// 处理卫生机构选择变化
const handleOrgChange = async (orgName) => {
// 当卫生机构变化时,重置科室选择
queryParams.value.deptName = ''
// 重新获取科室选项列表,以反映机构筛选
await fetchDepartmentOptions()
}
// 重置
const handleReset = async () => {
queryParams.value = {
orgName: '',
deptName: ''
}
pagination.value.currentPage = 1
await fetchDepartmentOptions()
await getDeptList()
}
// 预约设置弹窗显示
const handleAppointmentSetting = async () => {
// 获取当前机构的预约配置
try {
const res = await getAppointmentConfig()
if (res.code === 200 && res.data) {
// 回显已有配置
appointmentSettingForm.value = {
cancelAppointmentType: res.data.cancelAppointmentType || 'YEAR',
cancelAppointmentCount: res.data.cancelAppointmentCount || 0
}
} else {
// 无配置时使用默认值
appointmentSettingForm.value = {
cancelAppointmentType: 'YEAR',
cancelAppointmentCount: 0
}
}
} catch (error) {
console.error('获取预约配置失败:', error)
appointmentSettingForm.value = {
cancelAppointmentType: 'YEAR',
cancelAppointmentCount: 0
}
}
appointmentSettingDialog.value = true
}
// 预约设置确定
const handleAppointmentSettingConfirm = async () => {
try {
const res = await saveAppointmentConfig({
cancelAppointmentType: appointmentSettingForm.value.cancelAppointmentType,
cancelAppointmentCount: appointmentSettingForm.value.cancelAppointmentCount,
validFlag: 1
})
if (res.code === 200) {
ElMessage.success('预约设置保存成功')
appointmentSettingDialog.value = false
} else {
ElMessage.error(res.msg || '保存失败')
}
} catch (error) {
console.error('保存预约配置失败:', error)
ElMessage.error('保存失败')
}
}
// 预约设置取消
const handleAppointmentSettingCancel = () => {
appointmentSettingDialog.value = false
}
// 路由和导航
const router = useRouter()
// 根据开始时间自动判断时段(上午/下午)
const getTimeSlot = (startTime) => {
if (!startTime) return '上午';
const hour = parseInt(startTime.split(':')[0]);
return hour < 12 ? '上午' : '下午';
};
// 生成一周排班数据
const generateWeekSchedule = (startDate, workTimeConfig) => {
const days = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
const timeSlots = [
{
label: getTimeSlot(workTimeConfig?.morningStart || '08:00'),
startTime: workTimeConfig?.morningStart || '08:00',
endTime: workTimeConfig?.morningEnd || '12:00'
},
{
label: getTimeSlot(workTimeConfig?.afternoonStart || '14:30'),
startTime: workTimeConfig?.afternoonStart || '14:30',
endTime: workTimeConfig?.afternoonEnd || '18:00'
}
];
const defaultQuota = workTimeConfig?.quota || '';
const schedule = []
// 生成一周7天的数据
for (let i = 0; i < 7; i++) {
// 创建新日期对象,避免修改原始日期
const year = startDate.getFullYear();
const month = startDate.getMonth();
const day = startDate.getDate();
// 创建新日期,添加偏移天数
const currentDate = new Date(year, month, day + i);
// 使用本地日期格式而不是ISO字符串避免时区转换问题
const dateStr = `${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(2, '0')}-${String(currentDate.getDate()).padStart(2, '0')}`;
const weekday = days[currentDate.getDay()];
// 每个时间段生成一条记录
timeSlots.forEach(slot => {
schedule.push({
id: `${dateStr}-${slot.label}`,
date: dateStr,
weekday: weekday,
timeSlot: slot.label,
startTime: slot.startTime,
endTime: slot.endTime,
doctorName: '',
doctorId: null,
room: '',
maxNumber: defaultQuota,
appointmentItem: '',
registrationFee: 0,
clinicItem: '',
treatmentFee: 0,
online: true,
stopClinic: false,
stopReason: ''
})
})
}
return schedule
}
// 格式化日期为 yyyy-MM-dd 字符串
const formatDateStr = (date) => {
const d = new Date(date)
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
}
// 重新加载排班数据
const reloadScheduleData = async () => {
const row = currentDept.value
if (!row) return
const weekSchedule = generateWeekSchedule(filterParams.value.startDate, workTimeConfig.value)
const startDateStr = formatDateStr(filterParams.value.startDate)
const endDate = new Date(filterParams.value.startDate)
endDate.setDate(endDate.getDate() + 6)
const endDateStr = formatDateStr(endDate)
try {
const response = await getDoctorScheduleListByDeptIdAndDateRange(row.id, startDateStr, endDateStr)
if (response.code === 200) {
const actualData = response.data
const deptSchedules = Array.isArray(actualData)
? actualData
: (actualData && actualData.code === 200 ? actualData.data || [] : [])
const selectedRegType = filterParams.value.appointmentType === '专家' ? 1 : 0
const filteredSchedules = deptSchedules.filter(schedule => Number(schedule.regType) === selectedRegType)
// 同日期同时段可能存在多条排班,按 key 聚合成数组
const scheduleMap = {}
filteredSchedules.forEach(schedule => {
const key = `${schedule.scheduleDate}-${schedule.timePeriod}`
if (!scheduleMap[key]) {
scheduleMap[key] = []
}
scheduleMap[key].push(schedule)
})
const allAvailableDoctors = [...doctorOptions.value['普通'], ...doctorOptions.value['专家']]
const mergedSchedule = []
const applyScheduleToSlot = (targetSlot, existingSchedule) => {
targetSlot.doctorName = existingSchedule.doctorName || existingSchedule.doctor || ''
targetSlot.doctorId = existingSchedule.doctorId != null ? String(existingSchedule.doctorId) : null
const matchedDoctorById = allAvailableDoctors.find(doc => doc.id === targetSlot.doctorId)
if (!matchedDoctorById && targetSlot.doctorName) {
const matchedDoctorByName = allAvailableDoctors.find(doc => doc.label === targetSlot.doctorName)
if (matchedDoctorByName) {
targetSlot.doctorId = matchedDoctorByName.id
}
}
targetSlot.room = existingSchedule.clinic || existingSchedule.clinicRoom || ''
targetSlot.startTime = existingSchedule.startTime
targetSlot.endTime = existingSchedule.endTime
targetSlot.maxNumber = existingSchedule.limitNumber
targetSlot.appointmentItem = existingSchedule.registerItem
targetSlot.registrationFee = existingSchedule.registerFee
targetSlot.clinicItem = existingSchedule.diagnosisItem
targetSlot.treatmentFee = existingSchedule.diagnosisFee
targetSlot.online = existingSchedule.isOnline
targetSlot.stopClinic = existingSchedule.isStopped
targetSlot.stopReason = existingSchedule.stopReason
targetSlot.regType = existingSchedule.regType
targetSlot.backendId = existingSchedule.id
}
weekSchedule.forEach(slot => {
const key = `${slot.date}-${slot.timeSlot}`
const existingSchedules = scheduleMap[key] || []
if (existingSchedules.length === 0) {
mergedSchedule.push(slot)
return
}
existingSchedules.forEach((existingSchedule, index) => {
const targetSlot = index === 0
? slot
: {
...slot,
id: `${slot.id}-dup-${existingSchedule.id || index}`,
doctorName: '',
doctorId: null,
room: '',
maxNumber: '',
appointmentItem: '',
registrationFee: 0,
clinicItem: '',
treatmentFee: 0,
online: true,
stopClinic: false,
stopReason: ''
}
applyScheduleToSlot(targetSlot, existingSchedule)
mergedSchedule.push(targetSlot)
})
})
scheduleList.value = mergedSchedule
return
}
} catch (error) {
console.error('获取科室排班数据失败:', error)
ElMessage.error('获取科室排班数据失败')
}
scheduleList.value = weekSchedule
}
const handleEdit = async (row) => {
// 设置当前科室和模式
currentDept.value = row
currentMode.value = 'edit'
scheduleDialogTitle.value = `编辑科室排班 - ${row.name || row.deptName}`
// 动态设置筛选参数
filterParams.value.orgName = row.orgName || row.organizationName || row.org || ''
filterParams.value.deptName = row.name || row.deptName
// 首先获取科室工作时间配置
try {
const res = await getDeptAppthoursList({ deptId: row.id, pageNum: 1, pageSize: 1 });
console.log("【调试信息】科室工作时间配置原始数据:", res); // Debug log
const records = res?.data?.records || res?.rows || [];
if (records.length > 0) {
workTimeConfig.value = records[0];
} else {
workTimeConfig.value = null; // 确保如果没有配置则清空
}
} catch (error) {
console.error("获取科室工作时间配置失败:", error);
ElMessage.error("获取科室工作时间配置失败");
workTimeConfig.value = null;
}
// 设置显示机构名称
if (workTimeConfig.value && workTimeConfig.value.institution) {
displayedInstitutionName.value = workTimeConfig.value.institution;
} else {
displayedInstitutionName.value = '未设置机构';
}
// 并行获取其他相关数据
await Promise.all([
fetchDoctorList(row.id),
fetchClinicRoomsByDept(row.name || row.deptName),
row && row.id ? fetchRegistrationItems(row.id) : Promise.resolve(),
fetchClinicItems(row.id) // 获取诊查项目选项传递科室ID
])
console.log("【调试信息】可供选择的医生列表:", JSON.parse(JSON.stringify(doctorOptions.value)));
// 加载排班数据
await reloadScheduleData()
// 显示排班弹窗
scheduleDialogVisible.value = true
}
// 查看
const handleView = async (row) => {
// 设置当前科室和模式
currentDept.value = row
currentMode.value = 'view'
scheduleDialogTitle.value = `查看科室排班 - ${row.name || row.deptName}`
// 动态设置筛选参数
filterParams.value.orgName = row.orgName || row.organizationName || row.org || ''
filterParams.value.deptName = row.name || row.deptName
// 首先获取科室工作时间配置
try {
const res = await getDeptAppthoursList({ deptId: row.id, pageNum: 1, pageSize: 1 });
const records = res?.data?.records || res?.rows || [];
if (records.length > 0) {
workTimeConfig.value = records[0];
} else {
workTimeConfig.value = null;
}
} catch (error) {
console.error("获取科室工作时间配置失败:", error);
ElMessage.error("获取科室工作时间配置失败");
workTimeConfig.value = null;
}
// 并行获取其他相关数据
await Promise.all([
fetchDoctorList(row.id),
row && row.id ? fetchRegistrationItems(row.id) : Promise.resolve(),
fetchClinicItems(row.id) // 获取诊查项目选项传递科室ID
])
// 加载排班数据
await reloadScheduleData()
// 显示排班弹窗
scheduleDialogVisible.value = true
}
// 监听日期变化,重新生成排班数据
watch(
() => filterParams.value.startDate,
(newDate) => {
if (scheduleDialogVisible.value) { // 只有在排班弹窗打开时才重新生成数据
reloadScheduleData();
}
}
)
// 诊室搜索
const searchClinicRooms = async (query) => {
if (!query) {
// 如果没有查询词,重新获取当前科室的所有诊室
await fetchClinicRoomsByDept(filterParams.value.deptName);
return;
}
try {
// 先获取所有诊室,然后在前端进行过滤
const response = await getClinicRoomList({
roomName: query,
pageNum: 1,
pageSize: 100
});
if (response.code === 200) {
let clinicRooms = [];
// 适配不同的后端数据结构
if (response.data && response.data.records) {
clinicRooms = response.data.records;
} else if (Array.isArray(response.data)) {
clinicRooms = response.data;
} else if (response.data && response.data.content) {
clinicRooms = response.data.content;
} else if (response.data && response.data.list) {
clinicRooms = response.data.list;
}
// 过滤出属于当前科室且符合查询条件的诊室
const filteredClinicRooms = clinicRooms.filter(room =>
room.department &&
(room.department.toLowerCase().includes(filterParams.value.deptName.toLowerCase()) ||
(room.deptName && room.deptName.toLowerCase().includes(filterParams.value.deptName.toLowerCase()))) &&
(query ? (room.roomName || room.name || room.room_no).toLowerCase().includes(query.toLowerCase()) : true)
);
// 更新诊室选项
clinicRoomOptions.value = filteredClinicRooms.map(room => ({
label: room.roomName || room.name || room.room_no,
value: room.roomName || room.name || room.room_no,
id: room.id
}));
}
} catch (error) {
console.error('搜索诊室失败:', error);
}
}
// 医生映射关系(普通医生 -> 专家医生)
// 这里可以根据实际的医生职称或类型进行动态映射
const doctorMapping = {}
// 排班类型变化处理
const handleAppointmentTypeChange = async () => {
if (currentDept.value) {
await reloadScheduleData()
return
}
scheduleList.value.forEach(item => {
item.doctorId = null
item.doctorName = ''
})
}
// 挂号项目变化处理
const handleAppointmentItemChange = (row) => {
if (row.appointmentItem) {
// 从挂号项目选项中查找对应的价格
const selectedItem = registrationItemOptions.value.find(item => item.value === row.appointmentItem);
if (selectedItem) {
row.registrationFee = selectedItem.price || 0;
} else {
row.registrationFee = 0;
}
} else {
row.registrationFee = 0;
}
}
// 诊查项目变化处理
const handleClinicItemChange = (row) => {
if (row.clinicItem) {
// 从诊查项目选项中查找对应的价格
const selectedItem = clinicItemOptions.value.find(item => item.value === row.clinicItem);
if (selectedItem) {
row.treatmentFee = selectedItem.price || 0;
} else {
row.treatmentFee = 0;
}
} else {
row.treatmentFee = 0;
}
}
// 医生选择变化处理
const handleDoctorChange = (selectedId, row) => {
if (!selectedId) {
row.doctorName = '';
return;
}
const typeDoctors = getDoctorOptions(filterParams.value.appointmentType)
const selectedDoctor = typeDoctors.find(doc => doc.id === String(selectedId));
if (selectedDoctor) {
row.doctorName = selectedDoctor.label;
}
}
const resolveDoctorName = (row) => {
if (row.doctorName && row.doctorName.trim() !== '') {
return row.doctorName.trim()
}
if (!row.doctorId) {
return ''
}
const allDoctors = [...doctorOptions.value['普通'], ...doctorOptions.value['专家']]
const matched = allDoctors.find(doc => doc.id === String(row.doctorId))
return matched ? matched.label : ''
}
// 添加排班
// 删除后兜底:当某天某时段被删空时,补一条重置行,保证可继续填写
const createResetScheduleRow = (row) => {
const regTypeValue = Number(row.regType)
return {
id: `reset-${Date.now()}-${Math.random()}`,
date: row.date,
weekday: row.weekday,
timeSlot: row.timeSlot,
startTime: row.startTime || '08:00',
endTime: row.endTime || '12:00',
doctorName: '',
doctorId: null,
room: '',
maxNumber: '',
appointmentItem: '',
registrationFee: 0,
clinicItem: '',
treatmentFee: 0,
online: true,
stopClinic: false,
stopReason: '',
regType: Number.isNaN(regTypeValue) ? 0 : regTypeValue,
isNew: true
}
}
const ensureResetRowAfterDelete = (row, insertIndex) => {
const hasSameSlot = scheduleList.value.some(item =>
item.date === row.date && item.timeSlot === row.timeSlot
)
if (hasSameSlot) {
return
}
const resetRow = createResetScheduleRow(row)
if (insertIndex >= 0 && insertIndex <= scheduleList.value.length) {
scheduleList.value.splice(insertIndex, 0, resetRow)
return
}
scheduleList.value.push(resetRow)
}
const isDraftScheduleRow = (row) => {
return !row.backendId || row.isNew
}
const isLastDraftRowInSlot = (row) => {
if (!isDraftScheduleRow(row)) {
return false
}
const sameSlotRows = scheduleList.value.filter(item =>
item.date === row.date && item.timeSlot === row.timeSlot
)
const draftRows = sameSlotRows.filter(item => isDraftScheduleRow(item))
return draftRows.length <= 1
}
const handleAddSchedule = (row) => {
// 创建新的排班记录,基于当前行的日期和时段
const startTime = row.startTime || '08:00';
const newSchedule = {
id: `new-${Date.now()}-${Math.random()}`,
date: row.date,
weekday: row.weekday,
timeSlot: getTimeSlot(startTime),
startTime: startTime,
endTime: row.endTime || '12:00',
doctorName: '',
room: '',
maxNumber: row.maxNumber ?? '',
appointmentItem: '',
registrationFee: 0,
clinicItem: '',
treatmentFee: 0,
online: true,
stopClinic: false,
stopReason: '',
isNew: true // 标记为新添加的记录
}
// 找到当前行在列表中的位置,在其后插入新记录
const currentIndex = scheduleList.value.findIndex(item => item.id === row.id)
if (currentIndex !== -1) {
scheduleList.value.splice(currentIndex + 1, 0, newSchedule)
ElMessage.success('添加排班成功')
} else {
// 如果找不到,添加到对应日期组的末尾
scheduleList.value.push(newSchedule)
ElMessage.success('添加排班成功')
}
}
// 时段变化时,更新默认的开始结束时间
const handleTimeSlotChange = (val, row) => {
const config = workTimeConfig.value;
if (val === '上午') {
if (!row.startTime || row.startTime >= '12:00') {
row.startTime = config?.morningStart || '08:00';
row.endTime = config?.morningEnd || '12:00';
}
} else if (val === '下午') {
if (!row.startTime || row.startTime < '12:00') {
row.startTime = config?.afternoonStart || '14:30';
row.endTime = config?.afternoonEnd || '18:00';
}
}
};
// 开始时间变化时,自动更新时段
const handleStartTimeChange = (val, row) => {
if (val) {
row.timeSlot = getTimeSlot(val);
}
};
// 删除排班
const handleDeleteSchedule = (row) => {
if (isLastDraftRowInSlot(row)) {
ElMessage.warning('当前时段需保留最后一条待填写记录,不能删除')
return
}
ElMessageBox.confirm('确定要删除这条排班记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 如果是已保存的记录有后端ID调用删除接口
if (row.backendId && !row.isNew) {
deleteDoctorSchedule(row.backendId).then(res => {
const removed = res?.data?.data === true || res?.data === true
if (res.code === 200 && removed) {
// 从列表中移除
const index = scheduleList.value.findIndex(item => item.id === row.id)
if (index !== -1) {
scheduleList.value.splice(index, 1)
ensureResetRowAfterDelete(row, index)
}
ElMessage.success('删除成功')
} else {
ElMessage.error(res.msg || '删除失败,记录可能未真正删除')
}
}).catch(error => {
console.error('删除排班失败:', error)
ElMessage.error('删除失败,请稍后重试')
})
} else {
// 如果是新添加的记录(未保存),直接从列表中移除
const index = scheduleList.value.findIndex(item => item.id === row.id)
if (index !== -1) {
scheduleList.value.splice(index, 1)
ensureResetRowAfterDelete(row, index)
ElMessage.success('删除成功')
}
}
}).catch(() => {
// 用户取消删除
})
}
// 根据ID删除排班
const handleDeleteScheduleById = (id) => {
ElMessageBox.confirm('确定要删除这条排班记录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deleteDoctorSchedule(id).then(res => {
if (res.code === 200) {
// 从列表中移除
const index = doctorScheduleList.value.findIndex(item => item.id === id)
if (index !== -1) {
doctorScheduleList.value.splice(index, 1)
}
ElMessage.success('删除成功')
} else {
ElMessage.error(res.msg || '删除失败')
}
}).catch(error => {
console.error('删除排班失败:', error)
ElMessage.error('删除失败,请稍后重试')
})
}).catch(() => {
// 用户取消删除
})
}
// 编辑排班
const handleEditSchedule = (row) => {
console.log('编辑排班:', row)
// 设置当前模式为编辑模式
currentMode.value = 'edit'
scheduleDialogTitle.value = `编辑医生排班 - ${row.doctor}`
// 显示排班弹窗
scheduleDialogVisible.value = true
// 生成一周的排班数据
const weekSchedule = generateWeekSchedule(filterParams.value.startDate)
// 找到与当前编辑行匹配的排班数据并更新
const targetSchedule = weekSchedule.find(item =>
item.weekday === row.weekday &&
item.timeSlot === row.timePeriod && // 注意这里应该是timeSlot而不是timePeriod
item.doctorName === row.doctor
)
if (targetSchedule) {
// 更新目标排班的属性
targetSchedule.doctorName = row.doctor
targetSchedule.room = row.clinic
targetSchedule.startTime = row.startTime
targetSchedule.endTime = row.endTime
targetSchedule.maxNumber = row.limitNumber
targetSchedule.appointmentItem = row.registerItem
targetSchedule.registrationFee = row.registerFee
targetSchedule.clinicItem = row.diagnosisItem
targetSchedule.treatmentFee = row.diagnosisFee
targetSchedule.online = row.isOnline
targetSchedule.stopClinic = row.isStopped
targetSchedule.stopReason = row.stopReason
}
// 设置科室信息
filterParams.value.orgName = currentDept.value?.orgName || currentDept.value?.organizationName || currentDept.value?.org || ''
filterParams.value.deptName = '未知科室' // 这里可能需要根据实际情况设置
// 设置排班列表
scheduleList.value = weekSchedule
}
// 计算号源记录
const calculateAppointmentRecords = (row) => {
const { startTime, endTime, maxNumber } = row
// 将时间转换为分钟数
const [startHour, startMinute] = startTime.split(':').map(Number)
const [endHour, endMinute] = endTime.split(':').map(Number)
const startTotalMinutes = startHour * 60 + startMinute
const endTotalMinutes = endHour * 60 + endMinute
// 计算总时长和间隔
const totalDuration = endTotalMinutes - startTotalMinutes
const interval = Math.floor(totalDuration / maxNumber)
// 生成号源记录
const records = []
for (let i = 0; i < maxNumber; i++) {
const minutes = startTotalMinutes + i * interval
const hour = Math.floor(minutes / 60).toString().padStart(2, '0')
const minute = (minutes % 60).toString().padStart(2, '0')
records.push({
index: i + 1,
time: `${hour}:${minute}`
})
}
return records
}
// 查看号源记录
const handleViewRecord = (row) => {
// 验证开始时间、结束时间和限号数量
if (!row.startTime || !row.endTime || !row.maxNumber) {
ElMessageBox.confirm('请先设置开始时间、结束时间和限号数量', '', {
confirmButtonText: '确定',
type: 'warning',
showCancelButton: false
})
return
}
// 计算号源记录
currentRow.value = row
appointmentRecords.value = calculateAppointmentRecords(row)
recordDialogVisible.value = true
}
// 保存排班
const handleSave = async () => {
try {
// 验证必填字段 - 只验证用户真正填写了信息的记录
const filledSchedules = scheduleList.value.filter(item => {
// 已有排班可能只有 doctorId没有 doctorName二者任一存在都视为可保存记录
return !!resolveDoctorName(item) || !!item.doctorId;
});
// 验证结束时间必须大于开始时间
const invalidTimeSchedules = filledSchedules.filter(item => {
if (item.startTime && item.endTime) {
return item.startTime >= item.endTime;
}
return false;
});
if (invalidTimeSchedules.length > 0) {
ElMessage.warning('结束时间必须大于开始时间,请检查排班记录');
return;
}
const incompleteSchedules = filledSchedules.filter(item => {
const isDoctorValid = !!resolveDoctorName(item) || !!item.doctorId;
const isRoomValid = item.room && item.room.trim() !== '';
const isStartTimeValid = item.startTime && typeof item.startTime === 'string';
const isEndTimeValid = item.endTime && typeof item.endTime === 'string';
const isMaxNumberValid = item.maxNumber !== null && item.maxNumber !== undefined &&
item.maxNumber !== '' && !isNaN(item.maxNumber) && parseInt(item.maxNumber) > 0;
return !(isDoctorValid && isRoomValid && isStartTimeValid && isEndTimeValid && isMaxNumberValid);
});
if (incompleteSchedules.length > 0) {
ElMessage.warning('请完善已填写排班记录的必填信息(医生、诊室、开始时间、结束时间、限号数量)');
return;
}
// 转换数据格式
const schedulesToProcess = filledSchedules.map(item => {
const regTypeValue = Number(
item.regType ?? (filterParams.value.appointmentType === '专家' ? 1 : 0)
);
const scheduleData = {
weekday: item.weekday,
timePeriod: item.timeSlot,
doctor: resolveDoctorName(item),
doctorId: item.doctorId,
clinic: item.room,
startTime: item.startTime,
endTime: item.endTime,
limitNumber: parseInt(item.maxNumber) || 0,
registerItem: item.appointmentItem || '',
registerFee: item.registrationFee || 0,
diagnosisItem: item.clinicItem || '',
diagnosisFee: item.treatmentFee || 0,
isOnline: item.online,
isStopped: item.stopClinic,
stopReason: item.stopClinic ? (item.stopReason || '') : '',
deptId: currentDept.value?.id || null,
regType: Number.isNaN(regTypeValue) ? 0 : regTypeValue,
scheduledDate: item.date // 添加具体日期字段
};
if (item.backendId) {
scheduleData.id = item.backendId;
}
return { scheduleData, isNew: !item.backendId };
});
if (schedulesToProcess.length === 0) {
ElMessage.info('没有需要保存的排班记录');
return;
}
let addSuccess = 0, addFail = 0;
let updateSuccess = 0, updateFail = 0;
const errors = [];
// 分别处理新增和更新
const recordsToAdd = schedulesToProcess.filter(p => p.isNew);
const recordsToUpdate = schedulesToProcess.filter(p => !p.isNew);
// 处理新增
for (const { scheduleData } of recordsToAdd) {
try {
const res = await addDoctorScheduleWithDate(scheduleData);
if (res.code === 200) {
addSuccess++;
} else {
addFail++;
errors.push(`新增失败: ${res.msg}`);
}
} catch (error) {
addFail++;
errors.push(`新增异常: ${error.message}`);
}
}
// 处理更新
for (const { scheduleData } of recordsToUpdate) {
try {
const res = await updateDoctorSchedule(scheduleData);
if (res.code === 200) {
updateSuccess++;
} else {
updateFail++;
errors.push(`更新失败: ${res.msg}`);
}
} catch (error) {
updateFail++;
errors.push(`更新异常: ${error.message}`);
}
}
// 统一报告结果
if (errors.length > 0) {
console.error('保存错误详情:', errors);
}
const messages = [];
if (addSuccess > 0) messages.push(`新增成功 ${addSuccess}`);
if (updateSuccess > 0) messages.push(`更新成功 ${updateSuccess}`);
if (addFail > 0 || updateFail > 0) {
const errorMessages = [];
if (addFail > 0) errorMessages.push(`新增失败 ${addFail}`);
if (updateFail > 0) errorMessages.push(`更新失败 ${updateFail}`);
ElMessage.warning([...messages, ...errorMessages].join(''));
} else if (messages.length > 0) {
ElMessage.success(messages.join(''));
}
// 如果有任何成功操作,则刷新数据
if (addSuccess > 0 || updateSuccess > 0) {
await reloadScheduleData();
}
} catch (error) {
console.error('保存排班失败:', error);
ElMessage.error('保存失败,请稍后重试');
}
}
// 取消操作
const handleCancel = () => {
scheduleDialogVisible.value = false
}
// 分页大小变化
const handleSizeChange = (size) => {
pagination.value.pageSize = size
getDeptList()
}
// 当前页码变化
const handleCurrentChange = (current) => {
pagination.value.currentPage = current
getDeptList()
}
// 获取门诊挂号的就诊科室数据
const fetchDepartmentOptions = async () => {
try {
// 使用当前查询参数进行过滤
const params = {
pageNum: 1,
pageSize: 100 // 下拉菜单加载较多数据以供选择
}
if (queryParams.value.deptName) {
params.name = queryParams.value.deptName
}
if (queryParams.value.orgName) {
params.orgName = queryParams.value.orgName
}
const response = await getRegisterOrganizations(params)
if (response.code === 200) {
// 适配不同的后端数据结构
let actualData = response.data
// 处理嵌套data结构
if (actualData && actualData.code === 200 && actualData.msg) {
actualData = actualData.data
}
// 确保数据是数组格式
if (Array.isArray(actualData)) {
// 将组织机构数据转换为科室选项格式
departmentOptions.value = actualData.map(org => ({
id: org.id,
code: org.busNo, // 使用组织机构编码
name: org.name, // 使用组织机构名称作为科室名称
deptName: org.name // 保持兼容性
}))
} else if (actualData && actualData.records) {
// 将组织机构数据转换为科室选项格式
departmentOptions.value = actualData.records.map(org => ({
id: org.id,
code: org.busNo, // 使用组织机构编码
name: org.name, // 使用组织机构名称作为科室名称
deptName: org.name // 保持兼容性
}))
} else if (actualData && actualData.content) {
// 将组织机构数据转换为科室选项格式
departmentOptions.value = actualData.content.map(org => ({
id: org.id,
code: org.busNo, // 使用组织机构编码
name: org.name, // 使用组织机构名称作为科室名称
deptName: org.name // 保持兼容性
}))
} else if (actualData && actualData.list) {
// 将组织机构数据转换为科室选项格式
departmentOptions.value = actualData.list.map(org => ({
id: org.id,
code: org.busNo, // 使用组织机构编码
name: org.name, // 使用组织机构名称作为科室名称
deptName: org.name // 保持兼容性
}))
} else {
departmentOptions.value = []
}
} else {
console.error('获取科室列表失败:', response.msg)
departmentOptions.value = []
}
} catch (error) {
console.error('获取科室列表失败:', error)
departmentOptions.value = []
}
}
// 页面加载时获取初始化数据
onMounted(async () => {
// 优化:合并初始化请求,减少并发
await fetchOrganizationList() // 该函数内部已包含 getRegisterOrganizations 调用
await getDeptList()
// 延迟加载非核心选项
setTimeout(() => {
fetchDepartmentOptions()
}, 300)
})
</script>
<style scoped lang="scss">
.appoinmentmanage-container {
width: 100%;
height: 100%;
padding: 20px;
background-color: #f5f7fa;
}
.appoinmentmanage-header {
margin-bottom: 20px;
}
.appoinmentmanage-title {
font-size: 20px;
font-weight: 600;
color: #333;
margin: 0;
}
.appoinmentmanage-content {
background-color: #fff;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.query-condition {
display: flex;
align-items: center;
margin-bottom: 20px;
gap: 16px;
}
.query-select {
width: 200px;
}
.query-button, .reset-button, .appointment-setting-button {
min-width: 100px;
}
.dept-table-container {
width: 100%;
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
/* 表格居中样式 */
.centered-table {
:deep(.el-table__header-wrapper th.el-table__cell),
:deep(.el-table__body-wrapper td.el-table__cell) {
text-align: center;
}
}
/* 医生排班样式 */
.doctorschedule-content {
background-color: #fff;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.filter-condition {
display: flex;
align-items: center;
margin-bottom: 20px;
gap: 16px;
}
.filter-label {
font-weight: 500;
white-space: nowrap;
}
.filter-select {
width: 150px;
}
.filter-date-picker {
width: 200px;
}
.radio-group {
display: flex;
align-items: center;
gap: 10px;
}
.schedule-table-container {
margin-bottom: 20px;
width: 100%;
overflow-x: auto;
}
.daily-schedule {
margin-bottom: 20px;
border: 1px solid #ebeef5;
border-radius: 4px;
overflow: hidden;
width: 100%;
}
.daily-header {
background-color: #f5f7fa;
padding: 10px 15px;
border-bottom: 1px solid #ebeef5;
display: flex;
align-items: center;
gap: 15px;
font-size: 16px;
font-weight: 600;
}
.date-text {
color: #333;
}
.weekday-text {
color: #606266;
font-weight: normal;
}
.schedule-table {
width: 100% !important;
min-width: 100% !important;
:deep(.el-table__header-wrapper) {
width: 100% !important;
border-top: none;
}
:deep(.el-table__body-wrapper) {
width: 100% !important;
}
:deep(.el-table__header-wrapper th.el-table__cell),
:deep(.el-table__body-wrapper td.el-table__cell) {
text-align: center;
padding: 8px 0;
}
/* 确保表格容器填满 */
:deep(.el-table__inner-wrapper) {
width: 100% !important;
}
/* 确保表格本身填满 */
:deep(.el-table__body) {
width: 100% !important;
}
/* 确保表格列正确分配宽度 */
:deep(.el-table__header) {
width: 100% !important;
}
:deep(.el-table__header tr),
:deep(.el-table__body tr) {
width: 100% !important;
}
/* 确保表格容器的最小宽度与内容匹配 */
:deep(.el-table) {
width: 100% !important;
min-width: 100% !important;
}
}
.inline-select {
width: 100%;
}
.inline-input {
width: 100%;
}
.time-picker {
width: 100%;
}
.record-icon {
font-size: 18px;
color: #409eff;
cursor: pointer;
transition: color 0.2s;
}
.record-icon:hover {
color: #66b1ff;
}
.bottom-buttons {
display: flex;
justify-content: flex-end;
gap: 16px;
}
/* 号源记录对话框样式 */
.appointment-records {
max-height: 300px;
overflow-y: auto;
padding: 10px 0;
}
.record-item {
padding: 8px 0;
border-bottom: 1px solid #f0f0f0;
}
.record-item:last-child {
border-bottom: none;
}
.record-time {
font-size: 16px;
color: #333;
}
.dialog-footer {
text-align: center;
}
/* 隐藏数字输入框的增减按钮 */
:deep(.el-input__inner[type="number"]) {
appearance: textfield;
-moz-appearance: textfield;
}
:deep(.el-input__inner[type="number"])::-webkit-outer-spin-button,
:deep(.el-input__inner[type="number"])::-webkit-inner-spin-button {
appearance: none;
-webkit-appearance: none;
margin: 0;
}
</style>
<style>
/* 全局样式:增大并突出显示全屏医生排班弹窗的关闭按钮 */
.el-dialog.is-fullscreen .el-dialog__headerbtn {
top: 15px !important;
right: 15px !important;
height: 50px !important;
width: 50px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
transition: background-color 0.2s ease-in-out;
}
.el-dialog.is-fullscreen .el-dialog__headerbtn .el-dialog__close {
font-size: 36px !important;
width: 36px !important;
height: 36px !important;
color: #F56C6C !important; /* 直接设置为红色 */
transition: all 0.2s ease-in-out;
}
.el-dialog.is-fullscreen .el-dialog__headerbtn:hover {
background-color: #fef0f0 !important;
}
.el-dialog.is-fullscreen .el-dialog__headerbtn:hover .el-dialog__close {
color: #dd4a4a !important; /* 悬停时更深的红色 */
transform: scale(1.2);
}
</style>
<style scoped>
/* 确保弹窗内容区占满宽度 */
:deep(.el-dialog__body) {
padding: 20px;
}
</style>