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);
}
}