@jet-w/astro-blog 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/dist/chunk-FXPGR372.js +0 -0
  2. package/dist/chunk-GYLSY3OJ.js +173 -0
  3. package/dist/config/index.d.ts +166 -0
  4. package/dist/config/index.js +38 -0
  5. package/dist/index.d.ts +34 -0
  6. package/dist/index.js +59 -0
  7. package/dist/types/index.d.ts +75 -0
  8. package/dist/types/index.js +1 -0
  9. package/package.json +84 -0
  10. package/src/components/EChartsCard.vue +118 -0
  11. package/src/components/Mermaid.vue +73 -0
  12. package/src/components/about/ContentCard.astro +27 -0
  13. package/src/components/about/IconCard.astro +77 -0
  14. package/src/components/about/SocialLinks.astro +54 -0
  15. package/src/components/about/TagCard.astro +65 -0
  16. package/src/components/about/TagGroup.astro +33 -0
  17. package/src/components/about/TimelineCard.astro +52 -0
  18. package/src/components/blog/FloatingToc.vue +198 -0
  19. package/src/components/blog/Hero.astro +147 -0
  20. package/src/components/blog/NavigationTabs.vue +245 -0
  21. package/src/components/blog/PostCard.astro +161 -0
  22. package/src/components/blog/PostNavigation.astro +106 -0
  23. package/src/components/blog/RelatedPosts.astro +175 -0
  24. package/src/components/blog/TableOfContents.astro +153 -0
  25. package/src/components/blog/TagCloud.astro +91 -0
  26. package/src/components/home/FeaturedPostsSection.astro +54 -0
  27. package/src/components/home/QuickNavSection.astro +81 -0
  28. package/src/components/home/RecentPostsSection.astro +52 -0
  29. package/src/components/home/StatsSection.astro +44 -0
  30. package/src/components/layout/Footer.astro +103 -0
  31. package/src/components/layout/Header.astro +68 -0
  32. package/src/components/layout/Sidebar.astro +594 -0
  33. package/src/components/media/Bilibili.astro +114 -0
  34. package/src/components/media/Slides.astro +313 -0
  35. package/src/components/media/Video.astro +111 -0
  36. package/src/components/media/VideoPlayer.astro +89 -0
  37. package/src/components/media/YouTube.astro +92 -0
  38. package/src/components/pte/StudyCalendar.vue +1348 -0
  39. package/src/components/ui/Icon.astro +187 -0
  40. package/src/components/ui/MobileMenu.vue +201 -0
  41. package/src/components/ui/Pagination.astro +143 -0
  42. package/src/components/ui/SearchBox.vue +179 -0
  43. package/src/components/ui/SearchInterface.vue +409 -0
  44. package/src/components/ui/SidebarToggle.vue +57 -0
  45. package/src/components/ui/ThemeToggle.vue +90 -0
  46. package/src/layouts/AboutLayout.astro +18 -0
  47. package/src/layouts/BaseLayout.astro +362 -0
  48. package/src/layouts/PageLayout.astro +217 -0
  49. package/src/layouts/SlidesLayout.astro +320 -0
  50. package/src/plugins/rehype-clean-containers.mjs +24 -0
  51. package/src/plugins/rehype-relative-links.mjs +43 -0
  52. package/src/plugins/rehype-tabs.mjs +116 -0
  53. package/src/plugins/remark-containers.mjs +407 -0
  54. package/src/plugins/remark-mermaid.mjs +46 -0
  55. package/src/styles/global.css +870 -0
  56. package/src/styles/slides.css +220 -0
  57. package/src/utils/sidebar.ts +492 -0
  58. package/templates/default/astro.config.mjs +51 -0
  59. package/templates/default/content/pages/about.mdx +93 -0
  60. package/templates/default/content/pages/index.mdx +20 -0
  61. package/templates/default/content/posts/blog_docs/01-quick-start.md +162 -0
  62. package/templates/default/content/posts/blog_docs/02-frontmatter.md +277 -0
  63. package/templates/default/content/posts/blog_docs/03-markdown-basic.md +350 -0
  64. package/templates/default/content/posts/blog_docs/04-containers.md +331 -0
  65. package/templates/default/content/posts/blog_docs/05-code-blocks.md +388 -0
  66. package/templates/default/content/posts/blog_docs/06-mermaid.md +431 -0
  67. package/templates/default/content/posts/blog_docs/07-video.md +243 -0
  68. package/templates/default/content/posts/blog_docs/08-latex.md +382 -0
  69. package/templates/default/content/posts/blog_docs/09-icons.md +326 -0
  70. package/templates/default/content/posts/blog_docs/10-sidebar.md +445 -0
  71. package/templates/default/content/posts/blog_docs/11-config.md +334 -0
  72. package/templates/default/content/posts/blog_docs/12-slides.mdx +552 -0
  73. package/templates/default/content/posts/blog_docs/README.md +151 -0
  74. package/templates/default/content/slides/demo.md +146 -0
  75. package/templates/default/content/slides/docs/basic-demo.md +35 -0
  76. package/templates/default/content/slides/docs/code-demo.md +62 -0
  77. package/templates/default/content/slides/docs/echarts-demo.md +139 -0
  78. package/templates/default/content/slides/docs/fragment-demo.md +35 -0
  79. package/templates/default/content/slides/docs/math-demo.md +48 -0
  80. package/templates/default/content/slides/docs/mermaid-demo.md +105 -0
  81. package/templates/default/content/slides/docs/theme-demo.md +38 -0
  82. package/templates/default/content/slides/docs/vertical-demo.md +50 -0
  83. package/templates/default/package.json +31 -0
  84. package/templates/default/public/favicon-bak.svg +4 -0
  85. package/templates/default/public/images/avatar.jpg +0 -0
  86. package/templates/default/public/images/avatar.svg +142 -0
  87. package/templates/default/public/js/mermaid-container.js +402 -0
  88. package/templates/default/public/js/mermaid-init.js +131 -0
  89. package/templates/default/public/js/mermaid-render.js +98 -0
  90. package/templates/default/public/js/mermaid-simple.js +95 -0
  91. package/templates/default/public/js/tabs-init.js +86 -0
  92. package/templates/default/public/media/individual_portfolio/INDIVIDUAL PORTFOLIO.png +0 -0
  93. package/templates/default/public/slides/plugin/highlight/highlight.js +5 -0
  94. package/templates/default/public/slides/plugin/highlight/monokai.css +71 -0
  95. package/templates/default/public/slides/plugin/markdown/markdown.js +7 -0
  96. package/templates/default/public/slides/plugin/math/math.js +1 -0
  97. package/templates/default/public/slides/plugin/notes/notes.js +1 -0
  98. package/templates/default/public/slides/reveal.css +9 -0
  99. package/templates/default/public/slides/reveal.js +9 -0
  100. package/templates/default/public/slides/theme/beige.css +366 -0
  101. package/templates/default/public/slides/theme/black-contrast.css +362 -0
  102. package/templates/default/public/slides/theme/black.css +359 -0
  103. package/templates/default/public/slides/theme/blood.css +392 -0
  104. package/templates/default/public/slides/theme/dracula.css +385 -0
  105. package/templates/default/public/slides/theme/league.css +368 -0
  106. package/templates/default/public/slides/theme/moon.css +362 -0
  107. package/templates/default/public/slides/theme/night.css +360 -0
  108. package/templates/default/public/slides/theme/serif.css +363 -0
  109. package/templates/default/public/slides/theme/simple.css +362 -0
  110. package/templates/default/public/slides/theme/sky.css +370 -0
  111. package/templates/default/public/slides/theme/solarized.css +363 -0
  112. package/templates/default/public/slides/theme/white-contrast.css +362 -0
  113. package/templates/default/public/slides/theme/white.css +359 -0
  114. package/templates/default/public/slides/theme/white_contrast_compact_verbatim_headers.css +360 -0
  115. package/templates/default/public/test-complete.html +43 -0
  116. package/templates/default/public/test-mermaid.html +124 -0
  117. package/templates/default/src/config/index.ts +114 -0
  118. package/templates/default/src/content.config.ts +96 -0
  119. package/templates/default/src/pages/[...slug].astro +27 -0
  120. package/templates/default/src/pages/archives/[year]/[month]/page/[page].astro +176 -0
  121. package/templates/default/src/pages/archives/[year]/[month].astro +158 -0
  122. package/templates/default/src/pages/archives/index.astro +210 -0
  123. package/templates/default/src/pages/categories/[category]/page/[page].astro +218 -0
  124. package/templates/default/src/pages/categories/[category].astro +198 -0
  125. package/templates/default/src/pages/categories/index.astro +190 -0
  126. package/templates/default/src/pages/container-test.astro +79 -0
  127. package/templates/default/src/pages/mermaid-direct.html +78 -0
  128. package/templates/default/src/pages/posts/[...slug].astro +335 -0
  129. package/templates/default/src/pages/posts/index.astro +541 -0
  130. package/templates/default/src/pages/posts/page/[page].astro +146 -0
  131. package/templates/default/src/pages/rss.xml.ts +28 -0
  132. package/templates/default/src/pages/search-index.json.ts +21 -0
  133. package/templates/default/src/pages/search.astro +50 -0
  134. package/templates/default/src/pages/slides/[...slug].astro +54 -0
  135. package/templates/default/src/pages/slides/index.astro +135 -0
  136. package/templates/default/src/pages/tags/[tag]/page/[page].astro +211 -0
  137. package/templates/default/src/pages/tags/[tag].astro +191 -0
  138. package/templates/default/src/pages/tags/index.astro +167 -0
  139. package/templates/default/tailwind.config.mjs +78 -0
  140. package/templates/default/tsconfig.json +9 -0
