--- // 首页(zh/en 共用):名片区 / 文章列表 / 最新影像 / 热力图 / 项目墙 / 心跳 // 两个 index.astro 只是薄壳(传 locale),区块逻辑与样式集中在这里,避免双份漂移。 import { Image } from 'astro:assets'; import { getCollection } from 'astro:content'; import BaseLayout from '../../layouts/BaseLayout.astro'; import IdCard from '../IdCard.astro'; import PostList from '../PostList.astro'; import ProjectWall from '../geek/ProjectWall.astro'; import Heatmap from '../geek/Heatmap.astro'; import SocialLinks from '../SocialLinks.astro'; import { t } from '../../i18n/ui'; import { sortPosts } from '../../lib/post-order'; import type { Locale } from '../../lib/locale'; import { homeConfig, homeTagline, pageTypography, renderMarkdown, renderRich, } from '../../lib/site-content'; import { fetchHeartbeat, agoZh } from '../../lib/heartbeat'; interface Props { locale?: Locale; } const { locale = 'zh' } = Astro.props; // 首页可配项(后台主页设置):副标题/影像张数/区块开关/每轨条数/项目墙条数/公告/轨细分 const cfg = await homeConfig(locale); const subtitleHtml = cfg.subtitle ? await renderMarkdown(cfg.subtitle) : ''; const announcementHtml = cfg.announcement ? await renderRich(cfg.announcement) : ''; // 置顶优先 → 时间倒序(sortPosts 契约见 src/lib/post-order.ts) const posts = sortPosts( await getCollection('posts', ({ data }) => !data.draft && !data.tags.includes('hidden')), ); // en 轨列表标题吃 postsEn 译题(缺译回退 zh,与 /en/archives 同策略) const enTitle = locale === 'en' ? Object.fromEntries((await getCollection('postsEn')).map((e) => [e.id, e.data.title])) : undefined; // 最新影像取集合前 N 张;照片时间序由照片管线治理,此处只裁切 const photos = (await getCollection('photos')).slice(0, Math.max(1, cfg.latestPhotos)); const tagline = await homeTagline(locale); const taglineHtml = await renderRich(tagline, { inline: true }); const typo = await pageTypography('siteHome', 'home'); // 心跳只出现在中文首页(en 轨历史形态如此,保持零回归) const heartbeat = locale === 'zh' ? await fetchHeartbeat() : null; const galleryHref = locale === 'en' ? '/en/gallery/' : '/gallery/'; // 轨细分开关:只保留开启的轨道(全开时等价于不过滤,零回归) const activeTracks = Object.entries(cfg.postsTracks) .filter(([, on]) => on) .map(([k]) => k); const allTracksOn = activeTracks.length === Object.keys(cfg.postsTracks).length; const visiblePosts = allTracksOn ? posts : posts.filter((p) => activeTracks.includes(p.data.category)); ---

{subtitleHtml &&

} {announcementHtml && (
)} {cfg.sections.tracks && ( )} {cfg.sections.photos && cfg.latestPhotos > 0 && (
{t('latest_photos', locale)} {t('enter_gallery', locale)}
)} {cfg.sections.heatmap && ( )} {cfg.sections.projects && } {heartbeat && (

最近心跳:{agoZh(heartbeat.when)} · {heartbeat.msg}

)}