初始化:Astro 站点 + Sveltia CMS 后台 + 部署配置

This commit is contained in:
2026-06-11 17:18:51 +08:00
commit 53092b52db
37 changed files with 7586 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
---
import { getCollection, render } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro';
export async function getStaticPaths() {
const projects = await getCollection('projects');
return projects.map((p) => ({
params: { id: p.id },
props: { project: p },
}));
}
const { project } = Astro.props;
const { Content } = await render(project);
const d = project.data;
---
<BaseLayout title={d.title} description={d.description}>
<article class="prose">
<a href="/projects" class="back mono">← 返回项目</a>
<div class="meta">
<span class="tag">{d.category}</span>
{d.date && <time class="mono muted">{d.date.toISOString().slice(0, 10)}</time>}
</div>
<h1>{d.title}</h1>
<p class="lead muted">{d.description}</p>
{
d.tags.length > 0 && (
<div class="tags mono">
{d.tags.map((t) => <span>#{t}</span>)}
</div>
)
}
{
(d.repo || d.link) && (
<div class="links">
{d.repo && <a class="btn" href={d.repo} target="_blank" rel="noopener">源码</a>}
{d.link && <a class="btn btn-primary" href={d.link} target="_blank" rel="noopener">访问</a>}
</div>
)
}
<hr />
<div class="content">
<Content />
</div>
</article>
</BaseLayout>
<style>
.prose {
max-width: 760px;
margin: 0 auto;
padding: 2.5rem 0;
}
.back {
font-size: 0.85rem;
display: inline-block;
margin-bottom: 1.5rem;
}
.meta {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 0.5rem;
}
.meta time {
font-size: 0.82rem;
}
.prose h1 {
margin: 0.25rem 0 0.5rem;
}
.lead {
font-size: 1.05rem;
}
.tags {
display: flex;
gap: 0.75rem;
color: var(--text-faint);
font-size: 0.8rem;
margin-top: 0.75rem;
}
.links {
display: flex;
gap: 0.75rem;
margin-top: 1.25rem;
}
</style>

View File

@@ -0,0 +1,106 @@
---
import { getCollection } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro';
import ProjectCard from '../../components/ProjectCard.astro';
const projects = (await getCollection('projects')).sort(
(a, b) => b.data.order - a.data.order
);
const categories = ['全部', '硬件', '软件', '通信', '机器人', '其他'];
---
<BaseLayout title="项目" description="电子、软件、通信与机器人方向的项目展示">
<section class="page-head">
<h1>项目</h1>
<p class="muted">电子工程、软件、通信与机器人方向的作品与产品。</p>
</section>
{
projects.length > 0 ? (
<>
<div class="filters mono" id="filters">
{categories.map((c) => (
<button class="filter" data-cat={c}>
{c}
</button>
))}
</div>
<div class="grid" id="grid">
{projects.map((p) => (
<div class="grid-item" data-cat={p.data.category}>
<ProjectCard
title={p.data.title}
description={p.data.description}
category={p.data.category}
tags={p.data.tags}
href={`/projects/${p.id}`}
/>
</div>
))}
</div>
</>
) : (
<p class="muted">
暂无项目。在 <code>src/content/projects/</code> 新建 Markdown 文件即可添加。
</p>
)
}
</BaseLayout>
<script>
const buttons = document.querySelectorAll<HTMLButtonElement>('.filter');
const items = document.querySelectorAll<HTMLElement>('.grid-item');
buttons[0]?.classList.add('active');
buttons.forEach((btn) => {
btn.addEventListener('click', () => {
buttons.forEach((b) => b.classList.remove('active'));
btn.classList.add('active');
const cat = btn.dataset.cat;
items.forEach((item) => {
item.style.display =
cat === '全部' || item.dataset.cat === cat ? '' : 'none';
});
});
});
</script>
<style>
.page-head {
padding: 3rem 0 1.5rem;
}
.page-head h1 {
margin: 0 0 0.5rem;
}
.filters {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 1.5rem;
}
.filter {
background: var(--bg-elev);
border: 1px solid var(--border);
color: var(--text-dim);
border-radius: 999px;
padding: 0.3rem 0.85rem;
font-size: 0.8rem;
font-family: var(--font-mono);
cursor: pointer;
transition: all 0.15s ease;
}
.filter:hover {
color: var(--text);
border-color: var(--border-strong);
}
.filter.active {
color: var(--accent-strong);
border-color: var(--accent-dim);
background: rgba(57, 208, 216, 0.1);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 1rem;
}
</style>