--- import { getCollection, render } from "astro:content"; import { getSpecialPath } from "@/content.config"; import Layout from "@/components/Layout.astro"; import Breadcrumb from "@/components/Breadcrumb.astro"; import { ARTICLE_EXPIRY_CONFIG } from "@/consts"; import "@/styles/articles.css"; // 定义文章类型 interface ArticleEntry { id: string; data: { title: string; date: Date; tags?: string[]; summary?: string; }; } // 定义标题类型 interface Heading { depth: number; slug: string; text: string; } export async function getStaticPaths() { const articles = await getCollection("articles"); // 为每篇文章生成路由参数 const paths = []; for (const article of articles) { // 获取所有可能的路径形式 const possiblePaths = new Set([ article.id, // 只保留原始路径 ]); // 如果是多级目录,检查是否需要特殊处理 if (article.id.includes("/")) { const parts = article.id.split("/"); const fileName = parts[parts.length - 1]; const dirName = parts[parts.length - 2]; // 只有当文件名与其父目录名相同时才添加特殊路径 if (fileName === dirName) { possiblePaths.add(getSpecialPath(article.id)); } } // 为每个可能的路径生成路由 for (const path of possiblePaths) { paths.push({ params: { id: path }, props: { article, section: article.id.includes("/") ? article.id.split("/").slice(0, -1).join("/") : "", originalId: path !== article.id ? article.id : undefined, }, }); } } return paths; } // 获取文章内容 const { article, section, originalId } = Astro.props; // 获取搜索参数 const searchParams = new URLSearchParams(Astro.url.search); // 如果有原始ID,使用它来渲染内容 const articleToRender = originalId ? { ...article, id: originalId } : article; // 渲染文章内容 const { Content, headings } = await render(articleToRender); // 获取面包屑路径段 const pathSegments = section ? section.split("/") : []; // 获取相关文章 const allArticles = await getCollection("articles"); // 1. 尝试通过标签匹配相关文章 let relatedArticles = allArticles .filter((a: ArticleEntry) => { const hasCommonTags = a.id !== article.id && a.data.tags && article.data.tags && a.data.tags.length > 0 && article.data.tags.length > 0 && a.data.tags.some((tag: string) => article.data.tags?.includes(tag)); return hasCommonTags; }) .sort( (a: ArticleEntry, b: ArticleEntry) => b.data.date.getTime() - a.data.date.getTime(), ) .slice(0, 3); // 跟踪相关文章的匹配方式: "tag", "directory", "latest" let relatedArticlesMatchType = relatedArticles.length > 0 ? "tag" : ""; // 2. 如果标签匹配没有找到足够的相关文章,尝试根据目录结构匹配 if (relatedArticles.length < 3) { // 获取当前文章的目录路径 const currentPath = article.id.includes("/") ? article.id.substring(0, article.id.lastIndexOf("/")) : ""; // 如果有目录路径,查找同目录的其他文章 if (currentPath) { // 收集同目录下的文章,但排除已经通过标签匹配的和当前文章 const dirRelatedArticles = allArticles .filter( (a: ArticleEntry) => a.id !== article.id && a.id.startsWith(currentPath + "/") && !relatedArticles.some((r: ArticleEntry) => r.id === a.id), ) .sort( (a: ArticleEntry, b: ArticleEntry) => b.data.date.getTime() - a.data.date.getTime(), ) .slice(0, 3 - relatedArticles.length); if (dirRelatedArticles.length > 0) { relatedArticles = [...relatedArticles, ...dirRelatedArticles]; relatedArticlesMatchType = relatedArticles.length > 0 && !relatedArticlesMatchType ? "directory" : relatedArticlesMatchType; } } } // 3. 如果仍然没有找到足够的相关文章,则选择最新的文章(排除当前文章和已选择的文章) if (relatedArticles.length < 3) { const latestArticles = allArticles .filter( (a: ArticleEntry) => a.id !== article.id && !relatedArticles.some((r: ArticleEntry) => r.id === a.id), ) .sort( (a: ArticleEntry, b: ArticleEntry) => b.data.date.getTime() - a.data.date.getTime(), ) .slice(0, 3 - relatedArticles.length); if (latestArticles.length > 0) { relatedArticles = [...relatedArticles, ...latestArticles]; relatedArticlesMatchType = relatedArticles.length > 0 && !relatedArticlesMatchType ? "latest" : relatedArticlesMatchType; } } // 准备文章描述 const description = article.data.summary || `${article.data.title} - 发布于 ${article.data.date.toLocaleDateString("zh-CN")}`; // 处理特殊ID的函数 function getArticleUrl(articleId: string) { return `/articles/${getSpecialPath(articleId)}${searchParams.toString() ? `?${searchParams.toString()}` : ""}`; } // 预先生成目录结构 function generateTableOfContents(headings: Heading[]) { if (!headings || headings.length === 0) { return '

此文章没有目录

'; } // 查找最低级别的标题(数值最小) const minDepth = Math.min(...headings.map((h) => h.depth)); // 按照标题层级构建嵌套结构 const tocTree: any[] = []; const levelMap: Record = {}; headings.forEach((heading) => { const relativeDepth = heading.depth - minDepth; // 构建标题项 const headingItem = { slug: heading.slug, text: heading.text, depth: relativeDepth, children: [], }; // 更精确地处理嵌套关系 if (relativeDepth === 0) { // 顶级标题直接加入到树中 tocTree.push(headingItem); levelMap[0] = tocTree; } else { // 查找当前标题的父级 let parentDepth = relativeDepth - 1; // 向上查找可能的父级 while (parentDepth >= 0 && !levelMap[parentDepth]) { parentDepth--; } if (parentDepth >= 0 && levelMap[parentDepth] && levelMap[parentDepth].length > 0) { // 找到父层级,将此标题添加到最近的父标题的子标题数组中 const parentItems = levelMap[parentDepth]; const parent = parentItems[parentItems.length - 1]; parent.children.push(headingItem); // 更新当前深度的映射 if (!levelMap[relativeDepth]) { levelMap[relativeDepth] = []; } levelMap[relativeDepth].push(headingItem); } else { // 找不到有效父级,作为顶级标题处理 tocTree.push(headingItem); levelMap[relativeDepth] = [headingItem]; } } }); // 递归生成HTML function generateTocHTML(items: any[], level = 0) { if (items.length === 0) return ''; const isTopLevel = level === 0; let html = `'; return html; } return generateTocHTML(tocTree); } // 生成目录HTML const tableOfContents = generateTableOfContents(headings); ---
{ (() => { const publishDate = article.data.date; const currentDate = new Date(); const daysDiff = Math.floor( (currentDate.getTime() - publishDate.getTime()) / (1000 * 60 * 60 * 24), ); if ( ARTICLE_EXPIRY_CONFIG.enabled && daysDiff > ARTICLE_EXPIRY_CONFIG.expiryDays ) { return (

{ARTICLE_EXPIRY_CONFIG.warningMessage}

); } return null; })() }

{article.data.title}

{ section && ( {section} ) }
{ article.data.tags && article.data.tags.length > 0 && (
{article.data.tags.map((tag: string) => ( #{tag} ))}
) }
{ relatedArticles.length > 0 && ( ) }