feat: 优化首页个人名片与内容入口
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { siteConfig } from '~/config/site'
|
||||
import { decodeRouteParam, formatPostDate, getReadTime, isVisiblePost, normalizeSeriesName, sortPostsForSeries } from '~/utils/posts'
|
||||
|
||||
const route = useRoute()
|
||||
const rawName = Array.isArray(route.params.name) ? route.params.name[0] : route.params.name
|
||||
const seriesName = decodeRouteParam(String(rawName || ''))
|
||||
|
||||
const { data: posts } = await useAsyncData(`series-${seriesName}`, () =>
|
||||
queryCollection('posts')
|
||||
.order('published', 'ASC')
|
||||
.all()
|
||||
)
|
||||
|
||||
const seriesPosts = computed(() =>
|
||||
sortPostsForSeries(
|
||||
(posts.value || [])
|
||||
.filter(isVisiblePost)
|
||||
.filter(post => normalizeSeriesName(post.series) === seriesName)
|
||||
)
|
||||
)
|
||||
|
||||
if (!seriesPosts.value.length) {
|
||||
throw createError({ statusCode: 404, message: '系列不存在' })
|
||||
}
|
||||
|
||||
useSeoMeta({
|
||||
title: `${seriesName} - ${siteConfig.siteName}`,
|
||||
description: `${siteConfig.siteName} 的「${seriesName}」系列文章`,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container mx-auto max-w-4xl px-4 py-12">
|
||||
<header class="mb-12 anim-fade-in-up">
|
||||
<NuxtLink to="/series" class="mb-5 inline-flex items-center gap-1 text-sm hover:underline" style="color: var(--color-primary)">
|
||||
<Icon icon="mdi:arrow-left" class="h-4 w-4" />
|
||||
返回系列
|
||||
</NuxtLink>
|
||||
|
||||
<h1 class="mb-4 text-4xl font-bold">{{ seriesName }}</h1>
|
||||
<p class="text-muted-foreground">
|
||||
共 {{ seriesPosts.length }} 篇文章,按阅读顺序排列。
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div class="space-y-4 anim-fade-in-up anim-delay-1">
|
||||
<NuxtLink
|
||||
v-for="(post, index) in seriesPosts"
|
||||
:key="post.path || post.id"
|
||||
:to="post.path || '/posts'"
|
||||
class="group grid gap-4 rounded-xl border p-5 transition-all hover:-translate-y-0.5 hover:shadow-lg md:grid-cols-[3.5rem_1fr]"
|
||||
style="border-color: var(--color-border); background-color: var(--color-bg-card);"
|
||||
>
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-full text-lg font-bold" style="background-color: var(--color-bg-hover); color: var(--color-primary);">
|
||||
{{ post.seriesOrder || index + 1 }}
|
||||
</div>
|
||||
|
||||
<div class="min-w-0">
|
||||
<div class="mb-2 flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
|
||||
<time v-if="post.published">{{ formatPostDate(post.published) }}</time>
|
||||
<span>· 约 {{ getReadTime(post) }} 分钟</span>
|
||||
<span v-if="post.difficulty">· {{ post.difficulty }}</span>
|
||||
</div>
|
||||
|
||||
<h2 class="text-xl font-semibold transition-opacity group-hover:opacity-70">
|
||||
{{ post.title }}
|
||||
</h2>
|
||||
<p v-if="post.description" class="mt-2 text-sm text-muted-foreground">
|
||||
{{ post.description }}
|
||||
</p>
|
||||
|
||||
<div v-if="post.tags?.length" class="mt-3 flex flex-wrap gap-1.5">
|
||||
<span v-for="tag in post.tags" :key="tag" class="tag-pill">
|
||||
{{ tag }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,65 @@
|
||||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { siteConfig } from '~/config/site'
|
||||
import { formatPostDate, getSeriesSummaries, isVisiblePost } from '~/utils/posts'
|
||||
|
||||
useSeoMeta({
|
||||
title: `文章系列 - ${siteConfig.siteName}`,
|
||||
description: `${siteConfig.siteName} 的系列文章合集`,
|
||||
})
|
||||
|
||||
const { data: posts } = await useAsyncData('series-list-posts', () =>
|
||||
queryCollection('posts')
|
||||
.order('published', 'DESC')
|
||||
.all()
|
||||
)
|
||||
|
||||
const seriesList = computed(() => getSeriesSummaries((posts.value || []).filter(isVisiblePost)))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container mx-auto max-w-5xl px-4 py-12">
|
||||
<header class="mb-12 text-center anim-fade-in-up">
|
||||
<h1 class="mb-4 text-4xl font-bold">文章系列</h1>
|
||||
<p class="text-muted-foreground">把同一个主题的文章串起来,读起来更连贯</p>
|
||||
<p class="mt-2 text-sm text-muted-foreground">共 {{ seriesList.length }} 个系列</p>
|
||||
</header>
|
||||
|
||||
<div v-if="seriesList.length" class="grid gap-5 md:grid-cols-2 anim-fade-in-up anim-delay-1">
|
||||
<NuxtLink
|
||||
v-for="series in seriesList"
|
||||
:key="series.name"
|
||||
:to="series.path"
|
||||
class="group rounded-xl border p-5 transition-all hover:-translate-y-0.5 hover:shadow-lg"
|
||||
style="border-color: var(--color-border); background-color: var(--color-bg-card);"
|
||||
>
|
||||
<div class="mb-4 flex items-start justify-between gap-4">
|
||||
<div class="min-w-0">
|
||||
<h2 class="truncate text-xl font-semibold transition-opacity group-hover:opacity-70">
|
||||
{{ series.name }}
|
||||
</h2>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
{{ series.count }} 篇文章 · 最近更新 {{ formatPostDate(series.latestPost.updated || series.latestPost.published) }}
|
||||
</p>
|
||||
</div>
|
||||
<Icon icon="mdi:chevron-right" class="mt-1 h-5 w-5 shrink-0 text-muted-foreground transition-transform group-hover:translate-x-1" />
|
||||
</div>
|
||||
|
||||
<p v-if="series.latestPost.description" class="mb-4 line-clamp-2 text-sm text-muted-foreground">
|
||||
{{ series.latestPost.description }}
|
||||
</p>
|
||||
|
||||
<div v-if="series.tags.length" class="flex flex-wrap gap-1.5">
|
||||
<span v-for="tag in series.tags" :key="tag" class="tag-pill">
|
||||
{{ tag }}
|
||||
</span>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div v-else class="rounded-xl border p-8 text-center text-muted-foreground anim-fade-in-up anim-delay-1" style="border-color: var(--color-border); background-color: var(--color-bg-card);">
|
||||
<Icon icon="mdi:playlist-remove" class="mx-auto mb-3 h-8 w-8" />
|
||||
还没有系列。给文章 frontmatter 添加 series 字段后会自动出现。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user