
Travel Guidebook
- 46 installs
- 76 repo stars
- Updated April 19, 2026
- geekjourneyx/travel-guidebook
Create designed travel guidebook PDFs from trip itineraries through an end-to-end flow from deep research to Playwright PDF export, using Tabler Icons and inline SVG instead of AI images.
About
Runs a multi-stage pipeline from requirement confirmation and parallel research through HTML delivery to Playwright PDF export, producing a typographically designed travel guide. A developer or traveler uses it to turn trip notes and itineraries into a printable, professionally styled guidebook.
- End-to-end stages from research to HTML to PDF export via Playwright
- Zero AI-generated images; uses Tabler Icons and inline SVG decoration
Travel Guidebook by the numbers
- 46 all-time installs (skills.sh)
- Ranked #376 of 688 Office & Documents skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/geekjourneyx/travel-guidebook --skill travel-guidebookAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 46 |
|---|---|
| repo stars | ★ 76 |
| Last updated | April 19, 2026 |
| Repository | geekjourneyx/travel-guidebook ↗ |
What it does
Create designed travel guidebook PDFs from trip itineraries through an end-to-end flow from deep research to Playwright PDF export, using Tabler Icons and inline SVG instead of AI images.
Files
Travel Guidebook Maker
从调研到成书的一站式旅游路书生成引擎——零 AI 插图、零 emoji、纯 CDN 引入。通过 Tabler Icons + 内联 SVG 装饰 + Claude 设计美学,打造有温度、有设计感的旅行指南 PDF。
设计哲学
Monocle 的编辑品味 + DK 的视觉叙事 + Lonely Planet 的信息深度 + Claude 的温暖美学
这不是一本冰冷的导航手册,而是一本有温度的旅行伙伴——在羊皮纸般的暖色底上,用衬线体讲述目的地的故事,用图标系统标记功能信息,用装饰元素营造编辑出版感。
核心理念:排版即设计。 不依赖 AI 生图或照片,通过字体层级、功能色系统、内联 SVG 装饰、CSS 伪元素,让路书本身就具备设计感。
---
总体流程
Stage 0 环境准备 → Stage 1 需求确认
→ Stage 2 深度调研 [5 个 explore agent 并行]
→ Stage 3 路线架构
→ Stage 4 内容写作 [专职 general-purpose agent]
→ Stage 5 HTML 交付与检查 → Stage 6 PDF 导出Stage 0 自动检测 Node.js + Playwright 环境;Stage 2 并行调研加速信息收集;Stage 4 专职 agent 在干净上下文中写 HTML 保证一致性;Stage 5 是 HTML 检查点——用户确认满意后再执行 Stage 6 导出 PDF。
---
Stage 0: 环境准备
在路书制作开始前,确保 PDF 导出环境就绪。
按以下顺序逐项检测,任一步骤失败则停止并提示用户:
1. Node.js
node --version- ✅ v18.x 或更高 → 继续
- ❌ 命令不存在 → 停止,提示安装:https://nodejs.org/
2. package.json
ls package.json- ✅ 存在 → 继续
- ❌ 不存在 →
npm init -y
3. Playwright
node -e "require('playwright')"- ✅ 无报错 → 继续
- ❌ 报错 →
npm install playwright && npx playwright install chromium
4. html2pdf.mjs
已 bundled 在 scripts/html2pdf.mjs,Stage 6 时复制到工作目录。
5. 高德地图 MCP(可选增强)
尝试调用高德 MCP 工具验证可用性:
调用 maps_whether("{目的地城市}")- ✅ 返回天气数据 → 标记
AMAP_AVAILABLE = true - ❌ 工具不存在或报错 → 标记
AMAP_AVAILABLE = false
高德 MCP 是增强层——不可用时路书照常生成,仅精确数据降级为 LLM 估算。
用户未配置时的提示(仅提一次,不阻塞流程):
"💡 检测到高德地图 MCP 未配置。路书仍会正常生成,但距离/交通/天气数据将使用估算值。如需精确数据,可在 MCP 配置中添加 @amap/amap-maps-mcp-server。"环境就绪确认
所有检测通过后输出:
"✅ 环境准备就绪(Node.js {版本} + Playwright + Chromium{高德可用时加:+ 高德地图 MCP})。开始制作路书。"
---
Stage 1: 需求确认
向用户确认以下参数(能从指令推断的不问,推断不了的才问):
收到,准备制作旅游路书。请确认以下信息:
>
1. 路线名称:「{从指令推断}」,可以吗?
2. 旅行类型:自驾游 / 徒步 / 城市漫游 / 混合?
3. 目标读者:深度文化游 / 轻松休闲游 / 探险挑战游?
4. 总天数:{推断天数},对吗?
5. 个性化信息:需要在封面或末尾加上个人/团队信息吗?
>
直接回复修改项即可,没问题的我直接开始。
快捷模式:用户已说明足够信息时,跳过确认直接执行。
默认值:旅行类型=自驾游,目标读者=深度文化游,天数=根据路线推断,个性化信息=无。
智能路由
需求确认后,根据用户输入决定起始 Stage:
| 用户提供的内容 | 跳转到 | 说明 |
|---|---|---|
| 仅目的地/天数 | Stage 2 | 标准全流程 |
| 已有行程文件(Markdown/文本) | Stage 3 | 读取行程提取架构,跳过调研 |
| 已有行程 + "已确认/不用调研" | Stage 4 | 直接进入写作排版 |
| 已有 HTML 路书 | Stage 5 | 直接进入检查,可修改后导出 PDF |
判断依据:用户是否附带了 @文件名 引用、是否明确说"行程已确认"、"不需要调研"等。遇到不确定的情况,默认走完整流程,但主动问一句"需要我做调研还是直接开始写?"。
---
Stage 2: 深度调研(并行 Sub-Agent)
旅游路书的调研需要同时覆盖实用信息和文化深度。采用 并行 explore agent 加速调研——每个方向一个独立 agent,同时出发,互不阻塞。
并行调研架构
主 Agent explore agent 池(并行)
┌──────────┐ ┌─────────────────────────┐
│ 构造 5+1 │ ── background ──→│ research-transport │
│ agent │ ── background ──→│ research-attractions │
│ prompt │ ── background ──→│ research-food │
│ │ ── background ──→│ research-culture │
│ │ ── background ──→│ research-practical │
│ │ ── background ──→│ research-spatial (高德) │
│ │ └─────────────────────────┘
│ 等通知 │ ←── 自动通知 ─── (各 agent 完成后)
│ 收集合并 │ ── read_agent ──→ 获取每个 agent 结果
│ 输出报告 │
└──────────┘research-spatial仅在AMAP_AVAILABLE = true时启动。不可用时只启 5 个 agent。
### Agent 分工
使用 `task` 工具,`agent_type: "explore"`,`mode: "background"`,同时启动 5+1 个 agent:
| Agent 名 | 调研方向 | 搜索关键词示例(中英结合) |
|----------|---------|----------------------|
| `research-transport` | 交通路线 | "{起点}到{终点} 高铁/自驾 时刻 价格", "{destination} transport" |
| `research-attractions` | 核心景点 | "{目的地} 必去景点 门票 开放时间 TOP10", "{destination} attractions" |
| `research-food` | 当地美食 | "{目的地} 特色美食 推荐餐厅 美食街 人均", "{destination} local food" |
| `research-culture` | 文化历史 | "{目的地} 历史 文化 典故 民俗 方言", "{destination} history culture" |
| `research-practical` | 实用信息 | "{目的地} {月份}天气 预算 安全 注意事项", "{destination} travel tips" |
> 自驾游加第 7 个 agent `research-road`(路况、加油站、海拔变化)。
### 高德空间数据 Agent(AMAP_AVAILABLE = true 时)
第 6 个 agent `research-spatial`,专职采集高德地图数据:
你是空间数据采集专员。使用高德地图 MCP 工具采集以下数据。
目的地:{目的地} 已知景点列表:{从需求确认中提取的所有景点名称}
任务 1: 景点 POI 数据
对每个景点调用 maps_text_search("{景点名}"),提取:
- POI ID, 名称, 完整地址, 经纬度, 评分, 营业时间, 联系电话
任务 2: 周边发现
对每个核心景点(每天的主要景点)调用 maps_around_search:
- 关键词="餐厅|小吃", location={景点坐标}, radius=1000 → 附近餐厅 top 5
- 关键词="便利店|药店", location={景点坐标}, radius=500 → 应急设施
任务 3: 天气预报
maps_whether("{目的地城市名称或 adcode}")
任务 4: 地理编码
对所有地名调用 maps_gep 获取精确坐标,供 Stage 3 路径规划使用
输出格式
景点 POI 汇总
| 景点 | 地址 | 经纬度 | 评分 | 营业时间 |
|---|---|---|---|---|
| ... | ... | ... | ... | ... |
周边发现
{景点名} 周边
| 名称 | 类型 | 距离 | 评分 |
|---|---|---|---|
| ... | ... | ... | ... |
天气预报
| 日期 | 天气 | 温度 | 风力 |
|---|---|---|---|
| ... | ... | ... | ... |
> 自驾游加第 6 个 agent `research-road`(路况、加油站、海拔变化)。
### Agent Prompt 模板
每个 agent 的 prompt 遵循以下结构:
你是旅游调研专员,负责【{方向}】调研。
任务参数
- 目的地:{目的地}
- 出发地:{出发地}
- 日期:{具体日期}
- 天数:{N} 天
- 旅行类型:{自驾/高铁/混合}
调研要求
1. 使用 bash 执行 curl 或 web_fetch 获取以下 URL(逐个尝试,失败跳过):
- {预设 URL 列表,3-5 个相关网站}
2. 每个信源提取关键数据点 3. 如果所有 URL 都失败,基于已有知识整理,标注"⚠️ 模型知识"
输出格式(严格遵循)
{方向名}
核心发现
- 发现1:{具体数据}(来源:{URL})
- 发现2:...
推荐列表
| 名称 | 详情 | 价格 | 备注 |
|---|---|---|---|
| ... | ... | ... | ... |
信源
- {URL1}
- {URL2}
数据时效
- 以上信息获取于 {当前日期},建议出行前复核价格和开放时间
### 结果收集与合并
所有 agent 完成后:
1. 逐个 `read_agent` 获取结果
2. 合并为统一调研报告,按方向分章节
3. 标注每个数据点的来源(URL 或"模型知识")
4. 如果某个 agent 失败,主 agent 补充该方向的 LLM 知识
### 降级策略
| 场景 | 处理方式 |
|------|---------|
| task 工具不可用 | 退回**串行模式**:主 agent 逐方向 web_fetch |
| 部分 agent 超时/失败 | 主 agent 用 LLM 知识补充失败方向 |
| research-spatial 失败 | 距离/时间用 LLM 估算,标注"约"前缀 |
| 完全无法联网 | **离线模式**:全部基于 LLM 知识,标注"⚠️ 建议出行前核实" |
## 调研质量要求
- 并行模式:每个 agent 至少 3 个有效信源,总计 ≥ 15 个
- 串行模式:信源总数 ≥ 20 个(≤3 天短途:≥ 10 个)
- 离线模式:尽可能丰富,标注数据时效不确定
- 优先:官方旅游网站、资深旅行博主、当地媒体、专业论坛
- 价格信息须注明时效性
- 关键事实至少 2 个独立信源交叉验证
---
# Stage 3: 路线架构
基于调研结果生成每日行程架构。当高德空间数据可用时,使用**真实路径规划**替代 LLM 估算。
## 高德路径规划(AMAP_AVAILABLE = true 时)
Stage 2 的 `research-spatial` agent 已提供所有景点的精确坐标。在此基础上,主 agent 调用高德路径规划 API:
对每日行程中的连续景点对,根据旅行类型选择 API:
旅行类型 │ API │ 说明 ──────────┼──────────────────────────────────────┼────────── 自驾游 │ maps_direction_driving(A坐标, B坐标) │ 里程/用时/过路费 城市漫游 │ maps_direction_walking (≤3km) │ 步行距离/用时 │ maps_direction_transit_integrated (>3km)│ 地铁/公交方案 骑行段 │ maps_bicycling │ 骑行路线/距离 跨城移动 │ maps_direction_transit_integrated │ 火车/大巴方案 所有模式 │ maps_distance │ 每日总距离验证
> 高德不可用时,使用 LLM 知识估算距离和时间,在路书中用"约"前缀标注。
## Stage 3.1: 行程可行性门控(Feasibility Gate)
> **第一性原理**:行程是物理约束下的规划,不是景点的自由组合。
> 在写入任何每日行程前,必须通过以下验证——不通过则强制拆分或删除景点。
### 规则一:单日往返可行性公式
可用总时长 = 出发时间到返回时间(一般 12h,高原区域 10h)
验证公式: 出发点 → 景点A(单程用时)× 2 ← 来回交通 + 各景点游览时间之和 + 景点间交通用时之和 + 缓冲时间(1h) ≤ 可用总时长
不满足 → 必须拆分为多天 或 删去最远景点
**计算工具**(优先级):
- AMAP_AVAILABLE = true → 调用 `maps_direction_driving` / `maps_distance` 获取真实用时
- AMAP_AVAILABLE = false → 用 LLM 知识估算,并在路书中标注"约"
### 规则二:跨城景点强制隔离
| 距出发地单程距离 | 强制规则 |
|----------------|---------|
| ≤ 80km | 可与其他景点同天 |
| 80–150km | 同天内只能有该景点,无其他目的地 |
| > 150km | **强制独立成一天,或安排沿途住宿** |
> 典型陷阱:拉萨出发,羊卓雍错(100km)可单独一日游;日喀则扎什伦布寺/白居寺(280km)**绝对不可**与任何拉萨景点或羊湖捆绑在同一天。
### 规则三:特殊目的地约束
**高原地区(海拔 3500m+)**:
| 到达后天数 | 允许活动范围 | 禁止 |
|-----------|------------|------|
| Day 1 | 酒店周边步行,≤1h | 一切景区游览 |
| Day 2 | 市区平坦景点,≤3h | 海拔 4000m+ 景点 |
| Day 3+ | 正常游览市区 | 海拔 4500m+ 建议 Day 4 以后 |
| Day 4+ | 可前往高海拔(羊湖 4441m) | — |
| Day 5+ | 可尝试极高海拔(纳木错 4718m) | 仍需带氧气 |
**强制执行**:高原行程的 Day 1 不得安排任何收费景点,违反时自动将景点后移一天。
### 规则四:生成后审核清单
每日行程草稿生成后,必须逐一校验:
□ 当日总里程(往返)是否在可行范围? □ 所有景点是否在同一合理区域内?(不可跨越多个城市) □ 是否存在隐含的"长距离跳跃"?(如 A→B→C 看似相邻实则绕远) □ 高原行程:是否违反海拔适应规则? □ 含包车/自驾的一日游:是否有足够的观景/停留时间(≥2h)? □ 返程时间是否预留充足?(天黑前返回)
如任何一项不通过 → **红色警示**,重新规划该天行程后方可进入 Stage 4。
---
## 节奏设计
- **张弛有度**:长途驾驶日 + 深度游览日交替
- **高潮设计**:最精华目的地安排在行程中段(Day 3-5)
- **缓冲时间**:每 3-4 天安排"自由日"或"休整日"
- **情绪曲线**:出发兴奋 → 适应期 → 高潮 → 回味 → 离别不舍
## 每日行程结构
Day X: [起点] → [终点] ├─ 里程:XXkm(高德实测 / LLM 估算) ├─ 预计用时:Xh(含游览时间) ├─ 交通方案(高德可用时): │ ├─ 景点A→景点B:步行 12min / 0.9km │ ├─ 景点B→景点C:地铁2号线 3站 / 15min │ └─ 景点C→酒店:公交 537路 / 25min ├─ 天气(高德可用时):☁️ 多云 26°C ├─ 今日亮点:一句话概括 ├─ 必停点:2-3 个精选(含高德评分) ├─ 周边发现(高德可用时):景点周边高分餐厅/咖啡馆 ├─ 美食推荐:1-2 个 ├─ 住宿推荐:1-2 个 └─ 贴士:含天气建议 + 实际交通建议
> 标注"高德可用时"的字段在 `AMAP_AVAILABLE = false` 时省略或用 LLM 估算替代。
## 用户确认
展示行程架构供确认或修改,确认后进入 Stage 4。
**快捷模式**:用户要求"直接写不用确认"时跳过。
---
# Stage 4: 内容写作与排版
这是路书的核心阶段——将调研和架构转化为设计精美的 HTML 路书。
## 执行模式选择
根据可用工具自动选择最优模式:
| 条件 | 模式 | 说明 |
|------|------|------|
| task 工具可用 | **专职 Agent 模式**(推荐) | 启动 1 个 general-purpose agent 在干净上下文中写全部 HTML |
| task 不可用 | **主 Agent 直写模式** | 主 agent 用 heredoc 分批写入(传统方式) |
### 专职 Agent 模式(推荐)
**核心优势**:HTML 写作 agent 拥有干净上下文——不含调研过程、历史对话、路线讨论的噪音。从第一行写到最后一行,风格完全统一。
启动 1 个 `general-purpose` agent(`mode: "background"`),将以下内容**完整传入** prompt:
你是旅游路书 HTML 写作专家。请生成一个完整的单文件 HTML 路书。
路书参数
- 路线名称:{名称}
- 文件名:{名称}_guidebook.html
- 旅行日期:{日期}
- 天数:{N} 天
- 旅行类型:{类型}
CSS 规范(完整复制 references/layout-css.md 内容)
{layout-css.md 全文}
章节模板(完整复制 references/chapter-templates.md 内容)
{chapter-templates.md 全文}
调研数据(Stage 2 合并报告)
{调研报告全文}
路线架构(Stage 3 确认版)
{每日行程架构}
写作风格
三层需求驱动:
- 定向(Where)——精准清晰:地名、方向、里程用时准确
- 决策(What)——替用户筛选:每天 2-3 个必停点
- 情感(Why)——赋予意义:融入历史故事和当地人视角
技术要求
1. 使用 heredoc 分批写入(cat > 首批,cat >> 后续),单引号 << 'HTMLEOF' 2. ¥ 用 ¥ 替代,— 用 —,→ 用 →,° 用 ° 3. 每批写完后 wc -c 确认文件增长 4. 总批次 3-5 批,按逻辑段落分割 5. 所有 CSS 内嵌 <style>,所有 SVG 内联,零外部图片 6. 最后一批必须包含 </body></html> 闭合标签
**主 agent 在等待期间**:可做其他轻量工作或提示用户"正在生成 HTML,预计 2-3 分钟"。
Agent 完成后:
1. `read_agent` 获取结果,确认 HTML 文件已生成
2. `wc -c` 验证文件大小(5 天行程应在 50-80KB)
3. 进入 Stage 5
### 主 Agent 直写模式(降级)
当 task 工具不可用时,主 agent 直接使用 heredoc 分批写入。
## 章节结构
路书采用五部分结构。阅读 `references/chapter-templates.md` 获取每个章节的完整 HTML 模板:
1. **出发前**:封面 / 旅程概览 / 行前准备
2. **每日行程**:Day-by-Day(核心主体)——日程卡片、路线描述、必停点、美食、住宿、贴士、笔记区
3. **目的地深度**:2-3 个重要目的地的深度介绍
4. **实用附录**:紧急联系、常用语言、费用明细、推荐歌单
5. **封底**:旅行语录 + 个性化信息 + 旅行感言空间
## 排版规范
路书视觉设计继承 Claude 暖色调美学。阅读 `references/layout-css.md` 获取完整 CSS 规范,涵盖:
- 外部资源(仅 Google Fonts + Tabler Icons CDN)
- 页面布局(A4, max-width 680px)
- 字体系统(Noto Serif SC / Noto Sans SC / JetBrains Mono)
- 配色方案(Parchment 羊皮纸底 + Terracotta 赤陶色 + 功能色系统)
- 装饰元素(指南针、分隔线、DAY 徽章、卡片角标、首字下沉)
- 分页控制(封面独占、Day 新页、卡片不拆分)
- 打印适配(保留暖色调背景)
## 写作风格
三层需求驱动写作:
- **定向(Where)**——精准清晰:地名、方向、里程用时必须准确,使用具身化描述("左手边"、"前方 200 米")
- **决策(What)**——替用户筛选:每个目的地只推荐 2-3 个必停点,用"必停/可选/时间充裕可去"标记优先级
- **情感(Why)**——赋予意义:每个地点讲"为什么值得来",融入历史故事和当地人视角,用文学化语言描述风景
### 情感增强写作(高德数据驱动)
当高德空间数据可用时,应用以下四种情感增强模式:
**1. 距离叙事**——将精确距离转化为可感知的体验:
- ≤500m → "几分钟脚程" + 途中能看到/闻到/听到什么
- 500m-2km → "散步的距离" + 沿途风景描述
- \>2km → 交通建议 + 车窗外的风景
- 示例:"出黄鹤楼南门左转,沿武珞路走 600 米——大约一首歌的时间——就到了户部巷。"
**2. 周边惊喜**——呈现高德发现的高分 POI(LLM 可能不知道的新店/小店):
- 用"💡 旅途发现"卡片呈现
- 包含:名称、距离、评分、一句推荐理由
**3. 时间感知**——天气 + 距离 + 时间段 = 具体体验建议:
- 示例:"今日 26°C 多云——步行长江大桥的完美天气。桥面 1.6km,不急不慢走完约 22 分钟。建议 16:00 出发,走到桥中央正好赶上日落。"
**4. 安心感**——在每日贴士中嵌入周边便利设施:
- 示例:"今晚住宿 200m 内有全家便利店(24h),最近药房在 400m 外武珞路上。"
> 高德不可用时,这四种模式自动省略——路书仍有完整的文化叙事和 LLM 估算数据。
## HTML 生成(heredoc 分批策略)
生成一个完整的单 HTML 文件,所有 CSS 内嵌 `<style>`,所有 SVG 装饰内联,零外部图片。
> 以下 heredoc 策略适用于**专职 Agent 的内部执行**和**主 Agent 直写降级模式**。
### 大文件写入策略
路书 HTML 通常 50-80KB(5 天行程约 30 页),超出 write/create 工具的单次上限。采用 **heredoc 分批追加**策略:
第 1 批:HTML head + 完整 CSS(<style> 标签)
cat > {文件名}_guidebook.html << 'HTMLEOF' <!DOCTYPE html> <html lang="zh-CN"> <head>...</head> <style>/ 全部 CSS,参照 references/layout-css.md /</style> <body> HTMLEOF
第 2 批:封面 + 概览 + 行前准备
cat >> {文件名}_guidebook.html << 'HTMLEOF' <div class="cover">...</div> <div class="overview">...</div> <div class="preparation">...</div> HTMLEOF
第 3-N 批:每日行程(每天一批或每 2 天一批)
cat >> {文件名}_guidebook.html << 'HTMLEOF' <div class="day-chapter">...</div> HTMLEOF
最后一批:深度游 + 附录 + 封底 + 闭合标签
cat >> {文件名}_guidebook.html << 'HTMLEOF' <div class="deep-dive">...</div> <div class="appendix">...</div> <div class="back-cover">...</div> </body></html> HTMLEOF
**关键注意**:
- 使用**单引号** heredoc(`<< 'HTMLEOF'`)防止 shell 变量展开
- `¥` 符号在 heredoc 中用 `¥` 替代,避免编码问题
- 每批写完后用 `wc -c` 确认文件增长正常
- 总批次控制在 3-5 批,按逻辑段落分割(不要在 HTML 标签中间断开)
---
# Stage 5: HTML 交付与检查
## 交付内容
1. **HTML 文件**:`{路线名称}_guidebook.html`(单文件,零外部依赖)
2. **工作报告**:制作过程记录(模板见 `references/report-template.md`)
## HTML 检查点
HTML 完成后,必须先让用户检查,不能直接跳到 PDF:
> "HTML 路书已生成:`{文件名}_guidebook.html`。请在浏览器中打开检查内容和排版。满意后告诉我,我会导出 PDF。如果需要调整,告诉我哪里需要改。"
- 用户说"OK / 满意 / 导出 PDF" → 进入 Stage 6
- 用户说"修改 xxx" → 回到 Stage 4 修改后重新提示检查
---
# Stage 6: PDF 导出
> 用户在 Stage 5 确认 HTML 满意后方可执行。
## 执行步骤
### 1. 准备导出脚本
确保工作目录有 `html2pdf.mjs`。查找方式:
优先检查当前目录
ls html2pdf.mjs 2>/dev/null || \
查找 skill 目录下的 bundled 脚本
find ~/.agents/skills/travel-guidebook -name "html2pdf.mjs" 2>/dev/null | head -1 | xargs -I{} cp {} ./html2pdf.mjs || \ find . -path "*/travel-guidebook/scripts/html2pdf.mjs" 2>/dev/null | head -1 | xargs -I{} cp {} ./html2pdf.mjs
如果仍找不到,用以下最小脚本创建:
// html2pdf.mjs — Playwright A4 PDF export import { chromium } from 'playwright'; import { resolve } from 'path'; const html = resolve(process.argv[2]); const pdf = html.replace(/\.html$/, '.pdf'); const b = await chromium.launch(); const p = await b.newPage(); await p.goto('file://' + html, { waitUntil: 'networkidle' }); await p.waitForTimeout(2000); await p.pdf({ path: pdf, format: 'A4', margin: { top: '2cm', right: '1.8cm', bottom: '2.5cm', left: '1.8cm' }, printBackground: true }); await b.close(); console.log('PDF saved:', pdf);
### 2. 执行导出
node html2pdf.mjs {文件名}_guidebook.html
### 3. 验证与交付
检查 PDF 存在并显示大小,提示用户打开查看:
> "✅ PDF 路书已导出:`{文件名}_guidebook.pdf`({页数}页,{文件大小})。"
## PDF 参数
| 参数 | 值 | 说明 |
|------|----|------|
| 纸张 | A4 | 标准打印尺寸 |
| 上边距 | 2cm | |
| 下边距 | 2.5cm | 为页码留空间 |
| 左右边距 | 1.8cm | |
| 页脚 | 居中页码 | 9pt JetBrains Mono |
| 等待策略 | networkidle + 2s | 确保 CDN 资源加载 |
| 背景 | printBackground: true | 保留羊皮纸暖色纹理 |
## 备用方案
Playwright 失败时引导浏览器手动打印:Chrome/Edge 打开 HTML → Cmd/Ctrl+P → 另存为 PDF → 勾选"背景图形"。
---
# 全局规则
## 文件命名
- 调研报告:`【路线名称】-调研报告`
- 路书 HTML:`{路线名称}_guidebook.html`
- PDF 路书:`{路线名称}_guidebook.pdf`
- 导出脚本:`html2pdf.mjs`(从 scripts/ 复制)
- 工作报告:`【路线名称】-工作报告`
## 质量保证
### 调研
- 信源 ≥ 20 个,优先权威信源
- 路况信息近 6 个月内
- 交叉验证关键事实
### 内容
- 每天 2-3 个必停点,精选而非堆砌
- 通俗易懂,融入文化背景和当地人视角
- 实用信息(价格、时间、地址)准确无误
### 排版
- 字号略大于普通电子书(户外可读性)
- 对比度增强(户外光线)
- 分页正确,打印保留暖色调
## 稳定性
- 搜索失败时重试或跳过
- 任何错误不中断整体流程
## 用户体验
- 每个 Stage 开始时告知进度
- 关键步骤完成后反馈
- 确认点:Stage 1 需求(可跳过)、Stage 3 路线(可跳过)、Stage 5 HTML(必须)
---
# 执行检查清单
**Stage 0 前**
- [ ] Node.js >= 18
- [ ] Playwright + Chromium
**Stage 1 前**
- [ ] 理解用户需求
- [ ] 确认所有参数
**Stage 2 调研**
- [ ] 判断 task 工具是否可用
- [ ] 可用:启动 5 个并行 explore agent
- [ ] 不可用:退回串行 web_fetch 模式
- [ ] 所有 agent 完成后合并调研报告
**Stage 4 前**
- [ ] 路线架构已确认
- [ ] 调研内容充分
- [ ] 判断 task 工具是否可用
- [ ] 可用:读取 layout-css.md + chapter-templates.md 全文,构造 HTML agent prompt
- [ ] 不可用:主 agent 直接 heredoc 写入
**Stage 5 交付前**
- [ ] HTML 文件完整(wc -c 确认 50-80KB)
- [ ] SVG 装饰已内联
- [ ] 暖色调排版检查通过
- [ ] 打印 CSS 完整(orphans/widows、break-inside)
- [ ] 工作报告已生成
**Stage 6 导出前**
- [ ] 用户确认 HTML 满意
- [ ] html2pdf.mjs 已就绪
- [ ] PDF 生成且大小合理(通常 3-10 MB)
---
# 设计原则
1. **秒级可达** — 关键信息 3 秒内找到。日程卡片、地名、方向是最高优先级。
2. **三速阅读** — 支持扫视(3s)、浏览(30s)、沉浸(3min)三种模式。
3. **温暖如纸** — Claude 羊皮纸暖色调,数字路书拥有纸质书温度。
4. **编辑品味** — 像 Monocle 精选,不堆砌。每个推荐都带立场和温度。
5. **时空网格** — "天 × 地点"网格架构,支持随机访问。
6. **双重身份** — 旅行前是规划工具,旅行后是记忆载体。
7. **大地色系** — 赤陶、琥珀、橄榄、鼠尾草。没有冷色,没有荧光。
*.bak
assets/*.html
MIT License
Copyright (c) 2026 GeekJourney
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
<div align="center">
Travel Guidebook
从调研到成书的一站式旅行路书引擎
<img src="assets/banner.png" alt="Travel Guidebook — AI Agent Skill for generating beautifully typeset travel guidebook PDFs" width="100%">
  
</div>
---
这是什么
一个 AI Agent Skill,输入一句话行程需求,输出一本精美的旅行路书 PDF。
输入:「北京到上海,5.1 到 5.5,五日深度游」
输出:beijing_shanghai_guidebook.pdf (7MB, 30+ 页精排路书)不是模板填充,不是信息堆砌——而是一本有文学温度的旅行指南: 羊皮纸底色、衬线体标题、内联 SVG 装饰、功能色信息系统、每日手记区。
---
核心特性
<img src="assets/features.png" alt="Core Features — 8 key capabilities including parallel agents, Amap MCP, editorial writing, and typography-driven design" width="100%">
---
工作流程
<img src="assets/pipeline.png" alt="Generation Pipeline — 6-stage workflow from environment check to PDF export with parallel research agents" width="100%">
---
安装
前置依赖
# Node.js >= 18
node -v
# Playwright + Chromium (PDF 导出引擎)
npm install playwright
npx playwright install chromium安装 Skill
npx skills add https://github.com/geekjourneyx/travel-guidebook高德地图 MCP(可选,推荐)
配置后可获取实时 POI 数据、步行路线、公交方案。
// ~/.copilot/mcp-config.json
{
"mcpServers": {
"amap-maps": {
"command": "npx",
"args": ["-y", "@amap/amap-maps-mcp-server"],
"env": {
"AMAP_MAPS_API_KEY": "your_amap_api_key"
}
}
}
}在 高德开放平台 申请 Web 服务 API Key(免费)。
---
快速上手
安装完成后,在 Copilot CLI / Claude Code 中直接对话:
> 北京到上海,5.1 到 5.5,五日深度文化游
> 成都到重庆,自驾三日游,美食为主
> 杭州周末两日游,带老人和小孩Skill 会自动识别触发词(路书、旅行指南、行程规划等), 启动完整的 6 阶段工作流。
---
输出示例
project/
├── beijing_shanghai_guidebook.html # 82KB 自包含 HTML
└── beijing_shanghai_guidebook.pdf # 7.1MB 打印级 PDF路书包含
┌─────────────────────────────────────────┐
│ 封面 │
│ ├─ 指南针 SVG 装饰 │
│ ├─ 路线标题 + 主题标签 │
│ └─ 双线边框装饰 │
├─────────────────────────────────────────┤
│ 旅程概览 │
│ ├─ 天数 / 里程 / 季节 / 预算 │
│ └─ 核心亮点 │
├─────────────────────────────────────────┤
│ 行前准备 │
│ ├─ 证件 / 装备 / 天气穿衣 │
│ ├─ 预算表格 (经济/中档/高端) │
│ └─ 实用 APP 推荐 │
├─────────────────────────────────────────┤
│ Day 1 ~ Day N (每天独立章节) │
│ ├─ DAY 编号 SVG 徽章 │
│ ├─ 日程卡片 (交通/天气/亮点) │
│ ├─ 文学化路线描述 (首字下沉) │
│ ├─ 景点卡片 (POI 图片 + 地址 + 评分) │
│ ├─ 美食推荐 (实景图 + 人均 + 招牌菜) │
│ ├─ 住宿推荐 │
│ ├─ 今日贴士 │
│ └─ 手记区 (虚线笔记 + 票根位) │
├─────────────────────────────────────────┤
│ 深度游专题 (2-3 篇) │
│ ├─ 历史脉络 │
│ ├─ 文化特色 │
│ └─ 当地人视角 │
├─────────────────────────────────────────┤
│ 实用附录 │
│ ├─ 紧急联系 / 方言用语 │
│ ├─ 费用明细表 │
│ └─ 推荐歌单 │
├─────────────────────────────────────────┤
│ 封底 + 旅途感言 │
└─────────────────────────────────────────┘---
设计系统
<img src="assets/design-system.png" alt="Design System — color palette, typography specimens, and 5 design principles" width="100%">
---
项目结构
travel-guidebook/
├── SKILL.md # Skill 定义 (Agent 读取)
├── README.md # 本文件
├── LICENSE # MIT
├── references/
│ ├── layout-css.md # CSS 排版规范
│ ├── chapter-templates.md # HTML 章节模板
│ └── report-template.md # 调研报告模板
└── scripts/
└── html2pdf.mjs # Playwright PDF 导出---
技术栈
| 层级 | 技术 | 用途 |
|---|---|---|
| Agent 框架 | Copilot CLI / Claude Code | Skill 运行环境 |
| 并行调研 | explore agents × 4 | 交通/美食/景点/文化 |
| 空间数据 | Amap MCP (高德地图) | POI / 路线 / 天气 |
| HTML 写作 | general-purpose agent | 独立上下文写作 |
| 排版引擎 | CSS @page + break rules | A4 精确分页 |
| 图标系统 | Tabler Icons (CDN) | 1800+ 线性图标 |
| 字体 | Google Fonts (CDN) | 中文衬线 + 无衬线 + 等宽 |
| PDF 导出 | Playwright + Chromium | 打印级渲染 |
---
常见问题
<details> <summary><b>PDF 中图片不显示?</b></summary>
图片使用了 loading="lazy" 时,Playwright 打印模式不会触发懒加载。 解决:确保 html2pdf.mjs 中包含图片等待逻辑,或将图片改为 loading="eager"。
</details>
<details> <summary><b>高德 API 报 QPS 限制?</b></summary>
免费 Key 的 QPS 上限较低。Skill 内置了降级策略:API 失败时自动回退到 LLM 知识库数据。 建议避免同时发起超过 3 个高德 API 请求。
</details>
<details> <summary><b>没有高德 MCP 能用吗?</b></summary>
可以。Skill 在 Stage 0 自动检测 Amap MCP 是否可用,不可用时退回 LLM 知识模式。 路书质量略有下降(缺少实时 POI 坐标和图片),但核心功能完整。
</details>
<details> <summary><b>如何自定义样式?</b></summary>
修改 references/layout-css.md 中的 CSS 变量即可。 配色、字体、字号、间距均通过 CSS 自定义属性控制。
</details>
---
路线图
- [x] 6 阶段工作流
- [x] 并行 Agent 调研架构
- [x] 高德地图 MCP 集成
- [x] POI 实景图片嵌入
- [x] Playwright PDF 导出
- [ ] 多语言路书支持 (EN / JA / KO)
- [ ] 交互式 HTML 版本 (地图嵌入)
- [ ] 自定义主题 (暗色 / 莫兰迪 / 赛博朋克)
- [ ] 路书分享与协作
---
关于作者
| :globe_with_meridians: 个人主页 | geekjourney.dev |
| :octocat: GitHub | geekjourneyx |
| :bird: Twitter | @seekjourney |
| :speech_balloon: 公众号 | 微信搜「极客杰尼」 |
---
许可证
MIT — 自由使用、修改、分发。
---
<div align="center">
旅行不是为了到达,而是为了在路上。
</div>
路书章节结构——HTML 模板参考
本文件是 Stage 4 的参照资源。写作路书时,按照以下五部分结构和 HTML 模板生成内容。
所有 SVG 装饰元素均内联在 HTML 中,不使用任何外部图片。
目录
- 第一部分:出发前(Before You Go)
- 1.1 封面
- 1.2 旅程概览
- 1.3 行前准备
- 第二部分:每日行程(Day by Day)
- 第三部分:目的地深度(Deep Dives)
- 第四部分:实用附录(Appendix)
- 第五部分:封底(Back Cover)
- 内容写作规范
---
第一部分:出发前(Before You Go)
1.1 封面
<div class="cover"><!-- 双线边框装饰(纯 CSS) -->
<!-- 指南针装饰 --><div class="compass-rose" aria-hidden="true">
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="40" cy="40" r="38" stroke="currentColor" stroke-width="0.75"></circle>
<circle cx="40" cy="40" r="32" stroke="currentColor" stroke-width="0.5" stroke-dasharray="2 4"></circle>
<polygon points="40,8 43,36 40,32 37,36" fill="var(--terracotta)"></polygon>
<polygon points="40,72 37,44 40,48 43,44" fill="currentColor" opacity="0.3"></polygon>
<polygon points="8,40 36,37 32,40 36,43" fill="currentColor" opacity="0.3"></polygon>
<polygon points="72,40 44,43 48,40 44,37" fill="currentColor" opacity="0.3"></polygon>
<text x="40" y="7" text-anchor="middle" font-size="6" font-family="'Noto Serif SC', serif" font-weight="700" fill="var(--terracotta)">N</text>
<text x="40" y="79" text-anchor="middle" font-size="6" font-family="'Noto Serif SC', serif" fill="currentColor" opacity="0.5">S</text>
<text x="3" y="42" text-anchor="middle" font-size="6" font-family="'Noto Serif SC', serif" fill="currentColor" opacity="0.5">W</text>
<text x="77" y="42" text-anchor="middle" font-size="6" font-family="'Noto Serif SC', serif" fill="currentColor" opacity="0.5">E</text>
<circle cx="40" cy="40" r="2.5" fill="var(--terracotta)"></circle>
</svg></div>
<h1 class="cover-title">{路线名称}</h1><div class="cover-subtitle">{天数}天 · {主题标签} · {季节}</div><div class="cover-tagline">一句话概括这条路线的精髓</div></div>1.2 旅程概览(Journey Overview)
<div class="overview"><h2>旅程概览</h2>
<div class="overview-grid">
<div class="overview-item">
<i class="ti ti-calendar"></i>
<span class="overview-label">总天数</span>
<span class="overview-value">X 天</span>
</div>
<div class="overview-item">
<i class="ti ti-route"></i>
<span class="overview-label">总里程</span>
<span class="overview-value">XXX km</span>
</div>
<div class="overview-item">
<i class="ti ti-sun"></i>
<span class="overview-label">最佳季节</span>
<span class="overview-value">X-X 月</span>
</div>
<div class="overview-item">
<i class="ti ti-coin"></i>
<span class="overview-label">预算范围</span>
<span class="overview-value">¥XXX-XXX/人</span>
</div></div>
<div class="overview-highlights">
<h3>核心亮点</h3>
<ul>
<li><i class="ti ti-star"></i> 亮点 1</li>
<li><i class="ti ti-star"></i> 亮点 2</li>
<li><i class="ti ti-star"></i> 亮点 3</li>
</ul></div>
<!-- Flourish 分隔线 --><div class="divider divider-flourish" aria-hidden="true">
<svg width="240" height="20" viewBox="0 0 240 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M120 10 C100 10, 90 2, 60 2 S20 10, 0 10" stroke="currentColor" stroke-width="1" fill="none"></path>
<path d="M120 10 C140 10, 150 2, 180 2 S220 10, 240 10" stroke="currentColor" stroke-width="1" fill="none"></path>
<circle cx="120" cy="10" r="3" fill="currentColor"></circle>
</svg></div></div>1.3 行前准备(Before You Go)
<div class="preparation"><h2>行前准备</h2>
<div class="prep-section">
<h3><i class="ti ti-id"></i> 证件与文件</h3>
<ul>
<li>身份证 / 护照</li>
<li>驾驶证(自驾必备)</li>
<li>...</li>
</ul></div>
<div class="prep-section">
<h3><i class="ti ti-backpack"></i> 装备清单</h3>
<ul>
<li>根据季节和路线定制</li>
</ul></div>
<div class="prep-section">
<h3><i class="ti ti-coin"></i> 预算参考</h3>
<table>
<tbody><tr><td>交通</td><td>¥XXX</td></tr>
<tr><td>住宿</td><td>¥XXX</td></tr>
<tr><td>餐饮</td><td>¥XXX</td></tr>
<tr><td>门票</td><td>¥XXX</td></tr>
<tr><td>其他</td><td>¥XXX</td></tr>
</tbody></table></div></div>---
第二部分:每日行程(Day by Day)——核心主体
每天一个独立章节,结构统一:
<div class="day-chapter"><!-- DAY 编号装饰框 --><div class="day-badge" aria-hidden="true">
<svg width="72" height="72" viewBox="0 0 72 72" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2" y="2" width="68" height="68" rx="12" stroke="var(--terracotta)" stroke-width="1.5" fill="var(--ivory)"></rect>
<rect x="6" y="6" width="60" height="60" rx="9" stroke="var(--terracotta)" stroke-width="0.5" stroke-dasharray="3 3" fill="none"></rect>
<text x="36" y="30" text-anchor="middle" font-size="10" font-family="'JetBrains Mono', monospace" font-weight="500" letter-spacing="3" fill="var(--text-tertiary)">DAY</text>
<text x="36" y="54" text-anchor="middle" font-size="26" font-family="'Noto Serif SC', serif" font-weight="900" fill="var(--terracotta)">01</text>
</svg></div>
<!-- 日程卡片 --><div class="day-card">
<h2 class="day-title">{起点} → {终点}</h2>
<div class="day-meta">
<span class="meta-item"><i class="ti ti-car"></i> {里程}km</span>
<span class="meta-item"><i class="ti ti-clock"></i> {用时}h</span>
<span class="meta-item"><i class="ti ti-mountain"></i> 海拔 {海拔}m</span>
<span class="meta-item"><i class="ti ti-sun"></i> {天气}</span>
<span class="meta-item"><i class="ti ti-temperature"></i> {温度}°C</span>
</div>
<div class="day-highlight">
<i class="ti ti-star-filled"></i> 今日亮点:{一句话概括}
</div></div>
<!-- 路线描述(带首字下沉) --><div class="route-description">
<h3>路线描述</h3>
<p>
文学化的路线描写,不只是"从 A 到 B",而是沿途风景的感性描述。
例如:"离开大理古城后,公路沿洱海西岸蜿蜒,左手边是苍山的十九峰,
右手边是湖水从碧蓝渐变为翡翠绿..."
</p></div>
<!-- 菱形分隔线 --><div class="divider divider-diamond" aria-hidden="true">
<svg width="200" height="12" viewBox="0 0 200 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<line x1="0" y1="6" x2="88" y2="6" stroke="currentColor" stroke-width="0.5"></line>
<rect x="94" y="0" width="12" height="12" rx="1" transform="rotate(45 100 6)" fill="currentColor"></rect>
<line x1="112" y1="6" x2="200" y2="6" stroke="currentColor" stroke-width="0.5"></line>
</svg></div>
<!-- 必停点 --><div class="must-stops">
<h3>必停点</h3>
<div class="stop-card">
<span class="stop-badge"><i class="ti ti-star-filled"></i> 必停</span>
<h4 class="stop-name">{地点名称}</h4>
<div class="stop-name-en">{English Name}</div>
<p class="stop-description">
通俗易懂的介绍,融合历史文化和实用信息。
</p>
<div class="stop-info">
<span><i class="ti ti-map-pin"></i> {地址}</span>
<span><i class="ti ti-clock"></i> 建议 {时长}</span>
<span><i class="ti ti-coin"></i> {价格}</span>
<span><i class="ti ti-parking"></i> {停车信息}</span>
</div>
</div>
<!-- 重复 2-3 个必停点 --></div>
<!-- 三点分隔 --><div class="divider divider-dots" aria-hidden="true">
<svg width="60" height="8" viewBox="0 0 60 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="10" cy="4" r="2" fill="currentColor"></circle>
<circle cx="30" cy="4" r="2.5" fill="currentColor"></circle>
<circle cx="50" cy="4" r="2" fill="currentColor"></circle>
</svg></div>
<!-- 美食推荐 --><div class="food-recommendations">
<h3><i class="ti ti-tools-kitchen-2"></i> 美食推荐</h3>
<div class="food-card">
<h4 class="food-name">{餐厅名称}</h4>
<p class="food-description">
推荐理由和招牌菜介绍
</p>
<div class="food-info">
<span><i class="ti ti-map-pin"></i> {地址}</span>
<span><i class="ti ti-coin"></i> 人均 ¥{价格}</span>
<span><i class="ti ti-clock-hour-4"></i> {营业时间}</span>
<span><i class="ti ti-phone"></i> {电话}</span>
</div>
</div>
<!-- 1-2 个美食推荐 --></div>
<!-- 住宿推荐 --><div class="stay-recommendations">
<h3><i class="ti ti-bed"></i> 住宿推荐</h3>
<div class="stay-card">
<h4 class="stay-name">{酒店/客栈名称}</h4>
<p class="stay-description">
一句话点评:位置、特色、性价比
</p>
<div class="stay-info">
<span><i class="ti ti-map-pin"></i> {地址}</span>
<span><i class="ti ti-coin"></i> ¥{价格区间}</span>
<span><i class="ti ti-phone"></i> {预订方式}</span>
</div>
</div>
<!-- 1-2 个住宿推荐 --></div>
<!-- 贴士栏 --><div class="tips-box">
<h3><i class="ti ti-bulb"></i> 今日贴士</h3>
<ul>
<li><i class="ti ti-alert-triangle"></i> 路况提醒</li>
<li><i class="ti ti-shield-check"></i> 安全注意</li>
<li><i class="ti ti-coin"></i> 省钱技巧</li>
<li><i class="ti ti-info-circle"></i> 文化禁忌</li>
</ul></div>
<!-- 空白笔记区 --><div class="journal-space">
<h3><i class="ti ti-pencil"></i> 今日手记</h3>
<p class="journal-prompt">今天最打动你的瞬间是什么?</p>
<div class="journal-lines">
<div class="journal-line"></div>
<div class="journal-line"></div>
<div class="journal-line"></div>
<div class="journal-line"></div>
</div>
<p class="journal-prompt"><i class="ti ti-paperclip"></i> 贴上今天的票根、明信片或树叶</p></div></div>---
第三部分:目的地深度(Deep Dives)
对线路中最重要的 2-3 个目的地做深度介绍:
<div class="deep-dive"><h2>{目的地名称} 深度游</h2>
<div class="deep-dive-intro">
<p>引言:用文学化的语言介绍这个地方的气质</p></div>
<div class="deep-dive-history">
<h3>历史脉络</h3>
<p>简明的历史梳理,不超过 500 字</p></div>
<div class="deep-dive-culture">
<h3>文化特色</h3>
<p>民俗、建筑、艺术、生活方式</p></div>
<div class="deep-dive-local">
<h3>当地人的一天</h3>
<p>通过一个虚构或真实的当地人视角,展现日常生活</p></div>
<div class="deep-dive-tips">
<h3>深度游建议</h3>
<ul>
<li><i class="ti ti-camera"></i> 最佳拍摄点</li>
<li><i class="ti ti-compass"></i> 小众体验</li>
<li><i class="ti ti-alert-circle"></i> 避坑指南</li>
</ul></div></div>---
第四部分:实用附录(Appendix)
<div class="appendix"><h2>实用附录</h2>
<div class="appendix-section">
<h3><i class="ti ti-phone-call"></i> 紧急联系</h3>
<ul>
<li>报警:110</li>
<li>急救:120</li>
<li>路况查询:XXX</li>
<li>沿途医院列表</li>
</ul></div>
<div class="appendix-section">
<h3><i class="ti ti-language"></i> 常用语言</h3>
<p>当地方言/少数民族语言常用词汇对照</p></div>
<div class="appendix-section">
<h3><i class="ti ti-coin"></i> 费用明细参考</h3>
<table>
<tbody><tr><th>项目</th><th>预算</th><th>实际</th></tr>
<tr><td>Day 1</td><td>¥XXX</td><td>_____</td></tr>
<!-- ... -->
</tbody></table></div>
<div class="appendix-section">
<h3><i class="ti ti-music"></i> 推荐歌单</h3>
<p>适合这条路线的音乐/播客推荐</p></div></div>---
第五部分:封底(Back Cover)
<div class="back-cover"><div class="back-quote">
"旅行不是为了到达,而是为了在路上。"
</div>
<div class="back-info">
{个性化信息:制作者、团队、公众号等}
</div>
<div class="back-space">
<i class="ti ti-pencil"></i> 旅行感言
<div class="journal-line"></div>
<div class="journal-line"></div></div></div>---
内容写作规范
写作风格规则
定向需求(Where)——精准清晰
- 地名、方向、里程、用时必须准确无误
- 使用"左手边"、"右手边"、"前方 200 米"等具身化描述
- 路况描述要具体:"连续 15km 的盘山路,弯道多,限速 40km/h"
决策需求(What)——替用户筛选
- 每个目的地只推荐 2-3 个必停点,不堆砌
- 美食推荐不超过 2 个,附推荐理由
- 住宿推荐 1-2 个,覆盖不同预算
- 用"必停"、"可选"、"时间充裕可去"明确优先级
情感需求(Why)——赋予意义
- 每个地点都要讲"为什么值得来"
- 融入历史故事、文化背景、当地人视角
- 用文学化语言描述风景,而非枯燥罗列
- 例如:"喜洲的清晨,白族阿妈在四方街摆出刚出炉的喜洲粑粑,麦香混着玫瑰花瓣的甜,这是大理最地道的味道。"
贴士栏写作原则
贴士栏是路书的"安全网",必须包含:
- 路况提醒:山路、土路、限行、施工、季节性封路
- 安全注意:高海拔反应、野生动物、天气突变、通讯盲区
- 省钱技巧:避开旺季、团购、当地人推荐
- 文化禁忌:少数民族禁忌、宗教场所注意事项
空白笔记区设计
每天行程末尾留出空白区域——这让路书从"工具"变成"纪念品",旅行结束后就是一本独一无二的旅行日记。
<div class="journal-space"><h3><i class="ti ti-pencil"></i> 今日手记</h3><p class="journal-prompt">今天最打动你的瞬间是什么?</p><div class="journal-lines">
<div class="journal-line"></div>
<div class="journal-line"></div>
<div class="journal-line"></div>
<div class="journal-line"></div>
<div class="journal-line"></div></div><p class="journal-prompt"><i class="ti ti-paperclip"></i> 贴上今天的票根、明信片或树叶</p></div>排版规范——融合 Claude 设计语言
本文件包含路书 HTML 的完整 CSS 规范。Stage 4 写作时参照此文件设置所有样式。
核心原则:排版即设计——不依赖图片,通过字体层级、功能色、SVG 装饰和 CSS 伪元素创造设计感。
目录
---
外部资源引入(仅 3 行)
<!-- 1. 字体 --><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&family=Noto+Serif+SC:wght@400;600;700;900&family=JetBrains+Mono:wght@400;500&display=swap">
<!-- 2. 图标 --><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/dist/tabler-icons.min.css">
<!-- 3. 其余全部内联 —— 所有 SVG 装饰、CSS 样式、背景纹理都在 <style> 标签内 -->---
页面尺寸与布局
@page {
size: A4;
margin: 2cm 1.8cm 2.5cm 1.8cm;
}
body {
max-width: 680px;
margin: 0 auto;
padding: 0 1.5rem;
}---
字体系统
:root {
--font-serif: 'Noto Serif SC', 'Source Han Serif SC', serif;
--font-sans: 'Noto Sans SC', 'Source Han Sans SC', sans-serif;
--font-mono: 'JetBrains Mono', 'SF Mono', 'Consolas', monospace;
}
/* 标题用衬线体,正文用无衬线,功能信息用等宽 */
.cover-title,
.day-title,
h1, h2, h3 {
font-family: var(--font-serif);
}
body, p, li {
font-family: var(--font-sans);
}
.meta-item,
.page-number,
.stop-badge {
font-family: var(--font-mono);
}---
字号与行高
:root {
--text-base: 17px;
--text-sm: 15px;
--text-xs: 14px;
--text-lg: 24px;
--text-xl: 28px;
--text-2xl: 32px;
--text-3xl: 48px;
--leading-tight: 1.25;
--leading-normal: 1.85;
--leading-relaxed: 1.70;
}
body {
font-size: var(--text-base);
line-height: var(--leading-normal);
}
@media print {
body {
font-size: 11.5pt;
}
}---
配色方案——Claude 暖色调 + 路书功能色
:root {
/* 基础色——继承 Claude */
--parchment: #f5f4ed; /* 页面背景:温暖的羊皮纸 */
--ivory: #faf9f5; /* 卡片背景 */
--near-black: #141413; /* 主文字 */
--terracotta: #c96442; /* 品牌色/主 CTA/日程卡片强调 */
--coral: #d97757; /* 链接/强调 */
/* 功能色——路书专属 */
--route-blue: #4a7c8a; /* 路线/交通信息——沉稳的青蓝 */
--food-amber: #b8860b; /* 美食推荐——温暖的琥珀色 */
--stay-olive: #6b7c5e; /* 住宿信息——安静的橄榄绿 */
--alert-rust: #a0522d; /* 警告/注意——深赭色 */
--nature-sage: #8fbc8f; /* 自然/户外——鼠尾草绿 */
/* 中性色——保持 Claude 暖调,但对比度增强(户外可读性) */
--text-primary: #2d2b28;
--text-secondary: #524f4a;
--text-tertiary: #87867f;
--border-cream: #e8e6dc;
--border-warm: #d1cfc5;
}---
配色应用规则
/* 页面背景 + 纸张纹理 */
body {
background-color: var(--parchment);
background-image:
/* 极淡的斜纹 —— 模拟纸张纤维 */
repeating-linear-gradient(
120deg,
transparent,
transparent 2px,
rgba(139, 119, 90, 0.015) 2px,
rgba(139, 119, 90, 0.015) 3px
),
/* 大面积的温暖渐变 —— 模拟纸张不均匀的色调 */
radial-gradient(
ellipse at 20% 50%,
rgba(201, 100, 66, 0.03) 0%,
transparent 70%
),
radial-gradient(
ellipse at 80% 20%,
rgba(184, 134, 11, 0.02) 0%,
transparent 60%
);
}
/* 日程卡片 */
.day-card {
background: var(--ivory);
border: 1px solid var(--border-cream);
box-shadow: 0px 0px 0px 1px var(--border-warm);
}
/* 功能色应用 */
.route-description { border-left: 3px solid var(--route-blue); padding-left: 1rem; }
.food-card { border-left: 3px solid var(--food-amber); padding-left: 1rem; }
.stay-card { border-left: 3px solid var(--stay-olive); padding-left: 1rem; }
.tips-box {
background: rgba(160, 82, 45, 0.08);
border-left: 3px solid var(--alert-rust);
padding: 1rem 1.5rem;
}
/* 图标颜色 */
.ti { color: inherit; }
.meta-item .ti { color: var(--route-blue); }
.food-card .ti { color: var(--food-amber); }
.stay-card .ti { color: var(--stay-olive); }
.tips-box .ti { color: var(--alert-rust); }---
装饰元素样式
/* 指南针 */
.compass-rose {
display: flex;
justify-content: center;
margin: 2rem 0;
color: var(--text-tertiary);
}
/* 分隔线 */
.divider {
display: flex;
justify-content: center;
margin: 2.5rem 0;
color: var(--border-warm);
}
.divider-flourish { color: var(--terracotta); opacity: 0.5; }
.divider-diamond { color: var(--text-tertiary); }
.divider-dots { color: var(--border-warm); margin: 1.5rem 0; }
/* DAY 编号徽章 */
.day-badge {
display: flex;
justify-content: center;
margin: 3rem 0 2rem;
}
/* 封面双线边框 */
.cover {
position: relative;
padding: 4rem 3rem;
page-break-after: always;
}
.cover::before {
content: "";
position: absolute;
top: 1.5rem; left: 1.5rem; right: 1.5rem; bottom: 1.5rem;
border: 1.5px solid var(--terracotta);
border-radius: 4px;
opacity: 0.4;
}
.cover::after {
content: "";
position: absolute;
top: 2rem; left: 2rem; right: 2rem; bottom: 2rem;
border: 0.5px solid var(--terracotta);
border-radius: 2px;
opacity: 0.25;
}
/* 卡片角标装饰 */
.stop-card,
.food-card,
.stay-card {
position: relative;
background: var(--ivory);
border-radius: 10px;
padding: 1.5rem;
margin-bottom: 2rem;
}
.stop-card::before,
.food-card::before,
.stay-card::before {
content: "";
position: absolute;
top: 0; left: 0;
width: 24px; height: 24px;
border-top: 2px solid var(--terracotta);
border-left: 2px solid var(--terracotta);
border-top-left-radius: 10px;
opacity: 0.4;
}
.stop-card::after,
.food-card::after,
.stay-card::after {
content: "";
position: absolute;
bottom: 0; right: 0;
width: 24px; height: 24px;
border-bottom: 2px solid var(--terracotta);
border-right: 2px solid var(--terracotta);
border-bottom-right-radius: 10px;
opacity: 0.4;
}
/* 首字下沉 */
.route-description > p:first-of-type::first-letter {
float: left;
font-family: var(--font-serif);
font-size: 3.5em;
font-weight: 900;
line-height: 0.8;
margin-right: 0.08em;
margin-top: 0.05em;
color: var(--terracotta);
}
/* 笔记区虚线 */
.journal-line {
width: 100%;
height: 1.8rem;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='2' viewBox='0 0 600 2'%3E%3Cline x1='0' y1='1' x2='600' y2='1' stroke='%23d1cfc5' stroke-width='0.5' stroke-dasharray='8 4'/%3E%3C/svg%3E");
background-repeat: repeat-x;
background-position: bottom;
background-size: 600px 2px;
}
/* 徽章 */
.stop-badge {
display: inline-flex;
align-items: center;
gap: 0.3em;
background: var(--terracotta);
color: var(--ivory);
font-family: var(--font-mono);
font-size: 0.75rem;
font-weight: 500;
letter-spacing: 0.1em;
padding: 0.25em 0.75em;
border-radius: 3px;
text-transform: uppercase;
}
/* 功能信息标签 */
.meta-item {
display: inline-flex;
align-items: center;
gap: 0.35em;
font-family: var(--font-mono);
font-size: 0.88rem;
color: var(--text-secondary);
padding: 0.2em 0.6em;
background: rgba(232, 230, 220, 0.6);
border-radius: 4px;
margin-right: 0.5em;
margin-bottom: 0.4em;
}---
分页规则(优化版)
/* 1. 封面独占一页 */
.cover {
page-break-after: always;
break-after: page;
}
/* 2. 旅程概览自成一页 */
.overview {
page-break-before: always;
break-before: page;
}
/* 3. 行前准备——智能分页 */
.preparation {
page-break-before: auto;
break-before: auto;
page-break-inside: avoid;
break-inside: avoid;
}
/* 4. 每个 Day 章节从新页开始 */
.day-chapter {
page-break-before: always;
break-before: page;
}
/* 5. 防止卡片被拆分 */
.day-card,
.stop-card,
.food-card,
.stay-card,
.tips-box,
.journal-space {
page-break-inside: avoid;
break-inside: avoid;
}
/* 6. 防止表格被拆分 */
table, thead, tbody, tr {
page-break-inside: avoid;
break-inside: avoid;
}
/* 7. 标题不与正文分离 */
h1, h2, h3, h4 {
page-break-after: avoid;
break-after: avoid;
orphans: 3;
widows: 3;
}
/* 8. 深度游章节从新页开始 */
.deep-dive {
page-break-before: always;
break-before: page;
}
/* 9. 附录从新页开始 */
.appendix {
page-break-before: always;
break-before: page;
}
/* 10. 封底独占一页 */
.back-cover {
page-break-before: always;
break-before: page;
}
/* 11. 全局孤行控制 */
p {
orphans: 3;
widows: 3;
}
/* 12. 行前准备的子区块不拆分 */
.prep-section {
page-break-inside: avoid;
break-inside: avoid;
}---
打印适配
@media print {
body {
font-size: 11.5pt;
color: var(--text-primary);
}
.content {
max-width: 100%;
padding: 0;
}
/* 保留背景色——路书的暖色调是核心体验 */
.day-card, .stop-card, .food-card, .stay-card, .tips-box {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
/* 全局孤行/寡行控制 */
p, li, dd {
orphans: 3;
widows: 3;
}
/* 标题不与正文分离 */
h2, h3 {
break-after: avoid;
}
/* 卡片类元素不拆分 */
.timeline-card, .tip-card, .deep-dive-card {
break-inside: avoid;
}
/* 行前准备单独起页(内容量大时优化) */
.preparation-section {
break-before: page;
}
/* 封面页无上边距 */
@page :first {
margin-top: 0;
}
/* 页码——Playwright 使用 displayHeaderFooter 覆盖此规则 */
@page {
@bottom-center {
content: "— " counter(page) " —";
font-family: var(--font-mono);
font-size: 9pt;
color: var(--text-tertiary);
}
}
}工作报告模板
使用 write 工具生成工作报告,命名格式:【路线名称】-工作报告
# 工作报告:{路线名称}
> **项目**:{一句话描述}
> **完成时间**:YYYY-MM-DD
> **最终交付**:HTML 文件 + PDF 文件
---
## 一、任务理解
- 路线:{起点} → {终点}
- 天数:{X} 天
- 类型:{自驾/徒步/城市漫游}
- 目标读者:{深度文化游/轻松休闲游/探险挑战游}
## 二、调研阶段
- 调研方向:{6 个方向}
- 搜索次数:{N 次}
- 信源数量:{N 个}
- 核心发现摘要:
- 路线概况:{总结}
- 核心目的地:{总结}
- 美食住宿:{总结}
- 实用信息:{总结}
- 沿途亮点:{总结}
- 文化背景:{总结}
## 三、路线架构
- 总天数:{X} 天
- 总里程:{XXX} km
- 核心目的地:{X} 个
- 每日行程列表:
- Day 01:{起点} → {终点}({里程}km, {用时}h)
- Day 02:{起点} → {终点}({里程}km, {用时}h)
- ...
## 四、设计体系
- **图标系统**:Tabler Icons(6000+ 图标,CDN 引入)
- **装饰元素**:内联 SVG(指南针、分隔线、DAY 徽章、卡片角标)
- **字体系统**:Noto Serif SC(标题)+ Noto Sans SC(正文)+ JetBrains Mono(功能信息)
- **配色方案**:Parchment 羊皮纸底 + Terracotta 赤陶色强调 + 功能色系统
- **背景纹理**:CSS 渐变模拟纸张纹理(零图片)
## 五、排版与输出
- 设计风格:Claude 暖色调 + 旅游路书功能色
- 页面尺寸:A4
- 分页控制:✅ 优化(封面独占、Day 新页、卡片不拆)
- 打印友好:✅ 保留暖色调背景
## 六、产出文件清单
- {路线名称}_guidebook.html(单文件,包含所有样式和装饰)
- {路线名称}_guidebook.pdf(Playwright 导出,A4,含页码)
## 七、关键数据
| 指标 | 数值 |
|------|------|
| 总天数 | {X} |
| 总里程 | {XXX} km |
| 核心目的地 | {X} |
| 必停点 | {X} |
| 美食推荐 | {X} |
| 住宿推荐 | {X} |
| 信源数 | {X} |
| 预估页数 | {X} |
## 八、技术亮点
- ✅ 零 AI 生图依赖
- ✅ 零 emoji(全部用 Tabler Icons 替代)
- ✅ 零外部图片文件
- ✅ 单 HTML 文件交付
- ✅ Playwright 自动 PDF 导出
- ✅ 打印友好(保留暖色调)
- ✅ 响应式设计(屏幕/打印双适配)/**
* HTML → PDF 导出脚本(Playwright + Chromium)
*
* 用法:
* node html2pdf.mjs input.html # 指定输入文件
* node html2pdf.mjs input.html output.pdf # 指定输入和输出
*/
import { chromium } from "playwright";
import { resolve } from "path";
import { existsSync } from "fs";
const inputFile = process.argv[2];
if (!inputFile) {
console.error("用法: node html2pdf.mjs <input.html> [output.pdf]");
process.exit(1);
}
const outputFile = process.argv[3] || inputFile.replace(/\.html?$/, ".pdf");
const inputPath = resolve(inputFile);
if (!existsSync(inputPath)) {
console.error(`文件不存在: ${inputPath}`);
process.exit(1);
}
const fileUrl = `file://${inputPath}`;
console.log(`输入: ${inputPath}`);
console.log(`输出: ${resolve(outputFile)}`);
console.log("正在启动 Chromium...");
const browser = await chromium.launch();
const page = await browser.newPage();
// 等待字体和图标 CDN 加载
await page.goto(fileUrl, { waitUntil: "networkidle", timeout: 30000 });
// 额外等待字体渲染完成
await page.waitForTimeout(2000);
console.log("正在生成 PDF...");
await page.pdf({
path: resolve(outputFile),
format: "A4",
printBackground: true,
margin: {
top: "2cm",
bottom: "2.5cm",
left: "1.8cm",
right: "1.8cm",
},
displayHeaderFooter: true,
headerTemplate: "<span></span>",
footerTemplate: `
<div style="width:100%; text-align:center; font-size:9pt; color:#87867f; font-family:'JetBrains Mono',monospace;">
<span class="pageNumber"></span>
</div>
`,
});
await browser.close();
console.log(`✅ PDF 导出完成: ${resolve(outputFile)}`);