Files
chatroom/resources/views/admin/fishing/index.blade.php
lkddi 03ec3a9fbb 功能:钓鱼游戏后台管理系统
一、钓鱼全局开关
- 钓鱼纳入 GameConfig(game_key=fishing),游戏管理页可一键开关
- cast() 接口加开关校验,关闭时返回 403 友好提示
- GameConfigSeeder 新增 fishing 配置(含4个参数)

二、钓鱼事件数据库化
- 新建 fishing_events 表(emoji/name/message/exp/jjb/weight/is_active/sort)
- FishingEvent 模型含 rollOne() 加权随机方法
- FishingEventSeeder 填充7条初始事件(经验降低、金币提升)
- FishingController::randomFishResult() 改为读数据库事件

三、钓鱼参数迁移至 GameConfig
- fishing_cost/wait_min/wait_max/cooldown 改为 GameConfig::param() 读取
- 保留 Sysparam fallback 兼容旧数据

四、后台管理页面
- 新建 FishingEventController(CRUD + AJAX toggle)
- 新建 admin/fishing/index.blade.php(事件列表+概率显示+编辑弹窗)
- 侧边栏「游戏管理」下方新增「🎣 钓鱼事件」入口
- 游戏管理视图 gameParamLabels 新增钓鱼参数标签
2026-03-03 16:46:36 +08:00

