diff --git a/src/components/ArchiveTimeline.astro b/src/components/ArchiveTimeline.astro new file mode 100644 index 0000000..3c07667 --- /dev/null +++ b/src/components/ArchiveTimeline.astro @@ -0,0 +1,79 @@ +--- +import type { BlogPost } from '@/lib/posts'; + +interface Props { + posts: BlogPost[]; +} + +type ArchiveMonth = { + key: string; + label: string; + posts: BlogPost[]; +}; + +type ArchiveYear = { + year: number; + count: number; + months: ArchiveMonth[]; +}; + +const { posts } = Astro.props; +const monthFormatter = new Intl.DateTimeFormat('zh-CN', { month: 'long' }); +const dayFormatter = new Intl.DateTimeFormat('zh-CN', { day: '2-digit' }); +const archiveYears = posts.reduce((years, post) => { + const year = post.date.getFullYear(); + const monthKey = `${year}-${String(post.date.getMonth() + 1).padStart(2, '0')}`; + let yearGroup = years.find((item) => item.year === year); + + if (!yearGroup) { + yearGroup = { year, count: 0, months: [] }; + years.push(yearGroup); + } + + let monthGroup = yearGroup.months.find((item) => item.key === monthKey); + + if (!monthGroup) { + monthGroup = { + key: monthKey, + label: monthFormatter.format(post.date), + posts: [], + }; + yearGroup.months.push(monthGroup); + } + + yearGroup.count += 1; + monthGroup.posts.push(post); + + return years; +}, []); +--- + +
+ {archiveYears.map((yearGroup) => ( +
+ +
+

{yearGroup.year}

+ {yearGroup.count} 篇 +
+
+ {yearGroup.months.map((monthGroup) => ( +
+
+ {monthGroup.label} + {monthGroup.posts.length} 篇 +
+
+ {monthGroup.posts.map((post) => ( + + + {post.title} + + ))} +
+
+ ))} +
+
+ ))} +
diff --git a/src/pages/archives/index.astro b/src/pages/archives/index.astro index b787c9d..5f7f113 100644 --- a/src/pages/archives/index.astro +++ b/src/pages/archives/index.astro @@ -3,6 +3,7 @@ import BaseLayout from '@/layouts/BaseLayout.astro'; import Pagination from '@/components/Pagination.astro'; import PageHeaderCard from '@/components/PageHeaderCard.astro'; import StatsChart from '@/components/StatsChart.astro'; +import ArchiveTimeline from '@/components/ArchiveTimeline.astro'; import { getAllPosts } from '@/lib/posts'; import { getPageSlice } from '@/lib/pagination'; @@ -21,14 +22,7 @@ const archiveStats = [...posts.reduce((months, post) => {
-
- {pageData.items.map((post) => ( - - - {post.title} - - ))} -
+
diff --git a/src/pages/archives/page/[page].astro b/src/pages/archives/page/[page].astro index 4353bbd..8f33a50 100644 --- a/src/pages/archives/page/[page].astro +++ b/src/pages/archives/page/[page].astro @@ -3,6 +3,7 @@ import Pagination from '@/components/Pagination.astro'; import BaseLayout from '@/layouts/BaseLayout.astro'; import PageHeaderCard from '@/components/PageHeaderCard.astro'; import StatsChart from '@/components/StatsChart.astro'; +import ArchiveTimeline from '@/components/ArchiveTimeline.astro'; import { getAllPosts } from '@/lib/posts'; import { getPageSlice, getPaginationPaths } from '@/lib/pagination'; @@ -31,14 +32,7 @@ const archiveStats = [...posts.reduce((months, post) => {
-
- {pageData.items.map((post) => ( - - - {post.title} - - ))} -
+
diff --git a/src/styles/site.css b/src/styles/site.css index 4e9c341..2d71c18 100644 --- a/src/styles/site.css +++ b/src/styles/site.css @@ -2480,18 +2480,152 @@ time { color: var(--text); } -.archive-list { - margin-top: 24px; +.archive-timeline { + position: relative; display: grid; - gap: 10px; + gap: 26px; + margin-top: 28px; + padding-left: 34px; +} + +.archive-timeline::before { + content: ""; + position: absolute; + top: 10px; + bottom: 10px; + left: 10px; + width: 2px; + border-radius: 999px; + background: linear-gradient(180deg, rgba(62, 184, 190, 0.8), rgba(222, 216, 204, 0.18)); +} + +.archive-year { + position: relative; + display: grid; + grid-template-columns: 150px minmax(0, 1fr); + gap: 22px; +} + +.archive-year-marker { + position: absolute; + top: 12px; + left: -31px; + width: 18px; + height: 18px; + border: 4px solid rgba(255, 253, 248, 0.96); + border-radius: 999px; + background: #3eb8be; + box-shadow: + 0 0 0 6px rgba(62, 184, 190, 0.16), + 0 10px 24px rgba(15, 118, 110, 0.18); +} + +.archive-year-head { + position: sticky; + top: 92px; + align-self: start; + display: grid; + gap: 4px; + padding-top: 2px; +} + +.archive-year-head h2 { + margin: 0; + color: var(--accent-strong); + font-size: clamp(1.8rem, 4vw, 2.7rem); + line-height: 1; +} + +.archive-year-head span { + color: var(--muted); + font-size: 0.92rem; + font-weight: 700; +} + +.archive-months { + display: grid; + gap: 16px; + min-width: 0; +} + +.archive-month { + display: grid; + grid-template-columns: 92px minmax(0, 1fr); + gap: 18px; + border: 1px solid rgba(222, 216, 204, 0.72); + border-radius: 8px; + padding: 18px; + background: + linear-gradient(135deg, rgba(255, 255, 255, 0.76), rgba(242, 251, 237, 0.58)), + rgba(255, 253, 248, 0.78); + box-shadow: var(--shadow); +} + +.archive-month-label { + display: grid; + align-content: start; + gap: 6px; + color: var(--accent); + font-weight: 900; + line-height: 1.2; +} + +.archive-month-label small { + color: var(--muted); + font-size: 0.82rem; + font-weight: 700; +} + +.archive-list { + display: grid; + gap: 8px; + min-width: 0; } .archive-list a { display: grid; - grid-template-columns: 116px 1fr; - gap: 16px; - border-bottom: 1px solid var(--line); - padding: 10px 0; + grid-template-columns: 46px minmax(0, 1fr); + align-items: center; + gap: 14px; + border: 1px solid transparent; + border-radius: 8px; + padding: 9px 12px; + background: rgba(255, 253, 248, 0.54); + transition: + color 0.2s ease, + border-color 0.2s ease, + background-color 0.2s ease, + box-shadow 0.2s ease, + transform 0.2s ease; +} + +.archive-list a time { + display: inline-grid; + place-items: center; + width: 38px; + height: 38px; + border-radius: 8px; + color: #fff; + font-size: 0.9rem; + font-weight: 900; + line-height: 1; + background: #3eb8be; + box-shadow: 0 10px 24px rgba(15, 118, 110, 0.14); +} + +.archive-list a span { + min-width: 0; + overflow-wrap: anywhere; + color: var(--text); + font-weight: 800; + line-height: 1.45; +} + +.archive-list a:hover { + border-color: rgba(62, 184, 190, 0.32); + background: rgba(255, 255, 255, 0.82); + box-shadow: 0 12px 28px rgba(58, 51, 38, 0.1); + transform: translateY(-1px); } .pagination { @@ -3897,9 +4031,49 @@ meting-js { grid-template-columns: 1fr; } - .archive-list a { + .archive-timeline { + padding-left: 24px; + } + + .archive-timeline::before { + left: 7px; + } + + .archive-year { grid-template-columns: 1fr; - gap: 2px; + gap: 14px; + } + + .archive-year-marker { + left: -25px; + } + + .archive-year-head { + position: static; + } + + .archive-month { + grid-template-columns: 1fr; + gap: 12px; + padding: 16px; + } + + .archive-month-label { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 12px; + } + + .archive-list a { + grid-template-columns: 38px minmax(0, 1fr); + gap: 10px; + padding: 8px; + } + + .archive-list a time { + width: 34px; + height: 34px; } .stats-chart {