generated from dellevin/template
加入留言板博客按钮
This commit is contained in:
75
index.html
75
index.html
@@ -36,6 +36,7 @@
|
|||||||
@import url("./static/css/main.css");
|
@import url("./static/css/main.css");
|
||||||
@import url("./static/css/memos.css");
|
@import url("./static/css/memos.css");
|
||||||
@import url("./static/css/game_dialog.css");
|
@import url("./static/css/game_dialog.css");
|
||||||
|
@import url("./static/css/guestbook.css");
|
||||||
</style>
|
</style>
|
||||||
<title data-i18n="page_title">白荼 - BAITU</title>
|
<title data-i18n="page_title">白荼 - BAITU</title>
|
||||||
</head>
|
</head>
|
||||||
@@ -47,6 +48,78 @@
|
|||||||
<img id="lang-switch-btn" src="./static/img/中文.png" alt="Switch Language" title="切换语言" />
|
<img id="lang-switch-btn" src="./static/img/中文.png" alt="Switch Language" title="切换语言" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 留言板按钮 -->
|
||||||
|
<div class="guestbook-btn-container">
|
||||||
|
<button id="guestbook-btn" class="guestbook-btn" title="留言板">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M21 15C21 15.5304 20.7893 16.0391 20.4142 16.4142C20.0391 16.7893 19.5304 17 19 17H7L3 21V7C3 6.46957 3.21071 5.96086 3.58579 5.58579C3.96086 5.21071 4.46957 5 5 5H19C19.5304 5 20.0391 5.21071 20.4142 5.58579C20.7893 5.96086 21 6.46957 21 7V15Z"
|
||||||
|
stroke="#666" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
<span class="btn-label">留言板</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Hover 浮动预览面板 -->
|
||||||
|
<div id="guestbook-preview" class="guestbook-preview">
|
||||||
|
<div class="preview-header">
|
||||||
|
<h3 data-i18n="guestbook_title">留言板</h3>
|
||||||
|
</div>
|
||||||
|
<div class="preview-body">
|
||||||
|
<!-- 最新留言预览将在这里动态加载 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 博客文章按钮 -->
|
||||||
|
<div class="blog-posts-btn-container">
|
||||||
|
<button id="blog-posts-btn" class="blog-posts-btn" title="博客文章">
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M14 2H6C5.46957 2 4.96086 2.21071 4.58579 2.58579C4.21071 2.96086 4 3.46957 4 4V20C4 20.5304 4.21071 21.0391 4.58579 21.4142C4.96086 21.7893 5.46957 22 6 22H18C18.5304 22 19.0391 21.7893 19.4142 21.4142C19.7893 21.0391 20 20.5304 20 20V8L14 2Z"
|
||||||
|
stroke="#666" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M14 2V8H20" stroke="#666" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M16 13H8" stroke="#666" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M16 17H8" stroke="#666" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M10 9H8" stroke="#666" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
<span class="btn-label">博客文章</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Hover 浮动预览面板 -->
|
||||||
|
<div id="blog-posts-preview" class="blog-posts-preview">
|
||||||
|
<div class="preview-header">
|
||||||
|
<h3>最新文章</h3>
|
||||||
|
</div>
|
||||||
|
<div class="preview-body blog-posts-list">
|
||||||
|
<!-- 最新文章预览将在这里动态加载 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 完整博客文章弹窗(点击后显示) -->
|
||||||
|
<div id="blog-posts-full-modal" class="guestbook-full-modal">
|
||||||
|
<div class="full-modal-content">
|
||||||
|
<div class="guestbook-header">
|
||||||
|
<h2>博客文章</h2>
|
||||||
|
<button id="blog-posts-close" class="guestbook-close" title="关闭">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="guestbook-body">
|
||||||
|
<!-- 文章列表将在这里动态加载 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 完整留言板弹窗(点击后显示) -->
|
||||||
|
<div id="guestbook-full-modal" class="guestbook-full-modal">
|
||||||
|
<div class="full-modal-content">
|
||||||
|
<div class="guestbook-header">
|
||||||
|
<h2 data-i18n="guestbook_title">留言板</h2>
|
||||||
|
<button id="guestbook-close" class="guestbook-close" title="关闭">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="guestbook-body">
|
||||||
|
<!-- 留言列表将在这里动态加载 -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 头部导航 -->
|
<!-- 头部导航 -->
|
||||||
<div class="nav-tabs">
|
<div class="nav-tabs">
|
||||||
<div class="nav-tab active" data-tab="about"><span data-i18n="nav_about"></span></div>
|
<div class="nav-tab active" data-tab="about"><span data-i18n="nav_about"></span></div>
|
||||||
@@ -253,6 +326,8 @@
|
|||||||
<script src="./static/js/websites_my.js"></script>
|
<script src="./static/js/websites_my.js"></script>
|
||||||
<script src="./static/js/memos.js"></script>
|
<script src="./static/js/memos.js"></script>
|
||||||
<script src="./static/js/map-footprint.js"></script>
|
<script src="./static/js/map-footprint.js"></script>
|
||||||
|
<script src="./static/js/guestbook.js"></script>
|
||||||
|
<script src="./static/js/blog_posts.js"></script>
|
||||||
<script src="./static/js/tabchange.js"></script>
|
<script src="./static/js/tabchange.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,10 @@
|
|||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
width: 300px;
|
width: 300px;
|
||||||
display: none;
|
display: none;
|
||||||
transition: opacity 0.2s ease;
|
opacity: 0;
|
||||||
|
transform: translateY(10px) scale(0.95);
|
||||||
|
transition: opacity 0.6s cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
|
transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 悬浮框内容 */
|
/* 悬浮框内容 */
|
||||||
|
|||||||
826
static/css/guestbook.css
Normal file
826
static/css/guestbook.css
Normal file
@@ -0,0 +1,826 @@
|
|||||||
|
/* 留言板按钮容器 */
|
||||||
|
.guestbook-btn-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 80px;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 博客文章按钮容器 */
|
||||||
|
.blog-posts-btn-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 95px;
|
||||||
|
right: 80px;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.guestbook-btn {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 3px;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-posts-btn {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 3px;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-btn:hover,
|
||||||
|
.blog-posts-btn:hover {
|
||||||
|
border-color: #d0d0d0;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
|
||||||
|
transform: translateY(-2px) scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-btn svg,
|
||||||
|
.blog-posts-btn svg {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-btn svg path,
|
||||||
|
.blog-posts-btn svg path {
|
||||||
|
transition: stroke 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-btn:hover svg path,
|
||||||
|
.blog-posts-btn:hover svg path {
|
||||||
|
stroke: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover 浮动预览面板 */
|
||||||
|
.guestbook-preview,
|
||||||
|
.blog-posts-preview {
|
||||||
|
visibility: hidden;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: calc(100% + 15px); /* 在按钮左侧显示 */
|
||||||
|
width: 380px;
|
||||||
|
max-height: 400px;
|
||||||
|
background: linear-gradient(to bottom, #fafafa, #fff);
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15), 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
z-index: 1000;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(20px) scale(0.95);
|
||||||
|
transform-origin: center right;
|
||||||
|
transition: opacity 0.5s cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
|
transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275),
|
||||||
|
visibility 0.5s;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid rgba(240, 240, 240, 0.8);
|
||||||
|
pointer-events: none;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 当鼠标悬停在容器上时显示预览面板 */
|
||||||
|
.guestbook-btn-container:hover .guestbook-preview,
|
||||||
|
.blog-posts-btn-container:hover .blog-posts-preview {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0) scale(1);
|
||||||
|
pointer-events: auto;
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.18), 0 8px 20px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-header {
|
||||||
|
padding: 14px 18px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 15px;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-body {
|
||||||
|
padding: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: calc(400px - 50px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-item {
|
||||||
|
padding: 12px 18px;
|
||||||
|
border-bottom: 1px solid #f5f5f5;
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-item:hover {
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-nickname {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content {
|
||||||
|
color: #666;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-time {
|
||||||
|
color: #999;
|
||||||
|
font-size: 11px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-complete {
|
||||||
|
text-align: center;
|
||||||
|
padding: 15px;
|
||||||
|
color: #bbb;
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 完整留言板弹窗(点击后显示) */
|
||||||
|
.guestbook-full-modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 10000;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-full-modal.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-modal-content {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
width: 92%;
|
||||||
|
max-width: 850px;
|
||||||
|
max-height: 92vh;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 12px 48px rgba(0, 0, 0, 0.18);
|
||||||
|
animation: slideUp 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from {
|
||||||
|
transform: translateY(30px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toast 弹窗 */
|
||||||
|
.toast-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
z-index: 100000;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
background: rgba(51, 51, 51, 0.95);
|
||||||
|
color: #fff;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-20px);
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.show {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.success {
|
||||||
|
background: rgba(76, 175, 80, 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.error {
|
||||||
|
background: rgba(244, 67, 54, 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 留言板模态框 */
|
||||||
|
.guestbook-modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 10000;
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-modal.active {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-content {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 800px;
|
||||||
|
max-height: 85vh;
|
||||||
|
overflow: hidden;
|
||||||
|
/* box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); */
|
||||||
|
animation: slideUp 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from {
|
||||||
|
transform: translateY(30px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-header {
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-header h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-close {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #999;
|
||||||
|
cursor: pointer;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 0;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-close:hover {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-body {
|
||||||
|
padding: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: calc(92vh - 60px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 留言表单 - 固定在顶部 */
|
||||||
|
.guestbook-form {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding: 15px 20px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background: #fff;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 留言列表 */
|
||||||
|
.guestbook-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 15px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-item {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #fff;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-item:hover {
|
||||||
|
border-color: #e0e0e0;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-item-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 头像样式 */
|
||||||
|
.guestbook-item-header > div:first-child img {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
border: 2px solid #f5f5f5;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-item:hover .guestbook-item-header > div:first-child img {
|
||||||
|
border-color: #e0e0e0;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-reply .guestbook-item-header > div:first-child img {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-nickname {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-nickname a {
|
||||||
|
color: #333;
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px dotted #999;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-nickname a:hover {
|
||||||
|
color: #000;
|
||||||
|
border-bottom-color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-meta {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-content {
|
||||||
|
color: #555;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-size: 13px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 美化留言内容中的 HTML 元素 */
|
||||||
|
.guestbook-content p {
|
||||||
|
margin: 0 0 6px 0;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-content p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-content a {
|
||||||
|
color: #333;
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px dotted #999;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-content a:hover {
|
||||||
|
color: #000;
|
||||||
|
border-bottom-color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-content img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-content blockquote {
|
||||||
|
margin: 6px 0;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: #f9f9f9;
|
||||||
|
border-left: 3px solid #ddd;
|
||||||
|
color: #666;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-content code {
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #e74c3c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-content pre {
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin: 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-content pre code {
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 留言底部信息(浏览器、系统、地址) */
|
||||||
|
.guestbook-footer {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 8px;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px solid #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-info-item {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-info-item svg {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-info-item:hover {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-reply {
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 12px 15px;
|
||||||
|
background: #f9f9f9;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-left: 3px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-reply .guestbook-item {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 0;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-reply .guestbook-item:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-reply .guestbook-item:hover {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-reply .guestbook-nickname {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-reply .guestbook-content {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #777;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 留言表单 - 固定在顶部 */
|
||||||
|
.guestbook-form {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 12px 20px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background: #fff;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group-full {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
color: #666;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input,
|
||||||
|
.form-group textarea,
|
||||||
|
.form-group-full textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #333;
|
||||||
|
background: #fafafa;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
font-family: inherit;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input:focus,
|
||||||
|
.form-group textarea:focus,
|
||||||
|
.form-group-full textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #999;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group textarea,
|
||||||
|
.form-group-full textarea {
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 50px;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group textarea::placeholder,
|
||||||
|
.form-group-full textarea::placeholder {
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-submit {
|
||||||
|
text-align: right;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
background: #333;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn:hover {
|
||||||
|
background: #000;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn:disabled {
|
||||||
|
background: #ccc;
|
||||||
|
box-shadow: none;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载状态 */
|
||||||
|
.guestbook-loading {
|
||||||
|
text-align: center;
|
||||||
|
padding: 30px 25px;
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-empty {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px 25px;
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载更多按钮 */
|
||||||
|
.load-more-btn {
|
||||||
|
display: block;
|
||||||
|
margin: 20px auto 20px auto;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
color: #666;
|
||||||
|
padding: 10px 30px;
|
||||||
|
border-radius: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-more-btn:hover {
|
||||||
|
border-color: #999;
|
||||||
|
color: #333;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-more-btn:disabled {
|
||||||
|
color: #ccc;
|
||||||
|
border-color: #eee;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载完成提示 */
|
||||||
|
.load-complete {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px 25px;
|
||||||
|
color: #bbb;
|
||||||
|
font-size: 13px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 博客文章列表样式 */
|
||||||
|
.blog-post-item {
|
||||||
|
padding: 12px;
|
||||||
|
border-bottom: 1px solid #f5f5f5;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-item:hover {
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-title {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
line-height: 1.4;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-meta {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 11px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-post-category {
|
||||||
|
background: #f0f0f0;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.guestbook-btn-container {
|
||||||
|
right: 15px;
|
||||||
|
top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blog-posts-btn-container {
|
||||||
|
right: 15px;
|
||||||
|
top: 90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端预览面板调整位置,避免超出屏幕 */
|
||||||
|
.guestbook-preview,
|
||||||
|
.blog-posts-preview {
|
||||||
|
right: auto;
|
||||||
|
left: calc(100% + 10px);
|
||||||
|
transform-origin: center left;
|
||||||
|
transform: translateX(-20px) scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-btn-container:hover .guestbook-preview,
|
||||||
|
.blog-posts-btn-container:hover .blog-posts-preview {
|
||||||
|
transform: translateX(0) scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-content {
|
||||||
|
width: 95%;
|
||||||
|
max-height: 90vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-body {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-form {
|
||||||
|
padding: 15px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-list {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guestbook-loading,
|
||||||
|
.guestbook-empty,
|
||||||
|
.load-complete {
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端表单改为垂直排列 */
|
||||||
|
.form-row {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.letter-container {
|
.letter-container {
|
||||||
|
/* max-width: 65%; */
|
||||||
max-width: 850px;
|
max-width: 850px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
background: white;
|
background: white;
|
||||||
@@ -81,10 +82,26 @@ body {
|
|||||||
/* 内容区域 */
|
/* 内容区域 */
|
||||||
.tab-content {
|
.tab-content {
|
||||||
display: none;
|
display: none;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
transition: opacity 0.6s cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
|
transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-content.active {
|
.tab-content.active {
|
||||||
display: block;
|
display: block;
|
||||||
|
animation: tabFadeIn 0.6s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes tabFadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 信纸纹理效果 */
|
/* 信纸纹理效果 */
|
||||||
@@ -181,6 +198,15 @@ body {
|
|||||||
border: 1px solid #eee;
|
border: 1px solid #eee;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
|
box-shadow 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-item:hover,
|
||||||
|
.game-item:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool-name,
|
.tool-name,
|
||||||
@@ -248,6 +274,7 @@ body {
|
|||||||
/* 底部版权信息 - 在信纸外部 */
|
/* 底部版权信息 - 在信纸外部 */
|
||||||
#footer-wrap {
|
#footer-wrap {
|
||||||
max-width: 850px;
|
max-width: 850px;
|
||||||
|
/* max-width: 65%; */
|
||||||
margin: 0 auto 20px auto;
|
margin: 0 auto 20px auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
const endAt = Date.now(); // 当前时间的时间戳(毫秒)
|
const endAt = Date.now(); // 当前时间的时间戳(毫秒)
|
||||||
const headers = {
|
const headers = {
|
||||||
Authorization:
|
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",
|
"Content-Type": "application/json",
|
||||||
};
|
};
|
||||||
const params = new URLSearchParams({
|
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.left = `${left}px`;
|
||||||
tooltipDiv.style.display = "block";
|
tooltipDiv.style.display = "block";
|
||||||
|
|
||||||
|
// 触发重绘以启动过渡动画
|
||||||
|
void tooltipDiv.offsetWidth;
|
||||||
|
tooltipDiv.style.opacity = "1";
|
||||||
|
tooltipDiv.style.transform = "translateY(0) scale(1)";
|
||||||
|
|
||||||
// 记录当前打开的tooltip
|
// 记录当前打开的tooltip
|
||||||
currentlyOpenTooltip = { element: tooltipDiv, index: gameIndex };
|
currentlyOpenTooltip = { element: tooltipDiv, index: gameIndex };
|
||||||
|
|
||||||
@@ -299,11 +304,13 @@ function toggleGameDetailTooltip(gameElement, gameIndex) {
|
|||||||
// ========== 隐藏Tooltip的辅助函数 ==========
|
// ========== 隐藏Tooltip的辅助函数 ==========
|
||||||
function hideTooltip(tooltipElement) {
|
function hideTooltip(tooltipElement) {
|
||||||
if (tooltipElement) {
|
if (tooltipElement) {
|
||||||
tooltipElement.style.display = "none";
|
tooltipElement.style.opacity = "0";
|
||||||
// 如果想完全移除DOM,可以取消下面两行注释,但通常隐藏即可
|
tooltipElement.style.transform = "translateY(10px) scale(0.95)";
|
||||||
// if (tooltipElement.parentNode) {
|
|
||||||
// tooltipElement.parentNode.removeChild(tooltipElement);
|
// 等待动画结束后再隐藏 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_h3: "Leave a Message",
|
||||||
leave_message_p:
|
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",
|
leave_message_link: "click here",
|
||||||
// 小界面标题
|
// 小界面标题
|
||||||
my_website_h2: "My Websites",
|
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.",
|
"I still choose to spend the rest of my life in my own way although I am a failure.",
|
||||||
visitor_count_label: "Visitors:",
|
visitor_count_label: "Visitors:",
|
||||||
visit_count_label: "Total Visits:",
|
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_h3: "留言",
|
||||||
leave_message_p:
|
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: "点击此链接",
|
leave_message_link: "点击此链接",
|
||||||
// 小界面标题
|
// 小界面标题
|
||||||
my_website_h2: "我的网站",
|
my_website_h2: "我的网站",
|
||||||
@@ -57,4 +57,17 @@ const translationsZH = {
|
|||||||
footer_text: "我虽然是个废物,但我仍然选择用自己喜欢的方式度过自己的余生",
|
footer_text: "我虽然是个废物,但我仍然选择用自己喜欢的方式度过自己的余生",
|
||||||
visitor_count_label: "本站访客数 :",
|
visitor_count_label: "本站访客数 :",
|
||||||
visit_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