From eb00c133750b5cb539e21ef997b86a0f0d5518c7 Mon Sep 17 00:00:00 2001 From: joyzhao Date: Thu, 19 Jun 2025 13:24:23 +0800 Subject: [PATCH] feat(blog): add dynamic tag and category pages for multilingual support Implement dynamic routing for blog tags and categories in both English and Chinese Add sorting, filtering and sidebar navigation functionality Include responsive design with proper styling and fallbacks --- src/pages/blog/categories/[category].astro | 212 ++++++++++++++++++ src/pages/blog/tags/[tag].astro | 208 +++++++++++++++++ src/pages/zh/blog/categories/[category].astro | 212 ++++++++++++++++++ src/pages/zh/blog/tags/[tag].astro | 208 +++++++++++++++++ 4 files changed, 840 insertions(+) create mode 100644 src/pages/blog/categories/[category].astro create mode 100644 src/pages/blog/tags/[tag].astro create mode 100644 src/pages/zh/blog/categories/[category].astro create mode 100644 src/pages/zh/blog/tags/[tag].astro diff --git a/src/pages/blog/categories/[category].astro b/src/pages/blog/categories/[category].astro new file mode 100644 index 0000000..4ab49c1 --- /dev/null +++ b/src/pages/blog/categories/[category].astro @@ -0,0 +1,212 @@ +--- +import BlogLayout from '../../../layouts/BlogLayout.astro'; +import BlogList from '../../../components/BlogList.tsx'; +import { type Lang } from '@/i18n/utils'; +import { defaultLang } from '@/i18n/ui'; +import { type BlogPost } from '@/types'; + +// 为动态路由生成静态路径 +export async function getStaticPaths() { + const allPosts = await Astro.glob('../posts/*.md'); + + // 收集所有分类 + const uniqueCategories = new Set(); + + allPosts.forEach(post => { + if (post.frontmatter?.category) { + const categories = Array.isArray(post.frontmatter.category) + ? post.frontmatter.category + : [post.frontmatter.category]; + + categories.forEach(category => { + if (category) uniqueCategories.add(category.toLowerCase()); + }); + } + }); + + // 为每个分类生成路径 + return Array.from(uniqueCategories).map(category => ({ + params: { category }, + props: { category } + })); +} + +// 获取当前语言环境 +const lang = Astro.currentLocale as Lang || defaultLang; + +// 获取当前分类(从URL参数) +export interface Props { + category: string; +} + +const { category } = Astro.params; +const decodedCategory = category ? decodeURIComponent(category) : ''; + + +// 使用Astro.glob读取所有博客文章 +const allPosts = await Astro.glob('../posts/*.md'); + +// 处理博客文章数据 +const blogPosts: BlogPost[] = allPosts + .filter(post => { + // 检查文章是否属于当前分类 + if (post.frontmatter?.category) { + const postCategories = Array.isArray(post.frontmatter.category) + ? post.frontmatter.category + : [post.frontmatter.category]; + + return postCategories.some(cat => + cat.toLowerCase() === decodedCategory.toLowerCase() + ); + } + return false; + }) + .map((post) => { + const slug = post.url?.split('/').filter(Boolean).pop() || ''; + + // 获取文章的默认图片,如果frontmatter中没有指定 + const defaultImage = "https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=400&h=250&fit=crop&crop=center"; + + return { + title: post.frontmatter.title, + description: post.frontmatter.description || '', + image: post.frontmatter.image || defaultImage, + slug: slug, + tags: post.frontmatter.tags || [], + date: post.frontmatter.date || post.frontmatter.pubDate || '', + readTime: post.frontmatter.readTime || post.frontmatter.readingTime || '5 min read', + }; + }); + +// 按日期排序 +const sortedBlogPosts = blogPosts + .filter(post => post.date) // 过滤掉没有日期的文章 + .sort((a, b) => { + const dateA = new Date(a.date).getTime(); + const dateB = new Date(b.date).getTime(); + return dateB - dateA; // 降序排列,最新的文章在前 + }); + +// 从所有博客文章中提取分类和标签(用于侧边栏) +const allCategories = new Set(); +const allTags = new Set(); + +// 收集所有文章的分类和标签 +allPosts.forEach(post => { + // 处理分类 + if (post.frontmatter?.category) { + const categories = Array.isArray(post.frontmatter.category) + ? post.frontmatter.category + : [post.frontmatter.category]; + + categories.forEach(cat => { + if (cat) allCategories.add(cat); + }); + } + + // 处理标签 + if (post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) { + post.frontmatter.tags.forEach(tag => { + if (tag) allTags.add(tag); + }); + } +}); + +// 转换为数组并排序 +const categories = Array.from(allCategories).sort(); +const tags = Array.from(allTags).map(tag => `#${tag}`).sort(); + +// 获取当前分类的格式化名称(首字母大写) +const formattedCategory = decodedCategory.charAt(0).toUpperCase() + decodedCategory.slice(1); + +// 动态生成页面标题和描述 +const pageTitle = `${formattedCategory} - Blog | Joy Zhao`; +const pageDescription = `Explore articles about ${formattedCategory}. Dive into my thoughts on ${formattedCategory} and related topics.`; +--- + + +
+ +
+
+

