225 lines
7.3 KiB
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>
|