Browse Source

feat(page): add dynamic OpenGraph meta and useApi states

main
Mohan 1 month ago
parent
commit
7ec03fd67a
  1. 22
      src/composables/useApi.ts
  2. 46
      src/views/ArticleDetailView.vue

22
src/composables/useApi.ts

@ -0,0 +1,22 @@
import { ref } from 'vue'
export function useApi<T>(apiCall: () => Promise<T>) {
const data = ref<T | null>(null)
const loading = ref(false)
const error = ref<string | null>(null)
async function execute() {
loading.value = true
error.value = null
try {
const response = await apiCall()
data.value = response
} catch (err) {
error.value = err instanceof Error ? err.message : '请求失败'
} finally {
loading.value = false
}
}
return { data, loading, error, execute }
}

46
src/views/ArticleDetailView.vue

@ -3,16 +3,20 @@ import { ref, computed, watch } from 'vue'
import { useRoute } from 'vue-router'
import { articleApi } from '@/api/client'
import { renderMarkdown } from '@/utils/markdown'
import { setPageMeta } from '@/utils/seo'
import { useApi } from '@/composables/useApi'
import CommentSection from '@/components/CommentSection.vue'
import type { ArticleDetail } from '@/generated/api'
import type { ArticleDetail, ResponseArticleDetail } from '@/generated/api'
const route = useRoute()
const article = ref<ArticleDetail | null>(null)
const loading = ref(false)
const error = ref<string | null>(null)
const slug = computed(() => String(route.params.slug ?? ''))
const { data: resp, loading, error, execute } = useApi<ResponseArticleDetail>(
() => articleApi.apiArticlesSlugGet(slug.value).then((r) => r.data)
)
const article = ref<ArticleDetail | null>(null)
const renderedContent = computed(() => {
if (!article.value?.content) return ''
return renderMarkdown(article.value.content)
@ -25,17 +29,21 @@ function formatDate(iso?: string): string {
return d.toLocaleString('zh-CN', { hour12: false })
}
function applyMeta(data: ArticleDetail) {
setPageMeta({
title: data.title,
description: data.summary || data.content.slice(0, 200),
image: data.coverImage || undefined,
url: `http://localhost/post/${data.slug}`,
type: 'article'
})
}
async function load() {
loading.value = true
error.value = null
try {
const resp = await articleApi.apiArticlesSlugGet(slug.value)
article.value = resp.data.data ?? null
} catch (e) {
console.error(e)
error.value = '文章不存在或加载失败'
} finally {
loading.value = false
await execute()
article.value = resp.value?.data ?? null
if (article.value) {
applyMeta(article.value)
}
}
@ -43,7 +51,10 @@ watch(slug, load, { immediate: true })
</script>
<template>
<article v-if="article" class="detail">
<div v-if="loading" class="hint-text">加载中...</div>
<div v-else-if="error" class="error-text">{{ error }}</div>
<div v-else-if="!article" class="hint-text">文章不存在</div>
<article v-else class="detail">
<h1 class="detail-title">{{ article.title }}</h1>
<p v-if="article.summary" class="detail-summary">{{ article.summary }}</p>
<div class="detail-meta">
@ -57,9 +68,6 @@ watch(slug, load, { immediate: true })
<div class="article-content" v-html="renderedContent"></div>
<CommentSection :slug="slug" />
</article>
<p v-else-if="loading" class="hint-text">加载中...</p>
<p v-else class="error-text">{{ error || '文章不存在' }}</p>
</template>
<style scoped>

Loading…
Cancel
Save