Browse Source

feat(component): add star button to article detail page with login prompt

main
Mohan 1 month ago
parent
commit
39852a0f8a
  1. 71
      src/views/ArticleDetailView.vue

71
src/views/ArticleDetailView.vue

@ -1,6 +1,6 @@
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import { articleApi } from '@/api/client'
import { renderMarkdown } from '@/utils/markdown'
import { setPageMeta } from '@/utils/seo'
@ -10,6 +10,7 @@ import ArticleMetaBar from '@/components/ArticleMetaBar.vue'
import type { ArticleDetail, ResponseArticleDetail } from '@/generated/api'
const route = useRoute()
const router = useRouter()
const slug = computed(() => String(route.params.slug ?? ''))
const { data: resp, loading, error, execute } = useApi<ResponseArticleDetail>(
@ -18,11 +19,39 @@ const { data: resp, loading, error, execute } = useApi<ResponseArticleDetail>(
const article = ref<ArticleDetail | null>(null)
const starred = ref(false)
const starCount = ref(0)
const starBusy = ref(false)
const renderedContent = computed(() => {
if (!article.value?.content) return ''
return renderMarkdown(article.value.content)
})
async function toggleStar() {
if (!localStorage.getItem('token')) {
router.push('/login')
return
}
if (!article.value || starBusy.value) return
starBusy.value = true
const was = starred.value
starred.value = !was
starCount.value += was ? -1 : 1
try {
if (was) {
await articleApi.apiArticlesSlugStarDelete(article.value.slug)
} else {
await articleApi.apiArticlesSlugStarPost(article.value.slug)
}
} catch {
starred.value = was
starCount.value += was ? 1 : -1
} finally {
starBusy.value = false
}
}
function applyMeta(data: ArticleDetail) {
setPageMeta({
title: data.title,
@ -38,6 +67,8 @@ async function load() {
article.value = resp.value?.data ?? null
if (article.value) {
applyMeta(article.value)
starred.value = article.value.hasStarred ?? false
starCount.value = article.value.starCount
}
}
@ -59,6 +90,12 @@ watch(slug, load, { immediate: true })
:view-count="article.viewCount"
:content="article.content"
/>
<button class="star-btn" :class="{ starred }" @click="toggleStar">
<svg class="star-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" fill="currentColor"/>
</svg>
<span class="star-count">{{ starCount }}</span>
</button>
</div>
<div class="detail-tags">
<span v-for="tag in article.tagNames" :key="tag" class="tag">{{ tag }}</span>
@ -124,4 +161,36 @@ watch(slug, load, { immediate: true })
.error-text {
color: var(--ac-danger);
}
.star-btn {
display: inline-flex;
align-items: center;
gap: 4px;
border: 1px solid var(--ac-border);
border-radius: var(--song-radius-m);
background: var(--ac-bg-card);
color: var(--ac-text-disabled);
padding: 2px 10px;
cursor: pointer;
font-size: 13px;
transition: color var(--song-dur-1) var(--song-ease), border-color var(--song-dur-1) var(--song-ease);
}
.star-btn:hover {
border-color: var(--ac-primary);
color: var(--ac-primary-ink);
}
.star-btn.starred {
color: var(--ac-primary);
border-color: var(--ac-primary);
}
.star-icon {
flex-shrink: 0;
}
.star-count {
font-variant-numeric: tabular-nums;
}
</style>
Loading…
Cancel
Save