feat(blog): refactor blog components and improve category/tag handling
- Add new CategoryCard and TagCard components to centralize UI logic - Convert BlogList from React to Astro component - Improve category and tag matching by using IDs from frontmatter - Update all blog pages to use new components - Add line-height to global styles for better readability
This commit is contained in:
@@ -1,111 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslations, type Lang } from '@/i18n/utils';
|
||||
import { type BlogPost, type BlogListProps } from '@/types';
|
||||
|
||||
/**
|
||||
* Reusable blog list component that displays blog posts in a grid layout
|
||||
* @param posts - Array of blog posts to display
|
||||
* @param lang - Current language for internationalization
|
||||
* @param baseUrl - Base URL for blog post links
|
||||
*/
|
||||
export default function BlogList({ posts, lang, baseUrl = '/blog/posts/' }: BlogListProps) {
|
||||
const t = useTranslations(lang);
|
||||
|
||||
// Adjust base URL for Chinese language
|
||||
const postBaseUrl = lang === 'zh' ? '/zh/blog/posts/' : baseUrl;
|
||||
|
||||
// Get localized "Read More" text
|
||||
const readMoreText = lang === 'zh' ? '阅读更多' : 'Read More';
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{posts.map((post, index) => (
|
||||
<article key={post.slug || index} className="group">
|
||||
<div className="bg-card/50 backdrop-blur-sm rounded-2xl overflow-hidden border border-border hover:border-purple-500/50 transition-all duration-300 hover:transform hover:scale-[1.02]">
|
||||
<div className="flex flex-col md:flex-row">
|
||||
{/* Featured Image */}
|
||||
<div className="relative overflow-hidden md:w-80 md:flex-shrink-0">
|
||||
<img
|
||||
src={post.image}
|
||||
alt={post.title}
|
||||
className="w-full h-48 md:h-full object-cover group-hover:scale-110 transition-transform duration-300"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-t md:bg-gradient-to-r from-background/50 to-transparent"></div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-6 flex-1 flex flex-col justify-between">
|
||||
<div>
|
||||
<h2 className="text-xl font-bold text-card-foreground mb-3 group-hover:text-purple-500 transition-colors duration-200">
|
||||
<a href={`${postBaseUrl}${post.slug}`}>
|
||||
{post.title}
|
||||
</a>
|
||||
</h2>
|
||||
|
||||
<p className="text-muted-foreground mb-4 line-clamp-3">
|
||||
{post.description}
|
||||
</p>
|
||||
|
||||
{/* Tags */}
|
||||
<div className="flex flex-wrap gap-2 mb-4">
|
||||
{post.tags.map((tag, tagIndex) => {
|
||||
// 使用 tagId 如果存在,否则使用 tag 本身
|
||||
const tagRoute = post.tagId && Array.isArray(post.tagId) && tagIndex < post.tagId.length
|
||||
? post.tagId[tagIndex]
|
||||
: tag.toLowerCase();
|
||||
|
||||
return (
|
||||
<a
|
||||
href={`/${lang === 'en' ? '' : 'zh/'}blog/tags/${encodeURIComponent(tagRoute)}`}
|
||||
key={`${tag}-${tagIndex}`}
|
||||
className="px-2 py-1 text-xs bg-muted text-muted-foreground rounded-full hover:bg-muted/80 transition-colors"
|
||||
>
|
||||
# {tag}
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Meta Info */}
|
||||
<div className="flex items-center justify-between text-sm text-muted-foreground">
|
||||
<div className="flex items-center space-x-4">
|
||||
<span className="flex items-center">
|
||||
<svg className="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 002 2v12a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
{post.date}
|
||||
</span>
|
||||
<span>{post.readTime}</span>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href={`${postBaseUrl}${post.slug}`}
|
||||
className="text-purple-500 hover:text-purple-400 font-medium flex items-center group"
|
||||
>
|
||||
{readMoreText}
|
||||
<svg className="w-4 h-4 ml-1 group-hover:translate-x-1 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M17 8l4 4m0 0l-4 4m4-4H3"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* CSS styles for line clamping (to be included in global styles)
|
||||
*/
|
||||
export const blogListStyles = `
|
||||
.line-clamp-3 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
`;
|
||||
192
src/components/blog/BlogList.astro
Normal file
192
src/components/blog/BlogList.astro
Normal file
@@ -0,0 +1,192 @@
|
||||
---
|
||||
import { type BlogPost, type Lang } from '@/types';
|
||||
|
||||
interface Props {
|
||||
posts?: BlogPost[];
|
||||
lang: Lang;
|
||||
baseUrl?: string;
|
||||
category?: string;
|
||||
tag?: string;
|
||||
}
|
||||
|
||||
// 获取组件属性
|
||||
const { lang, baseUrl, category, tag, posts: providedPosts } = Astro.props;
|
||||
|
||||
// 如果没有提供 posts,则直接从文件系统读取
|
||||
let posts: BlogPost[] = [];
|
||||
|
||||
if (providedPosts) {
|
||||
// 使用提供的文章列表
|
||||
posts = providedPosts;
|
||||
} else {
|
||||
// 根据当前语言读取博客文章
|
||||
let allPosts = [];
|
||||
if (lang === 'zh') {
|
||||
// 读取中文博客文章
|
||||
allPosts = await Astro.glob('../zh/blog/posts/*.md');
|
||||
} else {
|
||||
// 读取英文博客文章
|
||||
allPosts = await Astro.glob('../blog/posts/*.md');
|
||||
}
|
||||
|
||||
// 处理博客文章数据
|
||||
posts = allPosts.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 || [],
|
||||
tagId: post.frontmatter.tagId || [],
|
||||
category: Array.isArray(post.frontmatter.category) ? post.frontmatter.category : post.frontmatter.category ? [post.frontmatter.category] : [],
|
||||
categoryId: Array.isArray(post.frontmatter.categoryId) ? post.frontmatter.categoryId : post.frontmatter.categoryId ? [post.frontmatter.categoryId] : [],
|
||||
date: post.frontmatter.date || post.frontmatter.pubDate || '',
|
||||
readTime: post.frontmatter.readTime || post.frontmatter.readingTime || '5 min read',
|
||||
};
|
||||
});
|
||||
|
||||
// 按日期排序
|
||||
posts = posts
|
||||
.filter(post => post.date) // 过滤掉没有日期的文章
|
||||
.sort((a, b) => {
|
||||
const dateA = new Date(a.date).getTime();
|
||||
const dateB = new Date(b.date).getTime();
|
||||
return dateB - dateA; // 降序排列,最新的文章在前
|
||||
});
|
||||
}
|
||||
|
||||
// 如果指定了分类,则过滤文章
|
||||
if (category) {
|
||||
posts = posts.filter(post => {
|
||||
// 优先检查文章是否包含当前分类ID
|
||||
if (post.categoryId && Array.isArray(post.categoryId)) {
|
||||
return post.categoryId.some(catId =>
|
||||
catId.toLowerCase() === category.toLowerCase()
|
||||
);
|
||||
}
|
||||
// 如果没有 categoryId,则检查 category
|
||||
else if (post.category && Array.isArray(post.category)) {
|
||||
return post.category.some(cat =>
|
||||
cat.toLowerCase() === category.toLowerCase()
|
||||
);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// 如果指定了标签,则过滤文章
|
||||
if (tag) {
|
||||
posts = posts.filter(post => {
|
||||
// 优先检查文章是否包含当前标签ID
|
||||
if (post.tagId && Array.isArray(post.tagId)) {
|
||||
return post.tagId.some(tagId =>
|
||||
tagId.toLowerCase() === tag.toLowerCase()
|
||||
);
|
||||
}
|
||||
// 如果没有 tagId,则检查 tags
|
||||
else if (post.tags && Array.isArray(post.tags)) {
|
||||
return post.tags.some(postTag =>
|
||||
postTag.toLowerCase() === tag.toLowerCase()
|
||||
);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// 调整基础URL
|
||||
const postBaseUrl = lang === 'zh' ? '/zh/blog/posts/' : '/blog/posts/';
|
||||
|
||||
// 获取本地化的"阅读更多"文本
|
||||
const readMoreText = lang === 'zh' ? '阅读更多' : 'Read More';
|
||||
---
|
||||
|
||||
<div class="space-y-6">
|
||||
{posts.map((post, index) => (
|
||||
<article class="group">
|
||||
<div class="bg-card/50 backdrop-blur-sm rounded-2xl overflow-hidden border border-border hover:border-purple-500/50 transition-all duration-300 hover:transform hover:scale-[1.02]">
|
||||
<div class="flex flex-col md:flex-row">
|
||||
{/* Featured Image */}
|
||||
<div class="relative overflow-hidden md:w-80 md:flex-shrink-0">
|
||||
<img
|
||||
src={post.image}
|
||||
alt={post.title}
|
||||
class="w-full h-48 md:h-full object-cover group-hover:scale-110 transition-transform duration-300"
|
||||
/>
|
||||
<div class="absolute inset-0 bg-gradient-to-t md:bg-gradient-to-r from-background/50 to-transparent"></div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div class="p-6 flex-1 flex flex-col justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-card-foreground mb-3 group-hover:text-purple-500 transition-colors duration-200">
|
||||
<a href={`${postBaseUrl}${post.slug}`}>
|
||||
{post.title}
|
||||
</a>
|
||||
</h2>
|
||||
|
||||
<p class="text-muted-foreground mb-4 line-clamp-3">
|
||||
{post.description}
|
||||
</p>
|
||||
|
||||
{/* Tags */}
|
||||
<div class="flex flex-wrap gap-2 mb-4">
|
||||
{post.tags.map((tag, tagIndex) => {
|
||||
// 使用 tagId 如果存在,否则使用 tag 本身
|
||||
const tagRoute = post.tagId && Array.isArray(post.tagId) && tagIndex < post.tagId.length
|
||||
? post.tagId[tagIndex]
|
||||
: tag.toLowerCase();
|
||||
|
||||
return (
|
||||
<a
|
||||
href={`/${lang === 'en' ? '' : 'zh/'}blog/tags/${encodeURIComponent(tagRoute)}`}
|
||||
class="px-2 py-1 text-xs bg-muted text-muted-foreground rounded-full hover:bg-muted/80 transition-colors"
|
||||
>
|
||||
# {tag}
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Meta Info */}
|
||||
<div class="flex items-center justify-between text-sm text-muted-foreground">
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="flex items-center">
|
||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 002 2v12a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
{post.date}
|
||||
</span>
|
||||
<span>{post.readTime}</span>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href={`${postBaseUrl}${post.slug}`}
|
||||
class="text-purple-500 hover:text-purple-400 font-medium flex items-center group"
|
||||
>
|
||||
{readMoreText}
|
||||
<svg class="w-4 h-4 ml-1 group-hover:translate-x-1 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.line-clamp-3 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
95
src/components/blog/CategoryCard.astro
Normal file
95
src/components/blog/CategoryCard.astro
Normal file
@@ -0,0 +1,95 @@
|
||||
---
|
||||
// 导入必要的依赖
|
||||
import { type Lang } from '@/i18n/utils';
|
||||
import { defaultLang } from '@/i18n/ui';
|
||||
|
||||
// 定义组件属性
|
||||
interface Props {
|
||||
lang?: Lang;
|
||||
currentCategory?: string;
|
||||
}
|
||||
|
||||
// 获取组件属性
|
||||
const { lang = defaultLang, currentCategory = '' } = Astro.props;
|
||||
|
||||
// 确定基础URL路径
|
||||
const baseUrl = lang === defaultLang ? '/blog' : `/${lang}/blog`;
|
||||
|
||||
// 读取所有博客文章 - 根据语言选择不同的静态路径
|
||||
let allPosts = [];
|
||||
if (lang === 'zh') {
|
||||
allPosts = await Astro.glob('/src/pages/zh/blog/posts/*.md');
|
||||
} else {
|
||||
allPosts = await Astro.glob('/src/pages/blog/posts/*.md');
|
||||
}
|
||||
|
||||
// 收集所有分类
|
||||
const allCategories = new Set<string>();
|
||||
|
||||
// 处理所有文章的分类
|
||||
const categoryMap = new Map<string, string>(); // 存储 categoryId 到 category 名称的映射
|
||||
|
||||
allPosts.forEach(post => {
|
||||
// 处理分类
|
||||
if (post.frontmatter?.category) {
|
||||
const categories = Array.isArray(post.frontmatter.category)
|
||||
? post.frontmatter.category
|
||||
: [post.frontmatter.category];
|
||||
|
||||
const categoryIds = Array.isArray(post.frontmatter.categoryId)
|
||||
? post.frontmatter.categoryId
|
||||
: post.frontmatter.categoryId ? [post.frontmatter.categoryId] : [];
|
||||
|
||||
// 如果有 categoryId,则使用 categoryId 和 category 的映射
|
||||
if (categoryIds.length > 0 && categoryIds.length === categories.length) {
|
||||
categories.forEach((cat, index) => {
|
||||
if (cat && categoryIds[index]) {
|
||||
allCategories.add(cat);
|
||||
categoryMap.set(cat, categoryIds[index]);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 如果没有 categoryId 或长度不匹配,则使用 category 名称作为 ID
|
||||
categories.forEach(cat => {
|
||||
if (cat) {
|
||||
allCategories.add(cat);
|
||||
categoryMap.set(cat, cat.toLowerCase());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// 转换为数组并排序
|
||||
const categories = Array.from(allCategories).sort();
|
||||
|
||||
// 多语言标题
|
||||
const titles = {
|
||||
en: 'Categories',
|
||||
zh: '分类'
|
||||
};
|
||||
|
||||
// 获取当前语言的标题
|
||||
const title = titles[lang] || titles[defaultLang];
|
||||
---
|
||||
|
||||
<div class="bg-card/50 backdrop-blur-sm rounded-2xl p-6 border border-border">
|
||||
<h3 class="text-xl font-semibold text-card-foreground mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14-7l2 2-2 2m2-2H9m10 0V9M5 19l2-2-2-2m2 2H3m2 0v2"></path>
|
||||
</svg>
|
||||
{title}
|
||||
</h3>
|
||||
<div class="space-y-2">
|
||||
{categories.map((cat) => {
|
||||
const categoryId = categoryMap.get(cat) || cat.toLowerCase();
|
||||
return (
|
||||
<a href={`${baseUrl}/categories/${encodeURIComponent(categoryId)}`}
|
||||
class={`block transition-colors duration-200 ${categoryId === currentCategory.toLowerCase() ? 'text-purple-500 font-medium' : 'text-muted-foreground hover:text-purple-500'}`}>
|
||||
{cat}
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
92
src/components/blog/TagCard.astro
Normal file
92
src/components/blog/TagCard.astro
Normal file
@@ -0,0 +1,92 @@
|
||||
---
|
||||
// 导入必要的依赖
|
||||
import { type Lang } from '@/i18n/utils';
|
||||
import { defaultLang } from '@/i18n/ui';
|
||||
|
||||
// 定义组件属性
|
||||
interface Props {
|
||||
lang?: Lang;
|
||||
currentTag?: string;
|
||||
}
|
||||
|
||||
// 获取组件属性
|
||||
const { lang = defaultLang, currentTag = '' } = Astro.props;
|
||||
|
||||
// 确定基础URL路径
|
||||
const baseUrl = lang === defaultLang ? '/blog' : `/${lang}/blog`;
|
||||
|
||||
// 读取所有博客文章 - 根据语言选择不同的静态路径
|
||||
let allPosts = [];
|
||||
if (lang === 'zh') {
|
||||
allPosts = await Astro.glob('/src/pages/zh/blog/posts/*.md');
|
||||
} else {
|
||||
allPosts = await Astro.glob('/src/pages/blog/posts/*.md');
|
||||
}
|
||||
|
||||
// 收集所有标签
|
||||
const allTags = new Set<string>();
|
||||
const tagMap = new Map<string, string>(); // 存储 tag 到 tagId 的映射
|
||||
|
||||
// 处理所有文章的标签
|
||||
allPosts.forEach(post => {
|
||||
// 处理标签
|
||||
if (post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) {
|
||||
const tags = post.frontmatter.tags;
|
||||
const tagIds = Array.isArray(post.frontmatter.tagId)
|
||||
? post.frontmatter.tagId
|
||||
: post.frontmatter.tagId ? [post.frontmatter.tagId] : [];
|
||||
|
||||
// 如果有 tagId,则使用 tagId 和 tag 的映射
|
||||
if (tagIds.length > 0 && tagIds.length === tags.length) {
|
||||
tags.forEach((tag, index) => {
|
||||
if (tag && tagIds[index]) {
|
||||
allTags.add(tag);
|
||||
tagMap.set(tag, tagIds[index]);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 如果没有 tagId 或长度不匹配,则使用 tag 名称作为 ID
|
||||
tags.forEach(tag => {
|
||||
if (tag) {
|
||||
allTags.add(tag);
|
||||
tagMap.set(tag, tag.toLowerCase());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 转换为数组,添加 # 前缀,并排序
|
||||
const tags = Array.from(allTags).map(tag => `# ${tag}`).sort();
|
||||
|
||||
// 多语言标题
|
||||
const titles = {
|
||||
en: '# Tags',
|
||||
zh: '# 标签'
|
||||
};
|
||||
|
||||
// 获取当前语言的标题
|
||||
const title = titles[lang] || titles[defaultLang];
|
||||
---
|
||||
|
||||
<div class="bg-card/50 backdrop-blur-sm rounded-2xl p-6 border border-border">
|
||||
<h3 class="text-xl font-semibold text-card-foreground mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
|
||||
</svg>
|
||||
{title}
|
||||
</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{tags.map((tagItem) => {
|
||||
const tagName = tagItem.slice(2); // 去掉 '# ' 前缀
|
||||
const tagId = tagMap.get(tagName) || tagName.toLowerCase();
|
||||
const isCurrentTag = tagId === currentTag.toLowerCase();
|
||||
return (
|
||||
<a href={`${baseUrl}/tags/${encodeURIComponent(tagId)}`}
|
||||
class={`inline-block px-3 py-1 text-sm rounded-full transition-all duration-200 ${isCurrentTag ? 'bg-purple-500/20 text-purple-500 font-medium' : 'bg-muted text-muted-foreground hover:bg-purple-500/20 hover:text-purple-500'}`}>
|
||||
{tagItem}
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
import BlogLayout from '../../../layouts/BlogLayout.astro';
|
||||
import BlogList from '../../../components/BlogList.tsx';
|
||||
import BlogList from '../../../components/blog/BlogList.astro';
|
||||
import CategoryCard from '../../../components/blog/CategoryCard.astro';
|
||||
import TagCard from '../../../components/blog/TagCard.astro';
|
||||
import { type Lang } from '@/i18n/utils';
|
||||
import { defaultLang } from '@/i18n/ui';
|
||||
import { type BlogPost } from '@/types';
|
||||
@@ -137,12 +139,33 @@ allPosts.forEach(post => {
|
||||
const categories = Array.from(allCategories).sort();
|
||||
const tags = Array.from(allTags).map(tag => `# ${tag}`).sort();
|
||||
|
||||
// 获取当前分类的格式化名称(首字母大写)
|
||||
const formattedCategory = decodedCategory.charAt(0).toUpperCase() + decodedCategory.slice(1);
|
||||
// 查找与当前分类ID匹配的分类名称
|
||||
let displayCategoryName = "";
|
||||
|
||||
// 从博客文章中查找匹配的分类名称
|
||||
for (const post of allPosts) {
|
||||
if (post.frontmatter?.categoryId && Array.isArray(post.frontmatter.categoryId) &&
|
||||
post.frontmatter?.category && Array.isArray(post.frontmatter.category)) {
|
||||
// 查找分类ID和分类名称的索引匹配
|
||||
const categoryIndex = post.frontmatter.categoryId.findIndex(id =>
|
||||
id.toLowerCase() === decodedCategory.toLowerCase()
|
||||
);
|
||||
|
||||
if (categoryIndex !== -1 && categoryIndex < post.frontmatter.category.length) {
|
||||
displayCategoryName = post.frontmatter.category[categoryIndex];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到匹配的分类名称,则使用分类ID并格式化(首字母大写)
|
||||
if (!displayCategoryName) {
|
||||
displayCategoryName = 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.`;
|
||||
const pageTitle = `${displayCategoryName} - Blog | Joy Zhao`;
|
||||
const pageDescription = `Explore articles about ${displayCategoryName}. Dive into my thoughts on ${displayCategoryName} and related topics.`;
|
||||
---
|
||||
|
||||
<BlogLayout title={pageTitle} description={pageDescription}>
|
||||
@@ -151,10 +174,10 @@ const pageDescription = `Explore articles about ${formattedCategory}. Dive into
|
||||
<div class="container mx-auto px-4 pt-24 pb-12">
|
||||
<div class="text-center mb-16">
|
||||
<h1 class="text-5xl md:text-6xl font-bold bg-gradient-to-r from-foreground via-purple-600 to-purple-800 dark:from-foreground dark:via-purple-200 dark:to-purple-300 bg-clip-text text-transparent mb-6">
|
||||
Category: <span class="text-purple-500">{formattedCategory}</span>
|
||||
Category: <span class="text-purple-500">{displayCategoryName}</span>
|
||||
</h1>
|
||||
<p class="text-xl text-muted-foreground max-w-3xl mx-auto">
|
||||
Explore articles about {formattedCategory}. Found {sortedBlogPosts.length} article{sortedBlogPosts.length !== 1 ? 's' : ''}.
|
||||
Explore articles about {displayCategoryName}. Found {sortedBlogPosts.length} article{sortedBlogPosts.length !== 1 ? 's' : ''}.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -164,47 +187,18 @@ const pageDescription = `Explore articles about ${formattedCategory}. Dive into
|
||||
<div class="grid grid-cols-1 lg:grid-cols-4 gap-8">
|
||||
<!-- Sidebar -->
|
||||
<div class="lg:col-span-1 space-y-8">
|
||||
<!-- Categories -->
|
||||
<div class="bg-card/50 backdrop-blur-sm rounded-2xl p-6 border border-border">
|
||||
<h3 class="text-xl font-semibold text-card-foreground mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14-7l2 2-2 2m2-2H9m10 0V9M5 19l2-2-2-2m2 2H3m2 0v2"></path>
|
||||
</svg>
|
||||
Categories
|
||||
</h3>
|
||||
<div class="space-y-2">
|
||||
{categories.map((cat) => (
|
||||
<a href={`/blog/categories/${encodeURIComponent(cat.toLowerCase())}`}
|
||||
class={`block transition-colors duration-200 ${cat.toLowerCase() === decodedCategory.toLowerCase() ? 'text-purple-500 font-medium' : 'text-muted-foreground hover:text-purple-500'}`}>
|
||||
{cat}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 分类卡片 -->
|
||||
<CategoryCard lang="en" currentCategory={decodedCategory} />
|
||||
|
||||
<!-- 标签卡片 -->
|
||||
<TagCard lang="en" />
|
||||
|
||||
<!-- Tags -->
|
||||
<div class="bg-card/50 backdrop-blur-sm rounded-2xl p-6 border border-border">
|
||||
<h3 class="text-xl font-semibold text-card-foreground mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
|
||||
</svg>
|
||||
# Tags
|
||||
</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{tags.map((tag) => (
|
||||
<a href={`/blog/tags/${encodeURIComponent(tag.slice(2).toLowerCase())}`}
|
||||
class="inline-block px-3 py-1 text-sm bg-muted text-muted-foreground rounded-full hover:bg-purple-500/20 hover:text-purple-500 transition-all duration-200">
|
||||
{tag}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Blog Posts -->
|
||||
<div class="lg:col-span-3">
|
||||
{sortedBlogPosts.length > 0 ? (
|
||||
<BlogList posts={sortedBlogPosts} lang="en" client:load />
|
||||
<BlogList posts={sortedBlogPosts} lang="en" category={decodedCategory} />
|
||||
) : (
|
||||
<div class="bg-card/50 backdrop-blur-sm rounded-2xl p-8 border border-border text-center">
|
||||
<h2 class="text-2xl font-semibold mb-4">No articles found</h2>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
---
|
||||
import BlogLayout from '../../layouts/BlogLayout.astro';
|
||||
import BlogList from '../../components/BlogList.tsx';
|
||||
import BlogList from '../../components/blog/BlogList.astro';
|
||||
import CategoryCard from '../../components/blog/CategoryCard.astro';
|
||||
import TagCard from '../../components/blog/TagCard.astro';
|
||||
import { type BlogPost } from '@/types';
|
||||
import { type Lang } from '@/i18n/utils';
|
||||
import { defaultLang } from '@/i18n/ui';
|
||||
import { type BlogPost } from '@/types';
|
||||
|
||||
// 使用Astro.currentLocale获取当前语言环境
|
||||
const lang = Astro.currentLocale as Lang || defaultLang;
|
||||
@@ -89,48 +91,18 @@ const tags = Array.from(allTags).map(tag => `# ${tag}`).sort();
|
||||
<!-- Main Content -->
|
||||
<div class="container mx-auto px-4 pb-20">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-4 gap-8">
|
||||
<!-- Sidebar -->
|
||||
<!-- 侧边栏 -->
|
||||
<div class="lg:col-span-1 space-y-8">
|
||||
<!-- Categories -->
|
||||
<div class="bg-card/50 backdrop-blur-sm rounded-2xl p-6 border border-border">
|
||||
<h3 class="text-xl font-semibold text-card-foreground mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14-7l2 2-2 2m2-2H9m10 0V9M5 19l2-2-2-2m2 2H3m2 0v2"></path>
|
||||
</svg>
|
||||
Categories
|
||||
</h3>
|
||||
<div class="space-y-2">
|
||||
{categories.map((category) => (
|
||||
<a href={`/blog/categories/${encodeURIComponent(category.toLowerCase())}`}
|
||||
class="block text-muted-foreground hover:text-purple-500 transition-colors duration-200">
|
||||
{category}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 分类卡片 -->
|
||||
<CategoryCard lang="en" />
|
||||
|
||||
<!-- Tags -->
|
||||
<div class="bg-card/50 backdrop-blur-sm rounded-2xl p-6 border border-border">
|
||||
<h3 class="text-xl font-semibold text-card-foreground mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
|
||||
</svg>
|
||||
# Tags
|
||||
</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{tags.map((tag) => (
|
||||
<a href={`/blog/tags/${encodeURIComponent(tag.slice(2).toLowerCase())}`}
|
||||
class="inline-block px-3 py-1 text-sm bg-muted text-muted-foreground rounded-full hover:bg-purple-500/20 hover:text-purple-500 transition-all duration-200">
|
||||
{tag}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 标签卡片 -->
|
||||
<TagCard lang="en" />
|
||||
</div>
|
||||
|
||||
<!-- Blog Posts -->
|
||||
<div class="lg:col-span-3">
|
||||
<BlogList posts={sortedBlogPosts} lang="en" client:load />
|
||||
<BlogList posts={sortedBlogPosts} lang="en" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
import BlogLayout from '../../../layouts/BlogLayout.astro';
|
||||
import BlogList from '../../../components/BlogList.tsx';
|
||||
import BlogList from '../../../components/blog/BlogList.astro';
|
||||
import CategoryCard from '../../../components/blog/CategoryCard.astro';
|
||||
import TagCard from '../../../components/blog/TagCard.astro';
|
||||
import { type Lang } from '@/i18n/utils';
|
||||
import { defaultLang } from '@/i18n/ui';
|
||||
import { type BlogPost } from '@/types';
|
||||
@@ -125,12 +127,33 @@ allPosts.forEach(post => {
|
||||
const categories = Array.from(allCategories).sort();
|
||||
const tags = Array.from(allTags).map(postTag => `# ${postTag}`).sort();
|
||||
|
||||
// 获取当前标签的格式化名称(首字母大写)
|
||||
const formattedTag = decodedTag.charAt(0).toUpperCase() + decodedTag.slice(1);
|
||||
// 查找与当前标签ID匹配的标签名称
|
||||
let displayTagName = "";
|
||||
|
||||
// 从博客文章中查找匹配的标签名称
|
||||
for (const post of allPosts) {
|
||||
if (post.frontmatter?.tagId && Array.isArray(post.frontmatter.tagId) &&
|
||||
post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) {
|
||||
// 查找标签ID和标签名称的索引匹配
|
||||
const tagIndex = post.frontmatter.tagId.findIndex(id =>
|
||||
id.toLowerCase() === decodedTag.toLowerCase()
|
||||
);
|
||||
|
||||
if (tagIndex !== -1 && tagIndex < post.frontmatter.tags.length) {
|
||||
displayTagName = post.frontmatter.tags[tagIndex];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到匹配的标签名称,则使用标签ID并格式化(首字母大写)
|
||||
if (!displayTagName) {
|
||||
displayTagName = 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.`;
|
||||
const pageTitle = `# ${displayTagName} - Blog | Joy Zhao`;
|
||||
const pageDescription = `Explore articles tagged with # ${displayTagName}. Dive into my thoughts on ${displayTagName} and related topics.`;
|
||||
---
|
||||
|
||||
<BlogLayout title={pageTitle} description={pageDescription}>
|
||||
@@ -139,10 +162,10 @@ const pageDescription = `Explore articles tagged with # ${formattedTag}. Dive in
|
||||
<div class="container mx-auto px-4 pt-24 pb-12">
|
||||
<div class="text-center mb-16">
|
||||
<h1 class="text-5xl md:text-6xl font-bold bg-gradient-to-r from-foreground via-purple-600 to-purple-800 dark:from-foreground dark:via-purple-200 dark:to-purple-300 bg-clip-text text-transparent mb-6">
|
||||
Tag: <span class="text-purple-500"># {formattedTag}</span>
|
||||
Tag: <span class="text-purple-500"># {displayTagName}</span>
|
||||
</h1>
|
||||
<p class="text-xl text-muted-foreground max-w-3xl mx-auto">
|
||||
Explore articles tagged with # {formattedTag}. Found {sortedBlogPosts.length} article{sortedBlogPosts.length !== 1 ? 's' : ''}.
|
||||
Explore articles tagged with # {displayTagName}. Found {sortedBlogPosts.length} article{sortedBlogPosts.length !== 1 ? 's' : ''}.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -152,51 +175,18 @@ const pageDescription = `Explore articles tagged with # ${formattedTag}. Dive in
|
||||
<div class="grid grid-cols-1 lg:grid-cols-4 gap-8">
|
||||
<!-- Sidebar -->
|
||||
<div class="lg:col-span-1 space-y-8">
|
||||
<!-- Categories -->
|
||||
<div class="bg-card/50 backdrop-blur-sm rounded-2xl p-6 border border-border">
|
||||
<h3 class="text-xl font-semibold text-card-foreground mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14-7l2 2-2 2m2-2H9m10 0V9M5 19l2-2-2-2m2 2H3m2 0v2"></path>
|
||||
</svg>
|
||||
Categories
|
||||
</h3>
|
||||
<div class="space-y-2">
|
||||
{categories.map((cat) => (
|
||||
<a href={`/blog/categories/${cat.toLowerCase()}`}
|
||||
class="block text-muted-foreground hover:text-purple-500 transition-colors duration-200">
|
||||
{cat}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 分类卡片 -->
|
||||
<CategoryCard lang="en" />
|
||||
|
||||
<!-- 标签卡片 -->
|
||||
<TagCard lang="en" currentTag={decodedTag} />
|
||||
|
||||
<!-- Tags -->
|
||||
<div class="bg-card/50 backdrop-blur-sm rounded-2xl p-6 border border-border">
|
||||
<h3 class="text-xl font-semibold text-card-foreground mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
|
||||
</svg>
|
||||
# Tags
|
||||
</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{tags.map((tagItem) => {
|
||||
const tagName = tagItem.slice(2).toLowerCase();
|
||||
const isCurrentTag = tagName === decodedTag.toLowerCase();
|
||||
return (
|
||||
<a href={`/blog/tags/${encodeURIComponent(tagName)}`}
|
||||
class={`inline-block px-3 py-1 text-sm rounded-full transition-all duration-200 ${isCurrentTag ? 'bg-purple-500/20 text-purple-500 font-medium' : 'bg-muted text-muted-foreground hover:bg-purple-500/20 hover:text-purple-500'}`}>
|
||||
{tagItem}
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Blog Posts -->
|
||||
<div class="lg:col-span-3">
|
||||
{sortedBlogPosts.length > 0 ? (
|
||||
<BlogList posts={sortedBlogPosts} lang="en" client:load />
|
||||
<BlogList posts={sortedBlogPosts} lang="en" tag={decodedTag} />
|
||||
) : (
|
||||
<div class="bg-card/50 backdrop-blur-sm rounded-2xl p-8 border border-border text-center">
|
||||
<h2 class="text-2xl font-semibold mb-4">No articles found</h2>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
import BlogLayout from '../../../../layouts/BlogLayout.astro';
|
||||
import BlogList from '../../../../components/BlogList.tsx';
|
||||
import BlogList from '../../../../components/blog/BlogList.astro';
|
||||
import CategoryCard from '../../../../components/blog/CategoryCard.astro';
|
||||
import TagCard from '../../../../components/blog/TagCard.astro';
|
||||
import { type Lang } from '@/i18n/utils';
|
||||
import { defaultLang } from '@/i18n/ui';
|
||||
import { type BlogPost } from '@/types';
|
||||
@@ -137,12 +139,33 @@ allPosts.forEach(post => {
|
||||
const categories = Array.from(allCategories).sort();
|
||||
const tags = Array.from(allTags).map(tag => `# ${tag}`).sort();
|
||||
|
||||
// 获取当前分类的格式化名称(首字母大写)
|
||||
const formattedCategory = decodedCategory.charAt(0).toUpperCase() + decodedCategory.slice(1);
|
||||
// 查找与当前分类ID匹配的分类名称
|
||||
let displayCategoryName = "";
|
||||
|
||||
// 从博客文章中查找匹配的分类名称
|
||||
for (const post of allPosts) {
|
||||
if (post.frontmatter?.categoryId && Array.isArray(post.frontmatter.categoryId) &&
|
||||
post.frontmatter?.category && Array.isArray(post.frontmatter.category)) {
|
||||
// 查找分类ID和分类名称的索引匹配
|
||||
const categoryIndex = post.frontmatter.categoryId.findIndex(id =>
|
||||
id.toLowerCase() === decodedCategory.toLowerCase()
|
||||
);
|
||||
|
||||
if (categoryIndex !== -1 && categoryIndex < post.frontmatter.category.length) {
|
||||
displayCategoryName = post.frontmatter.category[categoryIndex];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到匹配的分类名称,则使用分类ID并格式化(首字母大写)
|
||||
if (!displayCategoryName) {
|
||||
displayCategoryName = decodedCategory.charAt(0).toUpperCase() + decodedCategory.slice(1);
|
||||
}
|
||||
|
||||
// 动态生成页面标题和描述
|
||||
const pageTitle = `${formattedCategory} - 博客 | 赵桂阳`;
|
||||
const pageDescription = `探索关于${formattedCategory}的文章。深入了解我对${formattedCategory}和相关主题的思考。`;
|
||||
const pageTitle = `${displayCategoryName} - 博客 | 赵桂阳`;
|
||||
const pageDescription = `探索关于${displayCategoryName}的文章。深入了解我对${displayCategoryName}和相关主题的思考。`;
|
||||
---
|
||||
|
||||
<BlogLayout title={pageTitle} description={pageDescription}>
|
||||
@@ -151,10 +174,10 @@ const pageDescription = `探索关于${formattedCategory}的文章。深入了
|
||||
<div class="container mx-auto px-4 pt-24 pb-12">
|
||||
<div class="text-center mb-16">
|
||||
<h1 class="text-5xl md:text-6xl font-bold bg-gradient-to-r from-foreground via-purple-600 to-purple-800 dark:from-foreground dark:via-purple-200 dark:to-purple-300 bg-clip-text text-transparent mb-6">
|
||||
分类: <span class="text-purple-500">{formattedCategory}</span>
|
||||
分类: <span class="text-purple-500">{displayCategoryName}</span>
|
||||
</h1>
|
||||
<p class="text-xl text-muted-foreground max-w-3xl mx-auto">
|
||||
探索关于{formattedCategory}的文章。找到 {sortedBlogPosts.length} 篇文章。
|
||||
探索关于{displayCategoryName}的文章。找到 {sortedBlogPosts.length} 篇文章。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -164,47 +187,18 @@ const pageDescription = `探索关于${formattedCategory}的文章。深入了
|
||||
<div class="grid grid-cols-1 lg:grid-cols-4 gap-8">
|
||||
<!-- 侧边栏 -->
|
||||
<div class="lg:col-span-1 space-y-8">
|
||||
<!-- 分类 -->
|
||||
<div class="bg-card/50 backdrop-blur-sm rounded-2xl p-6 border border-border">
|
||||
<h3 class="text-xl font-semibold text-card-foreground mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14-7l2 2-2 2m2-2H9m10 0V9M5 19l2-2-2-2m2 2H3m2 0v2"></path>
|
||||
</svg>
|
||||
分类
|
||||
</h3>
|
||||
<div class="space-y-2">
|
||||
{categories.map((cat) => (
|
||||
<a href={`/zh/blog/categories/${encodeURIComponent(cat.toLowerCase())}`}
|
||||
class={`block transition-colors duration-200 ${cat.toLowerCase() === decodedCategory.toLowerCase() ? 'text-purple-500 font-medium' : 'text-muted-foreground hover:text-purple-500'}`}>
|
||||
{cat}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 分类卡片 -->
|
||||
<CategoryCard lang="zh" currentCategory={decodedCategory} />
|
||||
|
||||
<!-- 标签卡片 -->
|
||||
<TagCard lang="zh" />
|
||||
|
||||
<!-- 标签 -->
|
||||
<div class="bg-card/50 backdrop-blur-sm rounded-2xl p-6 border border-border">
|
||||
<h3 class="text-xl font-semibold text-card-foreground mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
|
||||
</svg>
|
||||
# 标签
|
||||
</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{tags.map((tag) => (
|
||||
<a href={`/zh/blog/tags/${encodeURIComponent(tag.slice(2).toLowerCase())}`}
|
||||
class="inline-block px-3 py-1 text-sm bg-muted text-muted-foreground rounded-full hover:bg-purple-500/20 hover:text-purple-500 transition-all duration-200">
|
||||
{tag}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 博客文章 -->
|
||||
<div class="lg:col-span-3">
|
||||
{sortedBlogPosts.length > 0 ? (
|
||||
<BlogList posts={sortedBlogPosts} lang="zh" client:load />
|
||||
<BlogList posts={sortedBlogPosts} lang="zh" category={decodedCategory} />
|
||||
) : (
|
||||
<div class="bg-card/50 backdrop-blur-sm rounded-2xl p-8 border border-border text-center">
|
||||
<h2 class="text-2xl font-semibold mb-4">未找到文章</h2>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
---
|
||||
import BlogLayout from '../../../layouts/BlogLayout.astro';
|
||||
import BlogList from '../../../components/BlogList.tsx';
|
||||
import BlogList from '../../../components/blog/BlogList.astro';
|
||||
import CategoryCard from '../../../components/blog/CategoryCard.astro';
|
||||
import TagCard from '../../../components/blog/TagCard.astro';
|
||||
import { type BlogPost } from '@/types';
|
||||
import { type Lang } from '@/i18n/utils';
|
||||
import { defaultLang } from '@/i18n/ui';
|
||||
import { type BlogPost } from '@/types';
|
||||
|
||||
// 使用Astro.currentLocale获取当前语言环境
|
||||
const lang = Astro.currentLocale as Lang || defaultLang;
|
||||
@@ -91,46 +93,17 @@ const tags = Array.from(allTags).map(tag => `# ${tag}`).sort();
|
||||
<div class="grid grid-cols-1 lg:grid-cols-4 gap-8">
|
||||
<!-- 侧边栏 -->
|
||||
<div class="lg:col-span-1 space-y-8">
|
||||
<!-- 分类 -->
|
||||
<div class="bg-card/50 backdrop-blur-sm rounded-2xl p-6 border border-border">
|
||||
<h3 class="text-xl font-semibold text-card-foreground mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14-7l2 2-2 2m2-2H9m10 0V9M5 19l2-2-2-2m2 2H3m2 0v2"></path>
|
||||
</svg>
|
||||
分类
|
||||
</h3>
|
||||
<div class="space-y-2">
|
||||
{categories.map((category) => (
|
||||
<a href={`/zh/blog/categories/${encodeURIComponent(category.toLowerCase())}`}
|
||||
class="block text-muted-foreground hover:text-purple-500 transition-colors duration-200">
|
||||
{category}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 分类卡片 -->
|
||||
<CategoryCard lang="zh" />
|
||||
|
||||
<!-- 标签卡片 -->
|
||||
<TagCard lang="zh" />
|
||||
|
||||
<!-- 标签 -->
|
||||
<div class="bg-card/50 backdrop-blur-sm rounded-2xl p-6 border border-border">
|
||||
<h3 class="text-xl font-semibold text-card-foreground mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
|
||||
</svg>
|
||||
# 标签
|
||||
</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{tags.map((tag) => (
|
||||
<a href={`/zh/blog/tags/${encodeURIComponent(tag.slice(2).toLowerCase())}`}
|
||||
class="inline-block px-3 py-1 text-sm bg-muted text-muted-foreground rounded-full hover:bg-purple-500/20 hover:text-purple-500 transition-all duration-200">
|
||||
{tag}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 博客文章 -->
|
||||
<div class="lg:col-span-3">
|
||||
<BlogList posts={sortedBlogPosts} lang="zh" client:load />
|
||||
<BlogList posts={sortedBlogPosts} lang="zh" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
import BlogLayout from '../../../../layouts/BlogLayout.astro';
|
||||
import BlogList from '../../../../components/BlogList.tsx';
|
||||
import BlogList from '../../../../components/blog/BlogList.astro';
|
||||
import CategoryCard from '../../../../components/blog/CategoryCard.astro';
|
||||
import TagCard from '../../../../components/blog/TagCard.astro';
|
||||
import { type Lang } from '@/i18n/utils';
|
||||
import { defaultLang } from '@/i18n/ui';
|
||||
import { type BlogPost } from '@/types';
|
||||
@@ -125,12 +127,33 @@ allPosts.forEach(post => {
|
||||
const categories = Array.from(allCategories).sort();
|
||||
const tags = Array.from(allTags).map(postTag => `# ${postTag}`).sort();
|
||||
|
||||
// 获取当前标签的格式化名称(首字母大写)
|
||||
const formattedTag = decodedTag.charAt(0).toUpperCase() + decodedTag.slice(1);
|
||||
// 查找与当前标签ID匹配的标签名称
|
||||
let displayTagName = "";
|
||||
|
||||
// 从博客文章中查找匹配的标签名称
|
||||
for (const post of allPosts) {
|
||||
if (post.frontmatter?.tagId && Array.isArray(post.frontmatter.tagId) &&
|
||||
post.frontmatter?.tags && Array.isArray(post.frontmatter.tags)) {
|
||||
// 查找标签ID和标签名称的索引匹配
|
||||
const tagIndex = post.frontmatter.tagId.findIndex(id =>
|
||||
id.toLowerCase() === decodedTag.toLowerCase()
|
||||
);
|
||||
|
||||
if (tagIndex !== -1 && tagIndex < post.frontmatter.tags.length) {
|
||||
displayTagName = post.frontmatter.tags[tagIndex];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到匹配的标签名称,则使用标签ID并格式化(首字母大写)
|
||||
if (!displayTagName) {
|
||||
displayTagName = decodedTag.charAt(0).toUpperCase() + decodedTag.slice(1);
|
||||
}
|
||||
|
||||
// 动态生成页面标题和描述
|
||||
const pageTitle = `# ${formattedTag} - 博客 | Joy Zhao`;
|
||||
const pageDescription = `浏览带有 # ${formattedTag} 标签的文章。深入了解我关于 ${formattedTag} 及相关主题的想法。`;
|
||||
const pageTitle = `# ${displayTagName} - 博客 | Joy Zhao`;
|
||||
const pageDescription = `浏览带有 # ${displayTagName} 标签的文章。深入了解我关于 ${displayTagName} 及相关主题的想法。`;
|
||||
---
|
||||
|
||||
<BlogLayout title={pageTitle} description={pageDescription}>
|
||||
@@ -139,10 +162,10 @@ const pageDescription = `浏览带有 # ${formattedTag} 标签的文章。深入
|
||||
<div class="container mx-auto px-4 pt-24 pb-12">
|
||||
<div class="text-center mb-16">
|
||||
<h1 class="text-5xl md:text-6xl font-bold bg-gradient-to-r from-foreground via-purple-600 to-purple-800 dark:from-foreground dark:via-purple-200 dark:to-purple-300 bg-clip-text text-transparent mb-6">
|
||||
标签: <span class="text-purple-500"># {formattedTag}</span>
|
||||
标签: <span class="text-purple-500"># {displayTagName}</span>
|
||||
</h1>
|
||||
<p class="text-xl text-muted-foreground max-w-3xl mx-auto">
|
||||
浏览带有 # {formattedTag} 标签的文章。找到 {sortedBlogPosts.length} 篇文章。
|
||||
浏览带有 # {displayTagName} 标签的文章。找到 {sortedBlogPosts.length} 篇文章。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -150,53 +173,20 @@ const pageDescription = `浏览带有 # ${formattedTag} 标签的文章。深入
|
||||
<!-- Main Content -->
|
||||
<div class="container mx-auto px-4 pb-20">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-4 gap-8">
|
||||
<!-- Sidebar -->
|
||||
<!-- 侧边栏 -->
|
||||
<div class="lg:col-span-1 space-y-8">
|
||||
<!-- Categories -->
|
||||
<div class="bg-card/50 backdrop-blur-sm rounded-2xl p-6 border border-border">
|
||||
<h3 class="text-xl font-semibold text-card-foreground mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14-7l2 2-2 2m2-2H9m10 0V9M5 19l2-2-2-2m2 2H3m2 0v2"></path>
|
||||
</svg>
|
||||
分类
|
||||
</h3>
|
||||
<div class="space-y-2">
|
||||
{categories.map((cat) => (
|
||||
<a href={`/zh/blog/categories/${cat.toLowerCase()}`}
|
||||
class="block text-muted-foreground hover:text-purple-500 transition-colors duration-200">
|
||||
{cat}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 分类卡片 -->
|
||||
<CategoryCard lang="zh" />
|
||||
|
||||
<!-- 标签卡片 -->
|
||||
<TagCard lang="zh" currentTag={decodedTag} />
|
||||
|
||||
<!-- Tags -->
|
||||
<div class="bg-card/50 backdrop-blur-sm rounded-2xl p-6 border border-border">
|
||||
<h3 class="text-xl font-semibold text-card-foreground mb-4 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"></path>
|
||||
</svg>
|
||||
# 标签
|
||||
</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{tags.map((tagItem) => {
|
||||
const tagName = tagItem.slice(2).toLowerCase();
|
||||
const isCurrentTag = tagName === decodedTag.toLowerCase();
|
||||
return (
|
||||
<a href={`/zh/blog/tags/${encodeURIComponent(tagName)}`}
|
||||
class={`inline-block px-3 py-1 text-sm rounded-full transition-all duration-200 ${isCurrentTag ? 'bg-purple-500/20 text-purple-500 font-medium' : 'bg-muted text-muted-foreground hover:bg-purple-500/20 hover:text-purple-500'}`}>
|
||||
{tagItem}
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Blog Posts -->
|
||||
<div class="lg:col-span-3">
|
||||
{sortedBlogPosts.length > 0 ? (
|
||||
<BlogList posts={sortedBlogPosts} lang="zh" client:load />
|
||||
<BlogList posts={sortedBlogPosts} lang="zh" tag={decodedTag} />
|
||||
) : (
|
||||
<div class="bg-card/50 backdrop-blur-sm rounded-2xl p-8 border border-border text-center">
|
||||
<h2 class="text-2xl font-semibold mb-4">未找到文章</h2>
|
||||
|
||||
@@ -122,6 +122,7 @@
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
line-height: 1.2;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
|
||||
Reference in New Issue
Block a user