Files
gongxue-new/docs/.vitepress/theme/pages/KnowledgeDetail.vue

225 lines
7.3 KiB
Vue

<script setup lang="ts">
import { computed } from 'vue'
import { useData } from 'vitepress'
import { ArrowLeft, Clock, Eye, Tag, Share2, BookOpen } from 'lucide-vue-next'
import { categoryLabel, formatDate, formatViewCount } from '../../utils/knowledge'
import articleIndex from '../../../knowledge/index.json'
const { frontmatter, page } = useData()
const currentFileName = computed(() => {
return page.value.relativePath?.split('/').pop()?.replace('.md', '') || ''
})
const article = computed(() => ({
id: frontmatter.value.id || currentFileName.value,
title: frontmatter.value.title || '',
summary: frontmatter.value.summary || '',
content: '',
category: frontmatter.value.category || '',
tags: frontmatter.value.tags || [],
coverImage: frontmatter.value.coverImage || '',
author: frontmatter.value.author || '',
isTop: frontmatter.value.isTop || false,
publishedAt: frontmatter.value.publishedAt || '',
updatedAt: '',
viewCount: frontmatter.value.viewCount || 0,
}))
const relatedArticles = computed(() => {
return (articleIndex as any[])
.filter(item =>
item.id !== currentFileName.value &&
item.category === article.value.category
)
.slice(0, 3)
.map(item => ({
path: `/knowledge/${item.id}`,
frontmatter: {
title: item.title,
category: item.category,
coverImage: item.coverImage || '',
viewCount: item.viewCount || 0,
}
}))
})
const handleShare = async () => {
const url = window.location.href
if (navigator.share) {
try {
await navigator.share({ title: article.value.title, url })
} catch {}
} else {
await navigator.clipboard.writeText(url).catch(() => {})
alert('链接已复制到剪贴板')
}
}
</script>
<template>
<div class="pt-24 min-h-screen bg-apple-grey animate-fade-in">
<div class="max-w-4xl mx-auto px-4 md:px-6 pb-20">
<a
href="/knowledge/"
class="inline-flex items-center gap-2 text-gray-500 hover:text-apple-blue mb-8 transition-colors"
>
<ArrowLeft :size="18" /> 返回知识库
</a>
<article class="bg-white rounded-3xl shadow-sm overflow-hidden">
<div
v-if="article.coverImage"
class="w-full h-48 md:h-72 overflow-hidden"
>
<img
:src="article.coverImage"
:alt="article.title"
class="w-full h-full object-cover"
/>
</div>
<div class="p-6 md:p-10 border-b border-gray-100">
<div class="flex flex-wrap items-center gap-2 mb-4">
<span class="bg-apple-blue/10 text-apple-blue text-xs font-medium px-3 py-1 rounded-full">
{{ categoryLabel[article.category] || article.category }}
</span>
<span
v-if="article.isTop"
class="bg-orange-100 text-orange-600 text-xs font-medium px-3 py-1 rounded-full"
>
置顶
</span>
<span
v-for="(tag, i) in article.tags.slice(0, 3)"
:key="i"
class="bg-gray-100 text-gray-600 text-xs px-2 py-0.5 rounded-full"
>
#{{ tag }}
</span>
</div>
<h1 class="text-2xl md:text-3xl font-bold text-gray-900 mb-6 leading-tight">
{{ article.title }}
</h1>
<div class="flex flex-wrap items-center gap-4 text-sm text-gray-500">
<span v-if="article.author" class="flex items-center gap-1">
<span class="w-6 h-6 bg-apple-blue/10 rounded-full flex items-center justify-center text-xs text-apple-blue font-medium">
{{ article.author.charAt(0) }}
</span>
{{ article.author }}
</span>
<span class="flex items-center gap-1">
<Clock :size="14" /> {{ formatDate(article.publishedAt) }}
</span>
<span class="flex items-center gap-1">
<Eye :size="14" /> {{ formatViewCount(article.viewCount) }} 阅读
</span>
<span class="flex items-center gap-1">
<Tag :size="14" /> {{ categoryLabel[article.category] || article.category }}
</span>
</div>
</div>
<div v-if="article.summary" class="px-6 md:px-10 py-4 bg-blue-50/50 border-b border-blue-100">
<p class="text-sm text-blue-800 leading-relaxed italic">
{{ article.summary }}
</p>
</div>
<div class="p-6 md:p-10 knowledge-prose">
<slot />
</div>
<div class="p-6 md:p-10 bg-gray-50 border-t border-gray-100">
<div class="flex flex-col md:flex-row items-center justify-between gap-4">
<div class="flex items-center gap-2 text-gray-500">
<BookOpen :size="18" />
<span>知识库</span>
</div>
<button
@click="handleShare"
class="flex items-center gap-2 px-6 py-2 bg-white border border-gray-200 rounded-full text-gray-600 hover:bg-gray-50 transition-colors"
>
<Share2 :size="16" /> 分享文章
</button>
</div>
</div>
</article>
<div v-if="relatedArticles.length > 0" class="mt-10">
<h2 class="text-xl font-bold text-apple-dark mb-6">相关文章</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<a
v-for="rel in relatedArticles"
:key="rel.path"
:href="rel.path"
class="bg-white p-5 rounded-2xl shadow-sm hover:shadow-md transition-all group"
>
<div
v-if="rel.frontmatter?.coverImage"
class="w-full h-28 rounded-lg overflow-hidden mb-3"
>
<img
:src="rel.frontmatter.coverImage"
:alt="rel.frontmatter.title"
class="w-full h-full object-cover"
loading="lazy"
/>
</div>
<h3 class="font-semibold text-gray-900 group-hover:text-apple-blue transition-colors line-clamp-2 text-sm">
{{ rel.frontmatter.title }}
</h3>
<div class="flex items-center gap-3 mt-2 text-xs text-gray-400">
<span>{{ categoryLabel[rel.frontmatter.category] || rel.frontmatter.category }}</span>
<span>{{ formatViewCount(rel.frontmatter.viewCount || 0) }} 阅读</span>
</div>
</a>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.knowledge-prose :deep(h1) {
@apply text-3xl font-bold text-gray-900 mb-6;
}
.knowledge-prose :deep(h2) {
@apply text-xl font-bold text-gray-900 mb-4 mt-8 border-b border-gray-100 pb-2;
}
.knowledge-prose :deep(h3) {
@apply text-lg font-semibold text-gray-900 mb-3 mt-6;
}
.knowledge-prose :deep(p) {
@apply text-gray-600 leading-relaxed mb-4;
}
.knowledge-prose :deep(ul),
.knowledge-prose :deep(ol) {
@apply mb-4 pl-6;
}
.knowledge-prose :deep(li) {
@apply text-gray-600 mb-2;
}
.knowledge-prose :deep(a) {
@apply text-apple-blue hover:underline;
}
.knowledge-prose :deep(img) {
@apply rounded-xl shadow-sm my-6;
}
.knowledge-prose :deep(blockquote) {
@apply border-l-4 border-apple-blue pl-4 italic text-gray-500 my-6;
}
.knowledge-prose :deep(table) {
@apply text-sm w-full;
}
.knowledge-prose :deep(th) {
@apply bg-gray-50 p-3 text-left;
}
.knowledge-prose :deep(td) {
@apply p-3 border-t;
}
</style>