diff --git a/openhis-ui-vue3/src/views/appoinmentmanage/outpatientAppointment/index.vue b/openhis-ui-vue3/src/views/appoinmentmanage/outpatientAppointment/index.vue
index 869d573a..99176dac 100644
--- a/openhis-ui-vue3/src/views/appoinmentmanage/outpatientAppointment/index.vue
+++ b/openhis-ui-vue3/src/views/appoinmentmanage/outpatientAppointment/index.vue
@@ -196,6 +196,7 @@
{{ index + 1 }} |
{{ patient.name }} |
{{ patient.identifierNo || patient.medicalCard || patient.id }} |
+ {{ patient.identifierNo }} |
{{ getGenderText(patient.genderEnum_enumText || patient.genderEnum || patient.gender || patient.sex) }} |
{{ patient.idCard }} |
{{ patient.phone }} |
@@ -312,6 +313,37 @@ export default {
}
},
computed: {
+ // 全部号源经过日期过期过滤后的数据(不按医生过滤,不按患者搜索过滤),用于统计医生余号
+ allTicketsForDoctorCount() {
+ let filtered = [...this.tickets];
+
+ // 🎯 只过滤过期号源,不按医生过滤,不按患者搜索过滤
+ // 这样余号统计总是基于该日期下所有号源,得到正确的每个医生余号
+ const now = new Date();
+
+ filtered = filtered.filter(ticket => {
+ // dateTime 格式示例:"2024-01-01 08:00-09:00"
+ const parts = (ticket.dateTime || '').split(' ');
+ if (parts.length < 2) return true; // 如果格式不正确,保留显示
+
+ const dateStr = parts[0];
+ const timeRangeStr = parts[1];
+ if (!dateStr || !timeRangeStr) return true;
+
+ // 提取开始时间
+ const startTimeStr = timeRangeStr.split('-')[0]; // "08:00"
+ if (!startTimeStr) return true;
+
+ // 构建号源开始时间的完整 Date 对象
+ const ticketStartStr = `${dateStr} ${startTimeStr}`;
+ const ticketStart = new Date(ticketStartStr);
+
+ // 只显示开始时间晚于当前时间的号源
+ return ticketStart > now;
+ });
+
+ return filtered;
+ },
filteredDoctors() {
let filtered = [...this.doctors];
@@ -329,10 +361,96 @@ export default {
);
}
+ // 🎯 实时更新余号数量:统计该医生当前筛选条件下剩余可预约(未预约 + 未过期)号源数量
+ // 使用全部未过期号源统计(不按选中医生过滤),这样所有医生余号都正确显示
+ const availableCountMap = {};
+ this.allTicketsForDoctorCount.forEach(ticket => {
+ const doctorId = String(ticket.doctorId || ticket.doctor_id);
+ if (!availableCountMap[doctorId]) {
+ availableCountMap[doctorId] = 0;
+ }
+ // 只有未预约的号源才算作可预约余号
+ if (ticket.status === '未预约') {
+ availableCountMap[doctorId]++;
+ }
+ });
+
+ // 更新每个医生的余号数量
+ filtered = filtered.map(doctor => {
+ const actualAvailable = availableCountMap[String(doctor.id)] || 0;
+ return {
+ ...doctor,
+ available: actualAvailable
+ };
+ });
+
return filtered;
},
+ // 过滤并排序后的完整号源列表(用于右侧显示)
+ filteredAndSortedTickets() {
+ // 从已经过滤掉过期的全部数据开始
+ let filtered = [...this.allTicketsForDoctorCount];
+
+ // 🎯 根据选中的医生过滤(右侧只显示选中医生的号源)
+ if (this.selectedDoctorId) {
+ const doctorIdStr = String(this.selectedDoctorId);
+ filtered = filtered.filter(ticket => {
+ const ticketDoctorId = String(ticket.doctorId || ticket.doctor_id || '');
+ return ticketDoctorId === doctorIdStr;
+ });
+ }
+
+ // 🎯 根据患者搜索条件过滤
+ if (this.patientName?.trim()) {
+ const keyword = this.patientName.trim().toLowerCase();
+ filtered = filtered.filter(ticket =>
+ (ticket.patientName || '').toLowerCase().includes(keyword)
+ );
+ }
+ if (this.patientCard?.trim()) {
+ const keyword = this.patientCard.trim().toLowerCase();
+ filtered = filtered.filter(ticket =>
+ (ticket.patientId || '').toLowerCase().includes(keyword) ||
+ (ticket.medicalCard || '').toLowerCase().includes(keyword)
+ );
+ }
+ if (this.patientPhone?.trim()) {
+ const keyword = this.patientPhone.trim().toLowerCase();
+ filtered = filtered.filter(ticket =>
+ (ticket.phone || '').toLowerCase().includes(keyword)
+ );
+ }
+
+ // 🎯 按开始时间升序排序 → 较早的号源排在前面
+ filtered.sort((a, b) => {
+ const getStartTime = (ticket) => {
+ const parts = (ticket.dateTime || '').split(' ');
+ if (parts.length < 2) return new Date(0).getTime();
+ const dateStr = parts[0];
+ const timeRangeStr = parts[1];
+ const startTimeStr = (timeRangeStr || '').split('-')[0];
+ if (!startTimeStr) return new Date(0).getTime();
+ const ticketStartStr = `${dateStr} ${startTimeStr}`;
+ return new Date(ticketStartStr).getTime();
+ };
+
+ const timeA = getStartTime(a);
+ const timeB = getStartTime(b);
+ return timeA - timeB;
+ });
+
+ return filtered;
+ },
+ // 🎯 分页:按照用户选择的每页条数分页,返回当前页的数据
filteredTickets() {
- return [...this.tickets];
+ const filtered = this.filteredAndSortedTickets;
+ const startIndex = (this.currentPage - 1) * this.pageSize;
+ const endIndex = startIndex + this.pageSize;
+ return filtered.slice(startIndex, endIndex);
+ },
+ // 更新总条数为过滤后的实际条数,分页自动处理
+ totalTickets() {
+ return this.filteredAndSortedTickets.length;
},
hasSearchCriteria() {
return !!this.patientKeyword?.trim();
@@ -342,6 +460,8 @@ export default {
selectDoctor(doctorId) {
this.selectedDoctorId = this.selectedDoctorId === doctorId ? null : doctorId;
this.currentPage = 1;
+ // 🔧 BugFix: 选择医生后不改变医生列表,余号计算基于 filteredAndSortedTickets 已经正确过滤
+ // 只需要重新获取号源,医生列表保持不变,余号计算会自动正确
this.fetchTickets({ refreshDepartments: false, refreshDoctors: false }).catch(() => {});
},
onTypeChange() {
@@ -433,21 +553,21 @@ export default {
this.selectedPatient = null;
this.searchPatients();
},
-
+
// 双击未预约卡片触发患者选择流程
handleDoubleClick(ticket) {
if (ticket.status === '未预约') {
this.currentTicket = ticket;
this.patientKeyword = '';
this.selectedPatientId = null;
- this.selectedPatient = null;
+ this.selectedPatient = null;
// 先打开弹窗,再加载患者数据,避免等待
this.showPatientModal = true;
// 调用患者搜索接口,加载患者列表
this.searchPatients();
}
},
-
+
// 右键已预约卡片显示取消预约菜单
handleRightClick(event, ticket) {
if (ticket.status === '已预约') {
@@ -456,13 +576,13 @@ export default {
this.contextMenuVisible = true;
}
},
-
+
// 关闭右键菜单
closeContextMenu() {
this.contextMenuVisible = false;
this.selectedTicketForCancel = null;
},
-
+
// 确认取消预约
confirmCancelAppointment() {
if (this.selectedTicketForCancel) {
@@ -484,7 +604,7 @@ export default {
});
}
},
-
+
// 取消预约API调用
cancelAppointment(ticket) {
if (!ticket || !ticket.slot_id) {
@@ -492,13 +612,13 @@ export default {
this.closeContextMenu();
return;
}
-
+
// 使用真实API调用取消预约,传递slot_id
- cancelTicket(ticket.slot_id).then(response => {
+ cancelTicket(ticket.slot_id).then(response => {
// 根据后端返回判断是否成功
if (response.code === 200 || response.msg === '取消成功' || response.message === '取消成功') {
console.log('取消预约成功,更新前端状态');
-
+
// API调用成功后,更新当前卡片状态
const ticketIndex = this.tickets.findIndex(t => t.slot_id === ticket.slot_id);
if (ticketIndex !== -1) {
@@ -513,7 +633,7 @@ export default {
}
this.fetchTickets({ refreshDepartments: false, refreshDoctors: true }).catch(() => {});
-
+
// 关闭上下文菜单
this.closeContextMenu();
ElMessage.success('预约已取消,号源已释放');
@@ -630,24 +750,24 @@ export default {
if (genderValue === null || genderValue === undefined) {
return '未知';
}
-
+
// 将值转换为字符串进行比较
const strValue = String(genderValue).toLowerCase();
-
+
// 处理男性值
- if (strValue === '0' || strValue === '男' || strValue === 'male' || strValue === 'm' ||
- strValue === 'malegender' || strValue === 'man' || strValue === 'boy' ||
+ if (strValue === '0' || strValue === '男' || strValue === 'male' || strValue === 'm' ||
+ strValue === 'malegender' || strValue === 'man' || strValue === 'boy' ||
strValue === '男性' || strValue === '男士') {
return '男';
}
-
+
// 处理女性值
- if (strValue === '1' || strValue === '女' || strValue === 'female' || strValue === 'f' ||
- strValue === 'femalegender' || strValue === 'woman' || strValue === 'girl' ||
+ if (strValue === '1' || strValue === '女' || strValue === 'female' || strValue === 'f' ||
+ strValue === 'femalegender' || strValue === 'woman' || strValue === 'girl' ||
strValue === '女性' || strValue === '女士') {
return '女';
}
-
+
// 如果都不是,返回"未知"
return '未知';
},
@@ -660,7 +780,7 @@ export default {
if (patient.gender !== undefined && patient.gender !== null) {
return patient.gender;
}
-
+
// 如果genderEnum_enumText是"男性"或"女性",转换为对应的数字
if (patient.genderEnum_enumText) {
const text = patient.genderEnum_enumText.toLowerCase();
@@ -673,7 +793,7 @@ export default {
// 默认返回0(男性)
return 0;
},
-
+
// 检测是否为移动设备
checkMobileDevice() {
this.isMobile = window.innerWidth <= 768;
@@ -682,21 +802,20 @@ export default {
return STATUS_CLASS_MAP[status] || 'status-unbooked';
},
buildQueryParams(page = this.currentPage) {
- const doctorId =
- this.selectedDoctorId === null || this.selectedDoctorId === undefined || this.selectedDoctorId === ''
- ? null
- : String(this.selectedDoctorId);
return {
date: this.selectedDate,
- status: this.selectedStatus === 'all' ? null : this.selectedStatus,
+ status: null, // 状态过滤在前端做
type: this.selectedType === 'all' ? null : this.selectedType,
department: this.selectedDepartment === 'all' ? null : this.selectedDepartment,
- doctorId,
- name: this.patientName?.trim() || null,
- card: this.patientCard?.trim() || null,
- phone: this.patientPhone?.trim() || null,
- page,
- limit: this.pageSize
+ doctorId: null, // 🎯 关键:永远不传 doctorId 给后端,后端返回全量数据
+ // 医生过滤、状态过滤、患者搜索都在前端做,才能保证所有医生余号统计正确
+ name: null,
+ card: null,
+ phone: null,
+ // 🎯 获取全量数据到前端,由前端做过滤和分页,保证余号统计总是正确
+ // 号源数量每个日期每个科室不会太多,全量获取可行
+ page: 1,
+ limit: 10000
};
},
buildDoctorQueryParams() {
@@ -724,20 +843,14 @@ export default {
if (!payload) {
this.tickets = [];
this.allTickets = [];
- this.totalTickets = 0;
return;
}
- const records = payload.list || payload.records || [];
- const filteredRecords = this.applyStatusFilter(records);
- const total = Number(payload.total);
- this.tickets = [...filteredRecords];
- this.allTickets = [...filteredRecords];
- // 当按状态筛选时,优先使用前端过滤后的数量,避免后端状态未生效导致“显示全部”
- if (this.selectedStatus && this.selectedStatus !== 'all') {
- this.totalTickets = this.tickets.length;
- } else {
- this.totalTickets = Number.isFinite(total) ? total : this.tickets.length;
- }
+ let records = payload.list || payload.records || [];
+ // 获取全量数据,应用状态过滤后保存所有数据到 tickets
+ // 过滤、余号统计、分页都由前端完成,保证余号计算正确
+ records = this.applyStatusFilter(records);
+ this.tickets = [...records];
+ this.allTickets = [...records];
},
applyStatusFilter(records = []) {
if (!Array.isArray(records) || records.length === 0) {
@@ -892,8 +1005,8 @@ export default {