282 lines
16 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@extends('admin.layouts.app')
@section('title', '钓鱼事件管理')
@section('content')
<div class="space-y-6">
{{-- 页头 --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 flex justify-between items-center">
<div>
<h2 class="text-lg font-bold text-gray-800">🎣 钓鱼事件管理</h2>
<p class="text-xs text-gray-500 mt-1">
管理钓鱼随机事件。权重越大被触发概率越高。
当前激活事件总权重:<strong class="text-indigo-600">{{ $totalWeight }}</strong>
</p>
</div>
<a href="{{ route('admin.game-configs.index') }}"
class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg text-sm font-bold hover:bg-gray-200 transition">
⚙️ 钓鱼参数设置
</a>
</div>
{{-- Flash --}}
@if (session('success'))
<div class="bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded-lg text-sm">
{{ session('success') }}
</div>
@endif
{{-- 钓鱼事件列表 --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
<table class="w-full text-sm">
<thead class="bg-gray-50 border-b border-gray-100">
<tr>
<th class="px-4 py-3 text-left text-xs font-bold text-gray-500 uppercase">排序</th>
<th class="px-4 py-3 text-left text-xs font-bold text-gray-500 uppercase">符号</th>
<th class="px-4 py-3 text-left text-xs font-bold text-gray-500 uppercase">名称</th>
<th class="px-4 py-3 text-left text-xs font-bold text-gray-500 uppercase w-1/3">播报内容</th>
<th class="px-4 py-3 text-center text-xs font-bold text-gray-500 uppercase">经验</th>
<th class="px-4 py-3 text-center text-xs font-bold text-gray-500 uppercase">金币</th>
<th class="px-4 py-3 text-center text-xs font-bold text-gray-500 uppercase">权重</th>
<th class="px-4 py-3 text-center text-xs font-bold text-gray-500 uppercase">概率</th>
<th class="px-4 py-3 text-center text-xs font-bold text-gray-500 uppercase">状态</th>
<th class="px-4 py-3 text-right text-xs font-bold text-gray-500 uppercase">操作</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-50">
@foreach ($events as $event)
<tr id="row-{{ $event->id }}" class="{{ $event->is_active ? '' : 'opacity-50' }}">
<td class="px-4 py-3 text-gray-500 text-xs">{{ $event->sort }}</td>
<td class="px-4 py-3 text-xl">{{ $event->emoji }}</td>
<td class="px-4 py-3 font-medium text-gray-800">{{ $event->name }}</td>
<td class="px-4 py-3 text-gray-500 text-xs">{{ $event->message }}</td>
<td
class="px-4 py-3 text-center font-mono text-xs
{{ $event->exp > 0 ? 'text-green-600' : ($event->exp < 0 ? 'text-red-500' : 'text-gray-400') }}">
{{ $event->exp > 0 ? '+' : '' }}{{ $event->exp }}
</td>
<td
class="px-4 py-3 text-center font-mono text-xs
{{ $event->jjb > 0 ? 'text-amber-600' : ($event->jjb < 0 ? 'text-red-500' : 'text-gray-400') }}">
{{ $event->jjb > 0 ? '+' : '' }}{{ $event->jjb }}
</td>
<td class="px-4 py-3 text-center text-xs text-indigo-600 font-bold">{{ $event->weight }}</td>
<td class="px-4 py-3 text-center text-xs text-gray-500">
@if ($totalWeight > 0 && $event->is_active)
{{ number_format(($event->weight / $totalWeight) * 100, 1) }}%
@else
@endif
</td>
<td class="px-4 py-3 text-center">
<button onclick="toggleEvent({{ $event->id }})" id="toggle-{{ $event->id }}"
class="px-2 py-1 rounded-full text-xs font-bold transition
{{ $event->is_active ? 'bg-emerald-100 text-emerald-700 hover:bg-emerald-200' : 'bg-gray-100 text-gray-500 hover:bg-gray-200' }}">
{{ $event->is_active ? '启用' : '禁用' }}
</button>
</td>
<td class="px-4 py-3 text-right">
<button onclick="openEdit({{ $event->id }})"
class="px-3 py-1 bg-indigo-50 text-indigo-700 rounded text-xs font-bold hover:bg-indigo-100 transition mr-1">
编辑
</button>
<form action="{{ route('admin.fishing.destroy', $event->id) }}" method="POST"
class="inline" onsubmit="return confirm('确定删除事件「{{ $event->name }}」?')">
@csrf @method('DELETE')
<button type="submit"
class="px-3 py-1 bg-red-50 text-red-600 rounded text-xs font-bold hover:bg-red-100 transition">
删除
</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
{{-- 新增事件卡片 --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
<div class="p-5 border-b border-gray-100 bg-gray-50">
<h3 class="font-bold text-gray-700 text-sm"> 新增钓鱼事件</h3>
</div>
<form action="{{ route('admin.fishing.store') }}" method="POST" class="p-5">
@csrf
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">Emoji 符号</label>
<input type="text" name="emoji" value="{{ old('emoji') }}" placeholder="🐟" required
class="w-full border border-gray-300 rounded-lg p-2 text-sm focus:border-indigo-400">
</div>
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">名称</label>
<input type="text" name="name" value="{{ old('name') }}" placeholder="小鲤鱼" required
class="w-full border border-gray-300 rounded-lg p-2 text-sm focus:border-indigo-400">
</div>
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">经验变化</label>
<input type="number" name="exp" value="{{ old('exp', 0) }}"
class="w-full border border-gray-300 rounded-lg p-2 text-sm focus:border-indigo-400">
</div>
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">金币变化</label>
<input type="number" name="jjb" value="{{ old('jjb', 0) }}"
class="w-full border border-gray-300 rounded-lg p-2 text-sm focus:border-indigo-400">
</div>
<div class="md:col-span-2">
<label class="block text-xs font-bold text-gray-600 mb-1">播报内容</label>
<input type="text" name="message" value="{{ old('message') }}" placeholder="钓到一条小鲤鱼..." required
class="w-full border border-gray-300 rounded-lg p-2 text-sm focus:border-indigo-400">
</div>
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">权重(概率)</label>
<input type="number" name="weight" value="{{ old('weight', 10) }}" min="1"
class="w-full border border-gray-300 rounded-lg p-2 text-sm focus:border-indigo-400">
</div>
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">排序</label>
<input type="number" name="sort" value="{{ old('sort', 0) }}" min="0"
class="w-full border border-gray-300 rounded-lg p-2 text-sm focus:border-indigo-400">
</div>
</div>
<div class="mt-4 flex items-center gap-4">
<button type="submit"
class="px-5 py-2 bg-indigo-600 text-white rounded-lg font-bold hover:bg-indigo-700 transition text-sm shadow-sm">
💾 添加事件
</button>
<label class="flex items-center gap-2 text-sm text-gray-600 cursor-pointer">
<input type="checkbox" name="is_active" value="1" checked class="rounded">
立即启用
</label>
</div>
</form>
</div>
</div>
{{-- 编辑弹窗 --}}
<div id="edit-modal" class="hidden fixed inset-0 bg-black/40 z-50 flex items-center justify-center p-4">
<div class="bg-white rounded-xl w-full max-w-lg shadow-2xl">
<div class="p-5 border-b border-gray-100 flex justify-between items-center">
<h3 class="font-bold text-gray-800">✏️ 编辑钓鱼事件</h3>
<button onclick="closeEdit()" class="text-gray-400 hover:text-gray-600 text-xl"></button>
</div>
<form id="edit-form" method="POST" class="p-5">
@csrf @method('PUT')
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">Emoji</label>
<input type="text" name="emoji" id="edit-emoji" required
class="w-full border border-gray-300 rounded-lg p-2 text-sm">
</div>
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">名称</label>
<input type="text" name="name" id="edit-name" required
class="w-full border border-gray-300 rounded-lg p-2 text-sm">
</div>
<div class="col-span-2">
<label class="block text-xs font-bold text-gray-600 mb-1">播报内容</label>
<input type="text" name="message" id="edit-message" required
class="w-full border border-gray-300 rounded-lg p-2 text-sm">
</div>
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">经验变化</label>
<input type="number" name="exp" id="edit-exp"
class="w-full border border-gray-300 rounded-lg p-2 text-sm">
</div>
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">金币变化</label>
<input type="number" name="jjb" id="edit-jjb"
class="w-full border border-gray-300 rounded-lg p-2 text-sm">
</div>
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">权重</label>
<input type="number" name="weight" id="edit-weight" min="1"
class="w-full border border-gray-300 rounded-lg p-2 text-sm">
</div>
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">排序</label>
<input type="number" name="sort" id="edit-sort" min="0"
class="w-full border border-gray-300 rounded-lg p-2 text-sm">
</div>
<div class="col-span-2">
<label class="flex items-center gap-2 text-sm cursor-pointer">
<input type="checkbox" name="is_active" id="edit-is-active" value="1" class="rounded">
启用此事件
</label>
</div>
</div>
<div class="mt-5 flex gap-3">
<button type="submit"
class="px-5 py-2 bg-indigo-600 text-white rounded-lg font-bold hover:bg-indigo-700 transition text-sm">
💾 保存修改
</button>
<button type="button" onclick="closeEdit()"
class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg font-bold hover:bg-gray-200 transition text-sm">
取消
</button>
</div>
</form>
</div>
</div>
<script>
// 事件数据(供编辑弹窗填充)
const fishingEvents = @json($events->keyBy('id'));
/**
* 打开编辑弹窗并填充数据
*/
function openEdit(id) {
const e = fishingEvents[id];
if (!e) return;
document.getElementById('edit-form').action = `/admin/fishing/${id}`;
document.getElementById('edit-emoji').value = e.emoji;
document.getElementById('edit-name').value = e.name;
document.getElementById('edit-message').value = e.message;
document.getElementById('edit-exp').value = e.exp;
document.getElementById('edit-jjb').value = e.jjb;
document.getElementById('edit-weight').value = e.weight;
document.getElementById('edit-sort').value = e.sort;
document.getElementById('edit-is-active').checked = !!e.is_active;
document.getElementById('edit-modal').classList.remove('hidden');
}
/**
* 关闭编辑弹窗
*/
function closeEdit() {
document.getElementById('edit-modal').classList.add('hidden');
}
/**
* 切换事件启用/禁用状态
*/
function toggleEvent(id) {
fetch(`/admin/fishing/${id}/toggle`, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}',
'Accept': 'application/json',
},
})
.then(r => r.json())
.then(data => {
if (!data.ok) return;
const btn = document.getElementById(`toggle-${id}`);
const row = document.getElementById(`row-${id}`);
btn.textContent = data.is_active ? '启用' : '禁用';
btn.className = `px-2 py-1 rounded-full text-xs font-bold transition ${
data.is_active
? 'bg-emerald-100 text-emerald-700 hover:bg-emerald-200'
: 'bg-gray-100 text-gray-500 hover:bg-gray-200'
}`;
row.classList.toggle('opacity-50', !data.is_active);
// 同步内存里的状态
if (fishingEvents[id]) fishingEvents[id].is_active = data.is_active;
});
}
</script>
@endsection