初始化:Astro 站点 + Sveltia CMS 后台 + 部署配置
This commit is contained in:
87
src/pages/projects/[...id].astro
Normal file
87
src/pages/projects/[...id].astro
Normal 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>
|
||||
106
src/pages/projects/index.astro
Normal file
106
src/pages/projects/index.astro
Normal 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>
|
||||
Reference in New Issue
Block a user