diff --git a/package-lock.json b/package-lock.json index 06b5ad4..ae2eb71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3622,60 +3622,60 @@ ] }, "node_modules/@shikijs/core": { - "version": "3.2.1", - "resolved": "https://registry.npmmirror.com/@shikijs/core/-/core-3.2.1.tgz", - "integrity": "sha512-FhsdxMWYu/C11sFisEp7FMGBtX/OSSbnXZDMBhGuUDBNTdsoZlMSgQv5f90rwvzWAdWIW6VobD+G3IrazxA6dQ==", + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/@shikijs/core/-/core-3.2.2.tgz", + "integrity": "sha512-yvlSKVMLjddAGBa2Yu+vUZxuu3sClOWW1AG+UtJkvejYuGM5BVL35s6Ijiwb75O9QdEx6IkMxinHZSi8ZyrBaA==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.2.1", + "@shikijs/types": "3.2.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "node_modules/@shikijs/engine-javascript": { - "version": "3.2.1", - "resolved": "https://registry.npmmirror.com/@shikijs/engine-javascript/-/engine-javascript-3.2.1.tgz", - "integrity": "sha512-eMdcUzN3FMQYxOmRf2rmU8frikzoSHbQDFH2hIuXsrMO+IBOCI9BeeRkCiBkcLDHeRKbOCtYMJK3D6U32ooU9Q==", + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/@shikijs/engine-javascript/-/engine-javascript-3.2.2.tgz", + "integrity": "sha512-tlDKfhWpF4jKLUyVAnmL+ggIC+0VyteNsUpBzh1iwWLZu4i+PelIRr0TNur6pRRo5UZIv3ss/PLMuwahg9S2hg==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.2.1", + "@shikijs/types": "3.2.2", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.1.0" } }, "node_modules/@shikijs/engine-oniguruma": { - "version": "3.2.1", - "resolved": "https://registry.npmmirror.com/@shikijs/engine-oniguruma/-/engine-oniguruma-3.2.1.tgz", - "integrity": "sha512-wZZAkayEn6qu2+YjenEoFqj0OyQI64EWsNR6/71d1EkG4sxEOFooowKivsWPpaWNBu3sxAG+zPz5kzBL/SsreQ==", + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/@shikijs/engine-oniguruma/-/engine-oniguruma-3.2.2.tgz", + "integrity": "sha512-vyXRnWVCSvokwbaUD/8uPn6Gqsf5Hv7XwcW4AgiU4Z2qwy19sdr6VGzMdheKKN58tJOOe5MIKiNb901bgcUXYQ==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.2.1", + "@shikijs/types": "3.2.2", "@shikijs/vscode-textmate": "^10.0.2" } }, "node_modules/@shikijs/langs": { - "version": "3.2.1", - "resolved": "https://registry.npmmirror.com/@shikijs/langs/-/langs-3.2.1.tgz", - "integrity": "sha512-If0iDHYRSGbihiA8+7uRsgb1er1Yj11pwpX1c6HLYnizDsKAw5iaT3JXj5ZpaimXSWky/IhxTm7C6nkiYVym+A==", + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/@shikijs/langs/-/langs-3.2.2.tgz", + "integrity": "sha512-NY0Urg2dV9ETt3JIOWoMPuoDNwte3geLZ4M1nrPHbkDS8dWMpKcEwlqiEIGqtwZNmt5gKyWpR26ln2Bg2ecPgw==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.2.1" + "@shikijs/types": "3.2.2" } }, "node_modules/@shikijs/themes": { - "version": "3.2.1", - "resolved": "https://registry.npmmirror.com/@shikijs/themes/-/themes-3.2.1.tgz", - "integrity": "sha512-k5DKJUT8IldBvAm8WcrDT5+7GA7se6lLksR+2E3SvyqGTyFMzU2F9Gb7rmD+t+Pga1MKrYFxDIeyWjMZWM6uBQ==", + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/@shikijs/themes/-/themes-3.2.2.tgz", + "integrity": "sha512-Zuq4lgAxVKkb0FFdhHSdDkALuRpsj1so1JdihjKNQfgM78EHxV2JhO10qPsMrm01FkE3mDRTdF68wfmsqjt6HA==", "license": "MIT", "dependencies": { - "@shikijs/types": "3.2.1" + "@shikijs/types": "3.2.2" } }, "node_modules/@shikijs/types": { - "version": "3.2.1", - "resolved": "https://registry.npmmirror.com/@shikijs/types/-/types-3.2.1.tgz", - "integrity": "sha512-/NTWAk4KE2M8uac0RhOsIhYQf4pdU0OywQuYDGIGAJ6Mjunxl2cGiuLkvu4HLCMn+OTTLRWkjZITp+aYJv60yA==", + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/@shikijs/types/-/types-3.2.2.tgz", + "integrity": "sha512-a5TiHk7EH5Lso8sHcLHbVNNhWKP0Wi3yVnXnu73g86n3WoDgEra7n3KszyeCGuyoagspQ2fzvy4cpSc8pKhb0A==", "license": "MIT", "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", @@ -11021,19 +11021,19 @@ } }, "node_modules/oniguruma-parser": { - "version": "0.5.4", - "resolved": "https://registry.npmmirror.com/oniguruma-parser/-/oniguruma-parser-0.5.4.tgz", - "integrity": "sha512-yNxcQ8sKvURiTwP0mV6bLQCYE7NKfKRRWunhbZnXgxSmB1OXa1lHrN3o4DZd+0Si0kU5blidK7BcROO8qv5TZA==", + "version": "0.11.2", + "resolved": "https://registry.npmmirror.com/oniguruma-parser/-/oniguruma-parser-0.11.2.tgz", + "integrity": "sha512-F7Ld4oDZJCI5/wCZ8AOffQbqjSzIRpKH7I/iuSs1SkhZeCj0wS6PMZ4W6VA16TWHrAo0Y9bBKEJOe7tvwcTXnw==", "license": "MIT" }, "node_modules/oniguruma-to-es": { - "version": "4.1.0", - "resolved": "https://registry.npmmirror.com/oniguruma-to-es/-/oniguruma-to-es-4.1.0.tgz", - "integrity": "sha512-SNwG909cSLo4vPyyPbU/VJkEc9WOXqu2ycBlfd1UCXLqk1IijcQktSBb2yRQ2UFPsDhpkaf+C1dtT3PkLK/yWA==", + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/oniguruma-to-es/-/oniguruma-to-es-4.2.0.tgz", + "integrity": "sha512-MDPs6KSOLS0tKQ7joqg44dRIRZUyotfTy0r+7oEEs6VwWWP0+E2PPDYWMFN0aqOjRyWHBYq7RfKw9GQk2S2z5g==", "license": "MIT", "dependencies": { "emoji-regex-xs": "^1.0.0", - "oniguruma-parser": "^0.5.4", + "oniguruma-parser": "^0.11.0", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } @@ -13406,17 +13406,17 @@ } }, "node_modules/shiki": { - "version": "3.2.1", - "resolved": "https://registry.npmmirror.com/shiki/-/shiki-3.2.1.tgz", - "integrity": "sha512-VML/2o1/KGYkEf/stJJ+s9Ypn7jUKQPomGLGYso4JJFMFxVDyPNsjsI3MB3KLjlMOeH44gyaPdXC6rik2WXvUQ==", + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/shiki/-/shiki-3.2.2.tgz", + "integrity": "sha512-0qWBkM2t/0NXPRcVgtLhtHv6Ak3Q5yI4K/ggMqcgLRKm4+pCs3namgZlhlat/7u2CuqNtlShNs9lENOG6n7UaQ==", "license": "MIT", "dependencies": { - "@shikijs/core": "3.2.1", - "@shikijs/engine-javascript": "3.2.1", - "@shikijs/engine-oniguruma": "3.2.1", - "@shikijs/langs": "3.2.1", - "@shikijs/themes": "3.2.1", - "@shikijs/types": "3.2.1", + "@shikijs/core": "3.2.2", + "@shikijs/engine-javascript": "3.2.2", + "@shikijs/engine-oniguruma": "3.2.2", + "@shikijs/langs": "3.2.2", + "@shikijs/themes": "3.2.2", + "@shikijs/types": "3.2.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } diff --git a/src/components/Breadcrumb.astro b/src/components/Breadcrumb.astro new file mode 100644 index 0000000..a6f538d --- /dev/null +++ b/src/components/Breadcrumb.astro @@ -0,0 +1,399 @@ +--- +interface Breadcrumb { + name: string; + path: string; +} + +export interface Props { + pageType: 'filter' | 'grid' | 'article'; + pathSegments?: string[]; // 路径段数组 + searchParams?: URLSearchParams; // 搜索参数 + articleTitle?: string; // 文章标题(仅在文章详情页使用) + path?: string; // 当前路径 +} + +const { + pageType, + pathSegments = [], + searchParams = new URLSearchParams(), + articleTitle = '', + path = '' +} = Astro.props; + +// 计算面包屑 +const breadcrumbs: Breadcrumb[] = pathSegments + .filter(segment => segment.trim() !== '') + .map((segment, index, array) => { + const path = array.slice(0, index + 1).join('/'); + return { name: segment, path }; + }); +--- + +
+
+ + + + + + 文章 + + + + {(pageType === 'grid' || (pageType === 'article' && breadcrumbs.length > 0)) && ( +
+ / + + +
+ {breadcrumbs.length > 2 ? ( + <> + + + {breadcrumbs[0].name} + + + + ... + + + {breadcrumbs.length > 1 && ( + + {breadcrumbs[breadcrumbs.length - 1].name} + + )} + + ) : ( + breadcrumbs.map((crumb: Breadcrumb, index: number) => { + const crumbPath = breadcrumbs.slice(0, index + 1).map((b: Breadcrumb) => b.name).join('/'); + return ( + + {index > 0 && /} + + {crumb.name} + + + ); + }) + )} +
+ + + +
+ )} + + + {pageType === 'filter' && searchParams.toString() && ( +
+ / + + 搜索结果 + +
+ )} + + + {pageType === 'article' && articleTitle && ( + <> + / + {articleTitle} + + )} +
+ + + {(pageType === 'filter' || pageType === 'grid') && ( +
+ + + + + + + + + + + + +
+ )} + + + {pageType === 'article' && ( +
+ + + + + 返回文章列表 + +
+ )} +
+ + \ No newline at end of file diff --git a/src/components/Breadcrumb.tsx b/src/components/Breadcrumb.tsx deleted file mode 100644 index 4ab25e7..0000000 --- a/src/components/Breadcrumb.tsx +++ /dev/null @@ -1,70 +0,0 @@ -interface Breadcrumb { - name: string; - path: string; -} - -export interface BreadcrumbProps { - pageType: 'articles' | 'article' | 'timeline'; // 页面类型 - pathSegments?: string[]; // 路径段数组 - tagFilter?: string; // 标签过滤器 - articleTitle?: string; // 文章标题(仅在文章详情页使用) -} - -export function Breadcrumb({ - pageType, - pathSegments = [], - tagFilter = '', - articleTitle = '' -}: BreadcrumbProps) { - // 将路径段转换为面包屑对象 - const breadcrumbs: Breadcrumb[] = pathSegments - .filter(segment => segment.trim() !== '') - .map((segment, index, array) => { - const path = array.slice(0, index + 1).join('/'); - return { name: segment, path }; - }); - - return ( -
- {/* 文章列表链接 */} - - - - - 文章 - - - {/* 标签过滤 */} - {tagFilter && ( - <> - / - - - - - {tagFilter} - - - )} - - {/* 目录路径 */} - {!tagFilter && breadcrumbs.map((crumb: Breadcrumb, index: number) => { - const crumbPath = breadcrumbs.slice(0, index + 1).map((b: Breadcrumb) => b.name).join('/'); - return ( - - / - {crumb.name} - - ); - })} - - {/* 文章标题 */} - {pageType === 'article' && articleTitle && ( - <> - / - {articleTitle} - - )} -
- ); -} \ No newline at end of file diff --git a/src/components/Header.astro b/src/components/Header.astro index 58a5c6b..2ebb16b 100644 --- a/src/components/Header.astro +++ b/src/components/Header.astro @@ -291,7 +291,7 @@ const normalizedPath = diff --git a/src/components/Layout.astro b/src/components/Layout.astro index 710b147..a5e1865 100644 --- a/src/components/Layout.astro +++ b/src/components/Layout.astro @@ -9,16 +9,14 @@ interface Props { title?: string; description?: string; date?: Date; - author?: string; tags?: string[]; - image?: string; } // 获取完整的 URL const canonicalURL = new URL(Astro.url.pathname, Astro.site); // 从props中获取页面特定信息 -const { title = SITE_NAME, description = SITE_DESCRIPTION, date, author, tags, image } = Astro.props; +const { title = SITE_NAME, description = SITE_DESCRIPTION, date, tags } = Astro.props; --- @@ -39,18 +37,15 @@ const { title = SITE_NAME, description = SITE_DESCRIPTION, date, author, tags, i - {image && } - {image && } {date && } - {author && } {tags && tags.map(tag => ( ))} diff --git a/src/content.config.ts b/src/content.config.ts index fce889e..e8f9bb7 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -135,11 +135,7 @@ const articles = defineCollection({ date: z.date(), tags: z.array(z.string()).optional(), summary: z.string().optional(), - image: z.string().optional(), - author: z.string().optional(), draft: z.boolean().optional().default(false), - section: z.string().optional(), - weight: z.number().optional(), }), }); diff --git a/src/content/常用软件.md b/src/content/常用软件.md index d5e8edc..35dc7dc 100644 --- a/src/content/常用软件.md +++ b/src/content/常用软件.md @@ -2,6 +2,7 @@ title: "常用软件" date: 2023-04-28T20:56:00Z tags: [] +summary : "常用的应用合集" --- ### Windows 应用 diff --git a/src/pages/api/articles.ts b/src/pages/api/articles.ts index 014ebdb..ae4cbf4 100644 --- a/src/pages/api/articles.ts +++ b/src/pages/api/articles.ts @@ -2,69 +2,62 @@ import type { APIRoute } from 'astro'; import { getCollection } from 'astro:content'; import { getSpecialPath } from '../../content.config'; +// 从文章内容中提取摘要的函数 +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; +} + // 处理特殊ID的函数 function getArticleUrl(articleId: string) { return `/articles/${getSpecialPath(articleId)}`; } export const GET: APIRoute = async ({ request }) => { - // 获取查询参数 - const url = new URL(request.url); - const page = parseInt(url.searchParams.get('page') || '1'); - const limit = parseInt(url.searchParams.get('limit') || '10'); - const tag = url.searchParams.get('tag') || ''; - const path = url.searchParams.get('path') || ''; - - // 获取所有文章 - const articles = await getCollection('articles'); - - // 根据条件过滤文章 - let filteredArticles = articles; - - // 如果有标签过滤 - if (tag) { - filteredArticles = filteredArticles.filter(article => - article.data.tags && article.data.tags.includes(tag) - ); - } - - // 如果有路径过滤,直接使用文章ID来判断 - if (path) { - const normalizedPath = path.toLowerCase(); - filteredArticles = filteredArticles.filter(article => { - return article.id.toLowerCase().includes(normalizedPath); + try { + // 获取所有文章 + const articles = await getCollection('articles'); + // 格式化文章数据 + const formattedArticles = articles.map(article => ({ + id: article.id, + title: article.data.title, + date: article.data.date, + tags: article.data.tags || [], + summary: article.data.summary || (article.body ? extractSummary(article.body) : ''), + url: getArticleUrl(article.id) // 使用特殊ID处理函数 + })); + + return new Response(JSON.stringify({ + articles: formattedArticles, + total: formattedArticles.length, + success: true + }), { + headers: { + 'Content-Type': 'application/json', + // 添加缓存头,缓存1小时 + 'Cache-Control': 'public, max-age=3600' + } + }); + } catch (error) { + return new Response(JSON.stringify({ + error: '获取文章数据失败', + success: false, + articles: [], + total: 0 + }), { + status: 500, + headers: { + 'Content-Type': 'application/json' + } }); } - - // 按日期排序(最新的在前面) - const sortedArticles = filteredArticles.sort( - (a, b) => b.data.date.getTime() - a.data.date.getTime() - ); - - // 计算分页 - const startIndex = (page - 1) * limit; - const endIndex = startIndex + limit; - const paginatedArticles = sortedArticles.slice(startIndex, endIndex); - - // 格式化文章数据 - const formattedArticles = paginatedArticles.map(article => ({ - id: article.id, - title: article.data.title, - date: article.data.date, - tags: article.data.tags || [], - summary: article.data.summary || '', - url: getArticleUrl(article.id) // 使用特殊ID处理函数 - })); - - return new Response(JSON.stringify({ - articles: formattedArticles, - total: sortedArticles.length, - page, - limit, - totalPages: Math.ceil(sortedArticles.length / limit) - }), { - headers: { - 'Content-Type': 'application/json' - } - }); }; \ No newline at end of file diff --git a/src/pages/api/search.ts b/src/pages/api/search.ts index 0df12aa..92de2f8 100644 --- a/src/pages/api/search.ts +++ b/src/pages/api/search.ts @@ -26,7 +26,6 @@ export async function GET() { date: article.data.date, summary: article.data.summary || '', tags: article.data.tags || [], - image: article.data.image || '', content: contentText // 添加文章内容 }; }) diff --git a/src/pages/articles/[...id].astro b/src/pages/articles/[...id].astro index a590d2a..bcdfcd3 100644 --- a/src/pages/articles/[...id].astro +++ b/src/pages/articles/[...id].astro @@ -2,7 +2,7 @@ import { getCollection, render } from "astro:content"; import { getSpecialPath } from "@/content.config"; import Layout from "@/components/Layout.astro"; -import { Breadcrumb } from "@/components/Breadcrumb.tsx"; +import Breadcrumb from "@/components/Breadcrumb.astro"; import { ARTICLE_EXPIRY_CONFIG } from "@/consts"; // 添加这一行,告诉Astro预渲染这个页面 @@ -10,7 +10,6 @@ export const prerender = true; export async function getStaticPaths() { const articles = await getCollection("articles"); - const views = ["grid", "timeline"]; // 为每篇文章生成路由参数 const paths = []; @@ -34,7 +33,6 @@ export async function getStaticPaths() { // 为每个可能的路径生成路由 for (const path of possiblePaths) { - // 添加基本路由 paths.push({ params: { id: path }, props: { @@ -43,24 +41,8 @@ export async function getStaticPaths() { ? article.id.split("/").slice(0, -1).join("/") : "", originalId: path !== article.id ? article.id : undefined, - view: undefined, }, }); - - // 为每个视图添加路由 - for (const view of views) { - paths.push({ - params: { id: `${path}/${view}` }, - props: { - article, - section: article.id.includes("/") - ? article.id.split("/").slice(0, -1).join("/") - : "", - originalId: path !== article.id ? article.id : undefined, - view, - }, - }); - } } } @@ -68,7 +50,10 @@ export async function getStaticPaths() { } // 获取文章内容 -const { article, section, originalId, view } = Astro.props; +const { article, section, originalId } = Astro.props; + +// 获取搜索参数 +const searchParams = new URLSearchParams(Astro.url.search); // 如果有原始ID,使用它来渲染内容 const articleToRender = originalId ? { ...article, id: originalId } : article; @@ -76,8 +61,8 @@ const articleToRender = originalId ? { ...article, id: originalId } : article; // 渲染文章内容 const { Content } = await render(articleToRender); -// 获取面包屑导航 -const breadcrumbs = section ? section.split("/") : []; +// 获取面包屑路径段 +const pathSegments = section ? section.split("/") : []; // 获取相关文章 const allArticles = await getCollection("articles"); @@ -151,17 +136,16 @@ const description = // 处理特殊ID的函数 function getArticleUrl(articleId: string) { - return `/articles/${getSpecialPath(articleId)}${view ? `/${view}` : ""}`; + return `/articles/${getSpecialPath(articleId)}${searchParams.toString() ? `?${searchParams.toString()}` : ''}`; } + ---
@@ -180,44 +164,18 @@ function getArticleUrl(articleId: string) {
-
-
+
+
- -
- {/* 返回按钮 */} - - - - - 返回文章列表 - -
@@ -267,7 +225,7 @@ function getArticleUrl(articleId: string) { /> {section} @@ -282,7 +240,7 @@ function getArticleUrl(articleId: string) {
{article.data.tags.map((tag) => ( @@ -341,7 +299,7 @@ function getArticleUrl(articleId: string) {
@@ -351,10 +309,10 @@ function getArticleUrl(articleId: string) { class="hidden 2xl:block fixed right-[calc(50%-48rem)] top-20 w-64 z-30" >

文章目录 @@ -362,7 +320,7 @@ function getArticleUrl(articleId: string) {

@@ -373,7 +331,7 @@ function getArticleUrl(articleId: string) { { relatedArticles.length > 0 && ( -
+

{relatedArticlesMatchType === "tag" ? "相关文章" : relatedArticlesMatchType === "directory" ? "同类文章" : "推荐阅读"} @@ -382,7 +340,7 @@ function getArticleUrl(articleId: string) { {relatedArticles.map((relatedArticle) => (

@@ -637,7 +595,7 @@ function getArticleUrl(articleId: string) { const language = languageMatch ? languageMatch[1] : "text"; const header = document.createElement("div"); - header.className = "code-header flex justify-between items-center text-xs px-4 py-2 bg-secondary-800 dark:bg-dark-card text-secondary-300 dark:text-secondary-400 rounded-t-lg"; + header.className = "code-header flex justify-between items-center text-xs px-4 py-2 bg-secondary-800 dark:bg-gray-900 text-secondary-300 dark:text-secondary-400 rounded-t-lg"; const languageLabel = document.createElement("span"); languageLabel.className = "code-language font-mono"; @@ -687,16 +645,6 @@ function getArticleUrl(articleId: string) { }); } - function cleanup() { - listeners.forEach(({ element, eventType, handler }) => { - try { - element.removeEventListener(eventType, handler); - } catch (err) {} - }); - - listeners.length = 0; - } - function init() { if (!document.querySelector("article")) return; @@ -718,6 +666,16 @@ function getArticleUrl(articleId: string) { window.addEventListener("beforeunload", cleanup, { once: true }); } + function cleanup() { + listeners.forEach(({ element, eventType, handler }) => { + try { + element.removeEventListener(eventType, handler); + } catch (err) {} + }); + + listeners.length = 0; + } + registerCleanup(); })(); diff --git a/src/pages/articles/[...path].astro b/src/pages/articles/[...path].astro index c75375a..2859503 100644 --- a/src/pages/articles/[...path].astro +++ b/src/pages/articles/[...path].astro @@ -1,106 +1,65 @@ --- -import ArticlesPage, { getStaticPaths as getOriginalPaths } from './index.astro'; +import ArticlesPage from './index.astro'; +import { getCollection } from 'astro:content'; // 启用静态预渲染 export const prerender = true; -// 重新导出 getStaticPaths,处理所有路径模式 +// 获取目录结构 export async function getStaticPaths() { - const paths = await getOriginalPaths(); - const allPaths = paths.map(({ props }) => { - const results = []; - - // 1. 如果有标签,添加标签路径 - if (props.tag) { - // 标签主页 - results.push({ - params: { path: `tag/${props.tag}` }, - props: { ...props } - }); - // 标签视图页 - results.push({ - params: { path: `tag/${props.tag}/grid` }, - props: { ...props, view: 'grid' } - }); - results.push({ - params: { path: `tag/${props.tag}/timeline` }, - props: { ...props, view: 'timeline' } - }); + const articles = await getCollection('articles'); + + // 从文章ID中提取所有目录路径 + const directories = new Set(); + + articles.forEach(article => { + if (article.id.includes('/')) { + // 获取所有层级的目录 + const parts = article.id.split('/'); + let currentPath = ''; + + // 逐级构建目录路径 + for (let i = 0; i < parts.length - 1; i++) { + currentPath = currentPath ? `${currentPath}/${parts[i]}` : parts[i]; + directories.add(currentPath); + } } - - // 2. 如果有路径,添加目录路径 - if (props.path) { - // 目录主页 - results.push({ - params: { path: props.path }, - props: { ...props } - }); - // 目录视图页 - results.push({ - params: { path: `${props.path}/grid` }, - props: { ...props, view: 'grid' } - }); - results.push({ - params: { path: `${props.path}/timeline` }, - props: { ...props, view: 'timeline' } - }); - } - - return results; - }).flat(); - - // 添加顶级视图路径 - allPaths.push( - { - params: { path: 'grid' }, - props: { path: '', tag: '', view: 'grid' } + }); + + // 准备路径数组 + const paths = []; + + // 为每个目录创建一个路由 + for (const path of directories) { + paths.push({ + params: { + // 对于 [...path] 参数,Astro 需要接收单个字符串 + path: path + }, + props: { + path, + pageType: 'grid' + } + }); + } + + // 添加根路径 (即 /articles/) + paths.push({ + params: { + path: undefined }, - { - params: { path: 'timeline' }, - props: { path: '', tag: '', view: 'timeline' } + props: { + path: '', + pageType: 'grid' } - ); - - return allPaths; + }); + + return paths; } // 使用主页面组件 -const { props } = Astro; - -// 解析路径参数 -const pathParts = (Astro.params.path as string | undefined)?.split('/') || []; - -// 初始化变量 -let path = ''; -let tag = ''; -let view = 'grid'; - -if (pathParts[0] === 'tag' && pathParts.length >= 2) { - // 标签路径处理 - tag = pathParts[1]; - view = pathParts[2] || 'grid'; -} else { - // 处理普通路径和视图 - const lastPart = pathParts[pathParts.length - 1]; - - if (['grid', 'timeline'].includes(lastPart)) { - // 如果最后一部分是视图类型,则移除它并设置视图 - view = lastPart; - pathParts.pop(); - } - - // 剩余的部分都作为路径 - path = pathParts.join('/'); -} - -// 合并属性 -const mergedProps = { - ...props, - path, - tag, - view -}; +const { path, pageType = 'grid' } = Astro.props; --- - \ No newline at end of file + \ No newline at end of file diff --git a/src/pages/articles/index.astro b/src/pages/articles/index.astro index b6a192c..ed6a551 100644 --- a/src/pages/articles/index.astro +++ b/src/pages/articles/index.astro @@ -3,109 +3,24 @@ import { getCollection } from 'astro:content'; import type { CollectionEntry } from 'astro:content'; import { contentStructure } from '../../content.config'; import Layout from "@/components/Layout.astro"; -import {Breadcrumb} from '@/components/Breadcrumb.tsx'; +import Breadcrumb from '@/components/Breadcrumb.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 searchParams = Astro.url.searchParams; +const path = Astro.props.path || ''; +// 视图类型判断逻辑: +// 1. 如果props中有明确指定pageType,则使用props的值 +// 2. 否则: +// a. 访问 /articles/ (带尾斜杠) 或其他目录路径 - 使用网格视图 +// b. 访问 /articles (不带尾斜杠) - 使用筛选视图 +// c. 如果有path属性(表示是目录浏览),也使用网格视图 +const currentUrl = Astro.url.pathname; +const isRootWithSlash = currentUrl === '/articles/'; +const isGridView = isRootWithSlash || (currentUrl.startsWith('/articles/') && currentUrl !== '/articles'); +const pageType = Astro.props.pageType || (isGridView || path ? 'grid' : 'filter'); const pathSegments = path ? path.split('/') : []; // 获取所有文章,并按日期排序 @@ -121,12 +36,6 @@ const tags = [...new Set(allTags)].sort(); // 获取内容结构 const { sections } = contentStructure; -// 获取标签参数 -const tagFilter = tag; - -// 获取视图模式参数 -const viewMode = view; - // 根据路径获取当前目录 function getCurrentSection(pathSegments: string[]) { // 过滤掉空字符串 @@ -138,7 +47,6 @@ function getCurrentSection(pathSegments: string[]) { let currentSections = sections; let currentPath = ''; - let currentArticles: string[] = []; // 遍历路径段,逐级查找 for (let i = 0; i < filteredSegments.length; i++) { @@ -178,6 +86,8 @@ const { sections: currentSections, articles: currentArticles, currentPath } = ge let filteredArticles = sortedArticles; let pageTitle = currentPath ? currentPath : '文章列表'; +// 使用URL搜索参数中的标签进行过滤 +const tagFilter = searchParams.get('tags'); if (tagFilter) { filteredArticles = sortedArticles.filter(article => article.data.tags && article.data.tags.includes(tagFilter) @@ -185,72 +95,42 @@ if (tagFilter) { pageTitle = `标签: ${tagFilter}`; } -// 获取面包屑导航 -interface Breadcrumb { - name: string; - path: string; -} - -// 处理特殊ID的函数 +// 处理文章链接 function getArticleUrl(articleId: string) { - return `/articles/${articleId}${view ? `/${view}` : ''}`; + return searchParams.toString() ? + `/articles/${articleId}?${searchParams.toString()}` : + `/articles/${articleId}`; } ---
-
+

{pageTitle}

- {viewMode === 'grid' ? ( + {pageType === 'grid' ? ( <>
{/* 上一级目录卡片 - 仅在浏览目录时显示 */} {!tagFilter && pathSegments.length > 0 && ( - 1 ? pathSegments.slice(0, -1).join('/') : ''}/${view}`} + 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 shadow-lg" data-astro-prefetch="hover">
@@ -278,7 +158,7 @@ function getArticleUrl(articleId: string) { const dirLink = currentPath ? `${currentPath}/${section.name}` : section.name; return ( -
@@ -322,40 +202,42 @@ function getArticleUrl(articleId: string) { {tagFilter ? ( // 显示标签过滤后的文章 filteredArticles.map(article => ( - -
-
- - - -
-
-

{article.data.title}

- {article.body && ( -

- {extractSummary(article.body)} -

- )} -
- - 阅读全文 +
+ +
+
+ + +
- {article.data.tags && article.data.tags.length > 0 && ( -
- {article.data.tags.map(tag => ( - - #{tag} - - ))} +
+

{article.data.title}

+ {article.body && ( +

+ {article.data.summary} +

+ )} +
+ + 阅读全文
- )} +
-
-
+ + {article.data.tags && article.data.tags.length > 0 && ( +
+ {article.data.tags.map(tag => ( + + #{tag} + + ))} +
+ )} +
)) ) : ( // 显示当前目录的文章 @@ -393,40 +275,42 @@ function getArticleUrl(articleId: string) { } return ( - -
-
- - - -
-
-

{article.data.title}

- {article.body && ( -

- {extractSummary(article.body)} -

- )} -
)} - - -
-

- - - - 文章标签 -

+ + ) : pageType === 'filter' ? ( + +
+ +
+
+

+ + + + 文章筛选 +

+ +
+ +
+ +
+ +
+ +
+ + + +
+
+ + + +
+ + +
+ +
+ +
+ + + +
+
+
+ + +
+ +
+ + +
+
+ +
+
+
+
-
- {tags.map(tag => { - const isActive = tag === tagFilter; - return ( - - {tag} - - ); - })} + +
+
已选筛选条件:
+
+
无筛选条件,显示全部文章
+ +
+
+ + +
+
+ 显示 0 篇文章(共 {sortedArticles.length} 篇) +
+ +
+ +
+ + +
+
- - ) : ( -
- {/* 时间线视图 */} -
- {sortedArticles.length > 0 ? ( - sortedArticles.map((article, index) => { - const isEven = index % 2 === 0; - return ( - + + + + + + + + + + + ) : ( + // 默认视图(参数错误时显示) +
+

无效的视图模式

+

您请求的视图模式"{pageType}"不存在,请选择其他视图。

+
- )} -
-
- \ No newline at end of file + ) + } + \ No newline at end of file diff --git a/src/pages/other.astro b/src/pages/other.astro index 438e501..636da05 100644 --- a/src/pages/other.astro +++ b/src/pages/other.astro @@ -7,6 +7,7 @@ import { VISITED_PLACES } from '@/consts';
+

其他内容

距离退休还有

diff --git a/src/pages/projects.astro b/src/pages/projects.astro index 31791e0..e68cd88 100644 --- a/src/pages/projects.astro +++ b/src/pages/projects.astro @@ -6,6 +6,7 @@ import { GitPlatform } from '@/components/GitProjectCollection';
+

项目