Files
chatroom/resources/views/duty-hall/index.blade.php
T
lkddi 5f30220609 feat: 任命/撤销通知系统 + 用户名片UI优化
- 任命/撤销事件增加 type 字段区分类型
- 任命:全屏礼花 + 紫色弹窗 + 紫色系统消息
- 撤销:灰色弹窗 + 灰色系统消息,无礼花
- 消息分发:操作者/被操作者显示在私聊面板,其他人显示在公屏
- 系统消息加随机鼓励语(各5条轮换)
- ChatStateService 修复 Redis key 前缀扫描问题(getAllActiveRoomIds)
- 用户名片折叠优化:管理员视野、职务履历均可折叠
- 管理操作 + 职务操作合并为「🔧 管理操作」折叠区
- 悄悄话改为「🎁 送礼物」按钮,礼物面板内联展开
2026-02-28 23:44:38 +08:00

240 lines
15 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{--
文件功能:勤务台页面(职务管理荣誉展示台)
左侧子菜单:任职列表、日榜、周榜、月榜、总榜
URL/duty-hall?tab=roster|day|week|month|all
@extends layouts.app
--}}
@extends('layouts.app')
@section('title', '勤务台 · ' . ($tabs[$tab]['label'] ?? '任职列表') . ' - 飘落流星')
@section('nav-icon', '🏛️')
@section('nav-title', '勤务台')
@section('content')
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6 flex gap-6 items-start">
{{-- ═══ 左侧子菜单 ═══ --}}
<aside
class="w-52 shrink-0 bg-white border border-gray-100 rounded-2xl shadow-sm overflow-hidden sticky top-20 self-start hidden md:block">
<div class="px-4 pt-4 pb-2">
<p class="text-xs font-bold text-gray-400 uppercase tracking-widest mb-3">勤务台</p>
<nav class="space-y-1">
@foreach ($tabs as $key => $meta)
<a href="{{ route('duty-hall.index', ['tab' => $key]) }}"
class="flex items-center gap-2.5 px-3 py-2.5 rounded-xl text-sm font-medium transition-colors
{{ $tab === $key
? 'bg-purple-50 text-purple-700 font-bold'
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-800' }}">
<span class="text-base">{{ $meta['icon'] }}</span>
<span>{{ $meta['label'] }}</span>
@if ($tab === $key)
<span class="ml-auto w-1.5 h-1.5 rounded-full bg-purple-600"></span>
@endif
</a>
@endforeach
</nav>
</div>
<div class="px-4 py-3 mt-2 border-t border-gray-50 text-xs text-gray-400 text-center">
光荣服务,公示透明
</div>
</aside>
{{-- ═══ 主内容区 ═══ --}}
<main class="flex-1 min-w-0">
{{-- ─── Tab:任职列表 ─── --}}
@if ($tab === 'roster')
<div class="mb-4">
<h2 class="text-lg font-bold text-gray-800">🏛️ 任职列表</h2>
<p class="text-xs text-gray-400 mt-0.5">按部门 · 职务展示当前全部在职人员</p>
</div>
@if ($currentStaff->isEmpty())
<div class="bg-white rounded-2xl border border-gray-100 shadow-sm py-20 text-center text-gray-400">
<div class="text-5xl mb-4">📭</div>
<p>暂未设置任何部门</p>
</div>
@else
<div class="space-y-5">
@foreach ($currentStaff as $dept)
<div class="bg-white rounded-2xl border border-gray-100 shadow-sm overflow-hidden">
{{-- 部门标题 --}}
<div
class="px-5 py-3 bg-gradient-to-r from-purple-50 to-indigo-50 border-b border-gray-100 flex items-center gap-2">
<span class="text-base">{{ $dept->icon ?? '🏢' }}</span>
<span class="font-bold text-purple-700 text-sm">{{ $dept->name }}</span>
<span class="ml-auto text-xs text-gray-400">{{ $dept->positions->count() }} 个职务</span>
</div>
@if ($dept->positions->isEmpty())
<div class="px-5 py-4 text-xs text-gray-400 italic">该部门暂无职务设置</div>
@else
<div class="divide-y divide-gray-50">
@foreach ($dept->positions as $position)
<div class="px-5 py-3">
{{-- 职务名 --}}
<div class="flex items-center gap-1.5 mb-2">
<span class="text-sm">{{ $position->icon }}</span>
<span
class="text-sm font-semibold text-gray-700">{{ $position->name }}</span>
@php
$current = $position->activeUserPositions->count();
$maxSlots = $position->max_persons;
$isFull = $maxSlots !== null && $current >= $maxSlots;
@endphp
{{-- 名额计数标签 --}}
<span
class="ml-1 inline-flex items-center gap-0.5 px-1.5 py-0.5 rounded-md text-[10px] font-bold
{{ $current === 0
? 'bg-gray-100 text-gray-400'
: ($isFull
? 'bg-red-100 text-red-600'
: 'bg-purple-100 text-purple-600') }}">
{{ $current }}
@if ($maxSlots !== null)
<span class="opacity-60">/{{ $maxSlots }}</span>
@endif
</span>
@if ($isFull)
<span class="text-[10px] text-red-400">已满</span>
@elseif ($current === 0)
<span class="text-[10px] text-gray-300">(暂缺)</span>
@endif
</div>
{{-- 在职人员列表 --}}
@if ($position->activeUserPositions->isNotEmpty())
<div class="flex flex-wrap gap-2">
@foreach ($position->activeUserPositions as $up)
<div
class="flex items-center gap-2 px-3 py-1.5 bg-gray-50 border border-gray-100 rounded-xl">
<img src="/images/headface/{{ strtolower($up->user?->headface ?? '1.gif') }}"
class="w-7 h-7 rounded-full border border-purple-100 object-cover bg-white"
onerror="this.src='/images/headface/1.gif'">
<div>
<p class="text-xs font-bold text-gray-800">
{{ $up->user?->username ?? '未知' }}</p>
<p class="text-[10px] text-gray-400">
{{ $up->appointed_at?->format('Y-m-d') }} ·
{{ $up->duration_days }} </p>
</div>
</div>
@endforeach
</div>
@else
{{-- 空缺占位 --}}
<div
class="flex items-center gap-2 px-3 py-2 border border-dashed border-gray-200 rounded-xl text-xs text-gray-300 w-fit">
<span>👤</span>
<span>暂无任职人员</span>
</div>
@endif
</div>
@endforeach
</div>
@endif
</div>
@endforeach
</div>
@endif
{{-- ─── Tab:日///总榜 ─── --}}
@else
@php
$tabMeta = $tabs[$tab];
$periodLabel = match ($tab) {
'day' => today()->format('Y年m月d日'),
'week' => now()->startOfWeek()->format('m月d日') . ' ' . now()->endOfWeek()->format('m月d日'),
'month' => now()->format('Y年m月'),
'all' => '历史累计',
};
@endphp
<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>
</div>
<div class="bg-white rounded-2xl border border-gray-100 shadow-sm overflow-hidden">
@if ($leaderboard->isEmpty())
<div class="py-20 text-center text-gray-400">
<div class="text-5xl mb-4">📊</div>
<p>该时段暂无勤务记录</p>
</div>
@else
{{-- 表头 --}}
<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">
<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>
@foreach ($leaderboard as $i => $row)
@php
$h = intdiv($row->total_seconds, 3600);
$m = intdiv($row->total_seconds % 3600, 60);
$medal = match ($i) {
0 => '🥇',
1 => '🥈',
2 => '🥉',
default => null,
};
@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">
{{-- 名次 --}}
<div class="col-span-1 text-center">
@if ($medal)
<span class="text-lg">{{ $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">
<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>
</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">
<span class="text-sm font-bold text-indigo-500 tabular-nums">{{ $row->checkin_count }}
</span>
</div>
</div>
@endforeach
@endif
</div>
@endif
</main>
</div>
{{-- 移动端底部 Tab 导航 --}}
<div class="md:hidden fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 flex z-30">
@foreach ($tabs as $key => $meta)
<a href="{{ route('duty-hall.index', ['tab' => $key]) }}"
class="flex-1 flex flex-col items-center py-2 text-xs
{{ $tab === $key ? 'text-purple-600 font-bold' : 'text-gray-500' }}">
<span class="text-lg">{{ $meta['icon'] }}</span>
<span class="mt-0.5">{{ $meta['label'] }}</span>
</a>
@endforeach
</div>
@endsection