Files
chatroom/resources/views/admin/riddles/index.blade.php
T

305 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')
@php require resource_path('views/admin/partials/list-theme.php'); @endphp
@php
$quizTypes = $typeOptions;
$idiomPayload = $idioms->mapWithKeys(
fn ($item) => [
(string) $item->id => [
'id' => $item->id,
'type' => $item->type,
'answer' => $item->answer,
'hint' => $item->hint,
'sort' => $item->sort,
'is_active' => (bool) $item->is_active,
'update_url' => route('admin.riddles.update', $item->id),
],
],
);
@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 }}">
这里只管理题目本身;奖励、自动出题、参与房间和手动开题请前往
<a href="{{ route('admin.game-configs.index') }}" class="font-semibold text-indigo-600 hover:text-indigo-500">游戏管理</a>
页面操作。
</p>
</div>
</div>
<div class="{{ $adminListCardClass }}">
<div class="{{ $adminListSectionHeadClass }}">
<div>
<h3 class="{{ $adminListSectionTitleClass }}">🔎 题库筛选</h3>
<p class="mt-1 text-xs text-slate-500">支持按题型和关键词快速定位题目。</p>
</div>
</div>
<form action="{{ route('admin.riddles.index') }}" method="GET" class="p-5">
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
<div>
<label class="{{ $adminListFilterLabelClass }}">题型</label>
<select name="type" class="w-full {{ $adminListFilterInputClass }}">
<option value="">全部题型</option>
@foreach ($quizTypes as $quizType => $quizLabel)
<option value="{{ $quizType }}" @selected($selectedType === $quizType)>{{ $quizLabel }}</option>
@endforeach
</select>
</div>
<div>
<label class="{{ $adminListFilterLabelClass }}">关键词</label>
<input type="text" name="keyword" value="{{ $keyword }}" placeholder="匹配答案或题面" class="w-full {{ $adminListFilterInputClass }}">
</div>
<div class="flex items-end gap-3">
<button type="submit" class="{{ $adminListPrimaryButtonClass }}">筛选</button>
<a href="{{ route('admin.riddles.index') }}" class="{{ $adminListSecondaryButtonClass }}">重置</a>
</div>
</div>
<div class="mt-4 flex flex-wrap gap-2 text-xs text-slate-500">
@foreach ($quizTypes as $quizType => $quizLabel)
<span class="rounded-full bg-slate-100 px-3 py-1">
{{ $quizLabel }}{{ $typeStats[$quizType] ?? 0 }}
</span>
@endforeach
</div>
</form>
</div>
<div class="{{ $adminListCardClass }}">
<div class="{{ $adminListSectionHeadClass }}">
<h3 class="{{ $adminListSectionTitleClass }}">📚 题目列表</h3>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-slate-200 text-sm">
<thead class="bg-slate-50 text-slate-600">
<tr>
<th class="px-4 py-3 text-left font-semibold">ID</th>
<th class="px-4 py-3 text-left font-semibold">题型</th>
<th class="px-4 py-3 text-left font-semibold">标准答案</th>
<th class="px-4 py-3 text-left font-semibold">题面 / 提示</th>
<th class="px-4 py-3 text-left font-semibold">排序</th>
<th class="px-4 py-3 text-left font-semibold">状态</th>
<th class="px-4 py-3 text-right font-semibold">操作</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100 bg-white">
@forelse ($idioms as $item)
<tr class="hover:bg-slate-50/80">
<td class="px-4 py-3 text-slate-500">#{{ $item->id }}</td>
<td class="px-4 py-3">
<span class="rounded-full px-2.5 py-1 text-xs font-semibold {{ $item->type === \App\Models\Riddle::TYPE_BRAIN_TEASER ? 'bg-amber-100 text-amber-700' : 'bg-indigo-100 text-indigo-700' }}">
{{ \App\Models\Riddle::labelForType($item->type) }}
</span>
</td>
<td class="px-4 py-3 font-semibold text-slate-800">{{ $item->answer }}</td>
<td class="px-4 py-3 text-slate-600">{{ $item->hint }}</td>
<td class="px-4 py-3 text-slate-500">{{ $item->sort }}</td>
<td class="px-4 py-3">
<button type="button"
data-idiom-toggle-url="{{ route('admin.riddles.toggle', $item->id) }}"
class="rounded-full px-3 py-1 text-xs font-semibold transition {{ $item->is_active ? 'bg-emerald-100 text-emerald-700 hover:bg-emerald-200' : 'bg-slate-200 text-slate-500 hover:bg-slate-300' }}">
{{ $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">
编辑
</button>
<form action="{{ route('admin.riddles.destroy', $item->id) }}" method="POST" class="inline" data-idiom-delete-confirm="确定删除题目「{{ $item->answer }}」?">
@csrf
@method('DELETE')
<input type="hidden" name="redirect_type" value="{{ $selectedType }}">
<input type="hidden" name="redirect_keyword" value="{{ $keyword }}">
<button type="submit" class="{{ $adminListActionButtonClass }} bg-red-50 text-red-600 hover:bg-red-100">
删除
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-4 py-8 text-center text-sm text-slate-400">当前筛选条件下暂无题目。</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
<div class="{{ $adminListCardClass }}">
<div class="{{ $adminListSectionHeadClass }}">
<h3 class="{{ $adminListSectionTitleClass }}"> 新增题目</h3>
</div>
<form action="{{ route('admin.riddles.store') }}" method="POST" class="p-5">
@csrf
<input type="hidden" name="redirect_type" value="{{ $selectedType }}">
<input type="hidden" name="redirect_keyword" value="{{ $keyword }}">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div>
<label class="{{ $adminListFilterLabelClass }}">题型</label>
<select name="type" class="w-full {{ $adminListFilterInputClass }}">
@foreach ($quizTypes as $quizType => $quizLabel)
<option value="{{ $quizType }}" @selected(old('type', $selectedType ?: \App\Models\Riddle::TYPE_IDIOM) === $quizType)>{{ $quizLabel }}</option>
@endforeach
</select>
</div>
<div>
<label class="{{ $adminListFilterLabelClass }}">排序</label>
<input type="number" name="sort" min="0" value="{{ old('sort', 0) }}" class="w-full {{ $adminListFilterInputClass }}">
</div>
<div>
<label class="{{ $adminListFilterLabelClass }}">标准答案</label>
<input type="text" name="answer" value="{{ old('answer') }}" required placeholder="请输入标准答案" class="w-full {{ $adminListFilterInputClass }}">
</div>
<div class="md:col-span-2">
<label class="{{ $adminListFilterLabelClass }}">题面 / 提示</label>
<input type="text" name="hint" value="{{ old('hint') }}" required placeholder="请输入题面或提示文案" class="w-full {{ $adminListFilterInputClass }}">
</div>
</div>
<div class="mt-4 flex items-center gap-4">
<button type="submit" class="{{ $adminListPrimaryButtonClass }}">💾 添加题目</button>
<label class="flex cursor-pointer items-center gap-2 text-sm text-slate-600">
<input type="checkbox" name="is_active" value="1" checked class="rounded border-slate-300">
立即启用
</label>
</div>
</form>
</div>
</div>
<div id="edit-modal" class="fixed inset-0 z-50 hidden items-center justify-center bg-black/40 p-4">
<div class="w-full max-w-lg rounded-xl bg-white shadow-2xl">
<div class="flex items-center justify-between border-b border-slate-100 p-5">
<h3 class="text-base font-bold text-slate-800">✏️ 编辑题目</h3>
<button type="button" data-idiom-edit-close class="text-xl text-slate-400 hover:text-slate-600"></button>
</div>
<form id="edit-form" method="POST" class="p-5">
@csrf
@method('PUT')
<input type="hidden" name="redirect_type" value="{{ $selectedType }}">
<input type="hidden" name="redirect_keyword" value="{{ $keyword }}">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div>
<label class="mb-1 block text-xs font-bold text-slate-600">题型</label>
<select name="type" id="edit-type" class="w-full rounded-lg border border-slate-300 p-2 text-sm">
@foreach ($quizTypes as $quizType => $quizLabel)
<option value="{{ $quizType }}">{{ $quizLabel }}</option>
@endforeach
</select>
</div>
<div>
<label class="mb-1 block text-xs font-bold text-slate-600">排序</label>
<input type="number" name="sort" id="edit-sort" min="0" class="w-full rounded-lg border border-slate-300 p-2 text-sm">
</div>
<div class="md:col-span-2">
<label class="mb-1 block text-xs font-bold text-slate-600">标准答案</label>
<input type="text" name="answer" id="edit-answer" required class="w-full rounded-lg border border-slate-300 p-2 text-sm">
</div>
<div class="md:col-span-2">
<label class="mb-1 block text-xs font-bold text-slate-600">题面 / 提示</label>
<input type="text" name="hint" id="edit-hint" required class="w-full rounded-lg border border-slate-300 p-2 text-sm">
</div>
<div class="md:col-span-2">
<label class="flex cursor-pointer items-center gap-2 text-sm text-slate-600">
<input type="checkbox" name="is_active" id="edit-is-active" value="1" class="rounded border-slate-300">
启用此题目
</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');
const editModal = document.getElementById('edit-modal');
const editForm = document.getElementById('edit-form');
if (!idiomsDataEl || !editModal || !editForm) {
return;
}
const idiomsData = JSON.parse(idiomsDataEl.textContent || '{}');
document.querySelectorAll('[data-idiom-toggle-url]').forEach((button) => {
button.addEventListener('click', async function () {
const url = this.getAttribute('data-idiom-toggle-url');
if (!url) {
return;
}
const response = await fetch(url, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}',
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json',
},
});
const result = await response.json();
if (!result.ok) {
alert(result.message || '状态切换失败。');
return;
}
window.location.reload();
});
});
document.querySelectorAll('[data-idiom-delete-confirm]').forEach((form) => {
form.addEventListener('submit', function (event) {
const message = this.getAttribute('data-idiom-delete-confirm') || '确定删除这条题目吗?';
if (!window.confirm(message)) {
event.preventDefault();
}
});
});
document.querySelectorAll('[data-idiom-edit-id]').forEach((button) => {
button.addEventListener('click', function () {
const idiomId = this.getAttribute('data-idiom-edit-id') || '';
const payload = idiomsData[idiomId];
if (!payload) {
return;
}
document.getElementById('edit-type').value = payload.type;
document.getElementById('edit-answer').value = payload.answer;
document.getElementById('edit-hint').value = payload.hint;
document.getElementById('edit-sort').value = payload.sort;
document.getElementById('edit-is-active').checked = Boolean(payload.is_active);
editForm.action = payload.update_url;
editModal.classList.remove('hidden');
editModal.classList.add('flex');
});
});
document.querySelectorAll('[data-idiom-edit-close]').forEach((button) => {
button.addEventListener('click', function () {
editModal.classList.add('hidden');
editModal.classList.remove('flex');
});
});
});
</script>
@endpush