Merge pull request #532 from nagisa77/codex/add-comment-pinning-feature

feat: support comment pinning
This commit is contained in:
Tim
2025-08-13 16:31:12 +08:00
committed by GitHub
9 changed files with 165 additions and 11 deletions

View File

@@ -22,6 +22,7 @@
:to="`/users/${comment.userId}?tab=achievements`"
>{{ getMedalTitle(comment.medal) }}</router-link
>
<i v-if="comment.pinned" class="fas fa-thumbtack pin-icon"></i>
<span v-if="level >= 2">
<i class="fas fa-reply reply-icon"></i>
<span class="user-name reply-user-name">{{ comment.parentUserName }}</span>
@@ -74,6 +75,7 @@
:comment="item"
:level="level + 1"
:default-show-replies="item.openReplies"
:post-author-id="postAuthorId"
/>
</template>
</BaseTimeline>
@@ -119,6 +121,10 @@ const CommentItem = {
type: Boolean,
default: false,
},
postAuthorId: {
type: [Number, String],
required: true,
},
},
setup(props, { emit }) {
const router = useRouter()
@@ -171,12 +177,22 @@ const CommentItem = {
})
const isAuthor = computed(() => authState.username === props.comment.userName)
const isPostAuthor = computed(() => Number(authState.userId) === Number(props.postAuthorId))
const isAdmin = computed(() => authState.role === 'ADMIN')
const commentMenuItems = computed(() =>
isAuthor.value || isAdmin.value
? [{ text: '删除评论', color: 'red', onClick: () => deleteComment() }]
: [],
)
const commentMenuItems = computed(() => {
const items = []
if (isAuthor.value || isAdmin.value) {
items.push({ text: '删除评论', color: 'red', onClick: () => deleteComment() })
}
if (isAdmin.value || isPostAuthor.value) {
if (props.comment.pinned) {
items.push({ text: '取消置顶', onClick: () => unpinComment() })
} else {
items.push({ text: '置顶', onClick: () => pinComment() })
}
}
return items
})
const deleteComment = async () => {
const token = getToken()
if (!token) {
@@ -196,6 +212,46 @@ const CommentItem = {
toast.error('操作失败')
}
}
const pinComment = async () => {
const token = getToken()
if (!token) {
toast.error('请先登录')
return
}
const url = isAdmin.value
? `${API_BASE_URL}/api/admin/comments/${props.comment.id}/pin`
: `${API_BASE_URL}/api/comments/${props.comment.id}/pin`
const res = await fetch(url, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
})
if (res.ok) {
props.comment.pinned = true
toast.success('已置顶')
} else {
toast.error('操作失败')
}
}
const unpinComment = async () => {
const token = getToken()
if (!token) {
toast.error('请先登录')
return
}
const url = isAdmin.value
? `${API_BASE_URL}/api/admin/comments/${props.comment.id}/unpin`
: `${API_BASE_URL}/api/comments/${props.comment.id}/unpin`
const res = await fetch(url, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
})
if (res.ok) {
props.comment.pinned = false
toast.success('已取消置顶')
} else {
toast.error('操作失败')
}
}
const submitReply = async (parentUserName, text, clear) => {
if (!text.trim()) return
isWaitingForReply.value = true
@@ -284,6 +340,9 @@ const CommentItem = {
isWaitingForReply,
commentMenuItems,
deleteComment,
pinComment,
unpinComment,
isPostAuthor,
lightboxVisible,
lightboxIndex,
lightboxImgs,
@@ -370,6 +429,12 @@ export default CommentItem
margin-left: 10px;
}
.pin-icon {
font-size: 12px;
margin-left: 10px;
opacity: 0.6;
}
@keyframes highlight {
from {
background-color: yellow;

View File

@@ -195,6 +195,7 @@
:comment="item"
:level="0"
:default-show-replies="item.openReplies"
:post-author-id="author.id"
@deleted="onCommentDeleted"
/>
</template>
@@ -405,6 +406,7 @@ export default {
avatar: c.author.avatar,
text: c.content,
reactions: c.reactions || [],
pinned: !!c.pinnedAt,
reply: (c.replies || []).map((r) => mapComment(r, c.author.username, level + 1)),
openReplies: level === 0,
src: c.author.avatar,