1. 从一份“能跑但看不懂”的 Astro 简历项目说起
个人简历网站搭建到第二步,很多人会卡在同一个地方:项目能npm run dev跑起来,页面也能打开,但打开src目录一看,layouts、components、pages、styles 混在一起,不知道哪个文件管哪块,改一处样式整页跟着乱。这篇就聚焦这个场景——先解析原有 Astro 项目结构,再用BaseLayout.astro和index.astro把首页骨架搭出来,配合 DaisyUI 与global.css完成样式接入,最后用 TaoToken 统一 Key/API 通道把 AI 辅助开发链路打通。
适合谁看:已经用 Astro 初始化过项目、或者从 GitHub 拉了一份简历模板但读不懂结构的人;想用 DaisyUI 快速做抽屉布局 + 卡片首页的人;以及希望把 AI 编码助手接进日常开发流、不想每次手动配一堆 Key 的人。整篇按“读结构 → 定骨架 → 写首页 → 接样式 → 验证 → 排障”的顺序走,每一步都给可复制的代码和命令,跟着敲就能看到页面变化。
我试过把一份抽屉布局的简历模板从零拆解,最深的感受是:Astro 的页面组织其实非常线性,BaseLayout负责全站外壳,index.astro只负责往slot里填内容,真正让人迷糊的是样式和交互散落在global.css与内联<script>里。把这条线理清,后面加项目页、加中英文切换都会顺很多。
2. 解析原有结构:先外壳,再内容,再路由
2.1 三层阅读法
拿到一个陌生 Astro 项目,别急着改代码,按“外壳 → 内容 → 路由”三层读,效率最高。
第一层看全站外壳,重点盯这几个文件:
astro.config.mjs src/layouts/BaseLayout.astro src/components/SideBar.astro src/components/Header.astro src/styles/global.css看什么:侧边栏宽度在哪定义、主内容区最大宽度在哪限制、移动端和桌面端怎么切换、语言切换按钮放哪最合适。
第二层看首页内容:
src/pages/index.astro src/components/HorizontalCard.astro src/components/Card.astro看什么:首页是静态写死还是抽成了数据数组、动效适合加在 hero 还是卡片、每个项目卡片现在怎么跳转。
第三层看数据和路由:
src/content/config.ts src/content/blog src/content/store src/pages/blog/[...page].astro src/pages/store/[...page].astro如果项目里已经有“内容集合 + 列表页 + 详情页”的模式,你的“项目页”可以直接照这个模式新增一套,不用另起炉灶。
2.2 目录结构对照
一个典型的 Astro 简历项目,整理后大致长这样:
my-resume/ ├── astro.config.mjs ├── package.json ├── public/ │ ├── profile.webp │ └── favicon.svg └── src/ ├── layouts/ │ └── BaseLayout.astro ├── components/ │ ├── SideBar.astro │ └── Header.astro ├── pages/ │ └── index.astro └── styles/ └── global.cssBaseLayout.astro是全站模板,index.astro这类页面只负责往slot里填内容。理解这一点,后面所有改动都有落点。
2.3 编辑器与命令行准备
第一次写 Astro,VS Code 里建议装这几个插件:Astro 官方插件(提供.astro语法高亮、诊断、跳转)、Tailwind CSS IntelliSense(项目大量用 Tailwind / DaisyUI,装了更好读 class)、ESLint 和 Prettier(可选,保持格式统一)。
命令行侧建议装:
npm install npx astro checkastro check能提前发现.astro文件里的类型和语法问题。前端预览用:
npm run dev和纯 HTML 不同,Astro 没有 Live Server 那种点击即看的插件,需要手动跑 dev server,然后浏览器访问终端输出的本地地址。
3. TaoToken 前置:把 AI 辅助开发链路接进来
3.1 为什么简历项目也需要统一通道
搭简历网站看着是纯前端活,但实际开发里你会频繁让 AI 帮你做这些事:解释一段看不懂的global.css、生成 DaisyUI 卡片结构、把index.astro里的静态文案抽成数据数组、排查astro check报的类型错误。如果每个工具都单独配 Key,切换一次就要改一次配置,很碎。
TaoToken 在这里的作用是提供一个统一的 Key 和 API 通道,让模型对话、编码助手、Agent 类工具都走同一个入口。官网入口:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api (不加 UTM)。
3.2 在 settings.json 中配置统一通道
以常见的编码助手配置为例,在项目的.vscode/settings.json或用户级 settings 里写入统一通道片段:
{ "aiAssistant.provider": "openai-compatible", "aiAssistant.baseUrl": "https://taotoken.net/api", "aiAssistant.apiKey": "sk-你的TaoTokenKey", "aiAssistant.model": "claude-sonnet-4-5", "aiAssistant.maxTokens": 8192, "aiAssistant.temperature": 0.3 }几个参数说明:baseUrl指向统一 API 入口,apiKey换成你在控制台生成的 Key,model按你实际订阅的模型填,temperature调低一点更适合读代码和生成结构化配置。
注意:Key 不要提交到 Git。把
.vscode/settings.json里含 Key 的部分放到本地用户配置,或者用环境变量注入,仓库里只留不含密钥的模板。
3.3 拿 Key 与验证通道
进入控制台生成 API Key:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,然后在 API Keys 页面管理密钥:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。生成后先用一条最小请求验证通道是否通:
curl https://taotoken.net/api/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-你的TaoTokenKey" \ -d '{ "model": "claude-sonnet-4-5", "messages": [{"role": "user", "content": "用一句话说明 Astro 的 slot 是什么"}] }'返回里有正常的choices内容,说明通道可用。如果只是想在网页里先验证模型行为,可以直接用模型对话入口:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite 。
4. 可复制配置:BaseLayout 与 index.astro 骨架
4.1 BaseLayout.astro 全站骨架
BaseLayout.astro的职责是规定每一页的整体外壳:统一头部、侧边栏、主内容区、动画、主题,让不同页面只往slot里填自己的内容。
--- import BaseHead from "../components/BaseHead.astro"; import SideBar from "../components/SideBar.astro"; import { SITE_TITLE, SITE_DESCRIPTION } from "../consts"; interface Props { title?: string; description?: string; includeSidebar?: boolean; sideBarActiveItemID?: string; } const { title = SITE_TITLE, description = SITE_DESCRIPTION, includeSidebar = true, sideBarActiveItemID = "", } = Astro.props; --- <!doctype html> <html lang="zh-CN">--- import { Image } from "astro:assets"; --- <div class="drawer-side z-40"> <label for="my-drawer" class="drawer-overlay"></label> <aside class="sidebar-shell"> <div class="sidebar-panel"> <div class="sidebar-top"> <a href="/" class="avatar"> <Image src="/profile.webp" alt="头像" width={44} height={44} format="webp" class="w-11 h-11 rounded-lg" /> </a> </div> <div class="sidebar-body"> <label for="my-drawer" class="sidebar-item sidebar-toggle" aria-label="Toggle sidebar"> <svg class="sidebar-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" /> </svg> <span class="sidebar-text">Toggle</span> </label> <a href="/" class="sidebar-item"> <svg class="sidebar-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l9-9 9 9M5 10v10h14V10" /> </svg> <span class="sidebar-text">Homepage</span> </a> <a href="#" class="sidebar-item"> <svg class="sidebar-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15a3 3 0 100-6 3 3 0 000 6z" /> </svg> <span class="sidebar-text">Settings</span> </a> </div> </div> </aside> </div>4.3 global.css 样式接入
global.css主要做四件事:侧栏宽度滑动、文字显隐动画、非侧栏灰层覆盖、图标菜单统一尺寸。
@import "tailwindcss"; @plugin "daisyui" { themes: silk --default; } .sidebar-shell { width: 3.5rem; min-height: 100vh; background: hsl(var(--b2)); border-right: 1px solid hsl(var(--b3)); transition: width 340ms ease; overflow: hidden; } #my-drawer:checked ~ .drawer-side .sidebar-shell { width: 10rem; } .sidebar-panel { display: flex; flex-direction: column; min-height: 100vh; padding: 0.5rem; } .sidebar-body { display: grid; gap: 0.5rem; margin-top: 1rem; } .sidebar-item { display: flex; align-items: center; gap: 0.75rem; min-height: 2.75rem; padding: 0.75rem 0.8rem; border-radius: 0.75rem; white-space: nowrap; } .sidebar-item:hover { background: color-mix(in oklab, hsl(var(--bc)) 8%, transparent); } .sidebar-icon { width: 1rem; height: 1rem; flex-shrink: 0; } .sidebar-text { max-width: 0; opacity: 0; transform: translateX(-6px); overflow: hidden; transition: max-width 300ms ease, opacity 300ms ease, transform 300ms ease; } #my-drawer:checked ~ .drawer-side .sidebar-text { max-width: 10rem; opacity: 1; transform: translateX(0); }提示:原模板里
.sidebar-top的justify-content和.sidebar-item的对齐规则存在重复覆盖,上面这版做了精简,只保留生效的那条,后续维护更省心。
4.4 index.astro 首页骨架
首页不再依赖骨架里的上边栏,而是自己定义顶部目录、个人信息首屏、教育经历、职业经历、技术实践,以及滚动高亮和卡片切换逻辑。
--- import BaseLayout from "../layouts/BaseLayout.astro"; const workExperiences = [ { company: "Arrakis Green FinTech", location: "中国 香港", role: "AI 独立研究员", time: "2024 - 至今", logoSrc: "/logos/arrakis.webp", logoAlt: "Arrakis" }, { company: "国泰君安证券", location: "中国 深圳", role: "量化研究实习生", time: "2023 - 2024", logoSrc: "/logos/gtja.webp", logoAlt: "国泰君安" }, { company: "Goldman Sachs", location: "中国 香港", role: "量化研究实习生", time: "2022 - 2023", logoSrc: "/logos/gs.webp", logoAlt: "Goldman Sachs" }, ]; const techPracticeGroups = [ { id: "quant", title: "Quant", description: "量化策略、回测框架与因子研究相关实践。", items: [ { title: "多因子回测框架", description: "基于 Python 的因子回测与绩效归因。", image: "/projects/quant-1.webp", alt: "多因子回测框架" }, { title: "期权定价工具", description: "蒙特卡洛与解析解对比的定价模块。", image: "/projects/quant-2.webp", alt: "期权定价工具" }, { title: "行情数据管道", description: "多源行情清洗与增量落库。", image: "/projects/quant-3.webp", alt: "行情数据管道" }, ], }, { id: "ai", title: "AI", description: "大模型应用、检索增强与智能体相关实践。", items: [ { title: "RAG 知识库", description: "文档切分、向量检索与重排。", image: "/projects/ai-1.webp", alt: "RAG 知识库" }, { title: "编码助手工作流", description: "统一 Key 通道接入日常开发。", image: "/projects/ai-2.webp", alt: "编码助手工作流" }, { title: "简历站点生成器", description: "用 Astro 快速产出个人主页。", image: "/projects/ai-3.webp", alt: "简历站点生成器" }, ], }, { id: "web3", title: "Web3", description: "链上数据、合约交互与去中心化应用实践。", items: [ { title: "链上数据看板", description: "地址行为与资金流向可视化。", image: "/projects/web3-1.webp", alt: "链上数据看板" }, { title: "合约交互脚本", description: "批量调用与事件监听。", image: "/projects/web3-2.webp", alt: "合约交互脚本" }, { title: "钱包签名工具", description: "离线签名与验签流程。", image: "/projects/web3-3.webp", alt: "钱包签名工具" }, ], }, { id: "others", title: "Others", description: "工程效率、自动化脚本与其他杂项。", items: [ { title: "CI 自动部署", description: "提交即构建与发布。", image: "/projects/other-1.webp", alt: "CI 自动部署" }, { title: "日志聚合脚本", description: "多机日志收集与检索。", image: "/projects/other-2.webp", alt: "日志聚合脚本" }, { title: "文档站点", description: "Markdown 驱动的知识库。", image: "/projects/other-3.webp", alt: "文档站点" }, ], }, ]; --- <BaseLayout sideBarActiveItemID="home"> <header id="page-header" class="sticky top-0 z-30 bg-base-100/80 backdrop-blur"> <div class="mx-auto max-w-6xl h-12 flex items-center justify-between px-6 sm:px-10 lg:px-14"> <nav class="page-nav flex gap-6 overflow-x-auto"> <a href="#personal-info" class="page-nav-link page-nav-link-active">npm install npm run dev终端会输出本地地址,通常是http://localhost:4321。浏览器打开后,你应该看到:左侧抽屉栏默认展开,宽度约 10rem,头像和菜单文字都可见;顶部有一条吸顶目录栏,右侧是品牌文字;正文首屏是右对齐的大标题加自我介绍,右侧是头像;往下依次是教育经历时间线、职业经历横向时间线、技术实践标签页加卡片网格。
5.2 交互验证清单
按下面几项逐个点一遍,确认骨架真的通了:
点击左侧 Toggle 菜单项,抽屉应平滑收起到约 3.5rem,菜单文字淡出,只留图标;再点一次恢复展开。
滚动页面,顶部目录的高亮项应随当前区块切换,从“个人信息”依次走到“技术实践”。
点击顶部目录任意一项,页面应平滑滚动到对应区块,且区块顶部不会被吸顶栏盖住(这就是scroll-margin-top和--page-header-offset的作用)。
点击技术实践的 Quant / AI / Web3 / Others 标签,下方三张卡片的图片、标题、描述应整体替换,第一张卡片默认高亮。
鼠标移入任意卡片,该卡片饱和度恢复、边框变主题色、阴影增强。
5.3 用 AI 辅助验证配置
如果想让 AI 帮你检查这段index.astro有没有类型问题,可以把文件内容贴进模型对话入口:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model-chat&utm_campaign=rewrite ,让它重点看techPracticeGroups的数据结构和define:vars注入的变量在浏览器端是否一致。长期做编码和 Agent 类工作的话,可以了解 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,接入文档在:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。
6. 本篇常见错排查
6.1 抽屉点了没反应
先确认BaseLayout.astro里那个隐藏 checkbox 的id和SideBar.astro里label的for是否一致,两边都必须是my-drawer。再看global.css里控制宽度的选择器是不是#my-drawer:checked ~ .drawer-side .sidebar-shell,如果 DOM 层级变了,~兄弟选择器就失效,需要改成对应的后代选择器。
6.2 侧栏文字一直显示或一直隐藏
.sidebar-text的默认态是max-width: 0; opacity: 0,展开态由#my-drawer:checked触发。如果文字一直显示,检查是不是漏了默认态;如果一直隐藏,检查展开态选择器有没有写对。另外overflow: hidden必须保留,否则文字会溢出而不是被裁掉。
6.3 顶部目录高亮不切换
IntersectionObserver依赖data-page-link和区块id一一对应。如果某个目录项的data-page-link写成了education而区块id是education-section,sections数组里就会缺一项,观察不到。逐个核对两边的值即可。rootMargin调得太激进也会导致高亮跳变,先用默认的-20% 0px -60% 0px跑通再微调。
6.4 技术实践切换后卡片不更新
renderGroup里通过data-tech-image、data-tech-title、data-tech-copy找节点,如果模板里漏了某个data-*属性,对应字段就不会更新。另外define:vars注入的techPracticeGroups必须是可序列化的纯数据,里面不能有函数或组件引用,否则浏览器端拿不到。
6.5 astro check 报类型错误
常见的是Astro.props没定义interface Props,或者define:vars的变量在<script>里被当成未声明。前者补上interface Props,后者确认define:vars的键名和脚本里用的名字完全一致。跑npx astro check会给出具体行号,按提示改就行。
6.6 样式不生效
DaisyUI 主题要在global.css里通过@plugin "daisyui"声明,data-theme="silk"写在<html>上。如果 Tailwind 的@import没放在文件最前面,后面的自定义类可能被覆盖。改完global.css记得重启 dev server,热更新偶尔会漏掉 CSS 变更。
6.7 锚点跳转被吸顶栏盖住
吸顶栏高度是动态的,所以用syncHeaderOffset把header.offsetHeight + 18写进--page-header-offset,再由scroll-margin-top消费。如果跳转还是贴顶,检查syncHeaderOffset有没有在DOMContentLoaded之后执行,以及header元素是否真的拿到了高度。
把上面这些点过一遍,首页骨架基本就稳了。下一步可以在这个结构上继续加项目详情页和中英文切换,BaseLayout和index.astro的分工已经很清楚,新增页面只需要复用同一套外壳。