feat: add post pinning

This commit is contained in:
Tim
2025-07-22 18:11:42 +08:00
parent 929b9baa0c
commit ee118c0bf4
6 changed files with 133 additions and 62 deletions

View File

@@ -58,6 +58,7 @@
<div class="article-item" v-for="article in articles" :key="article.id">
<div class="article-main-container">
<router-link class="article-item-title main-item" :to="`/posts/${article.id}`">
<i v-if="article.pinned" class="fas fa-thumbtack pinned-icon"></i>
{{ article.title }}
</router-link>
<div class="article-item-description main-item">{{ sanitizeDescription(article.description) }}</div>
@@ -236,7 +237,8 @@ export default {
members: (p.participants || []).map(m => ({ id: m.id, avatar: m.avatar })),
comments: (p.comments || []).length,
views: p.views,
time: TimeManager.format(p.createdAt)
time: TimeManager.format(p.createdAt),
pinned: !!p.pinnedAt
}))
)
if (data.length < pageSize) {
@@ -272,7 +274,8 @@ export default {
members: (p.participants || []).map(m => ({ id: m.id, avatar: m.avatar })),
comments: (p.comments || []).length,
views: p.views,
time: TimeManager.format(p.createdAt)
time: TimeManager.format(p.createdAt),
pinned: !!p.pinnedAt
}))
)
if (data.length < pageSize) {
@@ -502,6 +505,11 @@ export default {
text-decoration: underline;
}
.pinned-icon {
margin-right: 4px;
color: var(--primary-color);
}
.article-item-description {
margin-top: 10px;
font-size: 14px;

View File

@@ -138,6 +138,7 @@ export default {
const postReactions = ref([])
const comments = ref([])
const status = ref('PUBLISHED')
const pinnedAt = ref(null)
const isWaitingFetchingPost = ref(false);
const isWaitingPostingComment = ref(false);
const postTime = ref('')
@@ -157,6 +158,13 @@ export default {
if (isAuthor.value || isAdmin.value) {
items.push({ text: '删除文章', color: 'red', onClick: () => deletePost() })
}
if (isAdmin.value) {
if (pinnedAt.value) {
items.push({ text: '取消置顶', onClick: () => unpinPost() })
} else {
items.push({ text: '置顶', onClick: () => pinPost() })
}
}
if (isAdmin.value && status.value === 'PENDING') {
items.push({ text: '通过审核', onClick: () => approvePost() })
items.push({ text: '驳回', color: 'red', onClick: () => rejectPost() })
@@ -276,6 +284,7 @@ export default {
comments.value = (data.comments || []).map(mapComment)
subscribed.value = !!data.subscribed
status.value = data.status
pinnedAt.value = data.pinnedAt
postTime.value = TimeManager.format(data.createdAt)
await nextTick()
gatherPostItems()
@@ -394,6 +403,36 @@ export default {
}
}
const pinPost = async () => {
const token = getToken()
if (!token) return
const res = await fetch(`${API_BASE_URL}/api/admin/posts/${postId}/pin`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` }
})
if (res.ok) {
pinnedAt.value = new Date().toISOString()
toast.success('已置顶')
} else {
toast.error('操作失败')
}
}
const unpinPost = async () => {
const token = getToken()
if (!token) return
const res = await fetch(`${API_BASE_URL}/api/admin/posts/${postId}/unpin`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` }
})
if (res.ok) {
pinnedAt.value = null
toast.success('已取消置顶')
} else {
toast.error('操作失败')
}
}
const deletePost = async () => {
const token = getToken()
if (!token) {
@@ -508,12 +547,15 @@ export default {
approvePost,
onCommentDeleted,
deletePost,
pinPost,
unpinPost,
rejectPost,
lightboxVisible,
lightboxIndex,
lightboxImgs,
handleContentClick,
isMobile
isMobile,
pinnedAt
}
}
}