/** * 留言板功能 - 基于 Flask API */ (function () { 'use strict'; // Flask API 地址 const API_BASE_URL = 'https://comments.iletter.top'; const PAGE_SIZE = 10; let currentPage = 1; let hasMore = true; let isLoading = false; let allComments = []; // 存储所有已加载的留言 // DOM 元素 const guestbookBtn = document.getElementById('guestbook-btn'); const guestbookPreview = document.getElementById('guestbook-preview'); const guestbookFullModal = document.getElementById('guestbook-full-modal'); const guestbookClose = document.getElementById('guestbook-close'); const guestbookBody = document.querySelector('#guestbook-full-modal .guestbook-body'); const previewBody = document.querySelector('.preview-body'); // Toast 容器 let toastContainer = document.querySelector('.toast-container'); if (!toastContainer) { toastContainer = document.createElement('div'); toastContainer.className = 'toast-container'; document.body.appendChild(toastContainer); } // 表单元素(需要动态获取,因为表单会被重新渲染) let nicknameInput, emailInput, linkInput, contentInput, submitBtn; // 显示 Toast 提示 function showToast(message, type = 'success', duration = 2000) { const toast = document.createElement('div'); toast.className = `toast ${type}`; toast.textContent = message; toastContainer.appendChild(toast); // 触发动画 setTimeout(() => { toast.classList.add('show'); }, 10); // 自动消失 setTimeout(() => { toast.classList.remove('show'); setTimeout(() => { toast.remove(); }, 300); }, duration); } // 初始化 function init() { if (!guestbookBtn || !guestbookFullModal) return; // 点击按钮打开完整弹窗 guestbookBtn.addEventListener('click', openFullModal); // 关闭按钮 guestbookClose.addEventListener('click', closeFullModal); // 预览面板触底加载 if (previewBody) { previewBody.addEventListener('scroll', handlePreviewScroll); } // 表单提交事件使用事件委托 guestbookBody.addEventListener('submit', function(e) { if (e.target && e.target.id === 'guestbook-form') { handleSubmit(e); } }); // 页面加载时自动加载数据(用于 Hover 预览) loadGuestbook(true); } // 切换留言板显示/隐藏 function toggleGuestbook() { if (guestbookFullModal.classList.contains('active')) { closeFullModal(); } else { openFullModal(); } } // 打开完整弹窗 function openFullModal() { guestbookFullModal.classList.add('active'); document.body.style.overflow = 'hidden'; // 首次打开时加载留言 if (!document.querySelector('.guestbook-list')) { loadGuestbook(true); } } // 关闭完整弹窗 function closeFullModal() { guestbookFullModal.classList.remove('active'); document.body.style.overflow = ''; } // 点击模态框背景关闭 function handleModalClick(e) { if (e.target === guestbookModal) { closeGuestbook(); } } // 加载留言列表 async function loadGuestbook(reset = false) { if (isLoading || (!hasMore && !reset)) return; isLoading = true; if (reset) { currentPage = 1; hasMore = true; guestbookBody.innerHTML = '
正在加载留言...
'; } else { // 添加加载更多按钮状态 const loadMoreBtn = guestbookBody.querySelector('.load-more-btn'); if (loadMoreBtn) { loadMoreBtn.disabled = true; loadMoreBtn.textContent = '加载中...'; } } try { const response = await fetch(`${API_BASE_URL}/api/comment?path=www.iletter.top&pageSize=${PAGE_SIZE}&page=${currentPage}`, { method: 'GET', headers: { 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error('Network response was not ok'); } const result = await response.json(); const comments = result.data.data || []; const totalCount = result.data.count || 0; const totalPages = result.data.totalPages || 0; // 判断是否还有更多数据(根据总页数) hasMore = currentPage < totalPages; // 累积所有留言 if (reset) { allComments = comments; } else { allComments = allComments.concat(comments); } if (reset) { renderGuestbook(comments, totalCount); } else { appendGuestbook(comments); } } catch (error) { console.error('加载留言失败:', error); if (reset) { guestbookBody.innerHTML = `
加载留言失败,请稍后重试
`; } } finally { isLoading = false; } } // 渲染留言列表 function renderGuestbook(comments, totalCount) { let html = ''; // 先渲染表单(放在顶部) html += renderForm(); if (!comments || comments.length === 0) { html += `
暂无留言,快来抢沙发吧!
`; guestbookBody.innerHTML = html; return; } html += '
'; comments.forEach(comment => { html += renderCommentItem(comment); }); html += '
'; // 判断是否还有更多数据 if (hasMore) { html += ``; } // 否则不显示任何内容 guestbookBody.innerHTML = html; // 同时更新 Hover 预览面板(使用累积的所有留言) renderPreview(allComments, totalCount); } // 渲染 Hover 预览面板 function renderPreview(comments, totalCount = 0) { if (!previewBody) return; if (!comments || comments.length === 0) { previewBody.innerHTML = '
暂无留言,快来抢沙发吧!
'; return; } // 更新标题显示总数 const previewHeader = document.querySelector('#guestbook-preview .preview-header h3'); if (previewHeader && totalCount > 0) { const lang = localStorage.getItem('preferred_language') || 'zh'; const translations = { 'zh': typeof translationsZH !== 'undefined' ? translationsZH : {}, 'en': typeof translationsEN !== 'undefined' ? translationsEN : {} }; const titleText = translations[lang]['guestbook_title'] || '留言板'; previewHeader.textContent = `${titleText} (${totalCount})`; } let html = ''; comments.forEach(comment => { const date = new Date(comment.time); const timeStr = formatDate(date); html += `
`; html += `
${escapeHtml(comment.nick)}
`; // 提取纯文本内容(去除 HTML 标签) const tempDiv = document.createElement('div'); tempDiv.innerHTML = comment.comment || ''; const textContent = tempDiv.textContent || tempDiv.innerText || ''; html += `
${escapeHtml(textContent)}
`; html += `
${timeStr}
`; html += `
`; }); // 如果已经加载完所有数据,显示提示 if (!hasMore && allComments.length > 0) { html += `
已全部加载
`; } previewBody.innerHTML = html; } // 处理预览面板滚动事件 function handlePreviewScroll(e) { const target = e.target; const scrollTop = target.scrollTop; const scrollHeight = target.scrollHeight; const clientHeight = target.clientHeight; // 距离底部 50px 时触发加载 if (scrollHeight - scrollTop - clientHeight < 50) { if (!isLoading && hasMore) { currentPage++; loadGuestbook(false); } } } // 追加留言 function appendGuestbook(comments) { const listContainer = guestbookBody.querySelector('.guestbook-list'); const loadMoreBtn = guestbookBody.querySelector('.load-more-btn'); if (!listContainer) return; comments.forEach(comment => { const div = document.createElement('div'); div.innerHTML = renderCommentItem(comment); listContainer.appendChild(div.firstElementChild); }); // 移除旧的加载更多按钮 if (loadMoreBtn) { loadMoreBtn.remove(); } // 如果还有更多,添加新的加载更多按钮;否则显示"已显示全部" if (hasMore) { const btn = document.createElement('button'); btn.className = 'load-more-btn'; btn.textContent = '点击加载更多'; btn.onclick = window.loadMoreGuestbook; listContainer.after(btn); } else { const tip = document.createElement('div'); tip.className = 'load-complete'; tip.textContent = '已显示全部'; listContainer.after(tip); } // 更新预览面板 renderPreview(allComments, allComments.length); } // 渲染单个留言项 function renderCommentItem(comment) { const date = new Date(comment.time); const timeStr = formatDate(date); let html = `
`; html += `
`; // 头像和昵称 html += `
`; if (comment.avatar) { html += ``; } // 昵称和链接 if (comment.link) { html += `${escapeHtml(comment.nick)}`; } else { html += `${escapeHtml(comment.nick)}`; } html += `
`; html += `${timeStr}`; html += `
`; // 内容(comment 字段已经是 HTML) html += `
${comment.comment || ''}
`; // 底部信息(浏览器、系统、地址) html += renderCommentFooter(comment); // 回复列表 if (comment.children && comment.children.length > 0) { html += `
`; comment.children.forEach(reply => { html += renderReplyItem(reply); }); html += `
`; } html += `
`; return html; } // 渲染留言底部信息 function renderCommentFooter(comment) { const browser = comment.browser || ''; const os = comment.os || ''; const addr = comment.addr || ''; if (!browser && !os && !addr) return ''; let html = ''; return html; } // 渲染回复项 function renderReplyItem(reply) { const date = new Date(reply.time); const timeStr = formatDate(date); let html = `
`; html += `
`; html += `
`; if (reply.avatar) { html += ``; } if (reply.link) { html += `${escapeHtml(reply.nick)}`; } else { html += `${escapeHtml(reply.nick)}`; } html += `
`; html += `${timeStr}`; html += `
`; html += `
${reply.comment || ''}
`; html += `
`; return html; } // 渲染表单 function renderForm() { return `
`; } // 提交留言 async function handleSubmit(e) { e.preventDefault(); // 重新获取表单元素(因为表单可能被重新渲染) nicknameInput = document.getElementById('guestbook-nickname'); emailInput = document.getElementById('guestbook-email'); linkInput = document.getElementById('guestbook-link'); contentInput = document.getElementById('guestbook-content'); submitBtn = document.getElementById('guestbook-submit'); const nick = nicknameInput.value.trim() || '匿名'; const email = emailInput.value.trim(); const link = linkInput.value.trim(); const comment = contentInput.value.trim(); // 验证 if (!comment) { showToast('请输入留言内容', 'error'); return; } // 禁用提交按钮 submitBtn.disabled = true; submitBtn.textContent = '提交中...'; try { const response = await fetch(`${API_BASE_URL}/api/comment`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nick: nick, mail: email || '', link: link || '', comment: comment, path: 'www.iletter.top', ua: navigator.userAgent, url: window.location.href }) }); const data = await response.json(); if (response.ok && data.errno === 0) { showToast(getTranslation('guestbook_submit_success') || '留言提交成功!', 'success'); // 清空表单 nicknameInput.value = ''; emailInput.value = ''; linkInput.value = ''; contentInput.value = ''; // 重新加载第一页 loadGuestbook(true); } else { throw new Error(data.errmsg || '提交失败'); } } catch (error) { console.error('提交留言失败:', error); showToast(getTranslation('guestbook_submit_error') || '留言提交失败,请重试', 'error'); } finally { submitBtn.disabled = false; submitBtn.textContent = getTranslation('guestbook_submit') || '提交留言'; } } // 格式化日期 function formatDate(date) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); return `${year}-${month}-${day} ${hours}:${minutes}`; } // HTML 转义 function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // 获取翻译 function getTranslation(key) { const lang = localStorage.getItem('preferred_language') || 'zh'; const translations = { 'zh': typeof translationsZH !== 'undefined' ? translationsZH : {}, 'en': typeof translationsEN !== 'undefined' ? translationsEN : {} }; return translations[lang][key] || key; } // 加载更多 window.loadMoreGuestbook = function() { if (!isLoading && hasMore) { currentPage++; loadGuestbook(false); } }; // DOM 加载完成后初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();