feat: 文章详情页右下角悬浮操作栏 + 作者自赋能拦截
- 赞/踩/赋能/收藏/评论/回顶部移至右下角悬浮操作栏,固定定位 - 回顶部按钮页面顶部隐藏,滚动后显示,平滑滚动 - 评论按钮点击定位到评论区(NAV 下方) - 赋能按钮作者可见但禁止自赋能,点击弹出 toast 提示 - 未登录用户赋能/收藏点击跳转登录页 - postReactions 引用统一改为 postFloatBar
This commit is contained in:
@ -36,18 +36,37 @@
|
|||||||
<div class="post-detail-body" id="postContent"></div>
|
<div class="post-detail-body" id="postContent"></div>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<!-- 赞/踩/收藏按钮 -->
|
<!-- 右下角悬浮操作栏 -->
|
||||||
<div class="post-reactions" id="postReactions" data-post-id="{{.Post.ID}}">
|
<div class="post-float-bar" id="postFloatBar" data-post-id="{{.Post.ID}}">
|
||||||
<button class="reaction-btn reaction-like-btn" id="likeBtn" title="点赞">
|
<button class="float-btn float-like-btn" id="likeBtn" title="点赞">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="20" height="20"><path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3H14zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"/></svg>
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3H14zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"/></svg>
|
||||||
<span class="reaction-like-count" id="likesCount">{{.Post.LikesCount}}</span>
|
<span class="float-count" id="likesCount">{{.Post.LikesCount}}</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="reaction-btn reaction-dislike-btn" id="dislikeBtn" title="踩">
|
<button class="float-btn float-dislike-btn" id="dislikeBtn" title="踩">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="20" height="20"><path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3H10zM17 2h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"/></svg>
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3H10zM17 2h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<button class="reaction-btn reaction-fav-btn" id="favBtn" title="收藏">
|
{{if .IsLoggedIn}}
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="20" height="20"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/></svg>
|
<button class="float-btn float-energize-btn" id="energizeBtn" data-id="{{.Post.ID}}" data-uid="{{$.UID}}" data-author-uid="{{.Post.UserID}}" title="赋能">
|
||||||
<span class="reaction-like-count" id="favCount">{{.Post.FavoritesCount}}</span>
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
|
||||||
|
</button>
|
||||||
|
<button class="float-btn float-fav-btn" id="favBtn" title="收藏">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/></svg>
|
||||||
|
<span class="float-count" id="favCount">{{.Post.FavoritesCount}}</span>
|
||||||
|
</button>
|
||||||
|
{{else}}
|
||||||
|
<button class="float-btn float-energize-btn" onclick="location.href='/auth/login?redirect=/posts/{{.Post.ID}}'" title="赋能">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
|
||||||
|
</button>
|
||||||
|
<button class="float-btn float-fav-btn" onclick="location.href='/auth/login?redirect=/posts/{{.Post.ID}}'" title="收藏">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/></svg>
|
||||||
|
<span class="float-count">0</span>
|
||||||
|
</button>
|
||||||
|
{{end}}
|
||||||
|
<button class="float-btn float-comment-btn" id="floatCommentBtn" title="评论">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
||||||
|
</button>
|
||||||
|
<button class="float-btn float-top-btn" id="floatTopBtn" title="回顶部" style="display:none">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="18 15 12 9 6 15"/></svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -59,9 +78,6 @@
|
|||||||
{{if and $.Post (eq $.Post.UserID $.UID) (or (eq $.Post.Status "draft") (eq $.Post.Status "rejected")) (not $.Post.IsLocked)}}
|
{{if and $.Post (eq $.Post.UserID $.UID) (or (eq $.Post.Status "draft") (eq $.Post.Status "rejected")) (not $.Post.IsLocked)}}
|
||||||
<button class="btn btn-primary" id="submitAuditBtn" data-id="{{$.Post.ID}}">提交审核</button>
|
<button class="btn btn-primary" id="submitAuditBtn" data-id="{{$.Post.ID}}">提交审核</button>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if and $.Post (ne $.Post.UserID $.UID)}}
|
|
||||||
<button class="btn btn-primary" id="energizeBtn" data-id="{{$.Post.ID}}">赋能</button>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 赋能弹窗 -->
|
<!-- 赋能弹窗 -->
|
||||||
@ -158,5 +174,42 @@
|
|||||||
<script src="/static/js/post-reactions.js?v={{assetV "/static/js/post-reactions.js"}}" nonce="{{.CSPNonce}}"></script>
|
<script src="/static/js/post-reactions.js?v={{assetV "/static/js/post-reactions.js"}}" nonce="{{.CSPNonce}}"></script>
|
||||||
<script src="/static/js/post-favorite.js?v={{assetV "/static/js/post-favorite.js"}}" nonce="{{.CSPNonce}}"></script>
|
<script src="/static/js/post-favorite.js?v={{assetV "/static/js/post-favorite.js"}}" nonce="{{.CSPNonce}}"></script>
|
||||||
<script src="/static/js/post-comments.js?v={{assetV "/static/js/post-comments.js"}}" nonce="{{.CSPNonce}}"></script>
|
<script src="/static/js/post-comments.js?v={{assetV "/static/js/post-comments.js"}}" nonce="{{.CSPNonce}}"></script>
|
||||||
|
<script nonce="{{.CSPNonce}}">
|
||||||
|
(function() {
|
||||||
|
var topBtn = document.getElementById('floatTopBtn');
|
||||||
|
var commentBtn = document.getElementById('floatCommentBtn');
|
||||||
|
var comments = document.getElementById('comments');
|
||||||
|
var nav = document.querySelector('nav');
|
||||||
|
var navHeight = nav ? nav.offsetHeight : 60;
|
||||||
|
|
||||||
|
// 回顶部:页面顶部时隐藏按钮
|
||||||
|
function toggleTopBtn() {
|
||||||
|
if (!topBtn) return;
|
||||||
|
if (window.scrollY > 300) {
|
||||||
|
topBtn.style.display = '';
|
||||||
|
} else {
|
||||||
|
topBtn.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('scroll', toggleTopBtn);
|
||||||
|
toggleTopBtn();
|
||||||
|
|
||||||
|
if (topBtn) {
|
||||||
|
topBtn.addEventListener('click', function() {
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 评论按钮:滚动到评论区域顶部(NAV 下方)
|
||||||
|
if (commentBtn && comments) {
|
||||||
|
commentBtn.addEventListener('click', function() {
|
||||||
|
var rect = comments.getBoundingClientRect();
|
||||||
|
var scrollTop = window.scrollY + rect.top - navHeight - 12;
|
||||||
|
window.scrollTo({ top: scrollTop, behavior: 'smooth' });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1248,3 +1248,85 @@
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ========== 右下角悬浮操作栏 ========== */
|
||||||
|
.post-float-bar {
|
||||||
|
position: fixed;
|
||||||
|
right: 20px;
|
||||||
|
bottom: 120px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
z-index: 100;
|
||||||
|
background: var(--color-surface, #fff);
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 24px rgba(0,0,0,.1);
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-btn {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #6b7280;
|
||||||
|
transition: all .15s;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-btn:hover {
|
||||||
|
background: #f3f4f6;
|
||||||
|
color: #6366f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-btn.active {
|
||||||
|
color: #6366f1;
|
||||||
|
background: #eef2ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-btn svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-count {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1;
|
||||||
|
margin-top: 2px;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-top-btn {
|
||||||
|
border-top: 1px solid #f3f4f6;
|
||||||
|
border-radius: 0 0 8px 8px;
|
||||||
|
margin-top: 2px;
|
||||||
|
padding-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 赋能 Toast */
|
||||||
|
.energize-toast {
|
||||||
|
position: fixed;
|
||||||
|
top: 80px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: #1f2937;
|
||||||
|
color: #fff;
|
||||||
|
padding: 10px 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity .3s;
|
||||||
|
z-index: 10000;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energize-toast.show {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
var commentsSection = document.getElementById('comments');
|
var commentsSection = document.getElementById('comments');
|
||||||
if (!commentsSection) return;
|
if (!commentsSection) return;
|
||||||
|
|
||||||
var postId = commentsSection.closest('.container').querySelector('#postReactions').dataset.postId;
|
var postId = commentsSection.closest('.container').querySelector('#postFloatBar').dataset.postId;
|
||||||
var commentsList = document.getElementById('commentsList');
|
var commentsList = document.getElementById('commentsList');
|
||||||
var commentsTotal = document.getElementById('commentsTotal');
|
var commentsTotal = document.getElementById('commentsTotal');
|
||||||
var loadMoreBtn = document.getElementById('commentsLoadMore');
|
var loadMoreBtn = document.getElementById('commentsLoadMore');
|
||||||
|
|||||||
@ -9,6 +9,20 @@
|
|||||||
if (!btn || !overlay) return;
|
if (!btn || !overlay) return;
|
||||||
|
|
||||||
var postId = btn.dataset.id;
|
var postId = btn.dataset.id;
|
||||||
|
var isAuthor = btn.dataset.uid && btn.dataset.authorUid &&
|
||||||
|
btn.dataset.uid === btn.dataset.authorUid;
|
||||||
|
|
||||||
|
function showToast(msg) {
|
||||||
|
var toast = document.createElement('div');
|
||||||
|
toast.className = 'energize-toast';
|
||||||
|
toast.textContent = msg;
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
setTimeout(function() { toast.classList.add('show'); }, 10);
|
||||||
|
setTimeout(function() {
|
||||||
|
toast.classList.remove('show');
|
||||||
|
setTimeout(function() { toast.remove(); }, 300);
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
function showOverlay() {
|
function showOverlay() {
|
||||||
overlay.style.display = 'flex';
|
overlay.style.display = 'flex';
|
||||||
@ -21,6 +35,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
btn.addEventListener('click', function() {
|
btn.addEventListener('click', function() {
|
||||||
|
if (isAuthor) {
|
||||||
|
showToast('无法给自己赋能');
|
||||||
|
return;
|
||||||
|
}
|
||||||
fetch('/api/energy/info')
|
fetch('/api/energy/info')
|
||||||
.then(function(r) { return r.json(); })
|
.then(function(r) { return r.json(); })
|
||||||
.then(function(d) {
|
.then(function(d) {
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
(function() {
|
(function() {
|
||||||
var favBtn = document.getElementById('favBtn');
|
var favBtn = document.getElementById('favBtn');
|
||||||
var favCountEl = document.getElementById('favCount');
|
var favCountEl = document.getElementById('favCount');
|
||||||
var container = document.getElementById('postReactions');
|
var container = document.getElementById('postFloatBar');
|
||||||
var overlay = document.getElementById('favOverlay');
|
var overlay = document.getElementById('favOverlay');
|
||||||
var closeBtn = document.getElementById('favClose');
|
var closeBtn = document.getElementById('favClose');
|
||||||
var folderListEl = document.getElementById('favFolderCheckList');
|
var folderListEl = document.getElementById('favFolderCheckList');
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// 赞/踩系统
|
// 赞/踩系统
|
||||||
(function() {
|
(function() {
|
||||||
var container = document.getElementById('postReactions');
|
var container = document.getElementById('postFloatBar');
|
||||||
var likeBtn = document.getElementById('likeBtn');
|
var likeBtn = document.getElementById('likeBtn');
|
||||||
var dislikeBtn = document.getElementById('dislikeBtn');
|
var dislikeBtn = document.getElementById('dislikeBtn');
|
||||||
var likesCountEl = document.getElementById('likesCount');
|
var likesCountEl = document.getElementById('likesCount');
|
||||||
|
|||||||
Reference in New Issue
Block a user