+ Category: {formattedCategory} +

+

+ Explore articles about {formattedCategory}. Found {sortedBlogPosts.length} article{sortedBlogPosts.length !== 1 ? 's' : ''}. +

+
+
+ + +
+
+ +
+ +
+

+ + + + Categories +

+
+ {categories.map((cat) => ( + + {cat} + + ))} +
+
+ + +
+

+ + + + # Tags +

+
+ {tags.map((tag) => ( + + {tag} + + ))} +
+
+
+ + +
+ {sortedBlogPosts.length > 0 ? ( + + ) : ( +
+

No articles found

+

There are no articles in this category yet. Check back later or explore other categories.

+ + + + + Back to all posts + +
+ )} +
+
+
+
+
+ + \ No newline at end of file diff --git a/src/pages/blog/tags/[tag].astro b/src/pages/blog/tags/[tag].astro new file mode 100644 index 0000000..0f3ff70 --- /dev/null +++ b/src/pages/blog/tags/[tag].astro @@ -0,0 +1,208 @@ +--- +import BlogLayout from '../../../layouts/BlogLayout.astro'; +import BlogList from '../../../components/BlogList.tsx'; +import { type Lang } from '@/i18n/utils'; +import { defaultLang } from '@/i18n/ui'; +import { type BlogPost } from '@/types'; + +// 为动态路由生成静态路径 +export async function getStaticPaths() { + const allPosts = await Astro.glob('../posts/*.md'); + + // 收集所有标签 + const uniqueTags = new Set(); + + allPosts.forEach(post => { + if (post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) { + post.frontmatter.tags.forEach(tag => { + if (tag) uniqueTags.add(tag.toLowerCase()); + }); + } + }); + + // 为每个标签生成路径 + return Array.from(uniqueTags).map(tag => ({ + params: { tag }, + props: { tag } + })); +} + +// 获取当前语言环境 +const lang = Astro.currentLocale as Lang || defaultLang; + +// 获取当前标签(从URL参数) +export interface Props { + tag: string; +} + +const { tag } = Astro.params; +const decodedTag = tag ? decodeURIComponent(tag) : ''; + + +// 使用Astro.glob读取所有博客文章 +const allPosts = await Astro.glob('../posts/*.md'); + +// 处理博客文章数据 +const blogPosts: BlogPost[] = allPosts + .filter(post => { + // 检查文章是否包含当前标签 + if (post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) { + return post.frontmatter.tags.some(postTag => + postTag.toLowerCase() === decodedTag.toLowerCase() + ); + } + return false; + }) + .map((post) => { + const slug = post.url?.split('/').filter(Boolean).pop() || ''; + + // 获取文章的默认图片,如果frontmatter中没有指定 + const defaultImage = "https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=400&h=250&fit=crop&crop=center"; + + return { + title: post.frontmatter.title, + description: post.frontmatter.description || '', + image: post.frontmatter.image || defaultImage, + slug: slug, + tags: post.frontmatter.tags || [], + date: post.frontmatter.date || post.frontmatter.pubDate || '', + readTime: post.frontmatter.readTime || post.frontmatter.readingTime || '5 min read', + }; + }); + +// 按日期排序 +const sortedBlogPosts = blogPosts + .filter(post => post.date) // 过滤掉没有日期的文章 + .sort((a, b) => { + const dateA = new Date(a.date).getTime(); + const dateB = new Date(b.date).getTime(); + return dateB - dateA; // 降序排列,最新的文章在前 + }); + +// 从所有博客文章中提取分类和标签(用于侧边栏) +const allCategories = new Set(); +const allTags = new Set(); + +// 收集所有文章的分类和标签 +allPosts.forEach(post => { + // 处理分类 + if (post.frontmatter?.category) { + const categories = Array.isArray(post.frontmatter.category) + ? post.frontmatter.category + : [post.frontmatter.category]; + + categories.forEach(cat => { + if (cat) allCategories.add(cat); + }); + } + + // 处理标签 + if (post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) { + post.frontmatter.tags.forEach(postTag => { + if (postTag) allTags.add(postTag); + }); + } +}); + +// 转换为数组并排序 +const categories = Array.from(allCategories).sort(); +const tags = Array.from(allTags).map(postTag => `#${postTag}`).sort(); + +// 获取当前标签的格式化名称(首字母大写) +const formattedTag = decodedTag.charAt(0).toUpperCase() + decodedTag.slice(1); + +// 动态生成页面标题和描述 +const pageTitle = `#${formattedTag} - Blog | Joy Zhao`; +const pageDescription = `Explore articles tagged with #${formattedTag}. Dive into my thoughts on ${formattedTag} and related topics.`; +--- + + +
+ +
+
+

