You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular 16 FullCalendar初始加载无数据问题求助

问题分析与解决方案

问题根源

  1. 数据与日历初始化时序不匹配:初始加载时,异步数据还未处理完成就多次调用LoadCalendar,导致日历渲染时无法获取完整的考勤、节假日数据。
  2. 冗余的模板绑定:模板中单独绑定了[dayCellDidMount],但组件类中并无该独立方法,与calendarOptions内的回调逻辑冲突。
  3. 数据处理逻辑分散:在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 15:45:55