迁移钓鱼事件后台脚本
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
// 钓鱼事件后台管理页事件代理,替代 Blade 内联编辑和启停函数。
|
||||
|
||||
let adminFishingEventsControlsBound = false;
|
||||
let fishingEventsCache = null;
|
||||
|
||||
/**
|
||||
* 读取后台 layout 注入的 CSRF token。
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
function getCsrfToken() {
|
||||
return document.querySelector('meta[name="csrf-token"]')?.getAttribute("content") || "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取 Blade 注入的钓鱼事件快照。
|
||||
*
|
||||
* @returns {Record<string, Record<string, unknown>>}
|
||||
*/
|
||||
function getFishingEvents() {
|
||||
if (fishingEventsCache !== null) {
|
||||
return fishingEventsCache;
|
||||
}
|
||||
|
||||
const dataNode = document.getElementById("admin-fishing-events-data");
|
||||
if (!dataNode?.textContent) {
|
||||
fishingEventsCache = {};
|
||||
return fishingEventsCache;
|
||||
}
|
||||
|
||||
try {
|
||||
fishingEventsCache = JSON.parse(dataNode.textContent);
|
||||
} catch (error) {
|
||||
// JSON 被异常截断时保持页面可用,只是不再打开编辑弹窗。
|
||||
fishingEventsCache = {};
|
||||
}
|
||||
|
||||
return fishingEventsCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表单字段值。
|
||||
*
|
||||
* @param {string} id 字段 ID
|
||||
* @param {unknown} value 字段值
|
||||
* @returns {void}
|
||||
*/
|
||||
function setInputValue(id, value) {
|
||||
const input = document.getElementById(id);
|
||||
if (input instanceof HTMLInputElement) {
|
||||
input.value = String(value ?? "");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开钓鱼事件编辑弹窗并填充表单。
|
||||
*
|
||||
* @param {string} eventId 事件 ID
|
||||
* @returns {void}
|
||||
*/
|
||||
function openFishingEditModal(eventId) {
|
||||
const eventData = getFishingEvents()[eventId];
|
||||
const form = document.getElementById("edit-form");
|
||||
const modal = document.getElementById("edit-modal");
|
||||
|
||||
if (!eventData || !(form instanceof HTMLFormElement) || !modal) {
|
||||
return;
|
||||
}
|
||||
|
||||
form.action = String(eventData.update_url || "");
|
||||
setInputValue("edit-emoji", eventData.emoji);
|
||||
setInputValue("edit-name", eventData.name);
|
||||
setInputValue("edit-message", eventData.message);
|
||||
setInputValue("edit-exp", eventData.exp);
|
||||
setInputValue("edit-jjb", eventData.jjb);
|
||||
setInputValue("edit-weight", eventData.weight);
|
||||
setInputValue("edit-sort", eventData.sort);
|
||||
|
||||
const activeInput = document.getElementById("edit-is-active");
|
||||
if (activeInput instanceof HTMLInputElement) {
|
||||
activeInput.checked = Boolean(eventData.is_active);
|
||||
}
|
||||
|
||||
modal.classList.remove("hidden");
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭钓鱼事件编辑弹窗。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function closeFishingEditModal() {
|
||||
document.getElementById("edit-modal")?.classList.add("hidden");
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据接口返回状态更新钓鱼事件行和缓存。
|
||||
*
|
||||
* @param {string} eventId 事件 ID
|
||||
* @param {boolean} active 是否启用
|
||||
* @returns {void}
|
||||
*/
|
||||
function updateFishingToggleState(eventId, active) {
|
||||
const button = document.querySelector(`[data-fishing-toggle-id="${CSS.escape(eventId)}"]`);
|
||||
const row = document.getElementById(`row-${eventId}`);
|
||||
|
||||
if (button instanceof HTMLButtonElement) {
|
||||
button.textContent = active ? "启用" : "禁用";
|
||||
button.className = `px-2 py-1 rounded-full text-xs font-bold transition ${
|
||||
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", !active);
|
||||
|
||||
// 缓存状态要同步,否则后续打开编辑弹窗时会显示旧的启用状态。
|
||||
const eventData = getFishingEvents()[eventId];
|
||||
if (eventData) {
|
||||
eventData.is_active = active;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换钓鱼事件启用状态。
|
||||
*
|
||||
* @param {HTMLButtonElement} button 状态切换按钮
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function toggleFishingEvent(button) {
|
||||
const eventId = button.getAttribute("data-fishing-toggle-id") || "";
|
||||
const eventData = getFishingEvents()[eventId];
|
||||
const toggleUrl = String(eventData?.toggle_url || "");
|
||||
|
||||
if (!eventId || !toggleUrl || button.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
button.disabled = true;
|
||||
|
||||
try {
|
||||
const response = await fetch(toggleUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-CSRF-TOKEN": getCsrfToken(),
|
||||
"Accept": "application/json",
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data?.ok) {
|
||||
updateFishingToggleState(eventId, Boolean(data.is_active));
|
||||
}
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定钓鱼事件管理页操作按钮。
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
export function bindAdminFishingEventsControls() {
|
||||
if (adminFishingEventsControlsBound || typeof document === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
adminFishingEventsControlsBound = true;
|
||||
document.addEventListener("click", (event) => {
|
||||
if (!(event.target instanceof Element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const editButton = event.target.closest("[data-fishing-edit-id]");
|
||||
if (editButton) {
|
||||
event.preventDefault();
|
||||
openFishingEditModal(editButton.getAttribute("data-fishing-edit-id") || "");
|
||||
return;
|
||||
}
|
||||
|
||||
const closeButton = event.target.closest("[data-fishing-edit-close]");
|
||||
if (closeButton) {
|
||||
event.preventDefault();
|
||||
closeFishingEditModal();
|
||||
return;
|
||||
}
|
||||
|
||||
const toggleButton = event.target.closest("[data-fishing-toggle-id]");
|
||||
if (toggleButton instanceof HTMLButtonElement) {
|
||||
event.preventDefault();
|
||||
void toggleFishingEvent(toggleButton);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import './bootstrap';
|
||||
import { bindAdminAutoactControls } from './admin/autoact.js';
|
||||
import { bindAdminFishingEventsControls } from './admin/fishing-events.js';
|
||||
|
||||
// 后台共用入口只注册轻量事件代理,具体页面通过 data-* 属性决定是否响应。
|
||||
bindAdminAutoactControls();
|
||||
bindAdminFishingEventsControls();
|
||||
|
||||
@@ -3,6 +3,28 @@
|
||||
@section('title', '钓鱼事件管理')
|
||||
|
||||
@section('content')
|
||||
@php
|
||||
$fishingEventPayload = $events->mapWithKeys(
|
||||
fn($event) => [
|
||||
(string) $event->id => [
|
||||
'id' => $event->id,
|
||||
'emoji' => $event->emoji,
|
||||
'name' => $event->name,
|
||||
'message' => $event->message,
|
||||
'exp' => $event->exp,
|
||||
'jjb' => $event->jjb,
|
||||
'weight' => $event->weight,
|
||||
'sort' => $event->sort,
|
||||
'is_active' => (bool) $event->is_active,
|
||||
'update_url' => route('admin.fishing.update', $event->id),
|
||||
'toggle_url' => route('admin.fishing.toggle', $event->id),
|
||||
],
|
||||
],
|
||||
);
|
||||
@endphp
|
||||
|
||||
<script type="application/json" id="admin-fishing-events-data">@json($fishingEventPayload)</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
|
||||
{{-- 页头 --}}
|
||||
@@ -63,14 +85,15 @@
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-3 text-center">
|
||||
<button onclick="toggleEvent({{ $event->id }})" id="toggle-{{ $event->id }}"
|
||||
<button type="button" data-fishing-toggle-id="{{ $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 }})"
|
||||
<button type="button" data-fishing-edit-id="{{ $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>
|
||||
@@ -153,7 +176,7 @@
|
||||
<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>
|
||||
<button type="button" data-fishing-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')
|
||||
@@ -205,7 +228,7 @@
|
||||
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()"
|
||||
<button type="button" data-fishing-edit-close
|
||||
class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg font-bold hover:bg-gray-200 transition text-sm">
|
||||
取消
|
||||
</button>
|
||||
@@ -213,62 +236,4 @@
|
||||
</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
|
||||
|
||||
Reference in New Issue
Block a user