init: 初始化 VitePress 官网项目
This commit is contained in:
224
docs/.vitepress/theme/pages/KnowledgeDetail.vue
Normal file
224
docs/.vitepress/theme/pages/KnowledgeDetail.vue
Normal file
@@ -0,0 +1,224 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user