+ Tag: #{formattedTag} +

+

+ Explore articles tagged with #{formattedTag}. Found {sortedBlogPosts.length} article{sortedBlogPosts.length !== 1 ? 's' : ''}. +

+
+
+ + +
+
+ +
+ +
+

+ + + + Categories +

+
+ {categories.map((cat) => ( + + {cat} + + ))} +
+
+ + +
+

+ + + + # Tags +

+
+ {tags.map((tagItem) => { + const tagName = tagItem.slice(1).toLowerCase(); + const isCurrentTag = tagName === decodedTag.toLowerCase(); + return ( + + {tagItem} + + ); + })} +
+
+
+ + +
+ {sortedBlogPosts.length > 0 ? ( + + ) : ( +
+

No articles found

+

There are no articles with this tag yet. Check back later or explore other tags.

+ + + + + Back to all posts + +
+ )} +
+
+
+
+
+ + \ No newline at end of file diff --git a/src/pages/zh/blog/categories/[category].astro b/src/pages/zh/blog/categories/[category].astro new file mode 100644 index 0000000..4fdc87c --- /dev/null +++ b/src/pages/zh/blog/categories/[category].astro @@ -0,0 +1,212 @@ +--- +import BlogLayout from '../../../../layouts/BlogLayout.astro'; +import BlogList from '../../../../components/BlogList.tsx'; +import { type Lang } from '@/i18n/utils'; +import { defaultLang } from '@/i18n/ui'; +import { type BlogPost } from '@/types'; + +// 为动态路由生成静态路径 +export async function getStaticPaths() { + const allPosts = await Astro.glob('../posts/*.md'); + + // 收集所有分类 + const uniqueCategories = new Set(); + + allPosts.forEach(post => { + if (post.frontmatter?.category) { + const categories = Array.isArray(post.frontmatter.category) + ? post.frontmatter.category + : [post.frontmatter.category]; + + categories.forEach(category => { + if (category) uniqueCategories.add(category.toLowerCase()); + }); + } + }); + + // 为每个分类生成路径 + return Array.from(uniqueCategories).map(category => ({ + params: { category }, + props: { category } + })); +} + +// 获取当前语言环境 +const lang = Astro.currentLocale as Lang || defaultLang; + +// 获取当前分类(从URL参数) +export interface Props { + category: string; +} + +const { category } = Astro.params; +const decodedCategory = category ? decodeURIComponent(category) : ''; + + +// 使用Astro.glob读取所有中文博客文章 +const allPosts = await Astro.glob('../posts/*.md'); + +// 处理博客文章数据 +const blogPosts: BlogPost[] = allPosts + .filter(post => { + // 检查文章是否属于当前分类 + if (post.frontmatter?.category) { + const postCategories = Array.isArray(post.frontmatter.category) + ? post.frontmatter.category + : [post.frontmatter.category]; + + return postCategories.some(cat => + cat.toLowerCase() === decodedCategory.toLowerCase() + ); + } + return false; + }) + .map((post) => { + const slug = post.url?.split('/').filter(Boolean).pop() || ''; + + // 获取文章的默认图片,如果frontmatter中没有指定 + const defaultImage = "https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=400&h=250&fit=crop&crop=center"; + + return { + title: post.frontmatter.title, + description: post.frontmatter.description || '', + image: post.frontmatter.image || defaultImage, + slug: slug, + tags: post.frontmatter.tags || [], + date: post.frontmatter.date || post.frontmatter.pubDate || '', + readTime: post.frontmatter.readTime || post.frontmatter.readingTime || '5分钟阅读', + }; + }); + +// 按日期排序 +const sortedBlogPosts = blogPosts + .filter(post => post.date) // 过滤掉没有日期的文章 + .sort((a, b) => { + const dateA = new Date(a.date).getTime(); + const dateB = new Date(b.date).getTime(); + return dateB - dateA; // 降序排列,最新的文章在前 + }); + +// 从所有博客文章中提取分类和标签(用于侧边栏) +const allCategories = new Set(); +const allTags = new Set(); + +// 收集所有文章的分类和标签 +allPosts.forEach(post => { + // 处理分类 + if (post.frontmatter?.category) { + const categories = Array.isArray(post.frontmatter.category) + ? post.frontmatter.category + : [post.frontmatter.category]; + + categories.forEach(cat => { + if (cat) allCategories.add(cat); + }); + } + + // 处理标签 + if (post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) { + post.frontmatter.tags.forEach(tag => { + if (tag) allTags.add(tag); + }); + } +}); + +// 转换为数组并排序 +const categories = Array.from(allCategories).sort(); +const tags = Array.from(allTags).map(tag => `#${tag}`).sort(); + +// 获取当前分类的格式化名称(首字母大写) +const formattedCategory = decodedCategory.charAt(0).toUpperCase() + decodedCategory.slice(1); + +// 动态生成页面标题和描述 +const pageTitle = `${formattedCategory} - 博客 | 赵桂阳`; +const pageDescription = `探索关于${formattedCategory}的文章。深入了解我对${formattedCategory}和相关主题的思考。`; +--- + + +
+ +
+
+

