功能:勤务台榜单新增管理操作次数 + 奖励金币次数统计
DutyHallController: - 新增 position_authority_logs 关联查询 - 统计管理操作次数(warn/kick/mute/banip/other,排除人事任免) - 统计奖励金币次数及累计金额(action_type=reward) - 时间范围统一过滤(日/周/月/总) - 合并两表数据到榜单 Collection duty-hall/index.blade.php: - 表格扩展为 6 列:名次、成员、在线时长、登录次数、管理操作、奖励金币 - 奖励金币栏 hover 显示次数+总金额 tooltip - 移动端显示紧凑卡片(管理/奖励只在 >0 时显示) - 底部图例说明各列含义
This commit is contained in:
@@ -5,14 +5,20 @@
|
||||
* 左侧五个子菜单:任职列表、日榜、周榜、月榜、总榜
|
||||
* 路由:GET /duty-hall?tab=roster|day|week|month|all
|
||||
*
|
||||
* 榜单统计三项指标:
|
||||
* 1. 在线时长 — position_duty_logs.duration_seconds 合计
|
||||
* 2. 管理操作次数 — position_authority_logs 非任免类操作次数(warn/kick/mute/banip/other)
|
||||
* 3. 奖励金币次数 — position_authority_logs WHERE action_type='reward' 的次数及累计金额
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.1.0
|
||||
* @version 1.2.0
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Department;
|
||||
use App\Models\PositionAuthorityLog;
|
||||
use App\Models\PositionDutyLog;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
@@ -26,7 +32,7 @@ class DutyHallController extends Controller
|
||||
{
|
||||
$tab = $request->input('tab', 'roster');
|
||||
|
||||
// ── 任职列表:按部门→职务展示全部(含空缺) ────────────────────
|
||||
// ── 任职列表:按部门→职务展示全部(含空缺) ──────────────────
|
||||
$currentStaff = null;
|
||||
if ($tab === 'roster') {
|
||||
$currentStaff = Department::query()
|
||||
@@ -38,26 +44,64 @@ class DutyHallController extends Controller
|
||||
->get();
|
||||
}
|
||||
|
||||
// ── 日/周/月/总榜:勤务时长排行 ──────────────────────────────────
|
||||
// ── 日/周/月/总榜:三项指标综合排行 ─────────────────────────
|
||||
$leaderboard = null;
|
||||
if (in_array($tab, ['day', 'week', 'month', 'all'])) {
|
||||
$query = PositionDutyLog::query()
|
||||
|
||||
// ① 在线时长(position_duty_logs)
|
||||
$dutyQuery = PositionDutyLog::query()
|
||||
->selectRaw('user_id, SUM(duration_seconds) as total_seconds, COUNT(*) as checkin_count');
|
||||
|
||||
// 按时间段过滤
|
||||
// ② 管理操作(position_authority_logs,排除任命/撤销等人事操作)
|
||||
$authQuery = PositionAuthorityLog::query()
|
||||
->selectRaw('
|
||||
user_id,
|
||||
COUNT(*) as admin_count,
|
||||
SUM(CASE WHEN action_type = \'reward\' THEN 1 ELSE 0 END) as reward_count,
|
||||
SUM(CASE WHEN action_type = \'reward\' THEN COALESCE(amount, 0) ELSE 0 END) as reward_total
|
||||
')
|
||||
->whereNotIn('action_type', ['appoint', 'revoke']);
|
||||
|
||||
// 按时间段同步过滤两张表
|
||||
match ($tab) {
|
||||
'day' => $query->whereDate('login_at', today()),
|
||||
'week' => $query->whereBetween('login_at', [now()->startOfWeek(), now()->endOfWeek()]),
|
||||
'month' => $query->whereYear('login_at', now()->year)->whereMonth('login_at', now()->month),
|
||||
'all' => null, // 不加时间限制
|
||||
'day' => [
|
||||
$dutyQuery->whereDate('login_at', today()),
|
||||
$authQuery->whereDate('created_at', today()),
|
||||
],
|
||||
'week' => [
|
||||
$dutyQuery->whereBetween('login_at', [now()->startOfWeek(), now()->endOfWeek()]),
|
||||
$authQuery->whereBetween('created_at', [now()->startOfWeek(), now()->endOfWeek()]),
|
||||
],
|
||||
'month' => [
|
||||
$dutyQuery->whereYear('login_at', now()->year)->whereMonth('login_at', now()->month),
|
||||
$authQuery->whereYear('created_at', now()->year)->whereMonth('created_at', now()->month),
|
||||
],
|
||||
'all' => null, // 不限制时间
|
||||
};
|
||||
|
||||
$leaderboard = $query
|
||||
// 执行查询
|
||||
$dutyRows = $dutyQuery
|
||||
->groupBy('user_id')
|
||||
->orderByDesc('total_seconds')
|
||||
->limit(20)
|
||||
->with('user')
|
||||
->get();
|
||||
|
||||
// 管理操作数据(按 user_id 索引,方便后续合并)
|
||||
$authMap = $authQuery
|
||||
->groupBy('user_id')
|
||||
->get()
|
||||
->keyBy('user_id');
|
||||
|
||||
// 合并两表数据:为每条勤务记录附加管理操作指标
|
||||
$leaderboard = $dutyRows->map(function ($row) use ($authMap) {
|
||||
$auth = $authMap->get($row->user_id);
|
||||
$row->admin_count = (int) ($auth?->admin_count ?? 0);
|
||||
$row->reward_count = (int) ($auth?->reward_count ?? 0);
|
||||
$row->reward_total = (int) ($auth?->reward_total ?? 0);
|
||||
|
||||
return $row;
|
||||
});
|
||||
}
|
||||
|
||||
// 各榜标签配置
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
|
||||
<div class="mb-4">
|
||||
<h2 class="text-lg font-bold text-gray-800">{{ $tabMeta['icon'] }} 勤务{{ $tabMeta['label'] }}</h2>
|
||||
<p class="text-xs text-gray-400 mt-0.5">{{ $periodLabel }} · 按在职期间登录时长排名</p>
|
||||
<p class="text-xs text-gray-400 mt-0.5">{{ $periodLabel }} · 综合统计在线时长、管理操作、奖励金币</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl border border-gray-100 shadow-sm overflow-hidden">
|
||||
@@ -164,13 +164,15 @@
|
||||
<p>该时段暂无勤务记录</p>
|
||||
</div>
|
||||
@else
|
||||
{{-- 表头 --}}
|
||||
{{-- 表头(桌面端网格:名次1 + 成员4 + 在线3 + 签到2 + 管理操作2) --}}
|
||||
<div
|
||||
class="grid grid-cols-12 gap-4 px-5 py-2.5 bg-gray-50 border-b border-gray-100 text-xs font-bold text-gray-400 uppercase">
|
||||
class="hidden md:grid grid-cols-12 gap-2 px-5 py-2.5 bg-gray-50 border-b border-gray-100 text-xs font-bold text-gray-400 uppercase tracking-wide">
|
||||
<div class="col-span-1 text-center">名次</div>
|
||||
<div class="col-span-5">成员</div>
|
||||
<div class="col-span-3 text-right">在线时长</div>
|
||||
<div class="col-span-3 text-right">签到次数</div>
|
||||
<div class="col-span-4">成员</div>
|
||||
<div class="col-span-3 text-right">🕐 在线时长</div>
|
||||
<div class="col-span-2 text-right">📋 登录</div>
|
||||
<div class="col-span-1 text-right">⚡管理</div>
|
||||
<div class="col-span-1 text-right">🎁奖励</div>
|
||||
</div>
|
||||
|
||||
@foreach ($leaderboard as $i => $row)
|
||||
@@ -183,41 +185,115 @@
|
||||
2 => '🥉',
|
||||
default => null,
|
||||
};
|
||||
$rowBg = match ($i) {
|
||||
0 => 'bg-yellow-50/50',
|
||||
1 => 'bg-gray-50/70',
|
||||
2 => 'bg-amber-50/40',
|
||||
default => '',
|
||||
};
|
||||
@endphp
|
||||
|
||||
{{-- 桌面端行 --}}
|
||||
<div
|
||||
class="grid grid-cols-12 gap-4 items-center px-5 py-3 border-b border-gray-50 last:border-0
|
||||
{{ $i === 0 ? 'bg-yellow-50/40' : ($i === 1 ? 'bg-gray-50/60' : ($i === 2 ? 'bg-amber-50/40' : '')) }}
|
||||
hover:bg-purple-50/30 transition-colors">
|
||||
class="hidden md:grid grid-cols-12 gap-2 items-center px-5 py-3.5
|
||||
border-b border-gray-50 last:border-0 {{ $rowBg }}
|
||||
hover:bg-purple-50/30 transition-colors">
|
||||
|
||||
{{-- 名次 --}}
|
||||
<div class="col-span-1 text-center">
|
||||
@if ($medal)
|
||||
<span class="text-lg">{{ $medal }}</span>
|
||||
<span class="text-xl">{{ $medal }}</span>
|
||||
@else
|
||||
<span class="text-sm font-bold text-gray-300">{{ $i + 1 }}</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- 成员 --}}
|
||||
<div class="col-span-5 flex items-center gap-3">
|
||||
<div class="col-span-4 flex items-center gap-3">
|
||||
<img src="/images/headface/{{ strtolower($row->user?->headface ?? '1.gif') }}"
|
||||
class="w-9 h-9 rounded-full border-2 border-purple-100 object-cover bg-white"
|
||||
onerror="this.src='/images/headface/1.gif'">
|
||||
<div>
|
||||
<p class="font-bold text-sm text-gray-800">{{ $row->user?->username ?? '未知' }}</p>
|
||||
<p class="text-xs text-gray-400">LV.{{ $row->user?->user_level ?? '' }}</p>
|
||||
<p class="text-[10px] text-gray-400">LV.{{ $row->user?->user_level ?? '' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{-- 时长 --}}
|
||||
|
||||
{{-- 在线时长 --}}
|
||||
<div class="col-span-3 text-right">
|
||||
<span class="text-sm font-bold text-purple-600 tabular-nums">{{ $h }}h
|
||||
{{ $m }}m</span>
|
||||
</div>
|
||||
{{-- 次数 --}}
|
||||
<div class="col-span-3 text-right">
|
||||
|
||||
{{-- 登录次数 --}}
|
||||
<div class="col-span-2 text-right">
|
||||
<span class="text-sm font-bold text-indigo-500 tabular-nums">{{ $row->checkin_count }}
|
||||
次</span>
|
||||
</div>
|
||||
|
||||
{{-- 管理操作次数 --}}
|
||||
<div class="col-span-1 text-right">
|
||||
@if ($row->admin_count > 0)
|
||||
<span
|
||||
class="inline-flex items-center justify-end gap-0.5 text-sm font-bold text-orange-500 tabular-nums">
|
||||
{{ $row->admin_count }}
|
||||
</span>
|
||||
@else
|
||||
<span class="text-gray-300 text-sm">—</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- 奖励金币次数 --}}
|
||||
<div class="col-span-1 text-right">
|
||||
@if ($row->reward_count > 0)
|
||||
<span class="text-sm font-bold text-emerald-500 tabular-nums"
|
||||
title="{{ $row->reward_count }}次 · 共{{ $row->reward_total }}金币">
|
||||
{{ $row->reward_count }}
|
||||
</span>
|
||||
@else
|
||||
<span class="text-gray-300 text-sm">—</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 移动端:紧凑卡片 --}}
|
||||
<div
|
||||
class="md:hidden flex items-center gap-3 px-4 py-3 border-b border-gray-50 last:border-0 {{ $rowBg }}">
|
||||
<div class="w-7 text-center shrink-0">
|
||||
@if ($medal)
|
||||
<span class="text-lg">{{ $medal }}</span>
|
||||
@else
|
||||
<span class="text-xs font-bold text-gray-300">{{ $i + 1 }}</span>
|
||||
@endif
|
||||
</div>
|
||||
<img src="/images/headface/{{ strtolower($row->user?->headface ?? '1.gif') }}"
|
||||
class="w-8 h-8 rounded-full border border-purple-100 object-cover bg-white shrink-0"
|
||||
onerror="this.src='/images/headface/1.gif'">
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="font-bold text-sm text-gray-800 truncate">{{ $row->user?->username ?? '未知' }}
|
||||
</p>
|
||||
<p class="text-[10px] text-gray-400">{{ $h }}h{{ $m }}m ·
|
||||
登录{{ $row->checkin_count }}次</p>
|
||||
</div>
|
||||
<div class="text-right shrink-0 space-y-0.5">
|
||||
@if ($row->admin_count > 0)
|
||||
<p class="text-xs font-bold text-orange-500">⚡{{ $row->admin_count }}次管理</p>
|
||||
@endif
|
||||
@if ($row->reward_count > 0)
|
||||
<p class="text-xs font-bold text-emerald-500">🎁{{ $row->reward_count }}次奖励</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
{{-- 图例说明 --}}
|
||||
<div
|
||||
class="px-5 py-2.5 bg-gray-50/60 border-t border-gray-50 text-[10px] text-gray-400 flex flex-wrap gap-x-4 gap-y-1">
|
||||
<span>🕐 在线时长:在职期间进入房间的累计时长</span>
|
||||
<span>📋 登录:进入房间次数</span>
|
||||
<span>⚡ 管理:警告/踢出/禁言/封IP等操作次数</span>
|
||||
<span>🎁 奖励:发送奖励金币次数(悬停查看总金额)</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
Reference in New Issue
Block a user