From 281315d1cf936a032883950dd8a710aeb0a73f53 Mon Sep 17 00:00:00 2001 From: lkddi Date: Tue, 21 Apr 2026 16:43:17 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=81=8C=E5=8A=A1=E6=9D=83?= =?UTF-8?q?=E9=99=90=E7=AE=A1=E7=90=86=E4=B8=8E=E8=81=8A=E5=A4=A9=E5=AE=A4?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E6=9D=83=E9=99=90=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BaccaratLossCoverEventController.php | 20 ++ .../Controllers/Admin/PositionController.php | 26 ++- .../Controllers/AdminCommandController.php | 63 ++++-- app/Http/Controllers/ChatController.php | 17 +- app/Http/Controllers/RedPacketController.php | 15 +- app/Models/Position.php | 23 ++- app/Services/PositionPermissionService.php | 92 +++++++++ app/Support/PositionPermissionRegistry.php | 169 ++++++++++++++++ ...457_add_permissions_to_positions_table.php | 73 +++++++ database/seeders/DepartmentPositionSeeder.php | 2 + .../views/admin/positions/index.blade.php | 89 +++++++-- resources/views/chat/frame.blade.php | 4 + .../chat/partials/layout/input-bar.blade.php | 55 ++++-- routes/web.php | 6 +- .../BaccaratLossCoverControllerTest.php | 87 +++++++- tests/Feature/ChatControllerTest.php | 143 +++++++++++++- .../Feature/AdminCommandControllerTest.php | 158 ++++++++++++++- .../Feature/AdminPositionPermissionTest.php | 186 ++++++++++++++++++ tests/Feature/RedPacketControllerTest.php | 102 +++++++++- 19 files changed, 1243 insertions(+), 87 deletions(-) create mode 100644 app/Services/PositionPermissionService.php create mode 100644 app/Support/PositionPermissionRegistry.php create mode 100644 database/migrations/2026_04_21_155457_add_permissions_to_positions_table.php create mode 100644 tests/Feature/Feature/AdminPositionPermissionTest.php diff --git a/app/Http/Controllers/Admin/BaccaratLossCoverEventController.php b/app/Http/Controllers/Admin/BaccaratLossCoverEventController.php index 7d77086..3da9fd3 100644 --- a/app/Http/Controllers/Admin/BaccaratLossCoverEventController.php +++ b/app/Http/Controllers/Admin/BaccaratLossCoverEventController.php @@ -13,9 +13,14 @@ use App\Http\Controllers\Controller; use App\Http\Requests\StoreBaccaratLossCoverEventRequest; use App\Models\BaccaratLossCoverEvent; use App\Services\BaccaratLossCoverService; +use App\Services\PositionPermissionService; +use App\Support\PositionPermissionRegistry; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +/** + * 类功能:处理聊天室顶部快捷入口创建与结束百家乐买单活动。 + */ class BaccaratLossCoverEventController extends Controller { /** @@ -23,6 +28,7 @@ class BaccaratLossCoverEventController extends Controller */ public function __construct( private readonly BaccaratLossCoverService $lossCoverService, + private readonly PositionPermissionService $positionPermissionService, ) {} /** @@ -30,6 +36,13 @@ class BaccaratLossCoverEventController extends Controller */ public function store(StoreBaccaratLossCoverEventRequest $request): JsonResponse { + if (! $this->positionPermissionService->hasPermission($request->user(), PositionPermissionRegistry::ROOM_BACCARAT_LOSS_COVER)) { + return response()->json([ + 'ok' => false, + 'message' => '当前职务无权创建买单活动。', + ], 403); + } + try { $event = $this->lossCoverService->createEvent($request->user(), $request->validated()); } catch (\RuntimeException $exception) { @@ -51,6 +64,13 @@ class BaccaratLossCoverEventController extends Controller */ public function close(Request $request, BaccaratLossCoverEvent $event): JsonResponse { + if (! $this->positionPermissionService->hasPermission($request->user(), PositionPermissionRegistry::ROOM_BACCARAT_LOSS_COVER)) { + return response()->json([ + 'ok' => false, + 'message' => '当前职务无权结束买单活动。', + ], 403); + } + $event = $this->lossCoverService->forceCloseEvent($event, $request->user()); return response()->json([ diff --git a/app/Http/Controllers/Admin/PositionController.php b/app/Http/Controllers/Admin/PositionController.php index 74bc0b4..f1b40c6 100644 --- a/app/Http/Controllers/Admin/PositionController.php +++ b/app/Http/Controllers/Admin/PositionController.php @@ -16,10 +16,15 @@ use App\Http\Controllers\Controller; use App\Models\Department; use App\Models\Position; use App\Models\Sysparam; +use App\Support\PositionPermissionRegistry; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; +use Illuminate\Validation\Rule; use Illuminate\View\View; +/** + * 类功能:负责后台职务资料、任命白名单与聊天室权限配置的维护。 + */ class PositionController extends Controller { /** @@ -29,16 +34,25 @@ class PositionController extends Controller { // 按部门分组展示 $departments = Department::with([ - 'positions' => fn ($q) => $q->withCount(['activeUserPositions'])->ordered(), + 'positions' => fn ($q) => $q->withCount(['activeUserPositions'])->with('appointablePositions')->ordered(), ])->ordered()->get(); // 全部职务(供任命白名单多选框使用) - $allPositions = Position::with('department')->orderByDesc('rank')->get(); + $allPositions = Position::with('department')->ordered()->get(); // 全局奖励接收次数上限(0 = 不限) $globalRecipientDailyMax = (int) Sysparam::getValue('reward_recipient_daily_max', '0'); - return view('admin.positions.index', compact('departments', 'allPositions', 'globalRecipientDailyMax')); + $positionPermissions = PositionPermissionRegistry::groupedDefinitions(); + $permissionLabels = PositionPermissionRegistry::labelMap(); + + return view('admin.positions.index', compact( + 'departments', + 'allPositions', + 'globalRecipientDailyMax', + 'positionPermissions', + 'permissionLabels', + )); } /** @@ -59,10 +73,13 @@ class PositionController extends Controller 'sort_order' => 'required|integer|min:0', 'appointable_ids' => 'nullable|array', 'appointable_ids.*' => 'exists:positions,id', + 'permissions' => 'nullable|array', + 'permissions.*' => ['string', Rule::in(PositionPermissionRegistry::codes())], ]); $appointableIds = $data['appointable_ids'] ?? []; unset($data['appointable_ids']); + $data['permissions'] = array_values(array_unique($data['permissions'] ?? [])); $position = Position::create($data); @@ -147,10 +164,13 @@ class PositionController extends Controller 'sort_order' => 'required|integer|min:0', 'appointable_ids' => 'nullable|array', 'appointable_ids.*' => 'exists:positions,id', + 'permissions' => 'nullable|array', + 'permissions.*' => ['string', Rule::in(PositionPermissionRegistry::codes())], ]); $appointableIds = $data['appointable_ids'] ?? []; unset($data['appointable_ids']); + $data['permissions'] = array_values(array_unique($data['permissions'] ?? [])); $position->update($data); $position->appointablePositions()->sync($appointableIds); diff --git a/app/Http/Controllers/AdminCommandController.php b/app/Http/Controllers/AdminCommandController.php index 972c55a..1989262 100644 --- a/app/Http/Controllers/AdminCommandController.php +++ b/app/Http/Controllers/AdminCommandController.php @@ -4,7 +4,7 @@ * 文件功能:管理员聊天室实时命令控制器 * * 提供管理员在聊天室内对用户执行的管理操作: - * 警告(=J)、踢出(=T)、禁言(=B)、冻结(=Y)、查看私信(=S)、站长公屏讲话。 + * 警告(=J)、踢出(=T)、禁言(=B)、冻结(=Y)、查看私信(=S)、职务公屏讲话。 * * 对应原 ASP 文件:DOUSER.ASP / KILLUSER.ASP / LOCKIP.ASP / NEWSAY.ASP * @@ -24,13 +24,18 @@ use App\Models\PositionAuthorityLog; use App\Models\Sysparam; use App\Models\User; use App\Services\ChatStateService; +use App\Services\PositionPermissionService; use App\Services\UserCurrencyService; +use App\Support\PositionPermissionRegistry; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Redis; use Illuminate\Validation\Rule; +/** + * 类功能:处理聊天室内的实时管理命令与部分职务奖励操作。 + */ class AdminCommandController extends Controller { /** @@ -39,6 +44,7 @@ class AdminCommandController extends Controller public function __construct( private readonly ChatStateService $chatState, private readonly UserCurrencyService $currencyService, + private readonly PositionPermissionService $positionPermissionService, ) {} /** @@ -347,9 +353,10 @@ class AdminCommandController extends Controller } /** - * 站长公屏讲话 + * 聊天室公屏讲话 * - * 站长发送全聊天室公告,以特殊样式显示。 + * 拥有 room.public_broadcast 权限的职务可以发送全聊天室公告, + * id=1 站长仍然拥有完整兜底权限。 * * @param Request $request 请求对象,需包含 content, room_id * @return JsonResponse 操作结果 @@ -362,22 +369,21 @@ class AdminCommandController extends Controller ]); $admin = Auth::user(); - $superLevel = (int) Sysparam::getValue('superlevel', '100'); - - if ($admin->user_level < $superLevel) { - return response()->json(['status' => 'error', 'message' => '仅站长可发布公屏讲话'], 403); + if (! $this->positionPermissionService->hasPermission($admin, PositionPermissionRegistry::ROOM_PUBLIC_BROADCAST)) { + return response()->json(['status' => 'error', 'message' => '当前职务无权发布公屏讲话'], 403); } $roomId = $request->input('room_id'); $content = $request->input('content'); - // 广播站长公告 + // 按当前在职职务拼装发布者身份,避免继续显示为固定“站长公告” + $publisherLabel = $this->buildAnnouncementPublisherLabel($admin); $msg = [ 'id' => $this->chatState->nextMessageId($roomId), 'room_id' => $roomId, 'from_user' => '系统公告', 'to_user' => '大家', - 'content' => "📢 站长 {$admin->username} 讲话:{$content}", + 'content' => "📢 {$publisherLabel} {$admin->username} 发布公告:{$content}", 'is_secret' => false, 'font_color' => '#b91c1c', 'action' => '', @@ -390,6 +396,28 @@ class AdminCommandController extends Controller return response()->json(['status' => 'success', 'message' => '公告已发送']); } + /** + * 生成公屏公告发布者身份标签。 + * + * 普通在职用户按“部门+职务”显示;站长无在职职务时保持“站长”标识兜底。 + */ + private function buildAnnouncementPublisherLabel(User $user): string + { + $position = $user->activePosition?->position; + + if ($position) { + $departmentName = (string) ($position->department?->name ?? ''); + + return $departmentName.$position->name; + } + + if ($user->id === 1) { + return '站长'; + } + + return '管理员'; + } + /** * 管理员全员清屏 * @@ -407,11 +435,9 @@ class AdminCommandController extends Controller $admin = Auth::user(); $roomId = $request->input('room_id'); - $superLevel = (int) Sysparam::getValue('superlevel', '100'); - - // 需要站长权限才能全员清屏 - if ($admin->user_level < $superLevel) { - return response()->json(['status' => 'error', 'message' => '仅站长可执行全员清屏'], 403); + // 改为按职务权限控制聊天室顶部“清屏”按钮。 + if (! $this->positionPermissionService->hasPermission($admin, PositionPermissionRegistry::ROOM_CLEAR_SCREEN)) { + return response()->json(['status' => 'error', 'message' => '当前职务无权执行全员清屏'], 403); } // 清除 Redis 中该房间的消息缓存 @@ -427,7 +453,7 @@ class AdminCommandController extends Controller * 管理员触发全屏特效。 * * 向房间内所有用户广播 EffectBroadcast 事件,前端收到后播放对应 Canvas 动画。 - * 仅 superlevel 等级管理员可触发。 + * 仅拥有 room.fullscreen_effect 权限的职务可触发。 * * @param Request $request 请求对象,需包含 room_id, type * @return JsonResponse 操作结果 @@ -442,11 +468,8 @@ class AdminCommandController extends Controller $admin = Auth::user(); $roomId = $request->input('room_id'); $type = $request->input('type'); - $superLevel = (int) Sysparam::getValue('superlevel', '100'); - - // 仅 superlevel 等级可触发特效 - if ($admin->user_level < $superLevel) { - return response()->json(['status' => 'error', 'message' => '仅站长可触发特效'], 403); + if (! $this->positionPermissionService->hasPermission($admin, PositionPermissionRegistry::ROOM_FULLSCREEN_EFFECT)) { + return response()->json(['status' => 'error', 'message' => '当前职务无权触发特效'], 403); } // 广播特效事件给房间内所有在线用户 diff --git a/app/Http/Controllers/ChatController.php b/app/Http/Controllers/ChatController.php index e1eedbb..2742c7f 100644 --- a/app/Http/Controllers/ChatController.php +++ b/app/Http/Controllers/ChatController.php @@ -26,9 +26,11 @@ use App\Models\User; use App\Services\AppointmentService; use App\Services\ChatStateService; use App\Services\MessageFilterService; +use App\Services\PositionPermissionService; use App\Services\RoomBroadcastService; use App\Services\UserCurrencyService; use App\Services\VipService; +use App\Support\PositionPermissionRegistry; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Http\UploadedFile; @@ -58,6 +60,7 @@ class ChatController extends Controller private readonly UserCurrencyService $currencyService, private readonly AppointmentService $appointmentService, private readonly RoomBroadcastService $broadcast, + private readonly PositionPermissionService $positionPermissionService, ) {} /** @@ -278,7 +281,9 @@ class ChatController extends Controller ]; } - // 渲染主聊天框架视图 + // 渲染主聊天框架视图前,先计算当前用户的聊天室顶部管理权限。 + $roomPermissionMap = $this->positionPermissionService->permissionMapForUser($user); + return view('chat.frame', [ 'room' => $room, 'user' => $user, @@ -289,6 +294,8 @@ class ChatController extends Controller 'historyMessages' => $historyMessages, 'pendingProposal' => $pendingProposalData, 'pendingDivorce' => $pendingDivorceData, + 'roomPermissionMap' => $roomPermissionMap, + 'hasRoomManagementPermission' => in_array(true, $roomPermissionMap, true), ]); } @@ -892,7 +899,8 @@ class ChatController extends Controller /** * 设置房间公告/祝福语(滚动显示在聊天室顶部) - * 需要房间主人或等级达到 level_announcement 配置值 + * 需要当前在职职务拥有 room.announcement 权限, + * id=1 站长始终允许操作。 * * @param int $id 房间ID */ @@ -901,9 +909,8 @@ class ChatController extends Controller $user = Auth::user(); $room = Room::findOrFail($id); - // 权限检查:房间主人 或 等级 >= level_announcement - $requiredLevel = (int) Sysparam::getValue('level_announcement', '10'); - if ($user->username !== $room->master && $user->user_level < $requiredLevel) { + // 改为统一走职务权限判断,不再给房主单独保留公告特权。 + if (! $this->positionPermissionService->hasPermission($user, PositionPermissionRegistry::ROOM_ANNOUNCEMENT)) { return response()->json(['status' => 'error', 'message' => '权限不足,无法修改公告'], 403); } diff --git a/app/Http/Controllers/RedPacketController.php b/app/Http/Controllers/RedPacketController.php index 11f0851..3f21f23 100644 --- a/app/Http/Controllers/RedPacketController.php +++ b/app/Http/Controllers/RedPacketController.php @@ -4,7 +4,7 @@ * 文件功能:聊天室礼包(红包)控制器 * * 提供两个核心接口: - * - send() :superlevel 站长凭空发出 888 数量 10 份礼包(金币 or 经验) + * - send() :拥有权限的职务用户凭空发出 8888 数量 10 份礼包(金币 or 经验) * - claim() :在线用户抢礼包(先到先得,每人一份) * * 接入 UserCurrencyService 记录所有货币变动流水。 @@ -23,9 +23,10 @@ use App\Events\RedPacketSent; use App\Jobs\SaveMessageJob; use App\Models\RedPacketClaim; use App\Models\RedPacketEnvelope; -use App\Models\Sysparam; use App\Services\ChatStateService; +use App\Services\PositionPermissionService; use App\Services\UserCurrencyService; +use App\Support\PositionPermissionRegistry; use Illuminate\Database\UniqueConstraintViolationException; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -54,10 +55,11 @@ class RedPacketController extends Controller public function __construct( private readonly ChatStateService $chatState, private readonly UserCurrencyService $currencyService, + private readonly PositionPermissionService $positionPermissionService, ) {} /** - * superlevel 站长凭空发出礼包。 + * 拥有权限的职务用户凭空发出礼包。 * * 不扣发包人自身货币,888 数量凭空发出分 10 份。 * type 参数决定本次发出的是金币(gold)还是经验(exp)。 @@ -75,10 +77,9 @@ class RedPacketController extends Controller $roomId = (int) $request->input('room_id'); $type = $request->input('type'); // 'gold' 或 'exp' - // 权限校验:仅 superlevel 可发礼包 - $superLevel = (int) Sysparam::getValue('superlevel', '100'); - if ($user->user_level < $superLevel) { - return response()->json(['status' => 'error', 'message' => '仅站长可发礼包红包'], 403); + // 改为按职务权限码控制礼包发放。 + if (! $this->positionPermissionService->hasPermission($user, PositionPermissionRegistry::ROOM_RED_PACKET)) { + return response()->json(['status' => 'error', 'message' => '当前职务无权发礼包红包'], 403); } // 检查该用户在此房间是否有进行中的红包(防止刷包) diff --git a/app/Models/Position.php b/app/Models/Position.php index bc3d4a7..16b5758 100644 --- a/app/Models/Position.php +++ b/app/Models/Position.php @@ -3,7 +3,8 @@ /** * 文件功能:职务模型 * 对应 positions 表,职务属于某个部门,包含等级、图标、人数上限和奖励上限 - * 任命权限通过 position_appoint_limits 中间表多对多关联定义 + * 任命权限通过 position_appoint_limits 中间表多对多关联定义, + * 聊天室顶部管理权限通过 permissions JSON 字段配置 * * @author ChatRoom Laravel * @@ -35,6 +36,7 @@ class Position extends Model 'daily_reward_limit', 'recipient_daily_limit', 'sort_order', + 'permissions', ]; /** @@ -50,6 +52,7 @@ class Position extends Model 'daily_reward_limit' => 'integer', 'recipient_daily_limit' => 'integer', 'sort_order' => 'integer', + 'permissions' => 'array', ]; } @@ -123,6 +126,24 @@ class Position extends Model return $this->currentCount() >= $this->max_persons; } + /** + * 判断当前职务是否拥有指定权限码。 + */ + public function hasPermission(string $permission): bool + { + return in_array($permission, $this->permissions ?? [], true); + } + + /** + * 返回当前职务的权限码列表。 + * + * @return list + */ + public function permissionCodes(): array + { + return array_values($this->permissions ?? []); + } + /** * 查询范围:按位阶降序 */ diff --git a/app/Services/PositionPermissionService.php b/app/Services/PositionPermissionService.php new file mode 100644 index 0000000..52710ba --- /dev/null +++ b/app/Services/PositionPermissionService.php @@ -0,0 +1,92 @@ + + */ + public function permissionsForUser(?User $user): array + { + if (! $user) { + return []; + } + + if ($user->id === 1) { + return PositionPermissionRegistry::codes(); + } + + $position = $user->activePosition?->position; + if (! $position) { + return []; + } + + return array_values(array_intersect( + PositionPermissionRegistry::codes(), + $position->permissions ?? [], + )); + } + + /** + * 返回当前用户全部权限的布尔映射表。 + * + * @return array + */ + public function permissionMapForUser(?User $user): array + { + $permissionMap = array_fill_keys(PositionPermissionRegistry::codes(), false); + + foreach ($this->permissionsForUser($user) as $permission) { + $permissionMap[$permission] = true; + } + + return $permissionMap; + } + + /** + * 判断用户是否拥有指定权限码。 + */ + public function hasPermission(?User $user, string $permission): bool + { + if (! in_array($permission, PositionPermissionRegistry::codes(), true)) { + return false; + } + + return in_array($permission, $this->permissionsForUser($user), true); + } + + /** + * 判断用户是否至少拥有一项指定权限。 + * + * @param list $permissions + */ + public function hasAnyPermission(?User $user, array $permissions): bool + { + foreach ($permissions as $permission) { + if ($this->hasPermission($user, $permission)) { + return true; + } + } + + return false; + } +} diff --git a/app/Support/PositionPermissionRegistry.php b/app/Support/PositionPermissionRegistry.php new file mode 100644 index 0000000..f054167 --- /dev/null +++ b/app/Support/PositionPermissionRegistry.php @@ -0,0 +1,169 @@ + + */ + public static function definitions(): array + { + return [ + self::ROOM_ANNOUNCEMENT => [ + 'group' => '聊天室管理', + 'label' => '设置公告', + 'description' => '允许修改聊天室顶部滚动公告。', + ], + self::ROOM_PUBLIC_BROADCAST => [ + 'group' => '聊天室管理', + 'label' => '公屏讲话', + 'description' => '允许在聊天室内发送管理员公屏讲话。', + ], + self::ROOM_CLEAR_SCREEN => [ + 'group' => '聊天室管理', + 'label' => '全员清屏', + 'description' => '允许清除当前房间所有人的普通聊天记录。', + ], + self::ROOM_RED_PACKET => [ + 'group' => '活动管理', + 'label' => '礼包红包', + 'description' => '允许在聊天室内发出金币或经验礼包。', + ], + self::ROOM_BACCARAT_LOSS_COVER => [ + 'group' => '活动管理', + 'label' => '买单活动', + 'description' => '允许创建和结束百家乐买单活动。', + ], + self::ROOM_FULLSCREEN_EFFECT => [ + 'group' => '全屏特效', + 'label' => '全屏特效', + 'description' => '允许触发聊天室内全部全屏动画特效。', + ], + ]; + } + + /** + * 返回全部权限码列表。 + * + * @return list + */ + public static function codes(): array + { + return array_keys(self::definitions()); + } + + /** + * 返回权限码到中文标题的映射。 + * + * @return array + */ + public static function labelMap(): array + { + $labels = []; + + foreach (self::definitions() as $code => $definition) { + $labels[$code] = $definition['label']; + } + + return $labels; + } + + /** + * 按分组返回权限定义,供后台表单渲染。 + * + * @return array> + */ + public static function groupedDefinitions(): array + { + $grouped = []; + + foreach (self::definitions() as $code => $definition) { + $grouped[$definition['group']][$code] = [ + 'label' => $definition['label'], + 'description' => $definition['description'], + ]; + } + + return $grouped; + } + + /** + * 将权限码数组转换为中文标题列表。 + * + * @param list $codes + * @return list + */ + public static function summaryLabels(array $codes): array + { + $labels = self::labelMap(); + + return array_values(array_map( + fn (string $code): string => $labels[$code] ?? $code, + array_values(array_intersect(self::codes(), $codes)) + )); + } + + /** + * 根据职务等级返回默认权限。 + * + * 默认策略: + * - Lv.60 及以上默认拥有「设置公告」 + * - Lv.97 及以上默认拥有顶部管理菜单全部权限 + * + * @return list + */ + public static function defaultPermissionsForLevel(int $level): array + { + if ($level >= 97) { + return self::codes(); + } + + if ($level >= 60) { + return [self::ROOM_ANNOUNCEMENT]; + } + + return []; + } +} diff --git a/database/migrations/2026_04_21_155457_add_permissions_to_positions_table.php b/database/migrations/2026_04_21_155457_add_permissions_to_positions_table.php new file mode 100644 index 0000000..a41a674 --- /dev/null +++ b/database/migrations/2026_04_21_155457_add_permissions_to_positions_table.php @@ -0,0 +1,73 @@ +json('permissions')->nullable()->comment('聊天室权限码 JSON 数组'); + }); + + $positions = DB::table('positions')->select(['id', 'level'])->get(); + + foreach ($positions as $position) { + DB::table('positions') + ->where('id', $position->id) + ->update([ + 'permissions' => json_encode( + $this->defaultPermissionsForLevel((int) $position->level), + JSON_UNESCAPED_UNICODE + ), + ]); + } + } + + /** + * 回滚迁移:删除 permissions 字段。 + */ + public function down(): void + { + Schema::table('positions', function (Blueprint $table) { + $table->dropColumn('permissions'); + }); + } + + /** + * 按当前等级回填默认权限。 + * + * @return list + */ + private function defaultPermissionsForLevel(int $level): array + { + if ($level >= 97) { + return [ + 'room.announcement', + 'room.public_broadcast', + 'room.clear_screen', + 'room.red_packet', + 'room.baccarat_loss_cover', + 'room.fullscreen_effect', + ]; + } + + if ($level >= 60) { + return ['room.announcement']; + } + + return []; + } +}; diff --git a/database/seeders/DepartmentPositionSeeder.php b/database/seeders/DepartmentPositionSeeder.php index 595ea7b..1e339d8 100644 --- a/database/seeders/DepartmentPositionSeeder.php +++ b/database/seeders/DepartmentPositionSeeder.php @@ -15,6 +15,7 @@ namespace Database\Seeders; use App\Models\Department; use App\Models\Position; +use App\Support\PositionPermissionRegistry; use Illuminate\Database\Seeder; class DepartmentPositionSeeder extends Seeder @@ -48,6 +49,7 @@ class DepartmentPositionSeeder extends Seeder 'max_persons' => $row['max_persons'], 'max_reward' => $row['max_reward'], 'sort_order' => $row['sort_order'], + 'permissions' => PositionPermissionRegistry::defaultPermissionsForLevel((int) $row['level']), ] ); $positions["{$row['department']}::{$row['name']}"] = $position; diff --git a/resources/views/admin/positions/index.blade.php b/resources/views/admin/positions/index.blade.php index ae9af86..b3159b7 100644 --- a/resources/views/admin/positions/index.blade.php +++ b/resources/views/admin/positions/index.blade.php @@ -16,6 +16,7 @@ showForm: false, editing: null, selectedIds: [], + selectedPermissions: [], form: { department_id: '', name: '', @@ -32,12 +33,14 @@ openCreate() { this.editing = null; this.selectedIds = []; + this.selectedPermissions = []; this.form = { department_id: '', name: '', icon: '🎖️', rank: 50, level: 60, max_persons: 1, max_reward: '', daily_reward_limit: '', recipient_daily_limit: '', sort_order: 0 }; this.showForm = true; }, - openEdit(pos, appointableIds) { + openEdit(pos, appointableIds, permissions) { this.editing = pos; this.selectedIds = appointableIds; + this.selectedPermissions = permissions; this.form = { department_id: pos.department_id, name: pos.name, @@ -61,6 +64,16 @@ }, isSelected(id) { return this.selectedIds.includes(id); + }, + togglePermission(code) { + if (this.selectedPermissions.includes(code)) { + this.selectedPermissions = this.selectedPermissions.filter(item => item !== code); + } else { + this.selectedPermissions.push(code); + } + }, + isPermissionSelected(code) { + return this.selectedPermissions.includes(code); } }"> @@ -92,15 +105,6 @@ - @if (session('success')) -
- {{ session('success') }}
- @endif - @if (session('error')) -
- {{ session('error') }}
- @endif - {{-- 全局奖励接收上限配置卡片(失焦/回车自动保存) --}}
单次上限 单日上限 任命权 + 聊天室权限 @php $superLvl = (int) \App\Models\Sysparam::getValue('superlevel', '100'); @endphp @if (Auth::user()->user_level >= $superLvl) 操作 @@ -249,6 +254,34 @@ @endif + + @if (! empty($pos->permissions)) + @php + $permissionSummaryLabels = collect($pos->permissions) + ->map(fn ($permissionCode) => $permissionLabels[$permissionCode] ?? $permissionCode) + ->values(); + $permissionPreview = $permissionSummaryLabels->take(2)->implode('、'); + $permissionTitle = $permissionSummaryLabels->implode(' / '); + @endphp +
+
+ 已开通 + + {{ $permissionSummaryLabels->count() }} 项 + +
+
+ {{ $permissionPreview }} + @if ($permissionSummaryLabels->count() > 2) + 等 {{ $permissionSummaryLabels->count() }} 项 + @endif +
+
+ @else +
+ @endif + @php $superLvl = (int) \App\Models\Sysparam::getValue('superlevel', '100'); @endphp @if (Auth::user()->user_level >= $superLvl) @@ -266,7 +299,7 @@ recipient_daily_limit: {{ $pos->recipient_daily_limit ?? 'null' }}, sort_order: {{ $pos->sort_order }}, requestUrl: '{{ route('admin.positions.update', $pos->id) }}' - }, {{ json_encode($appointableIds) }})" + }, {{ json_encode($appointableIds) }}, {{ json_encode($pos->permissions ?? []) }})" class="text-xs bg-indigo-50 text-indigo-600 font-bold px-2 py-1 rounded hover:bg-indigo-600 hover:text-white transition"> 编辑 @@ -286,7 +319,8 @@ @empty - 该部门暂无职务 + 该部门暂无职务 @endforelse @@ -391,6 +425,37 @@
+ {{-- 聊天室权限多选 --}} +
+

+ 权限管理 + (控制聊天室输入框上方「管理」菜单中可见的功能按钮) +

+
+ @foreach ($positionPermissions as $groupName => $permissions) +
+
{{ $groupName }}
+
+ @foreach ($permissions as $permissionCode => $permissionMeta) + + @endforeach +
+
+ @endforeach +
+
+
diff --git a/resources/views/chat/frame.blade.php b/resources/views/chat/frame.blade.php index e185e53..77dd08c 100644 --- a/resources/views/chat/frame.blade.php +++ b/resources/views/chat/frame.blade.php @@ -26,6 +26,7 @@ $levelFreeze = (int) \App\Models\Sysparam::getValue('level_freeze', '14'); $superLevel = (int) \App\Models\Sysparam::getValue('superlevel', '100'); $myLevel = Auth::user()->user_level; + $positionPermissions = array_keys(array_filter($roomPermissionMap ?? [])); @endphp