Laravel 实现团队缺勤日历的高效渲染方案

Laravel 实现团队缺勤日历的高效渲染方案

本文介绍如何在 laravel 中正确渲染团队缺勤日历表格,解决因多重缺勤记录导致的单元格重复渲染问题,通过预计算用户缺勤日期集合提升 blade 模板性能与可维护性。

在构建团队缺勤日历(如按月展示每位成员每日出勤状态)时,一个常见陷阱是:当某用户存在多条缺勤记录(例如 2024-05-03 → 2024-05-05 和 2024-05-12 → 2024-05-14),若在 Blade 模板中对每个日期嵌套遍历其所有缺勤记录,会导致

标签被多次输出——即一个日期生成多个单元格,彻底破坏 HTML 表格结构。

根本原因在于原始逻辑:

@foreach($days as $day)
    @foreach ($user->absences as $absence)  ← 错误:每轮日期都遍历全部缺勤
        @if($day >= ... && $day <= ...) 
            x  ← 可能输出多次!
        @else
            o  ← 更危险:每次不匹配都输出一个“o”
        @endif
    @endforeach
@endforeach

这不仅造成 DOM 错乱,还严重违背“单日期 → 单单元格”的语义逻辑。

✅ 正确解法:将“判断某日是否缺勤”提前至控制器层完成,为每位用户预计算一个扁平化的缺勤日期数组(如 [3,4,5,12,13,14]),模板中仅需一次 in_array() 判断。

ClipDrop Relight

ClipDrop Relight

ClipDrop推出的AI图片图像打光工具

下载

✅ 推荐实现(Laravel 9+,Eloquent 风格)

在控制器中(如 AbsenceCalendarController@index):

use Carbon/Carbon;

$year = request('year', now()->year);
$month = request('month', now()->month);
$startDate = Carbon::create($year, $month, 1);
$endDate = $startDate->copy()->endOfMonth();

// 获取当月所有用户及其已加载的缺勤记录(含 eager loading 优化)
$users = User::with(['absences' => function ($q) use ($startDate, $endDate) {
    // 仅加载与当前月份有交集的缺勤记录(避免全表扫描)
    $q->whereDate('start', '<=', $endDate)
      ->whereDate('end', '>=', $startDate);
}])->get();

// 预计算每位用户的缺勤日期集合(去重、整数化、归一到当月日序号)
$users = $users->map(function ($user) use ($startDate, $endDate) {
    $absenceDays = collect();

    foreach ($user->absences as $absence) {
        // 计算该缺勤记录与当月的实际重叠日期范围
        $overlapStart = max($absence->start->startOfMonth(), $startDate)->day;
        $overlapEnd   = min($absence->end->endOfMonth(), $endDate)->day;

        if ($overlapStart <= $overlapEnd) {
            $absenceDays = $absenceDays->merge(range($overlapStart, $overlapEnd));
        }
    }

    // 去重并转为数值索引数组(确保 in_array 高效)
    $user->absenceDays = $absenceDays->unique()->sort()->values()->all();
    return $user;
});

// 生成当月日期数组:[1, 2, ..., 31]
$days = range(1, $endDate->day);

在 Blade 模板中(简洁、安全、无嵌套):


            @foreach ($days as $day)
                
            @endforeach
        
        @foreach ($users as $user)
            
                @foreach ($days as $day)
                    @if (in_array($day, $user->absenceDays))
                        
                    @else
                        
                    @endif
                @endforeach
            
        @endforeach
    
User{{ $day }}
{{ $user->name }}xo

⚠️ 关键注意事项

  • 数据库查询优化:使用 whereDate() 约束缺勤范围,避免加载无关历史记录;
  • 日期交集处理:start/end 可能跨月,必须用 max/min 计算实际影响的当月日期段;
  • 数据去重:同一日期若被多条缺勤覆盖(如重叠请假),unique() 确保只计一次;
  • 性能保障:所有计算在 PHP 层完成,模板仅做 O(1) 查找,避免 N+1 和嵌套循环;
  • 可扩展性:后续支持“部分缺勤”(上午/下午)、颜色分级(病假/事假/年假)时,只需扩展 $user->absenceDays 为关联数组(如 [‘5’ => ‘sick’, ’12’ => ‘vacation’])。

此方案彻底规避了模板层逻辑膨胀,符合 Laravel “Fat Model, Skinny Controller, Dumb View” 哲学,同时兼顾可读性、健壮性与性能。

https://www.php.cn/faq/1970657.html

发表回复

Your email address will not be published. Required fields are marked *