generated from dellevin/template
加入留言板博客按钮
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
const endAt = Date.now(); // 当前时间的时间戳(毫秒)
|
||||
const headers = {
|
||||
Authorization:
|
||||
"Bearer 3VvA8ETw0ahPhzuNNY+Zxi3agtfOBT2vNRbm0GcPqIyUhm7rExuwj8F8IwiQWcn/rOD2G/TnONPCFIvUECQYp6GuZRTnfOojki533vP/skqf0D6puOZDQQk8Y7ssihXnfyRu5naGhIoj1BCAC7S0D0RiYvzpSYF9zvZqvgxETrCbFazZsqUBolyJd8H2iZiM4Xx3VC+GnkZHZFgQfaaYUvm33a7CLM74PyFpPby63UExMjIPiLQRAOR2hs5wl5JAs5CTYUaq+QHCCz+tWgDQ4FPtIgoZoG8Ugnywv/YEEn1Jv9p3t8ge7m8ttThnPiZWw62PYPWQ3LpFh7nxX9jQX/Y/vaaAyacCoIP5J4VpiClA40GMMptZrThzEQjheegCilb9",
|
||||
"Bearer JMQBYQLIlwUsx1tnfYI35APN3bh75JrdpWkx1uk0LJTi6QEGPlno2W2D7j8ogCAS12pXTT36VAZaGw3Pqgiw3BEViZG7o3+93zcmR4Txm0wjmHsTnig7GAxWfQMs1eRY9ACFl9KAo2n0UNgC+CqZt64PFxsKwsaIE2UzuGq1ByrCW8TQwtf76ZHg/qYYjFKAGIUEoQeDvXyCjoew25f7nN3o9GTdimkqoUt0xK/2tWCW1ROOowf6r5KDJK17jRNJGV6jwUJ9AsAd53CNp+MBOHy+E8Scrk/0ATZL3HUGSAuaFKyeITkaQTCjjwunnkxq/VAaeGXZIwI1Vt1llz0Qhbjy2G7l28FhOXm4QZLTdWXtduKwHkfAoFB+HcAWGsIdanoX",
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
const params = new URLSearchParams({
|
||||
|
||||
214
static/js/blog_posts.js
Normal file
214
static/js/blog_posts.js
Normal file
@@ -0,0 +1,214 @@
|
||||
/**
|
||||
* 博客文章功能 - 获取并展示博客最新文章
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const BLOG_API_URL = 'https://blog.iletter.top/api/posts';
|
||||
const BLOG_TOKEN = '67192cda-156a-415a-9f3c-97e90a6b818a';
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
let currentPage = 1;
|
||||
let hasMore = true;
|
||||
let isLoading = false;
|
||||
|
||||
// DOM 元素
|
||||
const blogPostsBtn = document.getElementById('blog-posts-btn');
|
||||
const blogPostsPreview = document.getElementById('blog-posts-preview');
|
||||
const blogPostsFullModal = document.getElementById('blog-posts-full-modal');
|
||||
const blogPostsClose = document.getElementById('blog-posts-close');
|
||||
const blogPostsList = document.querySelector('.blog-posts-list');
|
||||
const blogPostsFullBody = document.querySelector('#blog-posts-full-modal .guestbook-body');
|
||||
|
||||
// 初始化
|
||||
function init() {
|
||||
if (!blogPostsBtn || !blogPostsFullModal) return;
|
||||
|
||||
// 点击按钮打开完整弹窗
|
||||
blogPostsBtn.addEventListener('click', openFullModal);
|
||||
|
||||
// 关闭按钮
|
||||
blogPostsClose.addEventListener('click', closeFullModal);
|
||||
|
||||
// 点击背景关闭
|
||||
blogPostsFullModal.addEventListener('click', function(e) {
|
||||
if (e.target === blogPostsFullModal) {
|
||||
closeFullModal();
|
||||
}
|
||||
});
|
||||
|
||||
// ESC 键关闭
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && blogPostsFullModal.classList.contains('active')) {
|
||||
closeFullModal();
|
||||
}
|
||||
});
|
||||
|
||||
// 页面加载时自动加载数据(用于 Hover 预览)
|
||||
loadBlogPosts(true);
|
||||
}
|
||||
|
||||
// 打开完整弹窗
|
||||
function openFullModal() {
|
||||
blogPostsFullModal.classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
// 首次打开时加载文章
|
||||
if (!document.querySelector('.blog-post-item')) {
|
||||
loadBlogPosts(true);
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭完整弹窗
|
||||
function closeFullModal() {
|
||||
blogPostsFullModal.classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
// 加载文章列表
|
||||
async function loadBlogPosts(reset = false) {
|
||||
if (isLoading || (!hasMore && !reset)) return;
|
||||
|
||||
isLoading = true;
|
||||
|
||||
if (reset) {
|
||||
currentPage = 1;
|
||||
hasMore = true;
|
||||
if (blogPostsList) blogPostsList.innerHTML = '<div class="guestbook-loading">正在加载文章...</div>';
|
||||
if (blogPostsFullBody) blogPostsFullBody.innerHTML = '<div class="guestbook-loading">正在加载文章...</div>';
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${BLOG_API_URL}?page=${currentPage}&pageSize=${PAGE_SIZE}&showContent=false&showDigest=excerpt&limit=30`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'token': BLOG_TOKEN
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
const posts = result.data.dataSet || [];
|
||||
const totalPages = result.data.pages || 0;
|
||||
|
||||
// 判断是否还有更多数据
|
||||
hasMore = currentPage < totalPages;
|
||||
|
||||
if (reset) {
|
||||
renderPosts(posts, true);
|
||||
} else {
|
||||
appendPosts(posts);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('加载文章失败:', error);
|
||||
if (reset) {
|
||||
const errorMsg = '<div class="guestbook-empty">加载文章失败,请稍后重试</div>';
|
||||
if (blogPostsList) blogPostsList.innerHTML = errorMsg;
|
||||
if (blogPostsFullBody) blogPostsFullBody.innerHTML = errorMsg;
|
||||
}
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染文章列表
|
||||
function renderPosts(posts, isReset = false) {
|
||||
let html = '';
|
||||
|
||||
if (!posts || posts.length === 0) {
|
||||
html = '<div class="guestbook-empty">暂无文章</div>';
|
||||
} else {
|
||||
html = '<div class="blog-posts-container">';
|
||||
posts.forEach(post => {
|
||||
html += renderPostItem(post);
|
||||
});
|
||||
html += '</div>';
|
||||
|
||||
if (hasMore) {
|
||||
html += `<button class="load-more-btn" onclick="window.loadMoreBlogPosts()">点击加载更多</button>`;
|
||||
} else {
|
||||
html += `<div class="load-complete">已显示全部</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
if (blogPostsList) blogPostsList.innerHTML = html;
|
||||
if (blogPostsFullBody) blogPostsFullBody.innerHTML = html;
|
||||
}
|
||||
|
||||
// 追加文章
|
||||
function appendPosts(posts) {
|
||||
const container = document.querySelector('.blog-posts-container');
|
||||
const loadMoreBtn = document.querySelector('.load-more-btn');
|
||||
const oldTip = document.querySelector('.load-complete');
|
||||
|
||||
if (!container) return;
|
||||
|
||||
posts.forEach(post => {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = renderPostItem(post);
|
||||
container.appendChild(div.firstElementChild);
|
||||
});
|
||||
|
||||
// 移除旧的加载更多按钮和提示
|
||||
if (loadMoreBtn) loadMoreBtn.remove();
|
||||
if (oldTip) oldTip.remove();
|
||||
|
||||
// 添加新的加载更多按钮或提示
|
||||
if (hasMore) {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'load-more-btn';
|
||||
btn.textContent = '点击加载更多';
|
||||
btn.onclick = window.loadMoreBlogPosts;
|
||||
container.after(btn);
|
||||
} else {
|
||||
const tip = document.createElement('div');
|
||||
tip.className = 'load-complete';
|
||||
tip.textContent = '已显示全部';
|
||||
container.after(tip);
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染单个文章项
|
||||
function renderPostItem(post) {
|
||||
const date = new Date(post.created * 1000);
|
||||
const dateStr = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
||||
const category = post.categories && post.categories.length > 0 ? post.categories[0].name : '未分类';
|
||||
|
||||
let html = `<div class="blog-post-item" onclick="window.open('${post.permalink}', '_blank')">`;
|
||||
html += `<div class="blog-post-title">${escapeHtml(post.title)}</div>`;
|
||||
html += `<div class="blog-post-meta">`;
|
||||
html += `<span>${dateStr}</span>`;
|
||||
html += `<span class="blog-post-category">${escapeHtml(category)}</span>`;
|
||||
html += `</div>`;
|
||||
html += `</div>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
// HTML 转义
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// 加载更多
|
||||
window.loadMoreBlogPosts = function() {
|
||||
if (!isLoading && hasMore) {
|
||||
currentPage++;
|
||||
loadBlogPosts(false);
|
||||
}
|
||||
};
|
||||
|
||||
// DOM 加载完成后初始化
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
@@ -287,6 +287,11 @@ function toggleGameDetailTooltip(gameElement, gameIndex) {
|
||||
tooltipDiv.style.left = `${left}px`;
|
||||
tooltipDiv.style.display = "block";
|
||||
|
||||
// 触发重绘以启动过渡动画
|
||||
void tooltipDiv.offsetWidth;
|
||||
tooltipDiv.style.opacity = "1";
|
||||
tooltipDiv.style.transform = "translateY(0) scale(1)";
|
||||
|
||||
// 记录当前打开的tooltip
|
||||
currentlyOpenTooltip = { element: tooltipDiv, index: gameIndex };
|
||||
|
||||
@@ -299,11 +304,13 @@ function toggleGameDetailTooltip(gameElement, gameIndex) {
|
||||
// ========== 隐藏Tooltip的辅助函数 ==========
|
||||
function hideTooltip(tooltipElement) {
|
||||
if (tooltipElement) {
|
||||
tooltipElement.style.display = "none";
|
||||
// 如果想完全移除DOM,可以取消下面两行注释,但通常隐藏即可
|
||||
// if (tooltipElement.parentNode) {
|
||||
// tooltipElement.parentNode.removeChild(tooltipElement);
|
||||
// }
|
||||
tooltipElement.style.opacity = "0";
|
||||
tooltipElement.style.transform = "translateY(10px) scale(0.95)";
|
||||
|
||||
// 等待动画结束后再隐藏 display
|
||||
setTimeout(() => {
|
||||
tooltipElement.style.display = "none";
|
||||
}, 600);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
548
static/js/guestbook.js
Normal file
548
static/js/guestbook.js
Normal file
@@ -0,0 +1,548 @@
|
||||
/**
|
||||
* 留言板功能 - 基于 Flask API
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// Flask API 地址
|
||||
const API_BASE_URL = 'http://localhost:8360';
|
||||
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);
|
||||
|
||||
// 点击背景关闭
|
||||
guestbookFullModal.addEventListener('click', function(e) {
|
||||
if (e.target === guestbookFullModal) {
|
||||
closeFullModal();
|
||||
}
|
||||
});
|
||||
|
||||
// ESC 键关闭
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && guestbookFullModal.classList.contains('active')) {
|
||||
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 = '<div class="guestbook-loading" data-i18n="guestbook_loading">正在加载留言...</div>';
|
||||
} else {
|
||||
// 添加加载更多按钮状态
|
||||
const loadMoreBtn = document.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 = `<div class="guestbook-empty" data-i18n="guestbook_load_error">加载留言失败,请稍后重试</div>`;
|
||||
}
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染留言列表
|
||||
function renderGuestbook(comments, totalCount) {
|
||||
let html = '';
|
||||
|
||||
// 先渲染表单(放在顶部)
|
||||
html += renderForm();
|
||||
|
||||
if (!comments || comments.length === 0) {
|
||||
html += `<div class="guestbook-empty" data-i18n="guestbook_no_data">暂无留言,快来抢沙发吧!</div>`;
|
||||
guestbookBody.innerHTML = html;
|
||||
return;
|
||||
}
|
||||
|
||||
html += '<div class="guestbook-list">';
|
||||
comments.forEach(comment => {
|
||||
html += renderCommentItem(comment);
|
||||
});
|
||||
html += '</div>';
|
||||
|
||||
// 判断是否还有更多数据
|
||||
if (hasMore) {
|
||||
html += `<button class="load-more-btn" onclick="window.loadMoreGuestbook()">点击加载更多</button>`;
|
||||
} else {
|
||||
html += `<div class="load-complete">已显示全部</div>`;
|
||||
}
|
||||
|
||||
guestbookBody.innerHTML = html;
|
||||
|
||||
// 同时更新 Hover 预览面板(使用累积的所有留言)
|
||||
renderPreview(allComments);
|
||||
}
|
||||
|
||||
// 渲染 Hover 预览面板
|
||||
function renderPreview(comments) {
|
||||
if (!previewBody || !comments || comments.length === 0) return;
|
||||
|
||||
let html = '';
|
||||
comments.forEach(comment => {
|
||||
const date = new Date(comment.time);
|
||||
const timeStr = formatDate(date);
|
||||
|
||||
html += `<div class="preview-item">`;
|
||||
html += `<div class="preview-nickname">${escapeHtml(comment.nick)}</div>`;
|
||||
|
||||
// 提取纯文本内容(去除 HTML 标签)
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = comment.comment || '';
|
||||
const textContent = tempDiv.textContent || tempDiv.innerText || '';
|
||||
|
||||
html += `<div class="preview-content">${escapeHtml(textContent)}</div>`;
|
||||
html += `<div class="preview-time">${timeStr}</div>`;
|
||||
html += `</div>`;
|
||||
});
|
||||
|
||||
// 如果已经加载完所有数据,显示提示
|
||||
if (!hasMore && allComments.length > 0) {
|
||||
html += `<div class="preview-complete">已全部加载</div>`;
|
||||
}
|
||||
|
||||
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 = document.querySelector('.guestbook-list');
|
||||
const loadMoreBtn = document.querySelector('.load-more-btn');
|
||||
const allDataTip = document.querySelector('.guestbook-body > div:last-child');
|
||||
|
||||
if (!listContainer) return;
|
||||
|
||||
comments.forEach(comment => {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = renderCommentItem(comment);
|
||||
listContainer.appendChild(div.firstElementChild);
|
||||
});
|
||||
|
||||
// 移除旧的加载更多按钮和提示
|
||||
if (loadMoreBtn) {
|
||||
loadMoreBtn.remove();
|
||||
}
|
||||
const oldTip = document.querySelector('.load-complete');
|
||||
if (oldTip) {
|
||||
oldTip.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);
|
||||
}
|
||||
|
||||
// 渲染单个留言项
|
||||
function renderCommentItem(comment) {
|
||||
const date = new Date(comment.time);
|
||||
const timeStr = formatDate(date);
|
||||
|
||||
let html = `<div class="guestbook-item">`;
|
||||
html += `<div class="guestbook-item-header">`;
|
||||
|
||||
// 头像和昵称
|
||||
html += `<div style="display: flex; align-items: center; gap: 8px;">`;
|
||||
if (comment.avatar) {
|
||||
html += `<img src="${escapeHtml(comment.avatar)}" alt="">`;
|
||||
}
|
||||
|
||||
// 昵称和链接
|
||||
if (comment.link) {
|
||||
html += `<span class="guestbook-nickname"><a href="${escapeHtml(comment.link)}" target="_blank">${escapeHtml(comment.nick)}</a></span>`;
|
||||
} else {
|
||||
html += `<span class="guestbook-nickname">${escapeHtml(comment.nick)}</span>`;
|
||||
}
|
||||
html += `</div>`;
|
||||
|
||||
html += `<span class="guestbook-meta">${timeStr}</span>`;
|
||||
html += `</div>`;
|
||||
|
||||
// 内容(comment 字段已经是 HTML)
|
||||
html += `<div class="guestbook-content">${comment.comment || ''}</div>`;
|
||||
|
||||
// 底部信息(浏览器、系统、地址)
|
||||
html += renderCommentFooter(comment);
|
||||
|
||||
// 回复列表
|
||||
if (comment.children && comment.children.length > 0) {
|
||||
html += `<div class="guestbook-reply">`;
|
||||
comment.children.forEach(reply => {
|
||||
html += renderReplyItem(reply);
|
||||
});
|
||||
html += `</div>`;
|
||||
}
|
||||
|
||||
html += `</div>`;
|
||||
return html;
|
||||
}
|
||||
|
||||
// 渲染留言底部信息
|
||||
function renderCommentFooter(comment) {
|
||||
const browser = comment.browser || '';
|
||||
const os = comment.os || '';
|
||||
const addr = comment.addr || '';
|
||||
|
||||
if (!browser && !os && !addr) return '';
|
||||
|
||||
let html = '<div class="guestbook-footer">';
|
||||
|
||||
if (browser) {
|
||||
html += `<span class="guestbook-info-item"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line></svg> ${escapeHtml(browser)}</span>`;
|
||||
}
|
||||
|
||||
if (os) {
|
||||
html += `<span class="guestbook-info-item"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line></svg> ${escapeHtml(os)}</span>`;
|
||||
}
|
||||
|
||||
if (addr) {
|
||||
html += `<span class="guestbook-info-item"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path><circle cx="12" cy="10" r="3"></circle></svg> ${escapeHtml(addr)}</span>`;
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
// 渲染回复项
|
||||
function renderReplyItem(reply) {
|
||||
const date = new Date(reply.time);
|
||||
const timeStr = formatDate(date);
|
||||
|
||||
let html = `<div class="guestbook-item" style="margin-top: 10px;">`;
|
||||
html += `<div class="guestbook-item-header">`;
|
||||
|
||||
html += `<div style="display: flex; align-items: center; gap: 8px;">`;
|
||||
if (reply.avatar) {
|
||||
html += `<img src="${escapeHtml(reply.avatar)}" alt="">`;
|
||||
}
|
||||
|
||||
if (reply.link) {
|
||||
html += `<span class="guestbook-nickname"><a href="${escapeHtml(reply.link)}" target="_blank">${escapeHtml(reply.nick)}</a></span>`;
|
||||
} else {
|
||||
html += `<span class="guestbook-nickname">${escapeHtml(reply.nick)}</span>`;
|
||||
}
|
||||
html += `</div>`;
|
||||
|
||||
html += `<span class="guestbook-meta">${timeStr}</span>`;
|
||||
html += `</div>`;
|
||||
html += `<div class="guestbook-content">${reply.comment || ''}</div>`;
|
||||
html += `</div>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
// 渲染表单
|
||||
function renderForm() {
|
||||
return `
|
||||
<form id="guestbook-form" class="guestbook-form">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="guestbook-nickname" data-i18n="guestbook_nickname">昵称(选填)</label>
|
||||
<input type="text" id="guestbook-nickname" placeholder="匿名">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="guestbook-email" data-i18n="guestbook_email">邮箱(选填)</label>
|
||||
<input type="email" id="guestbook-email" placeholder="用于接收回复通知">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="guestbook-link" data-i18n="guestbook_website">网站(选填)</label>
|
||||
<input type="url" id="guestbook-link" placeholder="https://example.com">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group-full">
|
||||
<label for="guestbook-content" data-i18n="guestbook_placeholder">说点什么吧...</label>
|
||||
<textarea id="guestbook-content" placeholder="请输入留言内容..." required maxlength="500"></textarea>
|
||||
</div>
|
||||
<div class="form-submit">
|
||||
<button type="submit" id="guestbook-submit" class="submit-btn" data-i18n="guestbook_submit">提交留言</button>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
}
|
||||
|
||||
// 提交留言
|
||||
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();
|
||||
}
|
||||
})();
|
||||
@@ -36,7 +36,7 @@ const translationsEN = {
|
||||
// 留言
|
||||
leave_message_h3: "Leave a Message",
|
||||
leave_message_p:
|
||||
'If you have any questions or suggestions, feel free to <a href="https://blog.iletter.top/401.html" target="_blank">click here</a> to leave me a message on my blog!',
|
||||
'If you have any questions or suggestions, feel free to <a href="https://blog.iletter.top/401.html" target="_blank">click here</a> to leave me a message on my blog, or click the guestbook link in the top right corner to leave a message!',
|
||||
leave_message_link: "click here",
|
||||
// 小界面标题
|
||||
my_website_h2: "My Websites",
|
||||
@@ -57,4 +57,17 @@ const translationsEN = {
|
||||
"I still choose to spend the rest of my life in my own way although I am a failure.",
|
||||
visitor_count_label: "Visitors:",
|
||||
visit_count_label: "Total Visits:",
|
||||
// Guestbook
|
||||
guestbook_title: "Guestbook",
|
||||
guestbook_placeholder: "Say something...",
|
||||
guestbook_nickname: "Nickname (optional)",
|
||||
guestbook_email: "Email (optional)",
|
||||
guestbook_website: "Website (optional)",
|
||||
guestbook_submit: "Submit",
|
||||
guestbook_loading: "Loading messages...",
|
||||
guestbook_no_data: "No messages yet, be the first to comment!",
|
||||
guestbook_load_error: "Failed to load messages, please try again later",
|
||||
guestbook_submit_success: "Message submitted successfully!",
|
||||
guestbook_submit_error: "Failed to submit message, please try again",
|
||||
guestbook_required_nickname: "Please enter a nickname",
|
||||
};
|
||||
|
||||
@@ -37,7 +37,7 @@ const translationsZH = {
|
||||
// 留言
|
||||
leave_message_h3: "留言",
|
||||
leave_message_p:
|
||||
'如果你有任何问题或建议,欢迎<a href="https://blog.iletter.top/401.html" target="_blank">点击此链接</a>去我的博客下面给我留言!',
|
||||
'如果你有任何问题或建议,欢迎<a href="https://blog.iletter.top/401.html" target="_blank">点击此链接</a>去我的博客下面给我留言,或者点击右上角的留言板进行留言哦!',
|
||||
leave_message_link: "点击此链接",
|
||||
// 小界面标题
|
||||
my_website_h2: "我的网站",
|
||||
@@ -57,4 +57,17 @@ const translationsZH = {
|
||||
footer_text: "我虽然是个废物,但我仍然选择用自己喜欢的方式度过自己的余生",
|
||||
visitor_count_label: "本站访客数 :",
|
||||
visit_count_label: "本站总访问量 :",
|
||||
// 留言板
|
||||
guestbook_title: "留言板",
|
||||
guestbook_placeholder: "说点什么吧...",
|
||||
guestbook_nickname: "昵称(选填)",
|
||||
guestbook_email: "邮箱(选填)",
|
||||
guestbook_website: "网站(选填)",
|
||||
guestbook_submit: "提交留言",
|
||||
guestbook_loading: "正在加载留言...",
|
||||
guestbook_no_data: "暂无留言,快来抢沙发吧!",
|
||||
guestbook_load_error: "加载留言失败,请稍后重试",
|
||||
guestbook_submit_success: "留言提交成功!",
|
||||
guestbook_submit_error: "留言提交失败,请重试",
|
||||
guestbook_required_nickname: "请输入昵称",
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user