功能:用户列表增加在线状态列,支持点击排序

- UserManagerController 注入 ChatStateService,从 Redis 聚合
  所有活跃房间在线用户名(跨房间去重)
- 排序白名单加入 'online',在线排序用 orderByRaw CASE WHEN 虚拟列
  desc = 在线用户优先显示,asc = 离线用户优先
- 视图表头加「在线 ↕」可排序列(绿色高亮箭头)
- 每行显示绿色实心点+「在线」/灰点+「离线」小徽章
- my-duty-logs 分页已有 paginate(30)+withQueryString+links(),无需改动
This commit is contained in:
2026-03-01 12:54:34 +08:00
parent 0dff79dd51
commit 2947f0f741
2 changed files with 44 additions and 6 deletions
@@ -14,6 +14,7 @@ namespace App\Http\Controllers\Admin;
use App\Enums\CurrencySource;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Services\ChatStateService;
use App\Services\UserCurrencyService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
@@ -25,14 +26,15 @@ use Illuminate\View\View;
class UserManagerController extends Controller
{
/**
* 注入统一积分服务(用于管理员调整经验/金币/魅力时记录流水)
* 注入统一积分服务和聊天室状态服务
*/
public function __construct(
private readonly UserCurrencyService $currencyService,
private readonly ChatStateService $chatState,
) {}
/**
* 显示用户列表及搜索(支持按等级/经验/金币/魅力排序)
* 显示用户列表及搜索(支持按等级/经验/金币/魅力/在线状态排序)
*/
public function index(Request $request): View
{
@@ -42,21 +44,41 @@ class UserManagerController extends Controller
$query->where('username', 'like', '%'.$request->input('username').'%');
}
// 从 Redis 获取所有在线用户名(跨所有房间去重)
$onlineUsernames = collect();
foreach ($this->chatState->getAllActiveRoomIds() as $roomId) {
$onlineUsernames = $onlineUsernames->merge(array_keys($this->chatState->getRoomUsers($roomId)));
}
$onlineUsernames = $onlineUsernames->unique()->values();
// 排序:允许的字段白名单,防止 SQL 注入
$sortable = ['user_level', 'exp_num', 'jjb', 'meili', 'id'];
$sortable = ['user_level', 'exp_num', 'jjb', 'meili', 'id', 'online'];
$sortBy = in_array($request->input('sort_by'), $sortable) ? $request->input('sort_by') : 'id';
$sortDir = $request->input('sort_dir') === 'asc' ? 'asc' : 'desc';
if ($sortBy === 'online') {
// 用虚拟列排序:在线用户标记为 1,离线为 0;desc = 在线优先
if ($onlineUsernames->isNotEmpty()) {
$placeholders = implode(',', array_fill(0, $onlineUsernames->count(), '?'));
$query->orderByRaw(
"CASE WHEN username IN ({$placeholders}) THEN 1 ELSE 0 END {$sortDir}",
$onlineUsernames->toArray(),
);
}
$query->orderBy('id', 'desc'); // 二级排序
} else {
$query->orderBy($sortBy, $sortDir);
}
$users = $query
->with(['activePosition.position.department', 'vipLevel'])
->orderBy($sortBy, $sortDir)
->paginate(20)
->withQueryString();
// VIP 等级选项列表(供编辑弹窗使用)
$vipLevels = \App\Models\VipLevel::orderBy('sort_order')->get();
return view('admin.users.index', compact('users', 'vipLevels', 'sortBy', 'sortDir'));
return view('admin.users.index', compact('users', 'vipLevels', 'sortBy', 'sortDir', 'onlineUsernames'));
}
/**