Angular 16 FullCalendar初始加载无数据问题求助
问题分析与解决方案
问题根源
- 数据与日历初始化时序不匹配:初始加载时,异步数据还未处理完成就多次调用
LoadCalendar,导致日历渲染时无法获取完整的考勤、节假日数据。 - 冗余的模板绑定:模板中单独绑定了
[dayCellDidMount],但组件类中并无该独立方法,与calendarOptions内的回调逻辑冲突。 - 数据处理逻辑分散:在
flatMap循环内每次处理一条数据就初始化日历,导致日历配置被多次覆盖,且数据未完全就绪。
修复步骤
1. 重构数据处理逻辑
修改Load方法,确保所有数据处理完成后再初始化/更新日历,同时清空旧数据避免月份切换时数据残留:
Load(fecha: Date) { // 清空历史数据,避免跨月份数据污染 this.datosAsistencia = {}; this.diasFeriadoLegal = {}; this.diasPermisoAdministrativo = {}; this.diasLicenciaMedica = {}; this.recursoFuncionarioService.AsistenciaDiariaGetItems(fecha).subscribe((response: Response) => { this.asistenciaDiaria = response.Data; // 批量处理所有考勤数据 this.asistenciaDiaria.forEach((item) => { const dia = new Date(item.Dia); const fechaKey = dia.toISOString().split('T')[0]; if (item.ConFeriadoLegal) this.diasFeriadoLegal[fechaKey] = true; if (item.ConPermisoAdministrativo) this.diasPermisoAdministrativo[fechaKey] = true; if (item.ConLicenciaMedica) this.diasLicenciaMedica[fechaKey] = true; this.datosAsistencia[fechaKey] = { entradaHora: item.EntradaHora ? this.formatTime(item.EntradaHora) : '', salidaHora: item.SalidaHora ? this.formatTime(item.SalidaHora) : '', entradaSede: item.EntradaSede || '', salidaSede: item.SalidaSede || '', horasTrabajadas: item.MinutosTrabajados ? this.formatWorkTime(item.MinutosTrabajados) : '' }; }); // 所有数据处理完成后再初始化日历 this.datosCargados = true; this.LoadCalendar(); // 日历已存在时强制刷新视图,确保新数据生效 if (this.fullcalendar) { this.fullcalendar.getApi().render(); } }); }
2. 修复模板冗余绑定
移除模板中多余的[dayCellDidMount]绑定,并添加加载状态提示:
<div class="grid"> <div class="col-12"> <div class="view-titulo">Consulta horario</div> </div> </div> <div class="flex justify-content-evenly flex-wrap p-3"> <div> <i class="pi pi-arrow-right" style="color: green"></i> <label class="pl-2">Entrada</label> </div> <div> <i class="pi pi-arrow-left" style="color: red"></i> <label class="pl-2">Salida</label> </div> <div> <i class="pi pi-clock" style="color: blue"></i> <label class="pl-2">Horas trabajadas</label> </div> <div> <i class="pi pi-calendar-plus" style="color: red"></i> <label class="pl-2">Licencia médica</label> </div> <div> <i class="pi pi-calendar" style="color: green"></i> <label class="pl-2">Feriado Legal</label> </div> <div> <i class="pi pi-briefcase" style="color: orange"></i> <label class="pl-2">Permiso Administrativo</label> </div> </div> <div class="grid"> <div class="col-12"> <!-- 添加加载状态提示 --> <div *ngIf="!datosCargados" class="text-center p-5">Cargando datos...</div> <full-calendar class="full-calendar-custom" *ngIf="calendarOptions" #fullcalendar [options]="calendarOptions"> </full-calendar> </div> </div> </div>
3. 优化LoadCalendar方法(可选)
避免每次都重新创建完整的calendarOptions,可复用已有配置仅更新必要部分:
LoadCalendar(): void { if(this.datosCargados){ // 若已初始化过日历,仅刷新视图;否则创建新配置 if (this.calendarOptions) { if (this.fullcalendar) { this.fullcalendar.getApi().render(); } return; } this.calendarOptions = { plugins: [dayGridPlugin], initialView: 'dayGridMonth', firstDay: 1, fixedWeekCount: true, showNonCurrentDates: false, editable: false, weekends: true, dayMaxEvents: true, locale: esLocale, eventContent: this.eventContentTemplate, headerToolbar: { left: 'prev,next', center: 'title', right: 'today' }, datesSet: (dateInfo) => { this.Load(dateInfo.start); }, dayCellDidMount: (arg) => { // 原逻辑保持不变 const fecha = arg.date.toISOString().split('T')[0]; const datosDia = this.datosAsistencia[fecha]; const esFeriadoLegal = !!this.diasFeriadoLegal[fecha]; const esPermisoAdministrativo = !!this.diasPermisoAdministrativo[fecha]; const esLicenciaMedica = !!this.diasLicenciaMedica[fecha]; const iconContainer = document.createElement('div'); iconContainer.style.position = 'absolute'; iconContainer.style.bottom = '0'; iconContainer.style.right = '0'; iconContainer.style.padding = '5px'; if (datosDia) { const asistenciaContainer = document.createElement('div'); asistenciaContainer.style.position = 'absolute'; asistenciaContainer.style.left = '0'; asistenciaContainer.style.top = '0'; asistenciaContainer.style.padding = '5px'; asistenciaContainer.style.textAlign = 'left'; asistenciaContainer.style.fontSize = '0.80rem'; asistenciaContainer.style.fontWeight = 'bold'; if (datosDia.entradaHora && datosDia.entradaSede) { const contenedorEntrada = document.createElement('div'); const iconEntrada = document.createElement('i'); iconEntrada.className = 'pi pi-arrow-right'; iconEntrada.style.color = 'green'; contenedorEntrada.appendChild(iconEntrada); const textoEntrada = document.createTextNode(` ${datosDia.entradaHora} - ${datosDia.entradaSede}`); contenedorEntrada.appendChild(textoEntrada); asistenciaContainer.appendChild(contenedorEntrada); } if (datosDia.salidaHora && datosDia.salidaSede) { const contenedorSalida = document.createElement('div'); const iconSalida = document.createElement('i'); iconSalida.className = 'pi pi-arrow-left'; iconSalida.style.color = 'red'; contenedorSalida.appendChild(iconSalida); const textoSalida = document.createTextNode(` ${datosDia.salidaHora} - ${datosDia.salidaSede}`); contenedorSalida.appendChild(textoSalida); asistenciaContainer.appendChild(contenedorSalida); } if (datosDia.horasTrabajadas) { const contenedorHoras = document.createElement('div'); const iconHoras = document.createElement('i'); iconHoras.className = 'pi pi-clock'; iconHoras.style.color = 'blue'; contenedorHoras.appendChild(iconHoras); const textoHoras = document.createTextNode(` ${datosDia.horasTrabajadas} Horas Trabajadas`); contenedorHoras.appendChild(textoHoras); asistenciaContainer.appendChild(contenedorHoras); } arg.el.appendChild(asistenciaContainer); } if (esFeriadoLegal) { const iconFeriado = document.createElement('i'); iconFeriado.className = 'pi pi-calendar'; iconFeriado.style.color = 'green'; iconContainer.appendChild(iconFeriado); } if (esPermisoAdministrativo) { const iconPermisoAdministrativo = document.createElement('i'); iconPermisoAdministrativo.className = 'pi pi-briefcase'; iconPermisoAdministrativo.style.color = 'orange'; iconContainer.appendChild(iconPermisoAdministrativo); } if (esLicenciaMedica) { const iconLicenciaMedica = document.createElement('i'); iconLicenciaMedica.className = 'pi pi-calendar-plus'; iconLicenciaMedica.style.color = 'red'; iconContainer.appendChild(iconLicenciaMedica); } arg.el.style.position = 'relative'; arg.el.appendChild(iconContainer); } }; } }
效果说明
- 初始加载时,等待数据处理完成后再渲染日历,确保考勤、节假日信息正确显示
- 切换月份时,清空旧数据并重新加载,避免数据混淆
- 添加加载提示,提升用户体验
- 修复冗余绑定导致的逻辑冲突
内容的提问来源于stack exchange,提问作者Felipe Levinir
相关产品推荐
相关产品推荐

