From 8a74bfd639a3a9672b3b006c7d2643bdf8c1ddf1 Mon Sep 17 00:00:00 2001 From: lkddi Date: Sun, 1 Mar 2026 20:17:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=B8=B8=E6=88=8F=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=B3=BB=E7=BB=9F=EF=BC=9A=E2=91=A0game=5Fconfigs?= =?UTF-8?q?=E8=A1=A8+=E6=A8=A1=E5=9E=8B(forGame/isEnabled/param=E9=9D=99?= =?UTF-8?q?=E6=80=81=E6=96=B9=E6=B3=95)=20=E2=91=A1GameConfigSeeder?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=965=E6=AC=BE=E6=B8=B8=E6=88=8F?= =?UTF-8?q?=E5=8F=82=E6=95=B0=20=E2=91=A2=E5=90=8E=E5=8F=B0=E5=8D=A1?= =?UTF-8?q?=E7=89=87=E5=BC=8F=E7=AE=A1=E7=90=86=E9=A1=B5(=E5=BC=80?= =?UTF-8?q?=E5=85=B3+=E5=8F=82=E6=95=B0=E8=A1=A8=E5=8D=95)=20=E2=91=A3?= =?UTF-8?q?=E4=BE=A7=E8=BE=B9=E6=A0=8F=E8=8F=9C=E5=8D=95=E3=80=8C=E6=B8=B8?= =?UTF-8?q?=E6=88=8F=E7=AE=A1=E7=90=86=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Admin/GameConfigController.php | 72 +++++ app/Models/GameConfig.php | 80 ++++++ ...03_01_201417_create_game_configs_table.php | 40 +++ database/seeders/GameConfigSeeder.php | 128 +++++++++ .../views/admin/game-configs/index.blade.php | 254 ++++++++++++++++++ resources/views/admin/layouts/app.blade.php | 4 + routes/web.php | 7 + 7 files changed, 585 insertions(+) create mode 100644 app/Http/Controllers/Admin/GameConfigController.php create mode 100644 app/Models/GameConfig.php create mode 100644 database/migrations/2026_03_01_201417_create_game_configs_table.php create mode 100644 database/seeders/GameConfigSeeder.php create mode 100644 resources/views/admin/game-configs/index.blade.php diff --git a/app/Http/Controllers/Admin/GameConfigController.php b/app/Http/Controllers/Admin/GameConfigController.php new file mode 100644 index 0000000..0f35ed5 --- /dev/null +++ b/app/Http/Controllers/Admin/GameConfigController.php @@ -0,0 +1,72 @@ +get(); + + return view('admin.game-configs.index', compact('games')); + } + + /** + * 切换游戏开启/关闭状态。 + */ + public function toggle(GameConfig $gameConfig): JsonResponse + { + $gameConfig->update(['enabled' => ! $gameConfig->enabled]); + $gameConfig->clearCache(); + + return response()->json([ + 'ok' => true, + 'enabled' => $gameConfig->enabled, + 'message' => $gameConfig->enabled + ? "「{$gameConfig->name}」已开启" + : "「{$gameConfig->name}」已关闭", + ]); + } + + /** + * 保存游戏核心参数。 + * + * 接收前端提交的 params JSON 对象并合并至现有配置。 + */ + public function updateParams(Request $request, GameConfig $gameConfig): RedirectResponse + { + $request->validate([ + 'params' => 'required|array', + ]); + + // 合并参数,保留已有键,只更新传入的键 + $current = $gameConfig->params ?? []; + $updated = array_merge($current, $request->input('params')); + + $gameConfig->update(['params' => $updated]); + $gameConfig->clearCache(); + + return back()->with('success', "「{$gameConfig->name}」参数已保存!"); + } +} diff --git a/app/Models/GameConfig.php b/app/Models/GameConfig.php new file mode 100644 index 0000000..c8ec343 --- /dev/null +++ b/app/Models/GameConfig.php @@ -0,0 +1,80 @@ + 'boolean', + 'params' => 'array', + ]; + } + + // ─── 静态工具方法 ───────────────────────────────────────────────── + + /** + * 根据游戏标识获取配置(带缓存)。 + * + * @param string $key 游戏标识,如 'baccarat' + */ + public static function forGame(string $key): ?static + { + return Cache::remember("game_config:{$key}", 60, fn () => static::where('game_key', $key)->first()); + } + + /** + * 判断游戏是否已开启。 + */ + public static function isEnabled(string $key): bool + { + return (bool) static::forGame($key)?->enabled; + } + + /** + * 获取指定游戏的某个参数值,不存在时返回默认值。 + * + * @param string $key 游戏标识 + * @param string $param 参数名 + * @param mixed $default 默认值 + */ + public static function param(string $key, string $param, mixed $default = null): mixed + { + return static::forGame($key)?->params[$param] ?? $default; + } + + /** + * 清除游戏配置缓存。 + */ + public function clearCache(): void + { + Cache::forget("game_config:{$this->game_key}"); + } +} diff --git a/database/migrations/2026_03_01_201417_create_game_configs_table.php b/database/migrations/2026_03_01_201417_create_game_configs_table.php new file mode 100644 index 0000000..9d84420 --- /dev/null +++ b/database/migrations/2026_03_01_201417_create_game_configs_table.php @@ -0,0 +1,40 @@ +id(); + $table->string('game_key', 50)->unique()->comment('游戏唯一标识(如 baccarat、slot_machine)'); + $table->string('name', 100)->comment('游戏中文名'); + $table->string('icon', 10)->default('🎮')->comment('游戏图标 Emoji'); + $table->text('description')->nullable()->comment('游戏简介'); + $table->boolean('enabled')->default(false)->comment('是否开启'); + $table->json('params')->nullable()->comment('游戏核心参数(JSON)'); + $table->timestamps(); + }); + } + + /** + * 回滚迁移。 + */ + public function down(): void + { + Schema::dropIfExists('game_configs'); + } +}; diff --git a/database/seeders/GameConfigSeeder.php b/database/seeders/GameConfigSeeder.php new file mode 100644 index 0000000..4fc852e --- /dev/null +++ b/database/seeders/GameConfigSeeder.php @@ -0,0 +1,128 @@ + 'baccarat', + 'name' => '百家乐', + 'icon' => '🎲', + 'description' => '系统每隔一段时间自动开一局,玩家在倒计时内押注大/小/豹子,骰子结果决定胜负。', + 'enabled' => false, + 'params' => [ + 'interval_minutes' => 2, // 多少分钟开一局 + 'bet_window_seconds' => 60, // 每局押注窗口(秒) + 'min_bet' => 100, // 最低押注金币 + 'max_bet' => 50000, // 最高押注金币 + 'payout_big' => 1, // 大:1:1 赔率 + 'payout_small' => 1, // 小:1:1 赔率 + 'payout_triple' => 24, // 豹子:1:24 赔率 + 'kill_points' => [3, 18], // 庄家收割点数(全灭) + ], + ], + + // ─── 老虎机 ────────────────────────────────────────────── + [ + 'game_key' => 'slot_machine', + 'name' => '老虎机', + 'icon' => '🎰', + 'description' => '消耗金币转动老虎机,三列图案匹配可获得不同倍率奖励,三个7大奖全服广播。', + 'enabled' => false, + 'params' => [ + 'cost_per_spin' => 100, // 每次旋转消耗 + 'house_edge_percent' => 15, // 庄家边际(%) + 'daily_limit' => 100, // 每日最多转动次数(0=不限) + 'jackpot_payout' => 100, // 三个7 赔率 + 'triple_payout' => 50, // 三个💎 赔率 + 'same_payout' => 10, // 其他三同 赔率 + 'pair_payout' => 2, // 两同 赔率 + 'curse_enabled' => true, // 是否开启诅咒(三💀扣双倍) + ], + ], + + // ─── 神秘箱子 ──────────────────────────────────────────── + [ + 'game_key' => 'mystery_box', + 'name' => '神秘箱子', + 'icon' => '📦', + 'description' => '管理员随时投放或系统定时自动投放神秘箱,最快发送暗号的用户开箱获得奖励。', + 'enabled' => false, + 'params' => [ + 'auto_drop_enabled' => false, // 是否自动定时投放 + 'auto_interval_hours' => 2, // 自动投放间隔(小时) + 'claim_window_seconds' => 60, // 领取窗口(秒) + 'min_reward' => 500, // 普通箱最低奖励 + 'max_reward' => 2000, // 普通箱最高奖励 + 'rare_min_reward' => 5000, // 稀有箱最低奖励 + 'rare_max_reward' => 20000, // 稀有箱最高奖励 + 'trap_chance_percent' => 10, // 黑化箱触发概率(%) + ], + ], + + // ─── 赛马竞猜 ──────────────────────────────────────────── + [ + 'game_key' => 'horse_racing', + 'name' => '赛马竞猜', + 'icon' => '🐎', + 'description' => '系统定期举办赛马,用户在倒计时内下注,按注池赔率结算,跑马过程 WebSocket 实时播报。', + 'enabled' => false, + 'params' => [ + 'interval_minutes' => 30, // 多少分钟一场 + 'bet_window_seconds' => 90, // 押注窗口(秒) + 'race_duration' => 30, // 跑马动画时长(秒) + 'horse_count' => 4, // 参赛马匹数量 + 'min_bet' => 100, // 最低押注 + 'max_bet' => 100000, // 最高押注 + 'house_take_percent' => 5, // 庄家抽水(%) + ], + ], + + // ─── 神秘占卜 ──────────────────────────────────────────── + [ + 'game_key' => 'fortune_telling', + 'name' => '神秘占卜', + 'icon' => '🔮', + 'description' => '每日一次免费占卜,系统生成玄学签文并赋予当日加成效果(幸运/倒霉)。额外占卜消耗金币。', + 'enabled' => false, + 'params' => [ + 'free_count_per_day' => 1, // 每日免费次数 + 'extra_cost' => 500, // 额外次数消耗金币 + 'buff_duration_hours' => 24, // 加成效果持续时间 + 'jackpot_chance' => 5, // 上上签概率(%) + 'good_chance' => 20, // 上签概率(%) + 'bad_chance' => 20, // 下签概率(%) + 'curse_chance' => 5, // 大凶签概率(%) + ], + ], + ]; + + foreach ($games as $game) { + GameConfig::updateOrCreate( + ['game_key' => $game['game_key']], + $game, + ); + } + } +} diff --git a/resources/views/admin/game-configs/index.blade.php b/resources/views/admin/game-configs/index.blade.php new file mode 100644 index 0000000..5feae73 --- /dev/null +++ b/resources/views/admin/game-configs/index.blade.php @@ -0,0 +1,254 @@ +@extends('admin.layouts.app') + +@section('title', '游戏管理') + +@section('content') +
+ + {{-- 页头 --}} +
+

🎮 游戏管理

+

统一管理聊天室所有娱乐游戏的开关状态与核心参数,所有游戏默认关闭。

+
+ + @if (session('success')) +
+ ✅ {{ session('success') }} +
+ @endif + + {{-- 游戏卡片列表 --}} +
+ @foreach ($games as $game) +
+ + {{-- 卡片头部:游戏名 + 开关 --}} +
+
+ {{ $game->icon }} +
+
+ {{ $game->name }} + + {{ $game->enabled ? '运行中' : '已关闭' }} + +
+
{{ $game->description }}
+
+
+ {{-- 大开关按钮 --}} + +
+ + {{-- 参数配置区域 --}} +
+
+ @csrf + + @php + $params = $game->params ?? []; + $labels = gameParamLabels($game->game_key); + @endphp + +
+ @foreach ($params as $paramKey => $paramValue) + @php $meta = $labels[$paramKey] ?? ['label' => $paramKey, 'type' => 'number', 'unit' => ''] @endphp +
+ + + @if ($meta['type'] === 'boolean') + + @elseif ($meta['type'] === 'array') + + @else + + @endif +
+ @endforeach +
+ +
+ + 修改后立即生效(缓存60秒刷新) +
+
+
+
+ @endforeach + + @if ($games->isEmpty()) +
+ 暂无游戏配置,请先运行 php artisan db:seed + --class=GameConfigSeeder +
+ @endif +
+
+ + +@endsection + +@php + /** + * 返回各游戏参数的中文标签说明。 + * + * @param string $gameKey + * @return array + */ + function gameParamLabels(string $gameKey): array + { + return match ($gameKey) { + 'baccarat' => [ + 'interval_minutes' => ['label' => '开局间隔', 'type' => 'number', 'unit' => '分钟', 'min' => 1], + 'bet_window_seconds' => ['label' => '押注窗口', 'type' => 'number', 'unit' => '秒', 'min' => 10], + 'min_bet' => ['label' => '最低押注', 'type' => 'number', 'unit' => '金币', 'min' => 1], + 'max_bet' => ['label' => '最高押注', 'type' => 'number', 'unit' => '金币', 'min' => 1], + 'payout_big' => ['label' => '大赔率(1:N)', 'type' => 'number', 'unit' => '', 'min' => 1], + 'payout_small' => ['label' => '小赔率(1:N)', 'type' => 'number', 'unit' => '', 'min' => 1], + 'payout_triple' => ['label' => '豹子赔率(1:N)', 'type' => 'number', 'unit' => '', 'min' => 1], + 'kill_points' => ['label' => '庄家收割点数', 'type' => 'array', 'unit' => '逗号分隔'], + ], + 'slot_machine' => [ + 'cost_per_spin' => ['label' => '每次旋转消耗', 'type' => 'number', 'unit' => '金币', 'min' => 1], + 'house_edge_percent' => [ + 'label' => '庄家边际', + 'type' => 'number', + 'unit' => '%', + 'min' => 0, + 'step' => 0.1, + ], + 'daily_limit' => ['label' => '每日转动上限', 'type' => 'number', 'unit' => '次(0=不限)', 'min' => 0], + 'jackpot_payout' => ['label' => '三个7赔率(1:N)', 'type' => 'number', 'unit' => '', 'min' => 1], + 'triple_payout' => ['label' => '三💎赔率(1:N)', 'type' => 'number', 'unit' => '', 'min' => 1], + 'same_payout' => ['label' => '其他三同(1:N)', 'type' => 'number', 'unit' => '', 'min' => 1], + 'pair_payout' => ['label' => '两同赔率(1:N)', 'type' => 'number', 'unit' => '', 'min' => 1], + 'curse_enabled' => ['label' => '开启诅咒(三💀)', 'type' => 'boolean', 'unit' => ''], + ], + 'mystery_box' => [ + 'auto_drop_enabled' => ['label' => '自动定时投放', 'type' => 'boolean', 'unit' => ''], + 'auto_interval_hours' => ['label' => '自动投放间隔', 'type' => 'number', 'unit' => '小时', 'min' => 1], + 'claim_window_seconds' => ['label' => '领取窗口', 'type' => 'number', 'unit' => '秒', 'min' => 10], + 'min_reward' => ['label' => '普通箱最低奖励', 'type' => 'number', 'unit' => '金币', 'min' => 1], + 'max_reward' => ['label' => '普通箱最高奖励', 'type' => 'number', 'unit' => '金币', 'min' => 1], + 'rare_min_reward' => ['label' => '稀有箱最低奖励', 'type' => 'number', 'unit' => '金币', 'min' => 1], + 'rare_max_reward' => ['label' => '稀有箱最高奖励', 'type' => 'number', 'unit' => '金币', 'min' => 1], + 'trap_chance_percent' => [ + 'label' => '黑化箱概率', + 'type' => 'number', + 'unit' => '%', + 'min' => 0, + 'max' => 100, + ], + ], + 'horse_racing' => [ + 'interval_minutes' => ['label' => '比赛间隔', 'type' => 'number', 'unit' => '分钟', 'min' => 5], + 'bet_window_seconds' => ['label' => '押注窗口', 'type' => 'number', 'unit' => '秒', 'min' => 10], + 'race_duration' => ['label' => '跑马动画时长', 'type' => 'number', 'unit' => '秒', 'min' => 10], + 'horse_count' => ['label' => '参赛马匹数', 'type' => 'number', 'unit' => '匹', 'min' => 2, 'max' => 8], + 'min_bet' => ['label' => '最低押注', 'type' => 'number', 'unit' => '金币', 'min' => 1], + 'max_bet' => ['label' => '最高押注', 'type' => 'number', 'unit' => '金币', 'min' => 1], + 'house_take_percent' => [ + 'label' => '庄家抽水', + 'type' => 'number', + 'unit' => '%', + 'min' => 0, + 'max' => 20, + ], + ], + 'fortune_telling' => [ + 'free_count_per_day' => ['label' => '每日免费次数', 'type' => 'number', 'unit' => '次', 'min' => 0], + 'extra_cost' => ['label' => '额外次数消耗', 'type' => 'number', 'unit' => '金币', 'min' => 0], + 'buff_duration_hours' => ['label' => '加成持续时间', 'type' => 'number', 'unit' => '小时', 'min' => 1], + 'jackpot_chance' => [ + 'label' => '上上签概率', + 'type' => 'number', + 'unit' => '%', + 'min' => 0, + 'max' => 100, + ], + 'good_chance' => ['label' => '上签概率', 'type' => 'number', 'unit' => '%', 'min' => 0, 'max' => 100], + 'bad_chance' => ['label' => '下签概率', 'type' => 'number', 'unit' => '%', 'min' => 0, 'max' => 100], + 'curse_chance' => [ + 'label' => '大凶签概率', + 'type' => 'number', + 'unit' => '%', + 'min' => 0, + 'max' => 100, + ], + ], + default => [], + }; + } +@endphp diff --git a/resources/views/admin/layouts/app.blade.php b/resources/views/admin/layouts/app.blade.php index 3cc97c9..44a827c 100644 --- a/resources/views/admin/layouts/app.blade.php +++ b/resources/views/admin/layouts/app.blade.php @@ -79,6 +79,10 @@ class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.holiday-events.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}"> {!! '🎊 节日福利' . $ro !!} + + {!! '🎮 游戏管理' . $ro !!} + {!! '🏛️ 部门管理' . $ro !!} diff --git a/routes/web.php b/routes/web.php index d1acc4f..0ff0a46 100644 --- a/routes/web.php +++ b/routes/web.php @@ -309,6 +309,13 @@ Route::middleware(['chat.auth', 'chat.has_position'])->prefix('admin')->name('ad Route::post('/{holidayEvent}/trigger-now', [\App\Http\Controllers\Admin\HolidayEventController::class, 'triggerNow'])->name('trigger-now'); Route::delete('/{holidayEvent}', [\App\Http\Controllers\Admin\HolidayEventController::class, 'destroy'])->name('destroy'); }); + + // 🎮 游戏管理(开关 + 核心参数配置) + Route::prefix('game-configs')->name('game-configs.')->group(function () { + Route::get('/', [\App\Http\Controllers\Admin\GameConfigController::class, 'index'])->name('index'); + Route::post('/{gameConfig}/toggle', [\App\Http\Controllers\Admin\GameConfigController::class, 'toggle'])->name('toggle'); + Route::post('/{gameConfig}/params', [\App\Http\Controllers\Admin\GameConfigController::class, 'updateParams'])->name('params'); + }); }); // ──────────────────────────────────────────────────────────────