activePosition?->position; $query = Position::query() ->with('department') ->orderByDesc('rank'); // 仅有具体职务(非 superlevel 直通)的操作人才限制 rank 范围 if ($operatorPosition && $operator->user_level < $superLevel) { $query->where('rank', '<', $operatorPosition->rank); } $positions = $query->get()->map(fn ($p) => [ 'id' => $p->id, 'name' => $p->name, 'icon' => $p->icon, 'rank' => $p->rank, 'department' => $p->department?->name, ]); return response()->json(['status' => 'success', 'positions' => $positions]); } /** * 快速任命:将目标用户任命为指定职务 * 成功后向操作人所在聊天室广播任命公告 */ public function appoint(Request $request): JsonResponse { $request->validate([ 'username' => 'required|string|exists:users,username', 'position_id' => 'required|exists:positions,id', 'remark' => 'nullable|string|max:100', 'room_id' => 'nullable|integer|exists:rooms,id', ]); $operator = Auth::user(); $target = User::where('username', $request->username)->firstOrFail(); $position = Position::with('department')->findOrFail($request->position_id); $result = $this->appointmentService->appoint($operator, $target, $position, $request->remark); // 任命成功后广播礼花通知:优先用前端传来的 room_id,否则从 Redis 查操作人所在房间 if ($result['ok']) { $roomId = $request->integer('room_id') ?: ($this->chatState->getUserRooms($operator->username)[0] ?? null); if ($roomId) { broadcast(new AppointmentAnnounced( roomId: (int) $roomId, targetUsername: $target->username, positionIcon: $position->icon ?? '🎖️', positionName: $position->name, departmentName: $position->department?->name ?? '', operatorName: $operator->username, )); // 给被任命用户补一条私聊提示,并复用右下角 toast 通知。 $this->pushTargetToastMessage( roomId: (int) $roomId, targetUsername: $target->username, content: "✨ {$operator->username} 已任命你为 {$position->icon} {$position->name}。", title: '✨ 职务任命通知', toastMessage: "{$operator->username} 已任命你为 {$position->icon} {$position->name}。", color: '#a855f7', icon: '✨', ); } } return response()->json([ 'status' => $result['ok'] ? 'success' : 'error', 'message' => $result['message'], ], $result['ok'] ? 200 : 422); } /** * 快速撤销:撤销目标用户当前的职务 */ public function revoke(Request $request): JsonResponse { $request->validate([ 'username' => 'required|string|exists:users,username', 'remark' => 'nullable|string|max:100', 'room_id' => 'nullable|integer|exists:rooms,id', ]); $operator = Auth::user(); $target = User::where('username', $request->username)->firstOrFail(); // 撤销前先取目标当前职务信息(撤销后就查不到了) $activeUp = $target->activePosition?->load('position.department'); $posIcon = $activeUp?->position?->icon ?? '🎖️'; $posName = $activeUp?->position?->name ?? ''; $deptName = $activeUp?->position?->department?->name ?? ''; $result = $this->appointmentService->revoke($operator, $target, $request->remark); // 撤销成功后广播通知到聊天室 if ($result['ok'] && $posName) { $roomId = $request->integer('room_id') ?: ($this->chatState->getUserRooms($operator->username)[0] ?? null); if ($roomId) { broadcast(new AppointmentAnnounced( roomId: (int) $roomId, targetUsername: $target->username, positionIcon: $posIcon, positionName: $posName, departmentName: $deptName, operatorName: $operator->username, type: 'revoke', )); // 给被撤职用户补一条私聊提示,并复用右下角 toast 通知。 $this->pushTargetToastMessage( roomId: (int) $roomId, targetUsername: $target->username, content: "📋 {$operator->username} 已撤销你的 {$posIcon} {$posName} 职务。", title: '📋 职务变动通知', toastMessage: "{$operator->username} 已撤销你的 {$posIcon} {$posName} 职务。", color: '#6b7280', icon: '📋', ); } } return response()->json([ 'status' => $result['ok'] ? 'success' : 'error', 'message' => $result['message'], ], $result['ok'] ? 200 : 422); } /** * 向目标用户补发一条系统私聊消息,并附带右下角 toast 配置。 */ private function pushTargetToastMessage( int $roomId, string $targetUsername, string $content, string $title, string $toastMessage, string $color, string $icon, ): void { $msg = [ 'id' => $this->chatState->nextMessageId($roomId), 'room_id' => $roomId, 'from_user' => '系统', 'to_user' => $targetUsername, 'content' => $content, 'is_secret' => true, 'font_color' => $color, 'action' => '', 'sent_at' => now()->toDateTimeString(), // 复用现有聊天 toast 机制,在右下角弹出职务变动提示。 'toast_notification' => [ 'title' => $title, 'message' => $toastMessage, 'icon' => $icon, 'color' => $color, 'duration' => 10000, ], ]; $this->chatState->pushMessage($roomId, $msg); broadcast(new MessageSent($roomId, $msg)); SaveMessageJob::dispatch($msg); } }