956 字
5 分钟Add commentMore actions
Fuwari添加系列栏

示例#

---
title: Fuwari添加系列栏
published: 2025-06-16
description: "静态博客养成计划"
image: ""
tags: ["Fuwari", "教程"]
category: Guides
draft: false
pinned: true
comments: false
series: "博客改造计划"
---

添加系列功能#

添加 series 字段#

src\types\config.ts
export type BlogPostData = {
12 collapsed lines
body: string;
title: string;
published: Date;
description: string;
tags: string[];
draft?: boolean;
image?: string;
category?: string;
prevTitle?: string;
prevSlug?: string;
nextTitle?: string;
nextSlug?: string;
comments?: boolean;
pinned?: boolean;
series?:string;
};
src\content\config.ts
const postsCollection = defineCollection({
9 collapsed lines
schema: z.object({
title: z.string(),
published: z.date(),
updated: z.date().optional(),
draft: z.boolean().optional().default(false),
description: z.string().optional().default(""),
image: z.string().optional().default(""),
tags: z.array(z.string()).optional().default([]),
category: z.string().optional().default(""),
lang: z.string().optional().default(""),
comments: z.boolean().optional().default(false),
pinned: z.boolean().optional().default(false),
series: z.string().optional(),
10 collapsed lines
/* For internal use */
prevTitle: z.string().default(""),
prevSlug: z.string().default(""),
nextTitle: z.string().default(""),
nextSlug: z.string().default(""),
}),
});
export const collections = {
posts: postsCollection,
};
src\layouts\MainGridLayout.astro
interface Props {
title?: string;
banner?: string;
description?: string;
lang?: string;
setOGTypeArticle?: boolean;
headings?: MarkdownHeading[];
series?: string;
}
const {
title,
banner,
description,
lang,
setOGTypeArticle,
headings = [],
series,
} = Astro.props;

国际化i18n翻译#

修改 📁i18n 里的文件,添加 series Key 并修改语言文件中的系列翻译以支持国际化

src\i18n\i18nKey.ts
enum I18nKey {
30 collapsed lines
home = "home",
about = "about",
archive = "archive",
search = "search",
tags = "tags",
categories = "categories",
recentPosts = "recentPosts",
comments = "comments",
untitled = "untitled",
uncategorized = "uncategorized",
noTags = "noTags",
wordCount = "wordCount",
wordsCount = "wordsCount",
minuteCount = "minuteCount",
minutesCount = "minutesCount",
postCount = "postCount",
postsCount = "postsCount",
themeColor = "themeColor",
lightMode = "lightMode",
darkMode = "darkMode",
systemMode = "systemMode",
more = "more",
author = "author",
publishedAt = "publishedAt",
license = "license",
friends = "friends",
series = "series",
}

src\i18n\languages\zh_CN.ts 为例,修改成对应语言即可

export const zh_CN: Translation = {
32 collapsed lines
[Key.home]: "主页",
[Key.about]: "关于",
[Key.archive]: "归档",
[Key.search]: "搜索",
[Key.tags]: "标签",
[Key.categories]: "分类",
[Key.recentPosts]: "最新文章",
[Key.comments]: "评论",
[Key.untitled]: "无标题",
[Key.uncategorized]: "未分类",
[Key.noTags]: "无标签",
[Key.wordCount]: "字",
[Key.wordsCount]: "字",
[Key.minuteCount]: "分钟",
[Key.minutesCount]: "分钟",
[Key.postCount]: "篇文章",
[Key.postsCount]: "篇文章",
[Key.themeColor]: "主题色",
[Key.lightMode]: "亮色",
[Key.darkMode]: "暗色",
[Key.systemMode]: "跟随系统",
[Key.more]: "更多",
[Key.author]: "作者",
[Key.publishedAt]: "发布于",
[Key.license]: "许可协议",
[Key.friends]: "友链",
[Key.series]: "系列",
};

添加 series 组件#

修改 MainGridLayout 组件#

修改 MainGridLayout 组件,接收 series 并将其传入 SideBar 组件

src\layouts\MainGridLayout.astro
<SideBar class="mb-4 row-start-2 row-end-3 col-span-2 lg:row-start-1 lg:row-end-2 lg:col-span-1 lg:max-w-[17.5rem] onload-animation" headings={headings} series={ series }></SideBar>

修改 […slug] 页面#

series 传入[...slug].astroMainGridLayout 组件:

src\pages\posts[...slug].astro
<MainGridLayout banner={entry.data.image} title={entry.data.title}
description={entry.data.description} lang={entry.data.lang}
setOGTypeArticle={true} series={entry.data.series}>
</MainGridLayout>

