本文详解如何在 Laravel 中构建用户维度的订单与发货数量统计报表,支持自定义起止日期筛选,通过 withCount 与条件关联查询精准获取每位用户在指定周期内的订单数和发货数。
本文详解如何在 laravel 中构建用户维度的订单与发货数量统计报表,支持自定义起止日期筛选,通过 `withcount` 与条件关联查询精准获取每位用户在指定周期内的订单数和发货数。
在开发后台报表功能时,常需统计每位用户在某段时间内的业务行为数据,例如订单提交量与发货请求量。本教程将基于你已定义的 Eloquent 关系(User ↔ Order、User ↔ Shipping),提供健壮、高效且可维护的查询实现方案。
你原代码中存在几个关键问题:
✅ 推荐写法(无 scope 版):
$report = User::withCount([ 'orders' => function ($q) use ($from, $to) { if (!empty($from)) { $q->whereDate('created_at', '>=', $from); } if (!empty($to)) { $q->whereDate('created_at', '<=', $to); } }, 'shippings' => function ($q) use ($from, $to) { if (!empty($from)) { $q->whereDate('created_at', '>=', $from); } if (!empty($to)) { $q->whereDate('created_at', '<=', $to); } }])->whereHas('shippings', function ($q) use ($from, $to) { if (!empty($from)) { $q->whereDate('created_at', '>=', $from); } if (!empty($to)) { $q->whereDate('created_at', '<=', $to); }})->orWhereHas('orders', function ($q) use ($from, $to) { if (!empty($from)) { $q->whereDate('created_at', '>=', $from); } if (!empty($to)) { $q->whereDate('created_at', '<=', $to); }})->orderByRaw('orders_count + shippings_count DESC')->paginate(25);
⚠️ 注意:whereHas(...)->orWhereHas(...) 的组合默认会生成 (has_shippings AND has_orders) OR ... 的歧义逻辑。为确保语义清晰(即「有该时间段内订单 或 发货的用户」),建议外层包裹 where(function () { ... }):
$report = User::withCount([ 'orders' => fn($q) => $this->applyDateFilter($q, $from, $to), 'shippings' => fn($q) => $this->applyDateFilter($q, $from, $to),])->where(function ($q) use ($from, $to) { $q->whereHas('shippings', fn($sub) => $this->applyDateFilter($sub, $from, $to)) ->orWhereHas('orders', fn($sub) => $this->applyDateFilter($sub, $from, $to));})->orderByRaw('orders_count + shippings_count DESC')->paginate(25);
并在 Controller 或 Trait 中定义复用方法:
protected function applyDateFilter($query, $from, $to){ if ($from) $query->whereDate('created_at', '>=', $from); if ($to) $query->whereDate('created_at', '<=', $to); return $query;}
为提升可读性与复用性,可在 Order 和 Shipping 模型中添加本地作用域:
// AppModelsOrder.phppublic function scopeWhereDateBetween($builder, $from = null, $to = null){ if ($from) { $builder->whereDate('created_at', '>=', $from); } if ($to) { $builder->whereDate('created_at', '<=', $to); } return $builder;}
同理为 Shipping 模型添加相同作用域。之后查询可大幅简化:
$report = User::withCount([ 'orders' => fn($q) => $q->whereDateBetween($from, $to), 'shippings' => fn($q) => $q->whereDateBetween($from, $to),])->where(function ($q) use ($from, $to) { $q->whereHas('shippings', fn($sub) => $sub->whereDateBetween($from, $to)) ->orWhereHas('orders', fn($sub) => $sub->whereDateBetween($from, $to));})->orderByDesc(DB::raw('orders_count + shippings_count'))->paginate(25);
最终,你可在 Blade 模板中直接访问:
@foreach($report as $user) <tr> <td>{{ $user->name }}</td> <td>{{ $user->orders_count }}</td> <td>{{ $user->shippings_count }}</td> <td>{{ $user->orders_count + $user->shippings_count }}</td> </tr>@endforeach
此方案兼顾准确性、可维护性与扩展性,是 Laravel 报表类查询的最佳实践之一。