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

1958 lines
53 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="ticket-management-container">
<!-- 顶部搜索区域 -->
<div id="topSearchArea" class="top-search-area">
<!-- 搜索标签行 -->
<div class="search-labels">
<div class="search-label">号源日期</div>
<div class="search-label">状态</div>
<div class="search-label">患者姓名</div>
<div class="search-label">就诊卡号</div>
<div class="search-label">手机号</div>
<div class="search-label"></div> <!-- 为查询按钮预留空间 -->
</div>
<!-- 搜索输入行 -->
<div class="search-inputs">
<!-- 移动端汉堡菜单按钮 -->
<div class="hamburger-menu" @click="toggleSidebar" v-if="isMobile">
<i class="el-icon-menu"></i>
</div>
<div id="datePicker" class="date-picker">
<el-date-picker
v-model="selectedDate"
type="date"
placeholder="选择日期"
format="YYYY/MM/DD"
value-format="YYYY-MM-DD"
:shortcuts="dateShortcuts"
class="date-picker"
@change="onDateChange"
/>
</div>
<div id="statusFilter" class="status-filter">
<select id="status-select" class="search-select" v-model="selectedStatus" @change="onSearch">
<option value="all">全部</option>
<option value="unbooked">未预约</option>
<option value="booked">已预约</option>
<option value="checked">已取号</option>
<option value="cancelled">已停诊</option>
<option value="returned">已退号</option>
</select>
</div>
<div id="patientSearch" class="patient-search">
<input id="patient-name-input" class="search-input" placeholder="姓名" v-model="patientName">
</div>
<div id="cardSearch" class="card-search">
<input id="patient-card-input" class="search-input" placeholder="就诊卡号" v-model="patientCard">
</div>
<div id="phoneSearch" class="phone-search">
<input id="patient-phone-input" class="search-input" placeholder="手机号" v-model="patientPhone">
</div>
<div class="search-button">
<button id="search-button" class="search-btn" @click="onSearch" :disabled="isLoading">
<span v-if="isLoading" class="loading-text">搜索中...</span>
<span v-else>查询</span>
</button>
</div>
</div>
</div>
<!-- 主要内容区域 -->
<div id="mainContent" class="main-content">
<!-- 左侧筛选区域 -->
<div id="leftSidebar" class="left-sidebar" :class="{ 'sidebar-hidden': isMobile && !showSidebar, 'sidebar-mobile': isMobile }">
<!-- 科室列表 -->
<div class="section">
<h3 class="section-title">科室列表</h3>
<select id="department-list" class="search-select" v-model="selectedDepartment" @change="onDepartmentChange">
<option value="all">全部科室</option>
<option v-for="department in departments" :key="department.value" :value="department.value">
{{ department.label }}
</option>
</select>
</div>
<!-- 号源类型 -->
<div class="section">
<h3 class="section-title">号源类型</h3>
<div class="radio-group">
<label>
<input type="radio" name="type" value="general" v-model="selectedType" @change="onTypeChange">
<span>普通号</span>
</label>
<label>
<input type="radio" name="type" value="expert" v-model="selectedType" @change="onTypeChange">
<span>专家号</span>
</label>
</div>
</div>
<!-- 医生号源列表 -->
<div class="section">
<h3 class="section-title">医生号源列表</h3>
<input id="doctor-search" class="search-input" placeholder="搜索医生姓名" v-model="searchQuery" @input="onDoctorSearch">
<div class="doctor-list">
<div class="doctor-item" v-for="doctor in filteredDoctors" :key="doctor.id" :class="{ selected: selectedDoctorId === doctor.id }" @click="selectDoctor(doctor.id)">
<div class="doctor-name">{{ doctor.name }} <span class="doctor-available">余号:{{ doctor.available }}</span></div>
</div>
</div>
</div>
</div>
<!-- 右侧号源卡片区域 -->
<div class="right-content" :class="{ 'right-content-full': isMobile && !showSidebar }">
<!-- 使用网格布局的号源卡片列表 -->
<div class="ticket-grid" v-if="filteredTickets.length > 0">
<div v-for="(item, index) in filteredTickets" :key="item.slot_id" class="ticket-card" @dblclick="handleDoubleClick(item)" @contextmenu.prevent="handleRightClick($event, item)">
<!-- 序号放在最右侧 -->
<div class="ticket-index">{{ item.seqNo != null ? item.seqNo : index + 1 }}</div>
<!-- 1.时间 -->
<div class="ticket-id-time">{{ item.dateTime }}</div>
<!-- 2. 状态标签 -->
<div class="ticket-status" :class="getStatusClass(item.status)">
<span class="status-dot"></span>
{{ item.status }}
</div>
<!-- 3. 医生姓名(截断显示,悬停展示完整信息) -->
<div class="ticket-doctor" :title="item.doctor">{{ item.doctor }}</div>
<!-- 4. 科室名称 -->
<div class="ticket-department" :title="item.department || '未知科室'">
科室:{{ item.department || '未知科室' }}
</div>
<!-- 5. 挂号费 -->
<div class="ticket-fee">挂号费:{{ item.fee }}</div>
<!-- 6. 号源类型 -->
<div class="ticket-type">{{ item.ticketType === 'general' ? '普通' : '专家' }}</div>
<!-- 7. 已预约患者信息 -->
<div v-if="(item.status === '已预约' || item.status === '已取号') && item.patientName" class="ticket-patient">
{{ item.patientName }}({{ item.patientId }},{{ getGenderText(item.gender || item.patientGender) }})
</div>
<div class="ticket-actions">
<button class="action-button book-button" @click="openPatientSelectModal(item.slot_id)" :disabled="item.status !== '未预约'" :class="{ 'disabled': item.status !== '未预约' }">
<i class="el-icon-tickets"></i>
预约
</button>
</div>
</div>
</div>
<div class="pagination-container" v-if="totalTickets > 0">
<el-pagination
background
layout="total, sizes, prev, pager, next, jumper"
:total="totalTickets"
:current-page="currentPage"
:page-size="pageSize"
:page-sizes="[15, 30, 50, 100]"
@current-change="handlePageChange"
@size-change="handlePageSizeChange"
/>
</div>
<div v-if="filteredTickets.length === 0" class="empty-state">
<div class="empty-text">暂无号源数据</div>
</div>
</div>
</div>
<!-- 患者选择弹窗 -->
<div id="patient-select-modal" class="modal" v-if="showPatientModal">
<div class="modal-content">
<div class="modal-header">
<h3>选择患者</h3>
<button class="close-btn" @click="closePatientSelectModal">&times;</button>
</div>
<div class="modal-body">
<div class="patient-search-toolbar">
<input
id="patient-keyword"
class="search-input patient-keyword-input"
placeholder="姓名/就诊卡号/手机号/证件号回车查询"
v-model.trim="patientKeyword"
@keyup.enter="searchPatients"
>
<button id="patient-search-btn" class="search-btn" @click="searchPatients">查询</button>
<button id="patient-reset-btn" class="reset-btn" @click="resetPatientSearch">重置</button>
</div>
<div class="patient-table-container">
<table id="patient-table" class="patient-table" v-if="patients.length > 0">
<thead>
<tr>
<th>序号</th>
<th>患者姓名</th>
<th>就诊卡号</th>
<th>性别</th>
<th>身份证号</th>
<th>手机号</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr
v-for="(patient, index) in patients"
:key="patient._rowKey"
:class="{ selected: selectedPatientId === patient._rowKey }"
@click="selectPatient(patient._rowKey)"
>
<td>{{ index + 1 }}</td>
<td>{{ patient.name }}</td>
<td>{{ patient.identifierNo || patient.medicalCard || patient.id }}</td>
<td>{{ getGenderText(patient.genderEnum_enumText || patient.genderEnum || patient.gender || patient.sex) }}</td>
<td>{{ patient.idCard }}</td>
<td>{{ patient.phone }}</td>
<td>
<button class="select-btn" @click.stop="selectPatient(patient._rowKey)" :disabled="!patient._rowKey">选择</button>
</td>
</tr>
</tbody>
</table>
<!-- 无搜索结果提示 -->
<div v-else-if="hasSearchCriteria && !isLoading" class="no-results">
未找到符合条件的患者,请检查搜索条件
</div>
</div>
</div>
<div class="modal-footer">
<button class="confirm-btn" @click="confirmPatientSelection">确认</button>
<button class="cancel-btn" @click="closePatientSelectModal">取消</button>
</div>
</div>
</div>
<!-- 右键菜单 -->
<div
v-if="contextMenuVisible"
class="context-menu"
:style="{
left: contextMenuPosition.x + 'px',
top: contextMenuPosition.y + 'px'
}"
@click.stop
@contextmenu.prevent
>
<div v-if="selectedTicketForCancel && selectedTicketForCancel.status === '已预约'" class="menu-item" @click="confirmCancelAppointment">
取消预约
</div>
</div>
<!-- 点击页面其他地方关闭右键菜单 -->
<div
v-if="contextMenuVisible"
class="context-menu-overlay"
@click="closeContextMenu"
@contextmenu.prevent
></div>
</div>
</template>
<script>
import { listTicket, listDoctorSummary, bookTicket, cancelTicket } from '@/api/appoinmentmanage/ticket';
import { getPatientList } from '@/api/cardRenewal/api';
import { listRegisterOrganizations } from '@/api/appoinmentmanage/dept';
import { ElDatePicker, ElPagination, ElMessageBox, ElMessage } from 'element-plus';
import useUserStore from '@/store/modules/user';
const STATUS_CLASS_MAP = {
'未预约': 'status-unbooked',
'已预约': 'status-booked',
'已取号': 'status-checked',
'已退号': 'status-returned',
'已停诊': 'status-cancelled',
'已取消': 'status-cancelled'
};
export default {
name: 'OutpatientAppointment',
components: {
ElDatePicker,
ElPagination
},
data() {
return {
selectedDate: new Date().toISOString().split('T')[0],
selectedStatus: 'unbooked',
selectedDepartment: 'all',
selectedType: 'general', // 普通号默认选中
selectedDoctorId: null,
showPatientModal: false,
selectedPatientId: null,
selectedPatient: null,
currentTicket: null,
patientName: '',
patientCard: '',
patientPhone: '',
isLoading: false,
// 移动端相关变量
isMobile: false,
showSidebar: false,
// 分页相关属性
currentPage: 1,
pageSize: 15,
totalTickets: 0,
// 右键菜单相关
contextMenuVisible: false,
contextMenuPosition: { x: 0, y: 0 },
selectedTicketForCancel: null,
departments: [
{ value: 'all', label: '全部科室' }
],
doctors: [],
allTickets: [], // 存储所有原始号源数据
tickets: [], // 从API获取的真实号源数据
patients: [], // 从API获取的真实患者数据
searchQuery: '',
patientKeyword: '',
dateShortcuts: [
{ text: '今天', value: new Date() },
{ text: '明天', value: () => { const date = new Date(); date.setDate(date.getDate() + 1); return date; } },
{ text: '本周', value: () => { const date = new Date(); const day = date.getDay() || 7; date.setDate(date.getDate() - day + 1); return date; } },
{ text: '下周', value: () => { const date = new Date(); const day = date.getDay() || 7; date.setDate(date.getDate() - day + 8); return date; } },
{ text: '本月', value: () => { const date = new Date(); date.setDate(1); return date; } },
{ text: '下月', value: () => { const date = new Date(); date.setMonth(date.getMonth() + 1); date.setDate(1); return date; } }
]
}
},
computed: {
filteredDoctors() {
let filtered = [...this.doctors];
// 根据号源类型过滤医生列表
if (this.selectedType === 'general') {
filtered = filtered.filter(doctor => doctor.type === 'general');
} else if (this.selectedType === 'expert') {
filtered = filtered.filter(doctor => doctor.type === 'expert');
}
// 根据搜索关键词过滤
if (this.searchQuery) {
filtered = filtered.filter(doctor =>
doctor.name.includes(this.searchQuery)
);
}
return filtered;
},
filteredTickets() {
// filteredTickets直接返回this.tickets因为handleTicketResponse中已经完成了时间过滤
return [...this.tickets];
},
hasSearchCriteria() {
return !!this.patientKeyword?.trim();
}
},
methods: {
selectDoctor(doctorId) {
this.selectedDoctorId = this.selectedDoctorId === doctorId ? null : doctorId;
this.currentPage = 1;
this.fetchTickets({ refreshDepartments: false, refreshDoctors: false }).catch(() => {});
},
onTypeChange() {
this.onSearch();
},
onDepartmentChange() {
this.onSearch();
},
onDoctorSearch() {
// 使用计算属性实时过滤
},
onDateChange() {
this.onSearch();
},
openPatientSelectModal(ticketId) {
this.currentTicket = this.tickets.find(ticket => ticket.slot_id === ticketId);
this.patientKeyword = '';
this.patients = [];
this.selectedPatientId = null;
this.selectedPatient = null;
this.showPatientModal = true;
this.searchPatients();
},
getPatientUniqueId(patient, index = null) {
return patient.id || patient.patientId || patient.identifierNo || patient.medicalCard || patient.idCard ||
(index !== null ? `temp_${index}` : null);
},
matchPatientKeyword(patient, keyword) {
const normalizedKeyword = String(keyword || '').trim().toLowerCase();
if (!normalizedKeyword) {
return true;
}
const fields = [
patient.name,
patient.id,
patient.patientId,
patient.identifierNo,
patient.medicalCard,
patient.idCard,
patient.phone
];
return fields.some(field => String(field || '').toLowerCase().includes(normalizedKeyword));
},
searchPatients() {
const keyword = this.patientKeyword?.trim() || '';
const requestParams = keyword ? { searchKey: keyword } : {};
this.isLoading = true;
getPatientList(requestParams).then(response => {
let records = [];
if (response.data && response.data.records) {
records = response.data.records;
} else if (response.data && Array.isArray(response.data)) {
records = response.data;
} else if (Array.isArray(response)) {
records = response;
}
this.patients = records
.filter(patient => this.matchPatientKeyword(patient, keyword))
.map((patient, index) => {
const rowKey = this.getPatientUniqueId(patient, index);
return {
...patient,
// 统一口径:预约场景的就诊卡号使用患者标识表中的 identifierNo
medicalCard: patient.identifierNo || patient.medicalCard || '',
_rowKey: rowKey
};
});
this.patients.forEach((patient, index) => {
if (!patient.idCard) {
patient.idCard = patient._rowKey || `temp_${index}`;
}
});
if (keyword && this.patients.length === 0) {
ElMessage.warning('未找到符合条件的患者');
}
}).catch(error => {
this.patients = [];
ElMessage.error('获取患者列表失败:' + (error.message || '未知错误'));
}).finally(() => {
this.isLoading = false;
});
},
resetPatientSearch() {
this.patientKeyword = '';
this.selectedPatientId = null;
this.selectedPatient = null;
this.searchPatients();
},
// 双击未预约卡片触发患者选择流程
handleDoubleClick(ticket) {
if (ticket.status === '未预约') {
this.currentTicket = ticket;
this.patientKeyword = '';
this.selectedPatientId = null;
this.selectedPatient = null;
// 先打开弹窗,再加载患者数据,避免等待
this.showPatientModal = true;
// 调用患者搜索接口,加载患者列表
this.searchPatients();
}
},
// 右键已预约卡片显示取消预约菜单
handleRightClick(event, ticket) {
if (ticket.status === '已预约') {
this.selectedTicketForCancel = ticket;
this.contextMenuPosition = { x: event.clientX, y: event.clientY };
this.contextMenuVisible = true;
}
},
// 关闭右键菜单
closeContextMenu() {
this.contextMenuVisible = false;
this.selectedTicketForCancel = null;
},
// 确认取消预约
confirmCancelAppointment() {
if (this.selectedTicketForCancel) {
// 使用 ElMessageBox.confirm 进行二次确认,显示患者姓名
ElMessageBox.confirm(
`确认取消患者${this.selectedTicketForCancel.patientName || ''}的预约?`,
'系统提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
).then(() => {
// 用户点击确定,调用API取消预约
this.cancelAppointment(this.selectedTicketForCancel);
}).catch(() => {
// 用户取消操作
this.closeContextMenu();
});
}
},
// 取消预约API调用
cancelAppointment(ticket) {
if (!ticket || !ticket.slot_id) {
ElMessage.error('取消预约失败:缺少号源信息');
this.closeContextMenu();
return;
}
// 使用真实API调用取消预约,传递slot_id
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) {
// 清除该号源关联的所有患者信息
this.tickets[ticketIndex].status = '未预约';
this.tickets[ticketIndex].patientName = null;
this.tickets[ticketIndex].patientId = null;
this.tickets[ticketIndex].patientGender = null;
this.tickets[ticketIndex].gender = null;
this.tickets[ticketIndex].medicalCard = null;
this.tickets[ticketIndex].phone = null;
}
// 重新获取号源数据和医生余号数据
this.onSearch();
// 关闭上下文菜单
this.closeContextMenu();
ElMessage.success('预约已取消,号源已释放');
} else {
// 取消失败
const errorMsg = response.msg || response.message || '取消预约失败';
ElMessage.error(`取消预约失败:${errorMsg}`);
this.closeContextMenu();
}
}).catch(error => {
console.error('取消预约失败:', error);
this.closeContextMenu();
});
},
closePatientSelectModal() {
this.showPatientModal = false;
this.selectedPatientId = null;
this.selectedPatient = null;
},
selectPatient(patientId) {
if (this.patients.length === 0) {
ElMessage.error('患者数据未加载,请先搜索患者');
return;
}
this.selectedPatientId = patientId;
this.selectedPatient = this.patients.find(patient => patient._rowKey === patientId);
if (this.selectedPatient) {
ElMessage.success('已选中患者: ' + this.selectedPatient.name);
} else {
ElMessage.error('未找到该患者,请重新选择');
this.selectedPatientId = null;
}
},
confirmPatientSelection() {
if (!this.selectedPatientId || !this.selectedPatient) {
ElMessage.error('请选择患者');
return;
}
if (!this.currentTicket) {
ElMessage.error('预约信息错误,请重新选择号源');
this.closePatientSelectModal();
return;
}
// 再次验证号源是否仍处于可预约状态
if (this.currentTicket.status !== '未预约') {
ElMessage.error('该号源已被预约,请选择其他号源');
this.closePatientSelectModal();
return;
}
try {
const userStore = useUserStore();
const patientPrimaryId = this.selectedPatient.id || this.selectedPatient.patientId;
const medicalCard = this.selectedPatient.identifierNo || this.selectedPatient.medicalCard;
if (!patientPrimaryId) {
ElMessage.error('患者ID缺失,无法预约,请重新选择患者');
return;
}
if (!medicalCard) {
ElMessage.error('就诊卡号缺失,无法预约,请先维护患者就诊卡号');
return;
}
const appointmentData = {
ticketId: this.currentTicket.slot_id,
slotId: this.currentTicket.slot_id,
patientId: patientPrimaryId,
patientName: this.selectedPatient.name,
medicalCard,
phone: this.selectedPatient.phone,
gender: this.getGenderValueForBackend(this.selectedPatient),
fee: Number(this.currentTicket.fee) || 0,
regType: this.currentTicket.ticketType === 'general' ? '普通' : '专家',
tenant_id: userStore.tenantId || '1'
};
// 验证必填字段
if (!appointmentData.ticketId) {
ElMessage.error('号源ID无效');
return;
}
bookTicket(appointmentData).then(() => {
const ticketIndex = this.tickets.findIndex(t => t.slot_id === this.currentTicket.slot_id);
if (ticketIndex !== -1) {
this.tickets[ticketIndex].status = '已预约';
this.tickets[ticketIndex].patientName = this.selectedPatient.name;
this.tickets[ticketIndex].patientId = medicalCard;
this.tickets[ticketIndex].patientGender = this.selectedPatient.genderEnum_enumText || this.selectedPatient.genderEnum || this.selectedPatient.gender || this.selectedPatient.sex;
this.tickets[ticketIndex].gender = this.getGenderText(this.tickets[ticketIndex].patientGender);
}
this.closePatientSelectModal();
// 重新加载号源数据,确保显示最新状态
this.onSearch();
ElMessage.success('预约成功,号源已锁定。患者到院签到时需缴费取号。');
}).catch(error => {
console.error('预约失败:', error);
});
} catch (error) {
console.error('操作异常:', error);
}
},
// 切换侧边栏显示/隐藏
toggleSidebar() {
this.showSidebar = !this.showSidebar;
},
// 获取性别文本
getGenderText(genderValue) {
// 如果值为null或undefined,返回"未知"
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' ||
strValue === '男性' || strValue === '男士') {
return '男';
}
// 处理女性值
if (strValue === '1' || strValue === '女' || strValue === 'female' || strValue === 'f' ||
strValue === 'femalegender' || strValue === 'woman' || strValue === 'girl' ||
strValue === '女性' || strValue === '女士') {
return '女';
}
// 如果都不是,返回"未知"
return '未知';
},
// 获取用于后端的性别值
getGenderValueForBackend(patient) {
// 优先使用数字类型的genderEnum或gender字段
if (patient.genderEnum !== undefined && patient.genderEnum !== null) {
return patient.genderEnum;
}
if (patient.gender !== undefined && patient.gender !== null) {
return patient.gender;
}
// 如果genderEnum_enumText是"男性"或"女性",转换为对应的数字
if (patient.genderEnum_enumText) {
const text = patient.genderEnum_enumText.toLowerCase();
if (text === '男性' || text === '男') {
return 0;
} else if (text === '女性' || text === '女') {
return 1;
}
}
// 默认返回0(男性)
return 0;
},
// 检测是否为移动设备。
checkMobileDevice() {
this.isMobile = window.innerWidth <= 768;
},
getStatusClass(status) {
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,
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: 1,
limit: 1000 // 获取全量数据,前端过滤后再分页,避免过滤后分页错乱
};
},
buildDoctorQueryParams() {
return {
date: this.selectedDate,
type: this.selectedType === 'all' ? null : this.selectedType,
department: this.selectedDepartment === 'all' ? null : this.selectedDepartment,
// 传递浏览器当前时间戳,后端用这个时间过滤过期号源,保证和前端过滤一致
currentTime: new Date().getTime()
};
},
handleTicketResponse(ticketResponse) {
const payload = (() => {
if (!ticketResponse) return null;
if (ticketResponse.list || ticketResponse.records || ticketResponse.total !== undefined) {
return ticketResponse;
}
if (ticketResponse.data?.list || ticketResponse.data?.records || ticketResponse.data?.total !== undefined) {
return ticketResponse.data;
}
if (ticketResponse.data?.data) {
return ticketResponse.data.data;
}
return ticketResponse.data || ticketResponse;
})();
if (!payload) {
this.tickets = [];
this.allTickets = [];
this.totalTickets = 0;
return;
}
const records = payload.list || payload.records || [];
// 🔧 BugFix#344: 映射后端 statusEnum_enumText 到前端 status 字段
const mappedRecords = records.map(record => ({
...record,
status: record.statusEnum_enumText || record.status
}));
// 🔧 BugFix#398/#399: 时间过滤仅对"未预约"号源生效
// 已预约、已取号、已退号等状态的记录不应因时间过期被过滤
const currentTime = new Date().getTime();
const timeFilteredRecords = mappedRecords.filter(ticket => {
// 非未预约状态的记录不过滤时间
if (ticket.status && ticket.status !== '未预约') {
return true;
}
// 未预约号源:过滤已过期的
const ticketTime = new Date(ticket.dateTime).getTime();
return ticketTime > currentTime;
});
// 再进行状态过滤
const filteredRecords = this.applyStatusFilter(timeFilteredRecords);
// 现在我们获取了全量数据,在前端过滤完成后再分页
// 这样保证过滤后分页正确,不会出现第一页空第二页有数据
this.allTickets = [...filteredRecords];
this.totalTickets = this.allTickets.length;
// 从过滤后的全量数据中取出当前页
const start = (this.currentPage - 1) * this.pageSize;
const end = start + this.pageSize;
this.tickets = this.allTickets.slice(start, end);
},
applyStatusFilter(records = []) {
if (!Array.isArray(records) || records.length === 0) {
return [];
}
if (!this.selectedStatus || this.selectedStatus === 'all') {
return records;
}
// 🔧 BugFix#399: 确保已取号状态正确匹配
const statusMap = {
unbooked: ['未预约'],
booked: ['已预约'],
checked: ['已取号', '已签到'],
cancelled: ['已停诊', '已取消'],
returned: ['已退号']
};
const matchedStatusList = statusMap[this.selectedStatus] || [];
if (matchedStatusList.length === 0) {
return records;
}
return records.filter(item => matchedStatusList.includes(item?.status));
},
updateDoctorsListFromApi(doctorResponse) {
let doctorList = [];
const data = doctorResponse?.data;
if (Array.isArray(doctorResponse)) {
doctorList = doctorResponse;
} else if (Array.isArray(data)) {
doctorList = data;
} else if (Array.isArray(data?.list)) {
doctorList = data.list;
} else if (Array.isArray(data?.records)) {
doctorList = data.records;
} else if (Array.isArray(data?.data)) {
doctorList = data.data;
}
// 初始用后端返回的数据
let initialDoctors = doctorList
.map((doctor, index) => {
const id = doctor?.id ?? doctor?.doctorId ?? index;
const available = Number(doctor?.available ?? doctor?.availableNum ?? doctor?.available_num ?? 0);
return {
id: String(id),
name: doctor?.name || doctor?.doctorName || '',
available: Number.isFinite(available) ? available : 0,
type: doctor?.type || doctor?.ticketType || 'general'
};
})
.filter(doctor => !!doctor.name);
this.doctors = initialDoctors;
if (this.selectedDoctorId && !this.doctors.some(d => d.id === this.selectedDoctorId)) {
this.selectedDoctorId = null;
}
},
fetchTickets({ refreshDepartments = false, refreshDoctors = false } = {}) {
this.isLoading = true;
const ticketPromise = listTicket(this.buildQueryParams(this.currentPage));
const deptPromise = refreshDepartments
? listRegisterOrganizations({
pageNum: 1,
pageSize: 200
})
: Promise.resolve(null);
const doctorPromise = refreshDoctors
? listDoctorSummary(this.buildDoctorQueryParams())
: Promise.resolve(null);
return Promise.all([ticketPromise, deptPromise, doctorPromise]).then(([ticketResponse, deptResponse, doctorResponse]) => {
this.handleTicketResponse(ticketResponse);
if (refreshDepartments) {
this.updateDepartmentsListFromApi(deptResponse);
}
if (refreshDoctors) {
this.updateDoctorsListFromApi(doctorResponse);
}
}).catch(error => {
console.error('获取数据失败:', error);
this.tickets = [];
this.allTickets = [];
this.totalTickets = 0;
throw error;
}).finally(() => {
this.isLoading = false;
});
},
onSearch() {
if (this.isLoading) {
return;
}
this.currentPage = 1;
this.fetchTickets({ refreshDepartments: true, refreshDoctors: true }).catch(() => {});
},
handlePageChange(page) {
if (this.isLoading || page === this.currentPage) {
return;
}
this.currentPage = page;
this.fetchTickets({ refreshDepartments: false, refreshDoctors: false }).catch(() => {});
},
handlePageSizeChange(size) {
if (this.isLoading || size === this.pageSize) {
return;
}
this.pageSize = size;
this.currentPage = 1;
this.fetchTickets({ refreshDepartments: false, refreshDoctors: false }).catch(() => {});
},
updateDepartmentsListFromApi(deptResponse) {
let deptList = [];
const data = deptResponse?.data;
if (Array.isArray(data)) {
deptList = data;
} else if (Array.isArray(data?.records)) {
deptList = data.records;
} else if (Array.isArray(data?.list)) {
deptList = data.list;
} else if (Array.isArray(data?.content)) {
deptList = data.content;
} else if (Array.isArray(data?.data)) {
deptList = data.data;
}
if (deptList.length > 0) {
const departmentArray = deptList
.map(item => {
const deptName = item?.deptName || item?.name;
return deptName ? { value: deptName, label: deptName } : null;
})
.filter(Boolean)
.filter((dept, index, self) => index === self.findIndex(d => d.value === dept.value));
this.departments = [{ value: 'all', label: '全部科室' }, ...departmentArray];
} else {
this.departments = [{ value: 'all', label: '全部科室' }];
}
}
},
mounted() {
document.addEventListener('click', this.closeContextMenu);
// 初始化数据
this.selectedDate = new Date().toISOString().split('T')[0];
// 调用onSearch获取初始数据
this.onSearch();
// 检测是否为移动设备
this.checkMobileDevice();
// 监听窗口大小变化
window.addEventListener('resize', this.checkMobileDevice);
},
beforeUnmount() {
document.removeEventListener('click', this.closeContextMenu);
// 移除窗口大小变化监听
window.removeEventListener('resize', this.checkMobileDevice);
}
}
</script>
<style scoped>
/* 颜色变量定义 - 使用组件内CSS变量 */
.ticket-management-container {
--primary-color: #1890FF;
--secondary-color: #FF6B35;
--status-unbooked: #5A8DEE;
--status-booked: #FAAD14;
--status-checked: #52C41A;
--status-cancelled: #F5222D;
--border-color: #d9d9d9;
--text-primary: #333;
--text-secondary: #666;
--text-tertiary: #999;
--bg-light: #f8f9fa;
--bg-white: #fff;
--shadow: 0 2px 8px rgba(90, 141, 238, 0.08);
}
/* 基础样式 */
.ticket-management-container {
padding: 20px;
background-color: var(--bg-light);
min-height: 100vh;
}
/* 顶部搜索区域 */
.top-search-area {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 20px;
padding: 12px 16px;
background-color: #FFFFFF;
border-radius: 4px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
border: 1px solid #E8E8E8;
}
/* 搜索标签行 */
.search-labels {
display: flex;
gap: 12px;
align-items: center;
}
/* 搜索标签样式 */
.search-label {
font-size: 14px;
color: #333333;
font-weight: 500;
text-align: left;
flex-grow: 1;
min-width: 80px;
}
/* 搜索输入行 */
.search-inputs {
display: flex;
gap: 12px;
align-items: center;
}
/* 为输入行的每个容器设置flex-grow,使其均匀分布 */
.search-inputs > div {
flex-grow: 1;
min-width: 100px;
}
.hamburger-menu {
display: none;
cursor: pointer;
font-size: 20px;
color: var(--primary-color);
}
.date-picker {
width: 160px;
z-index: 1;
}
/* 搜索控件样式 */
.search-input {
padding: 6px 12px;
border: 1px solid #E8E8E8;
border-radius: 4px;
font-size: 14px;
background-color: #FFFFFF;
width: 100%;
height: 32px;
box-sizing: border-box;
}
.search-input:focus {
border-color: #1890FF;
outline: none;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.search-select {
padding: 6px 12px;
border: 1px solid #E8E8E8;
border-radius: 4px;
font-size: 14px;
background-color: #FFFFFF;
cursor: pointer;
width: 100%;
height: 32px;
box-sizing: border-box;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23999' d='M6 9L1 4h10z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 8px center;
background-size: 12px;
}
.search-select:focus {
border-color: #1890FF;
outline: none;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.search-btn {
padding: 0 16px;
background-color: #1890FF;
color: white;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
height: 32px;
box-sizing: border-box;
transition: background-color 0.3s;
}
.search-btn:hover {
background-color: #40a9ff;
}
.search-btn:active {
background-color: #096dd9;
}
.search-btn:disabled {
background-color: #e6f7ff;
color: #91d5ff;
cursor: not-allowed;
}
/* 状态筛选器样式 */
.status-filter {
z-index: 2;
}
.el-date-editor {
width: 100% !important;
}
.el-date-editor .el-input__inner {
height: 32px !important;
border-radius: 4px;
border: 1px solid #E8E8E8;
font-size: 14px;
padding: 0 36px 0 12px !important;
}
.el-date-editor .el-input__inner:focus {
border-color: #1890FF;
outline: none;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.el-date-editor .el-input__suffix {
right: 8px !important;
top: 50%;
transform: translateY(-50%);
}
.el-date-editor .el-input__suffix-inner {
display: flex;
align-items: center;
justify-content: center;
}
/* 主要内容区域 */
.main-content {
display: flex;
gap: 20px;
height: calc(100vh - 104px); /* 减去顶部搜索区域高度和内边距 */
overflow: hidden;
}
.left-sidebar {
width: 220px;
height: 100%;
background-color: white;
border-radius: 8px;
padding: 20px;
box-shadow: var(--shadow);
flex-shrink: 0;
transition: all 0.3s ease;
overflow-y: auto; /* 当内容超出高度时显示滚动条 */
}
/* 移动端侧边栏样式 */
.sidebar-mobile {
position: fixed;
left: 0;
top: 0;
height: 100vh;
z-index: 1000;
}
/* 侧边栏隐藏样式 */
.sidebar-hidden {
transform: translateX(-100%);
}
.right-content {
flex: 1;
background-color: white;
border-radius: 8px;
padding: 20px;
display: flex;
flex-direction: column;
min-height: 0;
transition: all 0.3s ease;
}
/* 移动端右侧内容区占满屏幕 */
.right-content-full {
width: 100%;
}
/* 响应式设计 */
@media (max-width: 768px) {
/* 顶部搜索区域改为纵向排列 */
.top-search-area {
flex-direction: column;
height: auto;
padding: 16px;
gap: 16px;
}
/* 汉堡菜单显示 */
.hamburger-menu {
display: block;
align-self: flex-start;
}
/* 搜索区域元素宽度100% */
.date-picker,
.status-filter,
.patient-search,
.card-search,
.phone-search,
.search-button,
.add-patient {
width: 100%;
}
/* 右侧内容区卡片布局调整 */
.virtual-list {
grid-template-columns: 1fr;
gap: 12px;
padding: 12px;
}
/* 左侧边栏默认隐藏 */
.left-sidebar {
transform: translateX(-100%);
}
/* 右侧内容区占满屏幕 */
.right-content {
width: 100%;
}
}
/* 确保卡片样式正确应用 */
.ticket-card {
box-shadow: var(--shadow);
overflow: visible;
transition: all 0.3s ease;
}
/* 卡片悬停效果 */
.ticket-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(90, 141, 238, 0.12);
}
/* 按钮悬停效果 */
.search-btn,
.add-patient-btn {
transition: all 0.3s ease;
}
.search-btn:hover,
.add-patient-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}
/* 输入框焦点效果 */
.search-input {
transition: all 0.3s ease;
}
.search-input:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
/* 医生列表项悬停效果 */
.doctor-item {
transition: all 0.3s ease;
}
.doctor-item:hover {
background-color: rgba(24, 144, 255, 0.05);
cursor: pointer;
}
/* 医生列表项选中效果 */
.doctor-item.selected {
background-color: rgba(24, 144, 255, 0.2);
border-left: 4px solid var(--primary-color);
font-weight: 600;
}
/* 加载动画 */
.loading-container {
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
gap: 8px;
color: var(--primary-color);
}
.loading-spinner {
width: 16px;
height: 16px;
border: 2px solid rgba(24, 144, 255, 0.2);
border-top-color: var(--primary-color);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* 左侧筛选区域 */
.section {
margin-bottom: 24px;
}
.section-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 12px;
color: #333;
}
.radio-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.radio-group label {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
}
.doctor-list {
margin-top: 12px;
max-height: calc(100vh - 300px); /* 限制医生列表高度 */
overflow-y: auto; /* 当内容超出高度时显示滚动条 */
}
.doctor-item {
padding: 12px;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
margin-bottom: 8px;
}
.doctor-item:hover {
background-color: #f0f7ff;
}
.doctor-item.selected {
background-color: #e6f2ff;
border-left: 4px solid var(--primary-color);
}
.doctor-name {
font-weight: 500;
display: flex;
justify-content: space-between;
align-items: center;
}
.doctor-available {
font-size: 12px;
color: var(--status-unbooked);
font-weight: normal;
}
/* 右侧号源卡片网格布局 */
.ticket-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 16px;
padding: 8px;
flex: 1;
min-height: 0;
overflow-y: auto;
align-content: start;
}
/* 加载更多样式 */
.loading-more,
.no-more {
grid-column: 1 / -1;
text-align: center;
padding: 20px;
color: #999;
font-size: 14px;
}
.empty-state {
display: flex;
justify-content: center;
align-items: center;
height: 600px;
color: var(--text-tertiary);
}
.empty-text {
font-size: 16px;
}
.ticket-card {
background: white;
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 8px rgba(90, 141, 238, 0.08);
border: 1px solid #e8e8e8;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
align-items: stretch;
position: relative;
min-height: 210px;
height: auto;
}
.ticket-card:hover {
box-shadow: 0 4px 12px rgba(90, 141, 238, 0.12);
transform: translateY(-2px);
border-color: #1890ff;
}
.ticket-number {
position: absolute;
top: 12px;
left: 12px;
width: 24px;
height: 24px;
border-radius: 50%;
background-color: #e6f7ff;
color: #1890ff;
font-size: 12px;
font-weight: 500;
display: flex;
justify-content: center;
align-items: center;
}
.ticket-index {
position: absolute;
top: 12px;
left: 12px;
color: #1890ff;
font-size: 14px;
font-weight: bold;
}
.ticket-time {
font-size: 14px;
color: #333;
font-weight: 500;
margin: 8px 0;
}
.ticket-status {
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
text-align: center;
min-width: 60px;
margin-bottom: 8px;
}
.status-unbooked {
background-color: #e6f3ff;
color: #1890ff;
}
.status-locked {
background-color: #fff1f0;
color: #f5222d;
}
.status-booked {
background-color: #fff8e6;
color: #fa8c16;
}
.status-checked {
background-color: #f6ffed;
color: #52c41a;
}
.status-returned {
background-color: #fff7e6;
color: #d46b08;
}
.status-cancelled {
background-color: #fff1f0;
color: #ff4d4f;
}
.ticket-doctor {
font-size: 14px;
color: #333;
margin-bottom: 8px;
text-align: center;
width: 100%;
line-height: 1.35;
white-space: normal;
word-break: break-word;
}
.ticket-department {
font-size: 12px;
color: #666;
margin-bottom: 8px;
text-align: center;
width: 100%;
line-height: 1.35;
white-space: normal;
word-break: break-word;
}
.ticket-id-time {
font-size: 14px;
color: #333;
margin-bottom: 8px;
text-align: center;
}
.ticket-fee {
font-size: 14px;
color: #333;
margin-bottom: 8px;
width: 100%;
text-align: center;
line-height: 1.35;
white-space: normal;
word-break: break-word;
}
.ticket-type {
font-size: 12px;
color: #999;
margin-bottom: 12px;
width: 100%;
text-align: center;
}
.ticket-actions {
margin-top: auto;
padding-top: 8px;
width: 100%;
display: flex;
justify-content: center;
}
.book-button {
padding: 4px 16px;
background-color: #1890ff;
color: white;
border: none;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
transition: all 0.3s ease;
align-self: center;
}
.book-button:hover:not(:disabled) {
background-color: #40a9ff;
}
.book-button:disabled {
background-color: #e6f7ff;
color: #91d5ff;
cursor: not-allowed;
}
/* 患者信息样式 */
.ticket-patient {
margin-top: 8px;
padding: 4px;
background-color: rgba(24, 144, 255, 0.05);
border-radius: 4px;
font-size: 12px;
text-align: center;
color: #333;
width: 100%;
line-height: 1.35;
white-space: normal;
word-break: break-word;
}
/* 患者电话号码样式 */
.ticket-phone {
margin-top: 4px;
padding: 4px;
background-color: rgba(34, 177, 76, 0.05);
border-radius: 4px;
font-size: 12px;
text-align: center;
color: #22b14c;
font-weight: 500;
}
/* 响应式设计 */
@media (max-width: 768px) {
.ticket-grid {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 12px;
}
.ticket-card {
padding: 12px;
min-height: 190px;
}
.ticket-doctor {
font-size: 13px;
}
.ticket-id-time {
font-size: 13px;
}
.ticket-index {
font-size: 13px;
}
}
/* 弹窗样式 */
.modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background-color: white;
border-radius: 8px;
width: 80%;
max-width: 800px;
/* 使用固定高度计算,确保footer始终可见 */
height: 80vh;
display: flex;
flex-direction: column;
overflow: hidden;
/* 确保弹窗在视口中居中且不被遮挡 */
margin: 20px;
/* 相对定位,为绝对定位的footer提供参考 */
position: relative;
}
.modal-header {
padding: 16px 20px;
border-bottom: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
align-items: center;
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 {
flex: 1;
overflow-y: auto;
padding: 20px;
/* 给body添加底部内边距,确保内容不被footer遮挡 */
padding-bottom: 100px;
}
.modal-footer {
flex-shrink: 0;
/* 确保footer始终在底部可见,不受其他元素影响 */
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: 10;
padding: 16px 20px;
border-top: 1px solid var(--border-color);
display: flex;
justify-content: flex-end;
gap: 12px;
background-color: white;
/* 添加阴影,增强视觉层次感 */
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
/* 确保footer不被body内容遮挡 */
}
.confirm-btn {
padding: 8px 24px;
background-color: #1890ff;
color: white;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: background-color 0.3s;
font-weight: 500;
box-shadow: 0 2px 4px rgba(24, 144, 255, 0.2);
}
.confirm-btn:hover {
background-color: #40a9ff;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
}
.confirm-btn:active {
background-color: #096dd9;
transform: translateY(0);
}
.cancel-btn {
padding: 8px 24px;
background-color: white;
color: var(--text-secondary);
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s;
}
.cancel-btn:hover {
color: var(--primary-color);
border-color: var(--primary-color);
}
.patient-search-toolbar {
display: flex;
align-items: center;
gap: 12px;
}
.patient-keyword-input {
flex: 1;
}
.reset-btn {
padding: 0 16px;
background-color: #f5f5f5;
color: #333;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
height: 32px;
box-sizing: border-box;
transition: all 0.3s;
}
.reset-btn:hover {
border-color: #40a9ff;
color: #1890ff;
background-color: #f0f7ff;
}
.patient-table-container {
margin-top: 20px;
overflow-x: auto;
}
.patient-table {
width: 100%;
border-collapse: collapse;
}
.patient-table th,
.patient-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #e0e0e0;
}
.patient-table th {
background-color: #f5f5f5;
font-weight: 600;
}
.patient-table tr:hover {
background-color: #fafafa;
}
.patient-table tr.selected {
background-color: #d6e8ff;
}
.patient-table tr.selected td {
background-color: #d6e8ff;
color: #0f3f8f;
font-weight: 600;
}
.patient-table tr.selected td:first-child {
box-shadow: inset 4px 0 0 #1677ff;
}
.patient-table tr.selected:hover td {
background-color: #c9e1ff;
}
.select-btn {
padding: 6px 12px;
background-color: #1890ff;
color: white;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 500;
box-shadow: 0 2px 4px rgba(24, 144, 255, 0.2);
}
.select-btn:hover:not(:disabled) {
background-color: #40a9ff;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
}
.select-btn:disabled {
background-color: #d9d9d9;
color: #fff;
cursor: not-allowed;
box-shadow: none;
}
/* 搜索提示样式 */
.search-hint {
text-align: center;
color: #999;
font-size: 14px;
padding: 20px;
background-color: #fafafa;
border-radius: 4px;
margin: 10px 0;
}
/* 加载指示器样式 */
.loading-indicator {
text-align: center;
color: #666;
font-size: 14px;
padding: 20px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.loading-spinner {
width: 16px;
height: 16px;
border: 2px solid #f3f3f3;
border-top: 2px solid #1890ff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.no-results {
text-align: center;
color: #666;
font-size: 14px;
padding: 20px;
background-color: #fff2f0;
border: 1px solid #ffccc7;
border-radius: 4px;
margin: 10px 0;
}
/* 右键菜单样式 */
.context-menu {
position: fixed;
background-color: white;
border: 1px solid #e0e0e0;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
z-index: 1001;
}
.menu-item {
padding: 8px 16px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s;
}
.menu-item:hover {
background-color: #f5f5f5;
}
.context-menu-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
}
.loading-text {
display: flex;
align-items: center;
}
.search-btn:disabled {
opacity: 0.7;
cursor: not-allowed;
}
/* 分页组件样式 */
.pagination-container {
display: flex;
justify-content: center;
margin-top: 12px;
padding: 20px 0;
background-color: #fafafa;
border-radius: 4px;
flex-shrink: 0;
}
.pagination-container .el-pagination {
font-size: 14px;
}
.pagination-container .el-pagination .el-pager li {
min-width: 36px;
height: 36px;
line-height: 36px;
border-radius: 4px;
margin: 0 2px;
}
.pagination-container .el-pagination .el-pager li:hover {
background-color: #e6f7ff;
color: #1890ff;
}
.pagination-container .el-pagination .el-pager li.active {
background-color: #1890ff;
color: white;
}
.pagination-container .el-pagination .btn-prev,
.pagination-container .el-pagination .btn-next {
width: 36px;
height: 36px;
border-radius: 4px;
margin: 0 2px;
}
.pagination-container .el-pagination .el-pagination__sizes {
margin-right: 10px;
}
.pagination-container .el-pagination .el-pagination__sizes .el-input {
width: 100px;
}
.pagination-container .el-pagination .el-pagination__jump {
margin-left: 10px;
}
.pagination-container .el-pagination .el-pagination__jump .el-input {
width: 60px;
}
</style>