340 预约管理-门诊预约挂号:选择患者弹窗列表数据字段显示错位

This commit is contained in:
2026-04-08 08:58:18 +08:00
parent ce64c4519c
commit e7413396b2

View File

@@ -196,7 +196,6 @@
<td>{{ index + 1 }}</td> <td>{{ index + 1 }}</td>
<td>{{ patient.name }}</td> <td>{{ patient.name }}</td>
<td>{{ patient.identifierNo || patient.medicalCard || patient.id }}</td> <td>{{ patient.identifierNo || patient.medicalCard || patient.id }}</td>
<td>{{ patient.identifierNo }}</td>
<td>{{ getGenderText(patient.genderEnum_enumText || patient.genderEnum || patient.gender || patient.sex) }}</td> <td>{{ getGenderText(patient.genderEnum_enumText || patient.genderEnum || patient.gender || patient.sex) }}</td>
<td>{{ patient.idCard }}</td> <td>{{ patient.idCard }}</td>
<td>{{ patient.phone }}</td> <td>{{ patient.phone }}</td>
@@ -313,37 +312,6 @@ export default {
} }
}, },
computed: { 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() { filteredDoctors() {
let filtered = [...this.doctors]; let filtered = [...this.doctors];
@@ -361,96 +329,10 @@ 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; 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() { filteredTickets() {
const filtered = this.filteredAndSortedTickets; return [...this.tickets];
const startIndex = (this.currentPage - 1) * this.pageSize;
const endIndex = startIndex + this.pageSize;
return filtered.slice(startIndex, endIndex);
},
// 更新总条数为过滤后的实际条数,分页自动处理
totalTickets() {
return this.filteredAndSortedTickets.length;
}, },
hasSearchCriteria() { hasSearchCriteria() {
return !!this.patientKeyword?.trim(); return !!this.patientKeyword?.trim();
@@ -460,8 +342,6 @@ export default {
selectDoctor(doctorId) { selectDoctor(doctorId) {
this.selectedDoctorId = this.selectedDoctorId === doctorId ? null : doctorId; this.selectedDoctorId = this.selectedDoctorId === doctorId ? null : doctorId;
this.currentPage = 1; this.currentPage = 1;
// 🔧 BugFix: 选择医生后不改变医生列表,余号计算基于 filteredAndSortedTickets 已经正确过滤
// 只需要重新获取号源,医生列表保持不变,余号计算会自动正确
this.fetchTickets({ refreshDepartments: false, refreshDoctors: false }).catch(() => {}); this.fetchTickets({ refreshDepartments: false, refreshDoctors: false }).catch(() => {});
}, },
onTypeChange() { onTypeChange() {
@@ -553,21 +433,21 @@ export default {
this.selectedPatient = null; this.selectedPatient = null;
this.searchPatients(); this.searchPatients();
}, },
// 双击未预约卡片触发患者选择流程 // 双击未预约卡片触发患者选择流程
handleDoubleClick(ticket) { handleDoubleClick(ticket) {
if (ticket.status === '未预约') { if (ticket.status === '未预约') {
this.currentTicket = ticket; this.currentTicket = ticket;
this.patientKeyword = ''; this.patientKeyword = '';
this.selectedPatientId = null; this.selectedPatientId = null;
this.selectedPatient = null; this.selectedPatient = null;
// 先打开弹窗,再加载患者数据,避免等待 // 先打开弹窗,再加载患者数据,避免等待
this.showPatientModal = true; this.showPatientModal = true;
// 调用患者搜索接口,加载患者列表 // 调用患者搜索接口,加载患者列表
this.searchPatients(); this.searchPatients();
} }
}, },
// 右键已预约卡片显示取消预约菜单 // 右键已预约卡片显示取消预约菜单
handleRightClick(event, ticket) { handleRightClick(event, ticket) {
if (ticket.status === '已预约') { if (ticket.status === '已预约') {
@@ -576,13 +456,13 @@ export default {
this.contextMenuVisible = true; this.contextMenuVisible = true;
} }
}, },
// 关闭右键菜单 // 关闭右键菜单
closeContextMenu() { closeContextMenu() {
this.contextMenuVisible = false; this.contextMenuVisible = false;
this.selectedTicketForCancel = null; this.selectedTicketForCancel = null;
}, },
// 确认取消预约 // 确认取消预约
confirmCancelAppointment() { confirmCancelAppointment() {
if (this.selectedTicketForCancel) { if (this.selectedTicketForCancel) {
@@ -604,7 +484,7 @@ export default {
}); });
} }
}, },
// 取消预约API调用 // 取消预约API调用
cancelAppointment(ticket) { cancelAppointment(ticket) {
if (!ticket || !ticket.slot_id) { if (!ticket || !ticket.slot_id) {
@@ -612,13 +492,13 @@ export default {
this.closeContextMenu(); this.closeContextMenu();
return; return;
} }
// 使用真实API调用取消预约传递slot_id // 使用真实API调用取消预约传递slot_id
cancelTicket(ticket.slot_id).then(response => { cancelTicket(ticket.slot_id).then(response => {
// 根据后端返回判断是否成功 // 根据后端返回判断是否成功
if (response.code === 200 || response.msg === '取消成功' || response.message === '取消成功') { if (response.code === 200 || response.msg === '取消成功' || response.message === '取消成功') {
console.log('取消预约成功,更新前端状态'); console.log('取消预约成功,更新前端状态');
// API调用成功后更新当前卡片状态 // API调用成功后更新当前卡片状态
const ticketIndex = this.tickets.findIndex(t => t.slot_id === ticket.slot_id); const ticketIndex = this.tickets.findIndex(t => t.slot_id === ticket.slot_id);
if (ticketIndex !== -1) { if (ticketIndex !== -1) {
@@ -633,7 +513,7 @@ export default {
} }
this.fetchTickets({ refreshDepartments: false, refreshDoctors: true }).catch(() => {}); this.fetchTickets({ refreshDepartments: false, refreshDoctors: true }).catch(() => {});
// 关闭上下文菜单 // 关闭上下文菜单
this.closeContextMenu(); this.closeContextMenu();
ElMessage.success('预约已取消,号源已释放'); ElMessage.success('预约已取消,号源已释放');
@@ -750,24 +630,24 @@ export default {
if (genderValue === null || genderValue === undefined) { if (genderValue === null || genderValue === undefined) {
return '未知'; return '未知';
} }
// 将值转换为字符串进行比较 // 将值转换为字符串进行比较
const strValue = String(genderValue).toLowerCase(); const strValue = String(genderValue).toLowerCase();
// 处理男性值 // 处理男性值
if (strValue === '0' || strValue === '男' || strValue === 'male' || strValue === 'm' || if (strValue === '0' || strValue === '男' || strValue === 'male' || strValue === 'm' ||
strValue === 'malegender' || strValue === 'man' || strValue === 'boy' || strValue === 'malegender' || strValue === 'man' || strValue === 'boy' ||
strValue === '男性' || strValue === '男士') { strValue === '男性' || strValue === '男士') {
return '男'; return '男';
} }
// 处理女性值 // 处理女性值
if (strValue === '1' || strValue === '女' || strValue === 'female' || strValue === 'f' || if (strValue === '1' || strValue === '女' || strValue === 'female' || strValue === 'f' ||
strValue === 'femalegender' || strValue === 'woman' || strValue === 'girl' || strValue === 'femalegender' || strValue === 'woman' || strValue === 'girl' ||
strValue === '女性' || strValue === '女士') { strValue === '女性' || strValue === '女士') {
return '女'; return '女';
} }
// 如果都不是,返回"未知" // 如果都不是,返回"未知"
return '未知'; return '未知';
}, },
@@ -780,7 +660,7 @@ export default {
if (patient.gender !== undefined && patient.gender !== null) { if (patient.gender !== undefined && patient.gender !== null) {
return patient.gender; return patient.gender;
} }
// 如果genderEnum_enumText是"男性"或"女性",转换为对应的数字 // 如果genderEnum_enumText是"男性"或"女性",转换为对应的数字
if (patient.genderEnum_enumText) { if (patient.genderEnum_enumText) {
const text = patient.genderEnum_enumText.toLowerCase(); const text = patient.genderEnum_enumText.toLowerCase();
@@ -793,8 +673,8 @@ export default {
// 默认返回0男性 // 默认返回0男性
return 0; return 0;
}, },
// 检测是否为移动设备 // 检测是否为移动设备
checkMobileDevice() { checkMobileDevice() {
this.isMobile = window.innerWidth <= 768; this.isMobile = window.innerWidth <= 768;
}, },
@@ -802,20 +682,21 @@ export default {
return STATUS_CLASS_MAP[status] || 'status-unbooked'; return STATUS_CLASS_MAP[status] || 'status-unbooked';
}, },
buildQueryParams(page = this.currentPage) { buildQueryParams(page = this.currentPage) {
const doctorId =
this.selectedDoctorId === null || this.selectedDoctorId === undefined || this.selectedDoctorId === ''
? null
: String(this.selectedDoctorId);
return { return {
date: this.selectedDate, date: this.selectedDate,
status: null, // 状态过滤在前端做 status: this.selectedStatus === 'all' ? null : this.selectedStatus,
type: this.selectedType === 'all' ? null : this.selectedType, type: this.selectedType === 'all' ? null : this.selectedType,
department: this.selectedDepartment === 'all' ? null : this.selectedDepartment, department: this.selectedDepartment === 'all' ? null : this.selectedDepartment,
doctorId: null, // 🎯 关键:永远不传 doctorId 给后端,后端返回全量数据 doctorId,
// 医生过滤、状态过滤、患者搜索都在前端做,才能保证所有医生余号统计正确 name: this.patientName?.trim() || null,
name: null, card: this.patientCard?.trim() || null,
card: null, phone: this.patientPhone?.trim() || null,
phone: null, page,
// 🎯 获取全量数据到前端,由前端做过滤和分页,保证余号统计总是正确 limit: this.pageSize
// 号源数量每个日期每个科室不会太多,全量获取可行
page: 1,
limit: 10000
}; };
}, },
buildDoctorQueryParams() { buildDoctorQueryParams() {
@@ -843,14 +724,20 @@ export default {
if (!payload) { if (!payload) {
this.tickets = []; this.tickets = [];
this.allTickets = []; this.allTickets = [];
this.totalTickets = 0;
return; return;
} }
let records = payload.list || payload.records || []; const records = payload.list || payload.records || [];
// 获取全量数据,应用状态过滤后保存所有数据到 tickets const filteredRecords = this.applyStatusFilter(records);
// 过滤、余号统计、分页都由前端完成,保证余号计算正确 const total = Number(payload.total);
records = this.applyStatusFilter(records); this.tickets = [...filteredRecords];
this.tickets = [...records]; this.allTickets = [...filteredRecords];
this.allTickets = [...records]; // 当按状态筛选时,优先使用前端过滤后的数量,避免后端状态未生效导致“显示全部”
if (this.selectedStatus && this.selectedStatus !== 'all') {
this.totalTickets = this.tickets.length;
} else {
this.totalTickets = Number.isFinite(total) ? total : this.tickets.length;
}
}, },
applyStatusFilter(records = []) { applyStatusFilter(records = []) {
if (!Array.isArray(records) || records.length === 0) { if (!Array.isArray(records) || records.length === 0) {
@@ -1005,8 +892,8 @@ export default {
</script> </script>
<style scoped> <style scoped>
/* 颜色变量定义 */ /* 颜色变量定义 - 使用组件内CSS变量 */
:root { .ticket-management-container {
--primary-color: #1890FF; --primary-color: #1890FF;
--secondary-color: #FF6B35; --secondary-color: #FF6B35;
--status-unbooked: #5A8DEE; --status-unbooked: #5A8DEE;
@@ -1080,7 +967,8 @@ export default {
} }
.date-picker { .date-picker {
width: 100%; width: 160px;
z-index: 1;
} }
/* 搜索控件样式 */ /* 搜索控件样式 */
@@ -1151,12 +1039,6 @@ export default {
cursor: not-allowed; cursor: not-allowed;
} }
/* 日期选择器样式 */
.date-picker {
width: 160px;
z-index: 1;
}
/* 状态筛选器样式 */ /* 状态筛选器样式 */
.status-filter { .status-filter {
z-index: 2; z-index: 2;
@@ -1242,50 +1124,50 @@ export default {
width: 100%; width: 100%;
} }
/* 响应式设计 */ /* 响应式设计 */
@media (max-width: 768px) { @media (max-width: 768px) {
/* 顶部搜索区域改为纵向排列 */ /* 顶部搜索区域改为纵向排列 */
.top-search-area { .top-search-area {
flex-direction: column; flex-direction: column;
height: auto; height: auto;
padding: 16px; padding: 16px;
gap: 16px; gap: 16px;
} }
/* 汉堡菜单显示 */ /* 汉堡菜单显示 */
.hamburger-menu { .hamburger-menu {
display: block; display: block;
align-self: flex-start; align-self: flex-start;
} }
/* 搜索区域元素宽度100% */ /* 搜索区域元素宽度100% */
.date-picker, .date-picker,
.status-filter, .status-filter,
.patient-search, .patient-search,
.card-search, .card-search,
.phone-search, .phone-search,
.search-button, .search-button,
.add-patient { .add-patient {
width: 100%; width: 100%;
} }
/* 右侧内容区卡片布局调整 */ /* 右侧内容区卡片布局调整 */
.virtual-list { .virtual-list {
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: 12px; gap: 12px;
padding: 12px; padding: 12px;
} }
/* 左侧边栏默认隐藏 */ /* 左侧边栏默认隐藏 */
.left-sidebar { .left-sidebar {
transform: translateX(-100%); transform: translateX(-100%);
} }
/* 右侧内容区占满屏幕 */ /* 右侧内容区占满屏幕 */
.right-content { .right-content {
width: 100%; width: 100%;
} }
} }
/* 确保卡片样式正确应用 */ /* 确保卡片样式正确应用 */
.ticket-card { .ticket-card {
@@ -1711,9 +1593,28 @@ export default {
} }
.modal-header { .modal-header {
padding: 16px 20px;
border-bottom: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0; flex-shrink: 0;
} }
.modal-header h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
}
.close-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #999;
}
.modal-body { .modal-body {
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
@@ -1781,32 +1682,6 @@ export default {
border-color: var(--primary-color); border-color: var(--primary-color);
} }
.modal-header {
padding: 16px 20px;
border-bottom: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-header h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
}
.close-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #999;
}
.modal-body {
padding: 20px;
}
.patient-search-toolbar { .patient-search-toolbar {
display: flex; display: flex;
align-items: center; align-items: center;