${buildGameLabelChipHtml("礼包红包", accentColor)}
- ${escapeHtml(typeLabel)}
+ ${escapeHtml(typeLabel)}
${summary}
@@ -130,6 +149,26 @@ function buildQuizBadgeHtml(msg, accentColor = "#7c3aed") {
`;
}
+/**
+ * 判断当前公屏消息是否属于“我自己”的钓鱼结果广播。
+ *
+ * 说明:
+ * - 收竿后,钓鱼者本人已经会在包厢窗口收到本地结果提示;
+ * - 这里需要把同一条公屏广播对本人隐藏,避免自己同时看到两条。
+ */
+function isOwnFishingResultBroadcast(msg) {
+ const currentUsername = String(window.chatContext?.username || "").trim();
+ const fishingUsername = String(msg?.fishing_username || "").trim();
+
+ if (!currentUsername) {
+ return false;
+ }
+
+ return String(msg?.from_user || "") === "钓鱼播报"
+ && String(msg?.action || "") === "fishing_result"
+ && fishingUsername === currentUsername;
+}
+
/**
* 判断当前消息是否应该使用统一的游戏通知卡片。
*/
@@ -549,6 +588,10 @@ export function appendMessage(msg, renderBatch = null) {
state.trackMaxMsgId(msg.id || 0);
+ if (isOwnFishingResultBroadcast(msg)) {
+ return null;
+ }
+
const quizMeta = normalizeQuizRoundPayload(msg);
const idiomRoundId = quizMeta.roundId;
const isIdiomStartMessage = isQuizStartMessage(msg)
diff --git a/tests/Feature/Feature/AdminGameConfigControllerTest.php b/tests/Feature/Feature/AdminGameConfigControllerTest.php
new file mode 100644
index 0000000..880e327
--- /dev/null
+++ b/tests/Feature/Feature/AdminGameConfigControllerTest.php
@@ -0,0 +1,183 @@
+create([
+ 'id' => 1,
+ 'username' => 'site-owner',
+ 'user_level' => 100,
+ ]);
+
+ Room::query()->create([
+ 'id' => 1,
+ 'room_name' => 'test-room',
+ 'room_owner' => $siteOwner->username,
+ 'room_des' => '用于后台游戏配置测试',
+ 'room_time' => now(),
+ 'build_time' => now(),
+ ]);
+
+ $gameConfig = GameConfig::query()->create([
+ 'game_key' => 'fishing',
+ 'name' => '钓鱼',
+ 'icon' => 'fish',
+ 'description' => 'Fishing Game',
+ 'enabled' => true,
+ 'params' => [
+ 'room_scope_mode' => 'single',
+ 'room_ids' => [1],
+ 'fishing_cost' => 5,
+ 'fishing_wait_min' => 8,
+ 'fishing_wait_max' => 15,
+ 'fishing_cooldown' => 300,
+ ],
+ ]);
+
+ $response = $this->actingAs($siteOwner)->post(route('admin.game-configs.params', $gameConfig), [
+ 'params' => [
+ 'room_scope_mode' => 'single',
+ 'room_ids' => [1],
+ 'fishing_cost' => 5,
+ 'fishing_wait_min' => 8,
+ 'fishing_wait_max' => 15,
+ 'fishing_cooldown' => 120,
+ ],
+ ]);
+
+ $response->assertRedirect();
+ $response->assertSessionHas('success');
+
+ $this->assertSame(120, (int) ($gameConfig->fresh()->params['fishing_cooldown'] ?? 0));
+ }
+
+ /**
+ * 方法功能:验证其他游戏的普通参数也会通过统一保存入口真正落库。
+ */
+ public function test_admin_can_update_multiple_game_params_via_shared_config_endpoint(): void
+ {
+ $siteOwner = User::factory()->create([
+ 'id' => 1,
+ 'username' => 'site-owner',
+ 'user_level' => 100,
+ ]);
+
+ $cases = [
+ [
+ 'game_key' => 'baccarat',
+ 'name' => '百家乐',
+ 'icon' => 'dice',
+ 'params' => [
+ 'room_scope_mode' => 'single',
+ 'room_ids' => [1],
+ 'interval_minutes' => 2,
+ 'bet_window_seconds' => 60,
+ ],
+ 'updated_key' => 'bet_window_seconds',
+ 'updated_value' => 90,
+ ],
+ [
+ 'game_key' => 'lottery',
+ 'name' => '双色球彩票',
+ 'icon' => 'lottery',
+ 'params' => [
+ 'room_scope_mode' => 'single',
+ 'room_ids' => [1],
+ 'ticket_price' => 100,
+ 'draw_hour' => 20,
+ ],
+ 'updated_key' => 'ticket_price',
+ 'updated_value' => 188,
+ ],
+ [
+ 'game_key' => 'mystery_box',
+ 'name' => '神秘箱子',
+ 'icon' => 'box',
+ 'params' => [
+ 'room_scope_mode' => 'single',
+ 'room_ids' => [1],
+ 'claim_window_seconds' => 60,
+ 'normal_reward_min' => 500,
+ ],
+ 'updated_key' => 'claim_window_seconds',
+ 'updated_value' => 75,
+ ],
+ [
+ 'game_key' => 'idiom',
+ 'name' => '猜谜活动',
+ 'icon' => 'puzzle',
+ 'params' => [
+ 'room_scope_mode' => 'single',
+ 'room_ids' => [1],
+ 'reward_gold' => 50,
+ 'reward_exp' => 30,
+ ],
+ 'updated_key' => 'reward_gold',
+ 'updated_value' => 88,
+ ],
+ ];
+
+ foreach ($cases as $index => $case) {
+ $room = Room::query()->create([
+ 'room_name' => 'room-'.($index + 1),
+ 'room_owner' => $siteOwner->username,
+ 'room_des' => '用于后台游戏配置测试',
+ 'room_time' => now(),
+ 'build_time' => now(),
+ ]);
+ $roomId = (int) $room->id;
+
+ $gameConfig = GameConfig::query()->create([
+ 'game_key' => $case['game_key'],
+ 'name' => $case['name'],
+ 'icon' => $case['icon'],
+ 'description' => 'shared config endpoint test',
+ 'enabled' => true,
+ 'params' => array_merge($case['params'], [
+ 'room_ids' => [$roomId],
+ ]),
+ ]);
+
+ $requestParams = $case['params'];
+ $requestParams['room_ids'] = [$roomId];
+ $requestParams[$case['updated_key']] = $case['updated_value'];
+
+ $response = $this->actingAs($siteOwner)->post(route('admin.game-configs.params', $gameConfig), [
+ 'params' => $requestParams,
+ ]);
+
+ $response->assertRedirect();
+ $response->assertSessionHas('success');
+
+ $this->assertSame(
+ $case['updated_value'],
+ (int) ($gameConfig->fresh()->params[$case['updated_key']] ?? 0),
+ "{$case['game_key']} 参数未写入数据库。",
+ );
+ }
+ }
+}