添加 getPostSeries 方法#

content-utils.ts 末尾处添加 getPostSeries 方法:

src\utils\content-utils.ts
export async function getPostSeries(
seriesName: string,
): Promise<{ body: string; data: BlogPostData; slug: string }[]> {
const posts = (await getCollection('posts', ({ data }) => {
return (
(import.meta.env.PROD ? data.draft !== true : true) &&
data.series === seriesName
)
})) as unknown as { body: string; data: BlogPostData; slug: string }[]
posts.sort((a, b) => {
const dateA = new Date(a.data.published)
const dateB = new Date(b.data.published)
return dateA > dateB ? 1 : -1
})
return posts
}

创建 series 组件#

src\components\widget\Series.astro
---
import I18nKey from '../../i18n/i18nKey'
import { i18n } from '../../i18n/translation'
import { getPostSeries } from '../../utils/content-utils'
import { getPostUrlBySlug } from '../../utils/url-utils'
import WidgetLayout from './WidgetLayout.astro'
const COLLAPSED_HEIGHT = '7.5rem'
interface Props {
class?: string
style?: string
series: string
}
const className = Astro.props.class
const style = Astro.props.style
const seriesName = Astro.props.series
const series = await getPostSeries(seriesName)
const isCollapsed = series.length >= 10
---
<WidgetLayout name={i18n(I18nKey.series) + " - " + series[0].data.series} id="series" isCollapsed={isCollapsed} collapsedHeight={COLLAPSED_HEIGHT} class={className} style={style}>
<div class="flex flex-col gap-1">
{series.map(t => (
<a href={getPostUrlBySlug(t.slug)}
aria-label={t.data.title}
class="group btn-plain h-10 w-full rounded-lg hover:text-[initial]"
>
<!-- dot and line -->
<div class="w-[15%] md:w-[10%] relative dash-line h-full flex items-center">
<div class="transition-all mx-auto w-1 h-1 rounded group-hover:h-5
bg-[oklch(0.5_0.05_var(--hue))] group-hover:bg-[var(--primary)]
outline outline-4 z-50
outline-[var(--card-bg)]
group-hover:outline-[var(--btn-plain-bg-hover)]
group-active:outline-[var(--btn-plain-bg-active)]
"
></div>
</div>
<!-- post title -->
<div class="w-[85%] text-left font-bold
group-hover:translate-x-1 transition-all group-hover:text-[var(--primary)]
text-75 pr-15 whitespace-nowrap overflow-ellipsis overflow-hidden" title={t.data.title}
>
{t.data.title}
</div>
</a>
))}
</div>
</WidgetLayout>

导入 series 组件#

在SideBar组件中导入Series组件,接收series并将其传入Series组件

src\components\widget\SideBar.astro
---
import type { MarkdownHeading } from "astro";
import Categories from "./Categories.astro";
import Profile from "./Profile.astro";
import Series from "./Series.astro";
import Tag from "./Tags.astro";
interface Props {
class?: string;
headings?: MarkdownHeading[];
series?: string;
}
const className = Astro.props.class;
const series = Astro.props.series;
---
<div id="sidebar" class:list={[className, "w-full"]}>
<div class="flex flex-col w-full gap-4 mb-4">
<Profile></Profile>
</div>
<div id="sidebar-sticky" class="transition-all duration-700 flex flex-col w-full gap-4 top-4 sticky ">
<div id="series" class="flex flex-col w-full gap-4">
{ series && <Series series={ series }></Series> }
</div>
<Categories class="onload-animation" style="animation-delay: 150ms"></Categories>
<Tag class="onload-animation" style="animation-delay: 200ms"></Tag>
</div>
</div>

在 swup 的 containers 配置项里加上 #series:

astro.config.mjs
swup({
theme: false,
animationClass: "transition-swup-", // see https://swup.js.org/options/#animationselector
// the default value `transition-` cause transition delay
// when the Tailwind class `transition-all` is used
containers: ["main", "#toc", "#series"],
smoothScrolling: true,
cache: true,
preload: true,
accessibility: true,
updateHead: true,
updateBodyClass: false,
globalInstance: true,
}),

使用系列功能#

只需要在md文件frontmatter配置项中添加series,只要系列名相同就会被归类为同一系列

Fuwari添加系列栏
https://p1ume.vercel.app/posts/fuwari/series/
作者
p1ume
发布于
2025-06-16
许可协议
CC BY-NC-SA 4.0