+ 分类: {formattedCategory} +

+

+ 探索关于{formattedCategory}的文章。找到 {sortedBlogPosts.length} 篇文章。 +

+
+
+ + +
+
+ +
+ +
+

+ + + + 分类 +

+
+ {categories.map((cat) => ( + + {cat} + + ))} +
+
+ + +
+

+ + + + # 标签 +

+
+ {tags.map((tag) => ( + + {tag} + + ))} +
+
+
+ + +
+ {sortedBlogPosts.length > 0 ? ( + + ) : ( +
+

未找到文章

+

该分类下暂时没有文章。请稍后再来查看或浏览其他分类。

+ + + + + 返回所有文章 + +
+ )} +
+
+
+
+
+ + \ No newline at end of file diff --git a/src/pages/zh/blog/tags/[tag].astro b/src/pages/zh/blog/tags/[tag].astro new file mode 100644 index 0000000..e0e108d --- /dev/null +++ b/src/pages/zh/blog/tags/[tag].astro @@ -0,0 +1,208 @@ +--- +import BlogLayout from '../../../../layouts/BlogLayout.astro'; +import BlogList from '../../../../components/BlogList.tsx'; +import { type Lang } from '@/i18n/utils'; +import { defaultLang } from '@/i18n/ui'; +import { type BlogPost } from '@/types'; + +// 为动态路由生成静态路径 +export async function getStaticPaths() { + const allPosts = await Astro.glob('../posts/*.md'); + + // 收集所有标签 + const uniqueTags = new Set(); + + allPosts.forEach(post => { + if (post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) { + post.frontmatter.tags.forEach(tag => { + if (tag) uniqueTags.add(tag.toLowerCase()); + }); + } + }); + + // 为每个标签生成路径 + return Array.from(uniqueTags).map(tag => ({ + params: { tag }, + props: { tag } + })); +} + +// 获取当前语言环境 +const lang = Astro.currentLocale as Lang || defaultLang; + +// 获取当前标签(从URL参数) +export interface Props { + tag: string; +} + +const { tag } = Astro.params; +const decodedTag = tag ? decodeURIComponent(tag) : ''; + + +// 使用Astro.glob读取所有中文博客文章 +const allPosts = await Astro.glob('../posts/*.md'); + +// 处理博客文章数据 +const blogPosts: BlogPost[] = allPosts + .filter(post => { + // 检查文章是否包含当前标签 + if (post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) { + return post.frontmatter.tags.some(postTag => + postTag.toLowerCase() === decodedTag.toLowerCase() + ); + } + return false; + }) + .map((post) => { + const slug = post.url?.split('/').filter(Boolean).pop() || ''; + + // 获取文章的默认图片,如果frontmatter中没有指定 + const defaultImage = "https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=400&h=250&fit=crop&crop=center"; + + return { + title: post.frontmatter.title, + description: post.frontmatter.description || '', + image: post.frontmatter.image || defaultImage, + slug: slug, + tags: post.frontmatter.tags || [], + date: post.frontmatter.date || post.frontmatter.pubDate || '', + readTime: post.frontmatter.readTime || post.frontmatter.readingTime || '5 分钟阅读', + }; + }); + +// 按日期排序 +const sortedBlogPosts = blogPosts + .filter(post => post.date) // 过滤掉没有日期的文章 + .sort((a, b) => { + const dateA = new Date(a.date).getTime(); + const dateB = new Date(b.date).getTime(); + return dateB - dateA; // 降序排列,最新的文章在前 + }); + +// 从所有博客文章中提取分类和标签(用于侧边栏) +const allCategories = new Set(); +const allTags = new Set(); + +// 收集所有文章的分类和标签 +allPosts.forEach(post => { + // 处理分类 + if (post.frontmatter?.category) { + const categories = Array.isArray(post.frontmatter.category) + ? post.frontmatter.category + : [post.frontmatter.category]; + + categories.forEach(cat => { + if (cat) allCategories.add(cat); + }); + } + + // 处理标签 + if (post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) { + post.frontmatter.tags.forEach(postTag => { + if (postTag) allTags.add(postTag); + }); + } +}); + +// 转换为数组并排序 +const categories = Array.from(allCategories).sort(); +const tags = Array.from(allTags).map(postTag => `#${postTag}`).sort(); + +// 获取当前标签的格式化名称(首字母大写) +const formattedTag = decodedTag.charAt(0).toUpperCase() + decodedTag.slice(1); + +// 动态生成页面标题和描述 +const pageTitle = `#${formattedTag} - 博客 | Joy Zhao`; +const pageDescription = `浏览带有 #${formattedTag} 标签的文章。深入了解我关于 ${formattedTag} 及相关主题的想法。`; +--- + + +
+ +
+
+

+ 标签: #{formattedTag} +

+

+ 浏览带有 #{formattedTag} 标签的文章。找到 {sortedBlogPosts.length} 篇文章。 +

+
+
+ + +
+
+ +
+ +
+

+ + + + 分类 +

+
+ {categories.map((cat) => ( + + {cat} + + ))} +
+
+ + +
+

+ + + + # 标签 +

+
+ {tags.map((tagItem) => { + const tagName = tagItem.slice(1).toLowerCase(); + const isCurrentTag = tagName === decodedTag.toLowerCase(); + return ( + + {tagItem} + + ); + })} +
+
+
+ + +
+ {sortedBlogPosts.length > 0 ? ( + + ) : ( +
+

未找到文章

+

目前还没有带有此标签的文章。请稍后再来查看或浏览其他标签。

+ + + + + 返回所有文章 + +
+ )} +
+
+
+
+
+ + \ No newline at end of file