--- import { getCollection } from 'astro:content'; import type { CollectionEntry } from 'astro:content'; import { contentStructure, getRelativePath, getBasename, getDirPath, getSpecialPath } from '../../content.config'; import Layout from '@/components/Layout.astro'; import Breadcrumb from '@/components/Breadcrumb.astro'; import ArticleTimeline from '@/components/ArticleTimeline.astro'; // 启用静态预渲染 export const prerender = true; export function extractSummary(content: string, length = 150) { // 移除 Markdown 标记 const plainText = content .replace(/---[\s\S]*?---/, '') // 移除 frontmatter .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // 将链接转换为纯文本 .replace(/[#*`~>]/g, '') // 移除特殊字符 .replace(/\n+/g, ' ') // 将换行转换为空格 .trim(); // 提取指定长度的文本 return plainText.length > length ? plainText.slice(0, length).trim() + '...' : plainText; } // 生成所有可能的静态路径 export async function getStaticPaths() { const articles = await getCollection('articles'); const { sections } = contentStructure; const allTags = articles.flatMap(article => article.data.tags || []); const tags = [...new Set(allTags)].sort(); const views = ['grid', 'timeline']; // 生成所有可能的路径组合 const paths = []; // 1. 默认路径(无参数) paths.push({ params: { path: undefined }, props: { path: '', tag: '', view: 'grid' } }); // 2. 标签路径 for (const tag of tags) { // 标签主页 paths.push({ params: { tag }, props: { path: '', tag, view: 'grid' } }); // 标签视图页 for (const view of views) { paths.push({ params: { tag, view }, props: { path: '', tag, view } }); } } // 3. 目录路径 function addSectionPaths(section: any, currentPath = '') { const sectionPath = currentPath ? `${currentPath}/${section.name}` : section.name; // 添加当前目录的路径(不带 view 参数) paths.push({ params: { path: sectionPath }, props: { path: sectionPath, tag: '', view: 'grid' } }); // 添加当前目录的视图路径 for (const view of views) { paths.push({ params: { path: sectionPath, view }, props: { path: sectionPath, tag: '', view } }); } // 递归添加子目录的路径 for (const subSection of section.sections) { addSectionPaths(subSection, sectionPath); } } for (const section of sections) { addSectionPaths(section); } // 4. 添加所有可能的目录路径(不带 view 参数) function addAllPossiblePaths(section: any, currentPath = '') { const sectionPath = currentPath ? `${currentPath}/${section.name}` : section.name; // 添加当前目录的路径 paths.push({ params: { path: sectionPath }, props: { path: sectionPath, tag: '', view: 'grid' } }); // 递归添加子目录的路径 for (const subSection of section.sections) { addAllPossiblePaths(subSection, sectionPath); } } for (const section of sections) { addAllPossiblePaths(section); } return paths; } const { path = '', tag = '', view = 'grid' } = Astro.props; const pathSegments = path ? path.split('/') : []; // 获取所有文章,并按日期排序 const articles: CollectionEntry<'articles'>[] = await getCollection('articles'); const sortedArticles = articles.sort( (a, b) => b.data.date.getTime() - a.data.date.getTime() ); // 获取所有标签 const allTags = articles.flatMap(article => article.data.tags || []); const tags = [...new Set(allTags)].sort(); // 获取内容结构 const { sections } = contentStructure; // 获取标签参数 const tagFilter = tag; // 获取视图模式参数 const viewMode = view; // 根据路径获取当前目录 function getCurrentSection(pathSegments: string[]) { // 过滤掉空字符串 const filteredSegments = pathSegments.filter(segment => segment.trim() !== ''); if (filteredSegments.length === 0) { return { sections, articles: contentStructure.articles, currentPath: '' }; } let currentSections = sections; let currentPath = ''; let currentArticles: string[] = []; // 遍历路径段,逐级查找 for (let i = 0; i < filteredSegments.length; i++) { const segment = filteredSegments[i]; // 查找当前段对应的目录 const foundSection = currentSections.find(s => s.name === segment); if (!foundSection) { return { sections: [], articles: [], currentPath: '' }; } // 更新当前路径 currentPath = currentPath ? `${currentPath}/${segment}` : segment; // 如果是最后一个段,返回该目录的内容 if (i === filteredSegments.length - 1) { return { sections: foundSection.sections, articles: foundSection.articles, currentPath }; } // 否则继续向下查找 currentSections = foundSection.sections; } // 默认返回空 return { sections: [], articles: [], currentPath: '' }; } // 获取当前目录内容 const { sections: currentSections, articles: currentArticles, currentPath } = getCurrentSection(pathSegments); // 如果有标签过滤,则过滤文章 let filteredArticles = sortedArticles; let pageTitle = currentPath ? currentPath : '文章列表'; if (tagFilter) { filteredArticles = sortedArticles.filter(article => article.data.tags && article.data.tags.includes(tagFilter) ); pageTitle = `标签: ${tagFilter}`; } // 获取面包屑导航 interface Breadcrumb { name: string; path: string; } // 处理特殊ID的函数 function getArticleUrl(articleId: string) { return `/articles/${getSpecialPath(articleId)}`; } ---
{viewMode === 'grid' ? ( <>
{/* 上一级目录卡片 - 仅在浏览目录时显示 */} {!tagFilter && pathSegments.length > 0 && ( 1 ? pathSegments.slice(0, -1).join('/') : ''}`} class="group flex flex-col h-full p-5 border border-gray-200 dark:border-gray-700 rounded-xl bg-white dark:bg-gray-800 hover:shadow-xl hover:-translate-y-1 transition-all duration-300 shadow-lg">
返回上级目录
返回上一级
)} {/* 目录卡片 - 仅在浏览目录时显示 */} {!tagFilter && currentSections.map(section => { // 确保目录链接正确生成 const dirLink = currentPath ? `${currentPath}/${section.name}` : section.name; return (
{section.name}
{section.sections.length > 0 && ( {section.sections.length} 个子目录 )} {section.articles.length > 0 && ( {section.articles.length} 篇文章 )}
); })} {/* 文章卡片 - 根据是否有标签过滤显示不同内容 */} {tagFilter ? ( // 显示标签过滤后的文章 filteredArticles.map(article => (

{article.data.title}

{article.body && (

{extractSummary(article.body)}

)}
阅读全文
)) ) : ( // 显示当前目录的文章 currentArticles.map(articlePath => { // 获取文章ID const articleId = getRelativePath(articlePath); // 尝试不同的方式匹配文章 const article = articles.find(a => { // 1. 直接匹配完整路径 if (a.id === articleId) { return true; } // 2. 匹配文件名(不含路径和扩展名) const baseName = getBasename(articleId); if (a.id === baseName) { return true; } // 3. 尝试匹配相对路径的一部分 const articleParts = articleId.split('/'); const fileName = articleParts[articleParts.length - 1]; if (a.id.endsWith(fileName)) { return true; } // 4. 移除.md扩展名后匹配 const idWithoutExt = articleId.replace(/\.md$/, ''); if (a.id === idWithoutExt) { return true; } // 5. 处理多级目录结构 // 如果文章ID包含目录路径,尝试匹配最后的文件名部分 const articlePathParts = articlePath.split('/'); const articleFileName = articlePathParts[articlePathParts.length - 1]; const articleIdParts = a.id.split('/'); const articleIdFileName = articleIdParts[articleIdParts.length - 1]; if (articleFileName === articleIdFileName) { return true; } // 6. 移除扩展名后比较文件名 const fileNameWithoutExt = articleFileName.replace(/\.md$/, ''); if (articleIdFileName === fileNameWithoutExt) { return true; } return false; }); if (!article) { return (

文章不存在

ID: {articleId}

可用文章: {articles.map(a => a.id).join(', ')}
); } return (

{article.data.title}

{article.body && (

{extractSummary(article.body)}

)}
阅读全文
); }) )}
{/* 空内容提示 */} {((tagFilter && filteredArticles.length === 0) || (!tagFilter && currentSections.length === 0 && currentArticles.length === 0)) && (

{tagFilter ? `没有找到标签为 "${tagFilter}" 的文章` : '此目录为空'}

{tagFilter ? '请尝试其他标签或返回文章列表' : '此目录下暂无内容,请浏览其他目录或返回上一级'}

)}

文章标签

{tags.map(tag => { const isActive = tag === tagFilter; return ( {tag} ); })}
) : ( )}