@@ -0,0 +1,335 @@
1
+ ---
2
+ import { getCollection, type CollectionEntry, render } from 'astro:content';
3
+ import PageLayout from '@jet-w/astro-blog/layouts/PageLayout.astro';
4
+ import SlidesLayout from '@jet-w/astro-blog/layouts/SlidesLayout.astro';
5
+ import FloatingToc from '@jet-w/astro-blog/components/blog/FloatingToc.vue';
6
+
7
+ export async function getStaticPaths() {
8
+ const posts = await getCollection('posts');
9
+
10
+ // 按文件名称排序,readme.md 排在每个文件夹的最前面
11
+ const sortedPosts = posts.sort((a, b) => {
12
+ // 获取文件夹路径和文件名
13
+ const aDir = a.id.includes('/') ? a.id.substring(0, a.id.lastIndexOf('/')) : '';
14
+ const bDir = b.id.includes('/') ? b.id.substring(0, b.id.lastIndexOf('/')) : '';
15
+ const aFile = a.id.includes('/') ? a.id.substring(a.id.lastIndexOf('/') + 1) : a.id;
16
+ const bFile = b.id.includes('/') ? b.id.substring(b.id.lastIndexOf('/') + 1) : b.id;
17
+
18
+ // 先按文件夹排序
19
+ if (aDir !== bDir) {
20
+ return aDir.localeCompare(bDir);
21
+ }
22
+
23
+ // 同一文件夹内,readme 排最前面
24
+ const aIsReadme = aFile.toLowerCase() === 'readme' || aFile.toLowerCase() === 'readme.md';
25
+ const bIsReadme = bFile.toLowerCase() === 'readme' || bFile.toLowerCase() === 'readme.md';
26
+
27
+ if (aIsReadme && !bIsReadme) return -1;
28
+ if (!aIsReadme && bIsReadme) return 1;
29
+
30
+ // 其他文件按字母顺序排序
31
+ return aFile.localeCompare(bFile);
32
+ });
33
+
34
+ // 收集所有目录路径
35
+ const directories = new Set<string>();
36
+ posts.forEach(post => {
37
+ const parts = post.id.split('/');
38
+ let currentPath = '';
39
+ for (let i = 0; i < parts.length - 1; i++) {
40
+ currentPath = currentPath ? `${currentPath}/${parts[i]}` : parts[i];
41
+ directories.add(currentPath);
42
+ }
43
+ });
44
+
45
+ // 定义路径类型
46
+ type PathEntry = {
47
+ params: { slug: string };
48
+ props: {
49
+ post: CollectionEntry<'posts'> | null;
50
+ prevPost: CollectionEntry<'posts'> | null;
51
+ nextPost: CollectionEntry<'posts'> | null;
52
+ isDirectory: boolean;
53
+ };
54
+ };
55
+
56
+ // 生成文章路径
57
+ const paths: PathEntry[] = sortedPosts.map((post, index) => ({
58
+ params: { slug: post.id.toLowerCase() },
59
+ props: {
60
+ post,
61
+ prevPost: index > 0 ? sortedPosts[index - 1] : null,
62
+ nextPost: index < sortedPosts.length - 1 ? sortedPosts[index + 1] : null,
63
+ isDirectory: false,
64
+ },
65
+ }));
66
+
67
+ // 生成目录路径(查找对应的 README)
68
+ directories.forEach(dir => {
69
+ const readmePost = posts.find(post => {
70
+ const postId = post.id.toLowerCase();
71
+ return postId === `${dir.toLowerCase()}/readme` ||
72
+ postId === `${dir.toLowerCase()}/readme.md` ||
73
+ postId === `${dir.toLowerCase()}/readme.mdx`;
74
+ });
75
+
76
+ // 只有当目录路径不与现有文章冲突时才添加
77
+ const hasConflict = posts.some(post => post.id.toLowerCase() === dir.toLowerCase());
78
+ if (!hasConflict) {
79
+ paths.push({
80
+ params: { slug: dir.toLowerCase() },
81
+ props: {
82
+ post: readmePost || null,
83
+ prevPost: null,
84
+ nextPost: null,
85
+ isDirectory: true,
86
+ },
87
+ });
88
+ }
89
+ });
90
+
91
+ return paths;
92
+ }
93
+
94
+ type Props = {
95
+ post: CollectionEntry<'posts'> | null;
96
+ prevPost: CollectionEntry<'posts'> | null;
97
+ nextPost: CollectionEntry<'posts'> | null;
98
+ isDirectory?: boolean;
99
+ };
100
+
101
+ const { post, prevPost, nextPost, isDirectory } = Astro.props;
102
+ const Content = post ? (await render(post)).Content : null;
103
+
104
+ // 判断是否使用 slides 布局
105
+ const isSlides = post?.data.layout === 'slides';
106
+
107
+ // 解析 Markdown 为幻灯片结构(当 layout: slides 时)
108
+ function parseSlides(markdown: string): string[][] {
109
+ const cleanMarkdown = markdown
110
+ .replace(/<!--[\s\S]*?-->/g, '')
111
+ .trim();
112
+ const horizontalSlides = cleanMarkdown.split(/\n---\n/);
113
+ return horizontalSlides.map((hSlide) => {
114
+ return hSlide.split(/\n----\n/).map(s => s.trim());
115
+ });
116
+ }
117
+
118
+ const slidesData = isSlides && post?.body ? parseSlides(post.body) : [];
119
+
120
+ // 计算阅读时间(简单估算)
121
+ const readingTime = post ? Math.ceil((post.body?.length || 0) / 1000) : 0;
122
+
123
+ // 格式化日期
124
+ const formatDate = (date: Date) => {
125
+ return new Intl.DateTimeFormat('zh-CN', {
126
+ year: 'numeric',
127
+ month: 'long',
128
+ day: 'numeric',
129
+ }).format(date);
130
+ };
131
+ ---
132
+
133
+ {post && isSlides ? (
134
+ <!-- Slides 布局 -->
135
+ <SlidesLayout
136
+ title={post.data.title}
137
+ description={post.data.description}
138
+ theme={post.data.theme}
139
+ transition={post.data.transition}
140
+ controls={post.data.controls}
141
+ progress={post.data.progress}
142
+ center={post.data.center}
143
+ slideNumber={post.data.slideNumber}
144
+ >
145
+ {slidesData.map((verticalSlides) => (
146
+ <section>
147
+ {verticalSlides.map((slideContent) => (
148
+ <section data-markdown>
149
+ <textarea data-template set:text={slideContent}></textarea>
150
+ </section>
151
+ ))}
152
+ </section>
153
+ ))}
154
+ </SlidesLayout>
155
+ ) : post ? (
156
+ <PageLayout
157
+ title={post.data.title}
158
+ description={post.data.description}
159
+ showSidebar={true}
160
+ >
161
+ <!-- 浮动目录 -->
162
+ <FloatingToc client:load />
163
+
164
+ <!-- 文章头部 -->
165
+ <article class="prose prose-slate dark:prose-invert max-w-none">
166
+ <!-- 标题区域 -->
167
+ <header class="not-prose mb-12">
168
+ <div class="text-center">
169
+ <h1 class="text-4xl md:text-5xl font-bold text-slate-900 dark:text-slate-100 mb-6 leading-tight">
170
+ {post.data.title}
171
+ </h1>
172
+
173
+ <p class="text-xl text-slate-600 dark:text-slate-400 mb-8 max-w-3xl mx-auto">
174
+ {post.data.description}
175
+ </p>
176
+
177
+ <!-- 文章元信息 -->
178
+ <div class="flex flex-wrap items-center justify-center gap-6 text-sm text-slate-500 dark:text-slate-400">
179
+ <div class="flex items-center space-x-2">
180
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
181
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
182
+ </svg>
183
+ <span>{post.data.author || '作者'}</span>
184
+ </div>
185
+
186
+ {post.data.pubDate && (
187
+ <div class="flex items-center space-x-2">
188
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
189
+ <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 00-2 2v12a2 2 0 002 2z" />
190
+ </svg>
191
+ <time datetime={post.data.pubDate.toISOString()}>
192
+ {formatDate(post.data.pubDate)}
193
+ </time>
194
+ </div>
195
+ )}
196
+
197
+ <div class="flex items-center space-x-2">
198
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
199
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
200
+ </svg>
201
+ <span>约 {readingTime} 分钟阅读</span>
202
+ </div>
203
+ </div>
204
+
205
+ <!-- 标签 -->
206
+ {post.data.tags && post.data.tags.length > 0 && (
207
+ <div class="flex flex-wrap justify-center gap-2 mt-6">
208
+ {post.data.tags.map((tag) => (
209
+ <a
210
+ href={`/tags/${tag}`}
211
+ class="px-3 py-1 text-xs font-medium bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 rounded-full hover:bg-primary-200 dark:hover:bg-primary-900/50 transition-colors"
212
+ >
213
+ #{tag}
214
+ </a>
215
+ ))}
216
+ </div>
217
+ )}
218
+ </div>
219
+
220
+ <!-- 封面图片 -->
221
+ {post.data.image && (
222
+ <div class="mt-12 rounded-xl overflow-hidden">
223
+ <img
224
+ src={post.data.image}
225
+ alt={post.data.title}
226
+ class="w-full h-64 md:h-96 object-cover"
227
+ loading="lazy"
228
+ />
229
+ </div>
230
+ )}
231
+ </header>
232
+
233
+ <!-- 文章内容 -->
234
+ <div class="prose prose-slate dark:prose-invert max-w-none prose-lg">
235
+ {Content && <Content />}
236
+ </div>
237
+
238
+ <!-- 文章底部 -->
239
+ <footer class="not-prose mt-16 pt-8 border-t border-slate-200 dark:border-slate-700">
240
+ <!-- 分类信息 -->
241
+ {post.data.categories && post.data.categories.length > 0 && (
242
+ <div class="mb-8">
243
+ <h3 class="text-lg font-semibold text-slate-900 dark:text-slate-100 mb-4">
244
+ 分类
245
+ </h3>
246
+ <div class="flex flex-wrap gap-2">
247
+ {post.data.categories.map((category) => (
248
+ <a
249
+ href={`/categories/${category}`}
250
+ class="px-4 py-2 text-sm font-medium bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 rounded-lg hover:bg-slate-200 dark:hover:bg-slate-700 transition-colors"
251
+ >
252
+ {category}
253
+ </a>
254
+ ))}
255
+ </div>
256
+ </div>
257
+ )}
258
+
259
+ <!-- 更新时间 -->
260
+ {post.data.updatedDate && post.data.updatedDate !== post.data.pubDate && (
261
+ <div class="text-sm text-slate-500 dark:text-slate-400">
262
+ 最后更新:{formatDate(post.data.updatedDate)}
263
+ </div>
264
+ )}
265
+ </footer>
266
+ </article>
267
+
268
+ <!-- 文章导航 -->
269
+ <nav class="mt-16 pt-8 border-t border-slate-200 dark:border-slate-700">
270
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
271
+ <!-- 上一篇 -->
272
+ <div class="text-center md:text-left">
273
+ <div class="text-sm text-slate-500 dark:text-slate-400 mb-2">上一篇</div>
274
+ {prevPost ? (
275
+ <a
276
+ href={`/posts/${prevPost.id.toLowerCase()}`}
277
+ class="group inline-flex items-center text-primary-500 hover:text-primary-600 dark:hover:text-primary-400 font-medium transition-colors"
278
+ >
279
+ <svg class="w-4 h-4 mr-2 group-hover:-translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
280
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
281
+ </svg>
282
+ <span class="line-clamp-1">{prevPost.data.title}</span>
283
+ </a>
284
+ ) : (
285
+ <span class="text-slate-400 dark:text-slate-600">已经是第一篇了</span>
286
+ )}
287
+ </div>
288
+
289
+ <!-- 下一篇 -->
290
+ <div class="text-center md:text-right">
291
+ <div class="text-sm text-slate-500 dark:text-slate-400 mb-2">下一篇</div>
292
+ {nextPost ? (
293
+ <a
294
+ href={`/posts/${nextPost.id.toLowerCase()}`}
295
+ class="group inline-flex items-center justify-end text-primary-500 hover:text-primary-600 dark:hover:text-primary-400 font-medium transition-colors"
296
+ >
297
+ <span class="line-clamp-1">{nextPost.data.title}</span>
298
+ <svg class="w-4 h-4 ml-2 group-hover:translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
299
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
300
+ </svg>
301
+ </a>
302
+ ) : (
303
+ <span class="text-slate-400 dark:text-slate-600">已经是最后一篇了</span>
304
+ )}
305
+ </div>
306
+ </div>
307
+ </nav>
308
+ </PageLayout>
309
+ ) : (
310
+ <PageLayout
311
+ title="目录"
312
+ description="此目录暂无内容"
313
+ showSidebar={true}
314
+ >
315
+ <div class="text-center py-16">
316
+ <div class="inline-flex items-center justify-center w-20 h-20 bg-slate-100 dark:bg-slate-800 rounded-full mb-6">
317
+ <svg class="w-10 h-10 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
318
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
319
+ </svg>
320
+ </div>
321
+ <h1 class="text-2xl font-bold text-slate-900 dark:text-slate-100 mb-4">
322
+ 此目录暂无内容
323
+ </h1>
324
+ <p class="text-slate-600 dark:text-slate-400 mb-8">
325
+ 该目录下暂无 README 文件
326
+ </p>
327
+ <a href="/posts" class="inline-flex items-center px-4 py-2 bg-primary-500 text-white rounded-lg hover:bg-primary-600 transition-colors">
328
+ <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
329
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
330
+ </svg>
331
+ 返回文章列表
332
+ </a>
333
+ </div>
334
+ </PageLayout>
335
+ )}