feat(pages): /books 书架与 /opensource 开源页——导航第四项(T2/T5)
- BooksPage:色脊排版卡(mood 竖脊+文楷书名+作者+三行截断策展语)
× 年份分组规整网格;zh/en 双路由;阅读轨标题旁「书架 →」入口
- /opensource:全量仓墙(ProjectWall 加 limit prop,首页精选 6 不变)
+ 热力图 + 本站开源叙事段;导航第四项「开源」(i18n nav_opensource)
- 五书 frontmatter 策展字段(含泥潭=刘楚昕)
验证: build 28 页 exit 0;books/opensource zh/en 五路由产物断言过;
五书卡与作者 grep 在案;E2E 30/30 PASS
Spec: 2026-10-25_add-terminal-books-admin
Co-authored-by: GLM-5.3
This commit is contained in:
1 parent
3bd8369eda
commit
90574edce7
9 files changed
+180
-2
No files matched your search
@@ -13,6 +13,7 @@ const nav = [
|
||||
{ href: '/archives/', label: t('nav_posts', curLocale) },
|
||||
{ href: '/gallery/', label: t('nav_gallery', curLocale) },
|
||||
{ href: '/about/', label: t('nav_about', curLocale) },
|
||||
{ href: '/opensource/', label: t('nav_opensource', curLocale) },
|
||||
];
|
||||
const switchHref = localePath(path, curLocale === 'zh' ? 'en' : 'zh');
|
||||
const switchLabel = curLocale === 'zh' ? 'EN' : '中';
|
||||
|
||||
@@ -41,7 +41,12 @@ const mmdd = (d: Date) =>
|
||||
if (rows.length === 0) return null;
|
||||
return (
|
||||
<div class="track-block">
|
||||
<h3 class="track">{TRACK_LABEL[cat]}</h3>
|
||||
<h3 class="track">
|
||||
{TRACK_LABEL[cat]}
|
||||
{cat === 'reading' && (
|
||||
<a class="shelf-link" href={locale === 'en' ? '/en/books/' : '/books/'}>{locale === 'en' ? 'Books →' : '书架 →'}</a>
|
||||
)}
|
||||
</h3>
|
||||
<ul>
|
||||
{rows.map((post) => (
|
||||
<li>
|
||||
@@ -79,6 +84,7 @@ const mmdd = (d: Date) =>
|
||||
color: var(--ink-3);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.shelf-link { float: right; font-size: 12.5px; color: var(--accent); font-weight: 400; }
|
||||
.track {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
// 双源数据见 src/lib/projects.ts;语言色点用 linguist 常用色映射
|
||||
import { loadProjects, type RepoCard } from '../../lib/projects';
|
||||
|
||||
interface Props {
|
||||
/** 展示上限:首页精选 6;/opensource 全量传大数 */
|
||||
limit?: number;
|
||||
}
|
||||
const { limit = 6 } = Astro.props;
|
||||
const LANG_COLORS: Record<string, string> = {
|
||||
Astro: '#ff5a03', TypeScript: '#3178c6', JavaScript: '#f1e05a',
|
||||
Python: '#3572a5', Go: '#00add8', Rust: '#dea584', Shell: '#89e051',
|
||||
@@ -11,7 +16,7 @@ const LANG_COLORS: Record<string, string> = {
|
||||
|
||||
let projects: RepoCard[] = [];
|
||||
try {
|
||||
projects = await loadProjects();
|
||||
projects = (await loadProjects()).slice(0, limit);
|
||||
} catch {
|
||||
projects = []; // 兜底已在 lib 内处理,此处再守一层:空墙不破构建
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
---
|
||||
// 书评索引画廊(/books):色脊排版卡 × 年份分组规整网格
|
||||
// 预研方案 B:色脊天然安全(mood 深浅跨亮暗对比度风险回避),零 JS
|
||||
import { getCollection } from 'astro:content';
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import { t } from '../../i18n/ui';
|
||||
import type { Locale } from '../../lib/locale';
|
||||
|
||||
interface Props { locale?: Locale }
|
||||
const { locale = 'zh' } = Astro.props;
|
||||
|
||||
const books = (await getCollection('posts', ({ data }) => !data.draft && data.category === 'reading' && !data.tags.includes('hidden')))
|
||||
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
|
||||
|
||||
const years = [...new Set(books.map((b) => b.data.pubDate.getFullYear()))].sort((a, b) => b - a);
|
||||
const L = {
|
||||
shelf: locale === 'zh' ? '书架' : 'Books',
|
||||
count: (n: number) => (locale === 'zh' ? `${n} 本` : `${n} books`),
|
||||
};
|
||||
---
|
||||
|
||||
<BaseLayout title={`${L.shelf} · wait4x`} description={locale === 'zh' ? '读过的书与一句策展' : 'Books read, one line each'}>
|
||||
<h1 class="page-title">{L.shelf}<span class="count mono">{L.count(books.length)}</span></h1>
|
||||
{years.map((year) => (
|
||||
<section class="year-group">
|
||||
<h2 class="y-label mono">{year}</h2>
|
||||
<ul class="shelf">
|
||||
{books.filter((b) => b.data.pubDate.getFullYear() === year).map((b) => (
|
||||
<li>
|
||||
<a
|
||||
class={`book ${locale === 'en' ? 'en' : ''}`}
|
||||
href={locale === 'en' ? `/en/archives/${b.id}/` : `/archives/${b.id}/`}
|
||||
style={`--mood:${b.data.mood ?? 'var(--ink-4)'}`}
|
||||
>
|
||||
<i class="spine" aria-hidden="true"></i>
|
||||
<div class="bk-body">
|
||||
<strong class="bk-title">{b.data.title}</strong>
|
||||
{b.data.bookAuthor && <span class="bk-author">{b.data.bookAuthor}</span>}
|
||||
{b.data.description && <span class="bk-desc">{b.data.description}</span>}
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
))}
|
||||
</BaseLayout>
|
||||
|
||||
<style>
|
||||
.page-title {
|
||||
font-size: var(--text-h1);
|
||||
color: var(--ink-1);
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
.count { font-size: 13px; color: var(--ink-3); }
|
||||
.year-group { margin-top: var(--space-5); }
|
||||
.y-label { font-size: 16px; color: var(--ink-3); margin-bottom: var(--space-3); }
|
||||
.shelf {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: var(--space-3);
|
||||
}
|
||||
/* 色脊卡:纸底 + mood 竖脊 + 文楷书名;hover 边框转 mood(.repo 同语言) */
|
||||
.book {
|
||||
display: flex;
|
||||
min-height: 120px;
|
||||
background: var(--paper);
|
||||
border: 1px solid var(--paper-line);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
|
||||
}
|
||||
.book:hover { border-color: var(--mood); transform: translateY(-2px); }
|
||||
.spine {
|
||||
flex: none;
|
||||
width: 10px;
|
||||
background: var(--mood);
|
||||
border-radius: 10px 0 0 10px;
|
||||
}
|
||||
.bk-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
min-width: 0;
|
||||
}
|
||||
.bk-title {
|
||||
font-family: var(--font-kai);
|
||||
font-size: 17px;
|
||||
color: var(--paper-ink);
|
||||
line-height: 1.35;
|
||||
}
|
||||
.en .bk-title { font-family: var(--font-serif); }
|
||||
.bk-author {
|
||||
font-size: 12.5px;
|
||||
color: var(--ink-3);
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
.bk-desc {
|
||||
margin-top: 4px;
|
||||
font-size: 13px;
|
||||
color: var(--ink-2);
|
||||
font-family: var(--font-sans);
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user