Files
chatroom/resources/views/admin/idioms/index.blade.php
T
2026-04-29 10:32:12 +08:00

339 lines
17 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')
@php require resource_path('views/admin/partials/list-theme.php'); @endphp
@php
$idiomPayload = $idioms->mapWithKeys(
fn($item) => [
(string) $item->id => [
'id' => $item->id,
'answer' => $item->answer,
'hint' => $item->hint,
'sort' => $item->sort,
'is_active' => (bool) $item->is_active,
'update_url' => route('admin.idioms.update', $item->id),
'toggle_url' => route('admin.idioms.toggle', $item->id),
],
],
);
$idiomConfig = \App\Models\GameConfig::forGame('idiom');
$idiomParams = $idiomConfig?->params ?? [];
@endphp
<script type="application/json" id="admin-idioms-data">@json($idiomPayload)</script>
<div class="{{ $adminListPageClass }}">
{{-- 页头 --}}
<div class="{{ $adminListHeaderCardClass }}">
<div>
<h2 class="{{ $adminListHeaderTitleClass }}">🧩 猜成语题库管理</h2>
<p class="{{ $adminListHeaderSubtitleClass }}">
管理猜成语游戏的题目库,共 <strong class="text-indigo-600">{{ $idioms->count() }}</strong> 条题目
</p>
</div>
</div>
{{-- 游戏参数 + 出题 --}}
<div class="{{ $adminListCardClass }}">
<div class="{{ $adminListSectionHeadClass }}">
<h3 class="{{ $adminListSectionTitleClass }}">⚙️ 游戏参数</h3>
</div>
<form action="{{ route('admin.idioms.settings.save') }}" method="POST" class="p-5">
@csrf
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label class="{{ $adminListFilterLabelClass }}">答对奖励金币</label>
<input type="number" name="reward_gold"
value="{{ old('reward_gold', $idiomParams['reward_gold'] ?? 50) }}" min="0"
class="w-full {{ $adminListFilterInputClass }}">
</div>
<div>
<label class="{{ $adminListFilterLabelClass }}">答对奖励经验</label>
<input type="number" name="reward_exp"
value="{{ old('reward_exp', $idiomParams['reward_exp'] ?? 30) }}" min="0"
class="w-full {{ $adminListFilterInputClass }}">
</div>
<div>
<label class="{{ $adminListFilterLabelClass }}">自动出题间隔(分钟)</label>
<input type="number" name="auto_start_interval"
value="{{ old('auto_start_interval', $idiomParams['auto_start_interval'] ?? 0) }}" min="0"
class="w-full {{ $adminListFilterInputClass }}">
<p class="text-xs text-gray-400 mt-1">0=仅手动出题</p>
</div>
<div>
<label class="{{ $adminListFilterLabelClass }}">题目过期时间(分钟)</label>
<input type="number" name="expire_minutes"
value="{{ old('expire_minutes', $idiomParams['expire_minutes'] ?? 5) }}" min="0"
class="w-full {{ $adminListFilterInputClass }}">
<p class="text-xs text-gray-400 mt-1">0=不过期;大于 0 时超时会自动公布答案并结束回合</p>
</div>
</div>
<div class="mt-4 flex items-center gap-4">
<button type="submit" class="{{ $adminListPrimaryButtonClass }}">
💾 保存参数
</button>
<span class="text-sm text-gray-400">|</span>
<label class="text-sm text-gray-600">选择房间:</label>
<select id="idiom-start-room" class="border border-gray-300 rounded-lg px-3 py-1.5 text-sm">
@foreach (\App\Models\Room::orderBy('id')->get() as $room)
<option value="{{ $room->id }}">{{ $room->name }}</option>
@endforeach
</select>
<button type="button" id="idiom-start-btn"
class="inline-flex items-center gap-1.5 px-4 py-2 bg-gradient-to-r from-purple-600 to-indigo-600 text-white text-sm font-bold rounded-lg hover:opacity-90 transition">
🧩 出题
</button>
</div>
</form>
</div>
{{-- 题目列表 --}}
<div class="{{ $adminListCardClass }}">
<div class="{{ $adminListTableWrapClass }}">
<table class="{{ $adminListTableClass }}">
<thead class="{{ $adminListTableHeadRowClass }}">
<tr>
<th class="{{ $adminListTableHeadCellClass }}">排序</th>
<th class="{{ $adminListTableHeadCellClass }}">成语答案</th>
<th class="{{ $adminListTableHeadCellClass }} w-2/5">谜语提示</th>
<th class="{{ $adminListTableHeadCellClass }} text-center">状态</th>
<th class="{{ $adminListTableHeadCellClass }} text-right">操作</th>
</tr>
</thead>
<tbody class="{{ $adminListTableBodyClass }}">
@foreach ($idioms as $item)
<tr id="row-{{ $item->id }}" class="{{ $adminListTableRowClass }} {{ $item->is_active ? '' : 'opacity-50' }}">
<td class="px-4 py-3 {{ $adminListSecondaryTextClass }}">{{ $item->sort }}</td>
<td class="px-4 py-3 font-bold {{ $adminListPrimaryTextClass }}">{{ $item->answer }}</td>
<td class="px-4 py-3 {{ $adminListBodyTextClass }} text-sm">{{ $item->hint }}</td>
<td class="px-4 py-3 text-center">
<button type="button" data-idiom-toggle-id="{{ $item->id }}"
id="toggle-{{ $item->id }}"
class="{{ $adminListBadgeBaseClass }} px-2 py-1 transition
{{ $item->is_active ? 'border-emerald-200 bg-emerald-100 text-emerald-700 hover:bg-emerald-200' : 'border-gray-200 bg-gray-100 text-gray-500 hover:bg-gray-200' }}">
{{ $item->is_active ? '启用' : '禁用' }}
</button>
</td>
<td class="px-4 py-3 text-right">
<button type="button" data-idiom-edit-id="{{ $item->id }}"
class="{{ $adminListActionButtonClass }} bg-indigo-50 text-indigo-700 hover:bg-indigo-100 mr-1">
编辑
</button>
<form action="{{ route('admin.idioms.destroy', $item->id) }}" method="POST"
class="inline" data-idiom-delete-confirm="确定删除题目「{{ $item->answer }}」?">
@csrf @method('DELETE')
<button type="submit"
class="{{ $adminListActionButtonClass }} bg-red-50 text-red-600 hover:bg-red-100">
删除
</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
{{-- 新增题目卡片 --}}
<div class="{{ $adminListCardClass }}">
<div class="{{ $adminListSectionHeadClass }}">
<h3 class="{{ $adminListSectionTitleClass }}"> 新增成语题目</h3>
</div>
<form action="{{ route('admin.idioms.store') }}" method="POST" class="p-5">
@csrf
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="{{ $adminListFilterLabelClass }}">成语答案</label>
<input type="text" name="answer" value="{{ old('answer') }}" placeholder="画蛇添足" required
class="w-full {{ $adminListFilterInputClass }}">
</div>
<div>
<label class="{{ $adminListFilterLabelClass }}">排序</label>
<input type="number" name="sort" value="{{ old('sort', 0) }}" min="0"
class="w-full {{ $adminListFilterInputClass }}">
</div>
<div class="md:col-span-2">
<label class="{{ $adminListFilterLabelClass }}">谜语提示</label>
<input type="text" name="hint" value="{{ old('hint') }}" placeholder="🧩 四人比赛画蛇,最慢的那个反而多此一举。猜一成语" required
class="w-full {{ $adminListFilterInputClass }}">
</div>
</div>
<div class="mt-4 flex items-center gap-4">
<button type="submit" class="{{ $adminListPrimaryButtonClass }}">
💾 添加题目
</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 type="button" data-idiom-edit-close 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-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-bold text-gray-600 mb-1">成语答案</label>
<input type="text" name="answer" id="edit-answer" 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="sort" id="edit-sort" min="0"
class="w-full border border-gray-300 rounded-lg p-2 text-sm">
</div>
<div class="md:col-span-2">
<label class="block text-xs font-bold text-gray-600 mb-1">谜语提示</label>
<input type="text" name="hint" id="edit-hint" required
class="w-full border border-gray-300 rounded-lg p-2 text-sm">
</div>
<div class="md: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="{{ $adminListPrimaryButtonClass }}">
💾 保存修改
</button>
<button type="button" data-idiom-edit-close
class="{{ $adminListSecondaryButtonClass }}">
取消
</button>
</div>
</form>
</div>
</div>
@endsection
{{-- 前端编辑/切换交互脚本 --}}
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function () {
const idiomsDataEl = document.getElementById('admin-idioms-data');
if (!idiomsDataEl) return;
const idiomsData = JSON.parse(idiomsDataEl.textContent || '{}');
// ── 打开编辑弹窗 ──
document.querySelectorAll('[data-idiom-edit-id]').forEach(btn => {
btn.addEventListener('click', function () {
const id = this.dataset.idiomEditId;
const data = idiomsData[id];
if (!data) return;
document.getElementById('edit-answer').value = data.answer;
document.getElementById('edit-hint').value = data.hint;
document.getElementById('edit-sort').value = data.sort;
document.getElementById('edit-is-active').checked = data.is_active;
document.getElementById('edit-form').action = data.update_url;
document.getElementById('edit-modal').classList.remove('hidden');
});
});
// ── 关闭编辑弹窗 ──
document.querySelectorAll('[data-idiom-edit-close]').forEach(btn => {
btn.addEventListener('click', function () {
document.getElementById('edit-modal').classList.add('hidden');
});
});
// ── 切换启用/禁用(AJAX) ──
document.querySelectorAll('[data-idiom-toggle-id]').forEach(btn => {
btn.addEventListener('click', function () {
const id = this.dataset.idiomToggleId;
const data = idiomsData[id];
if (!data) return;
fetch(data.toggle_url, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content ?? '',
'Accept': 'application/json',
},
})
.then(r => r.json())
.then(res => {
if (res.ok) {
const row = document.getElementById('row-' + id);
if (row) row.style.opacity = res.is_active ? '1' : '0.5';
const btn = document.getElementById('toggle-' + id);
if (btn) {
btn.textContent = res.is_active ? '启用' : '禁用';
btn.className = (res.is_active
? 'border-emerald-200 bg-emerald-100 text-emerald-700 hover:bg-emerald-200'
: 'border-gray-200 bg-gray-100 text-gray-500 hover:bg-gray-200')
+ ' px-2 py-1 transition rounded-full text-xs font-semibold border';
}
}
})
.catch(() => alert('操作失败'));
});
});
// ── 删除确认 ──
document.querySelectorAll('[data-idiom-delete-confirm]').forEach(form => {
form.addEventListener('submit', function (e) {
if (!confirm(this.dataset.idiomDeleteConfirm)) {
e.preventDefault();
}
});
});
// ── 出题按钮 ──
const startBtn = document.getElementById('idiom-start-btn');
if (startBtn) {
startBtn.addEventListener('click', function () {
const roomSelect = document.getElementById('idiom-start-room');
const roomId = roomSelect?.value;
if (!roomId) return;
const btn = this;
btn.disabled = true;
btn.textContent = '出题中...';
fetch('/idiom-quiz/start', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.content ?? '',
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({ room_id: parseInt(roomId, 10) }),
})
.then(r => r.json())
.then(data => {
if (data.status === 'success') {
alert('✅ 出题成功!提示已发送到聊天室。');
} else {
alert(data.message || '出题失败');
}
})
.catch(() => alert('网络错误,出题失败'))
.finally(() => {
btn.disabled = false;
btn.textContent = '🧩 出题';
});
});
}
});
</script>
@endpush