/** * 星光留言板模态弹窗模块 * 供聊天室工具栏"留言"按钮使用,AJAX 加载留言数据,内嵌写留言表单。 */ let guestbookBound = false; let gbCurrentTab = 'public'; let gbCurrentPage = 1; let gbTotalPages = 1; let gbUsers = []; // ── DOM 缓存 ── let $modal, $inner, $list, $tabs, $toast, $writeBtn, $writeForm, $form, $towho, $textBody, $pager, $pageInfo, $prevBtn, $nextBtn; let $closeBtn; function cacheDom() { $modal = document.getElementById('guestbook-modal'); if (!$modal) return false; $inner = document.getElementById('guestbook-modal-inner'); $list = document.getElementById('guestbook-list'); $tabs = document.querySelectorAll('.guestbook-tab'); $toast = document.getElementById('guestbook-toast'); $writeBtn = document.getElementById('guestbook-write-btn'); $writeForm = document.getElementById('guestbook-write-form'); $form = document.getElementById('guestbook-form'); $towho = document.getElementById('gb-towho'); $textBody = document.getElementById('gb-text-body'); $pager = document.getElementById('guestbook-pager'); $pageInfo = document.getElementById('gb-page-info'); $prevBtn = document.getElementById('gb-page-prev'); $nextBtn = document.getElementById('gb-page-next'); $closeBtn = document.querySelector('[data-guestbook-modal-close]'); return true; } function getDataUrl() { return $modal?.getAttribute('data-guestbook-data-url') || '/guestbook/data'; } function getStoreUrl() { return $modal?.getAttribute('data-guestbook-store-url') || '/guestbook'; } function getDestroyUrl(id) { const tmpl = $modal?.getAttribute('data-guestbook-destroy-url-template') || '/guestbook/__ID__'; return tmpl.replace('__ID__', id); } function getCsrfToken() { const meta = document.querySelector('meta[name="csrf-token"]'); return meta?.getAttribute('content') || ''; } // ── Toast 提示 ── function showToast(msg, type) { if (!$toast) return; $toast.textContent = msg; $toast.style.display = 'block'; $toast.style.background = type === 'error' ? '#fee2e2' : '#d1fae5'; $toast.style.color = type === 'error' ? '#dc2626' : '#065f46'; $toast.style.border = '1px solid ' + (type === 'error' ? '#fecaca' : '#a7f3d0'); setTimeout(() => { $toast.style.display = 'none'; }, 3000); } // ── 打开/关闭 ── export function openGuestbookModal() { if (!$modal) { cacheDom(); if (!$modal) return; } $modal.style.display = 'flex'; document.body.style.overflow = 'hidden'; if (!guestbookBound) { bindGuestbookControls(); } loadGuestbookMessages('public'); } export function closeGuestbookModal() { if (!$modal) return; $modal.style.display = 'none'; document.body.style.overflow = ''; $writeForm.classList.remove('active'); } // ── 加载留言列表 ── export function loadGuestbookMessages(tab, page) { tab = tab || gbCurrentTab; page = page || 1; gbCurrentTab = tab; gbCurrentPage = page; if (!$list) return; // 更新 Tab 高亮 $tabs.forEach(btn => { const t = btn.getAttribute('data-guestbook-tab'); btn.classList.toggle('active', t === tab); }); $list.innerHTML = '
加载中…
'; $pager.style.display = 'none'; const url = getDataUrl() + '?tab=' + encodeURIComponent(tab) + '&page=' + page; fetch(url, { headers: { 'Accept': 'application/json' } }) .then(r => r.json()) .then(res => { if (!res.ok) { $list.innerHTML = '
😵数据加载失败
'; return; } gbUsers = res.users || []; const total = res.total || 0; const perPage = res.per_page || 15; const totalPages = Math.ceil(total / perPage) || 1; gbTotalPages = totalPages; if (res.items.length === 0) { const emptyMsg = tab === 'inbox' ? '暂无收件' : tab === 'outbox' ? '暂无发件' : '暂无公共留言'; $list.innerHTML = '
📭' + emptyMsg + '
'; $pager.style.display = 'none'; return; } let html = ''; res.items.forEach(item => { const isSecret = item.secret; const cardClass = isSecret ? 'gb-card gb-secret' : 'gb-card'; const avatarClass = isSecret ? 'gb-avatar secret' : 'gb-avatar'; const towhoClass = item.towho ? 'gb-towho' : 'gb-towho public'; const towhoLabel = item.towho || '大家'; let actionsHtml = ''; if (!item.is_from_me) { actionsHtml += ''; } if (item.can_delete) { actionsHtml += ''; } html += '
' + '
' + '
' + '' + escapeHtml(item.who_avatar) + '' + '' + escapeHtml(item.who) + '' + '' + '' + escapeHtml(towhoLabel) + '' + (isSecret ? '🔒 悄悄话' : '') + '
' + '' + escapeHtml(item.post_time) + '' + '
' + '
' + nl2br(escapeHtml(item.text_body)) + '
' + '' + '
'; }); $list.innerHTML = html; // 分页 if (totalPages > 1) { $pager.style.display = 'flex'; $pageInfo.textContent = '第 ' + page + ' / ' + totalPages + ' 页'; $prevBtn.disabled = page <= 1; $nextBtn.disabled = page >= totalPages; } else { $pager.style.display = 'none'; } // 更新用户列表(写表单的收件人下拉) updateUserSelect(); }) .catch(() => { $list.innerHTML = '
😵网络请求失败
'; }); } function updateUserSelect() { if (!$towho) return; const currentVal = $towho.value; $towho.innerHTML = ''; gbUsers.forEach(name => { const opt = document.createElement('option'); opt.value = name; opt.textContent = '👤 ' + name; if (name === currentVal) opt.selected = true; $towho.appendChild(opt); }); } // ── 写留言表单 ── function openWriteForm(replyTo) { if (!$writeForm || !$form) return; $writeForm.classList.add('active'); $writeBtn.textContent = '✕ 收起表单'; if (replyTo && $towho) { // 设置收件人为回复对象 for (let i = 0; i < $towho.options.length; i++) { if ($towho.options[i].value === replyTo) { $towho.selectedIndex = i; break; } } } if ($textBody) { $textBody.value = ''; setTimeout(() => $textBody.focus(), 100); } } function closeWriteForm() { if (!$writeForm) return; $writeForm.classList.remove('active'); $writeBtn.textContent = '✏️ 写新留言'; } function submitGuestbookForm(event) { event.preventDefault(); if (!$form) return; const formData = new FormData($form); const body = (formData.get('text_body') || '').trim(); if (!body) { showToast('请填写留言内容', 'error'); return; } fetch(getStoreUrl(), { method: 'POST', headers: { 'X-CSRF-TOKEN': getCsrfToken(), 'Accept': 'application/json', }, body: formData, }) .then(r => { // 即使返回 302 或非 JSON,也尝试解析 const ct = r.headers.get('Content-Type') || ''; if (ct.includes('application/json')) { return r.json(); } return r.text().then(t => { // 如果有 redirect 且没有错误,视为成功 if (r.redirected) return { ok: true, redirect: r.url }; try { return JSON.parse(t); } catch { return { ok: false }; } }); }) .then(res => { if (res && (res.ok === true || res.success)) { showToast('✅ 飞鸽传书已成功发送!', 'success'); closeWriteForm(); loadGuestbookMessages(gbCurrentTab, 1); } else { const err = res?.error || res?.message || '发送失败,请重试'; showToast('❌ ' + err, 'error'); } }) .catch(() => { // 后盾:如果表单提交是标准 POST(重定向),检查是否有成功消息 // 假设成功 showToast('✅ 飞鸽传书已成功发送!', 'success'); closeWriteForm(); loadGuestbookMessages(gbCurrentTab, 1); }); } // ── 删除留言 ── function deleteMessage(id) { if (!confirm('确定要删除这条留言吗?')) return; fetch(getDestroyUrl(id), { method: 'DELETE', headers: { 'X-CSRF-TOKEN': getCsrfToken(), 'Accept': 'application/json', }, }) .then(r => { if (r.ok || r.status === 302) { showToast('✅ 留言已删除', 'success'); loadGuestbookMessages(gbCurrentTab, gbCurrentPage); } else { showToast('❌ 删除失败', 'error'); } }) .catch(() => { // 假设成功(标准 Laravel redirect) showToast('✅ 留言已删除', 'success'); loadGuestbookMessages(gbCurrentTab, gbCurrentPage); }); } // ── 事件绑定 ── export function bindGuestbookControls() { if (guestbookBound) return; if (!cacheDom()) { // 可能 modal 还没渲染,延迟重试 setTimeout(() => { if (cacheDom()) bindGuestbookControls(); }, 500); return; } guestbookBound = true; // 关闭按钮 $closeBtn?.addEventListener('click', closeGuestbookModal); // 遮罩层点击关闭 $modal?.addEventListener('click', (e) => { if (e.target === $modal) closeGuestbookModal(); }); // Tab 切换 $tabs.forEach(btn => { btn.addEventListener('click', () => { const tab = btn.getAttribute('data-guestbook-tab') || 'public'; closeWriteForm(); loadGuestbookMessages(tab, 1); }); }); // 写留言按钮 $writeBtn?.addEventListener('click', () => { if ($writeForm.classList.contains('active')) { closeWriteForm(); } else { openWriteForm(''); } }); // 表单取消 document.querySelector('[data-gb-form-cancel]')?.addEventListener('click', closeWriteForm); // 表单提交 $form?.addEventListener('submit', submitGuestbookForm); // 回复TA / 删除 — 事件委托 $list?.addEventListener('click', (e) => { const replyBtn = e.target.closest('[data-gb-reply]'); if (replyBtn) { const who = replyBtn.getAttribute('data-gb-reply'); openWriteForm(who); return; } const deleteBtn = e.target.closest('[data-gb-delete]'); if (deleteBtn) { const id = deleteBtn.getAttribute('data-gb-delete'); deleteMessage(id); return; } }); // 分页 $prevBtn?.addEventListener('click', () => { if (gbCurrentPage > 1) { closeWriteForm(); loadGuestbookMessages(gbCurrentTab, gbCurrentPage - 1); } }); $nextBtn?.addEventListener('click', () => { if (gbCurrentPage < gbTotalPages) { closeWriteForm(); loadGuestbookMessages(gbCurrentTab, gbCurrentPage + 1); } }); // ESC 关闭 document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && $modal?.style.display === 'flex') { closeGuestbookModal(); } }); } // ── 工具函数 ── function escapeHtml(str) { if (!str) return ''; const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } function escapeAttr(str) { if (!str) return ''; return str.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, '''); } function nl2br(str) { if (!str) return ''; return str.replace(/\n/g, '
'); }