From 5a9f563425c584675d13f1a422bc7cf41ade604a Mon Sep 17 00:00:00 2001 From: yuwen <212556642@qq.com> Date: Thu, 2 Apr 2026 11:30:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20PDF=20=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E5=AF=BC=E5=87=BA=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 支持将 docsify 文档整体导出为单个 PDF 文件: - 解析 _sidebar.md 自动获取所有页面 - 使用 Puppeteer 无头浏览器逐页生成 PDF - 使用 pdf-lib 合并为单个 PDF - 自动添加页码 Co-Authored-By: Claude Opus 4.6 --- README.md | 17 ++++ package.json | 6 ++ scripts/README.md | 48 ++++++++++ scripts/export-pdf.js | 202 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 273 insertions(+) create mode 100644 package.json create mode 100644 scripts/README.md create mode 100644 scripts/export-pdf.js diff --git a/README.md b/README.md index 910a757..deb35d5 100644 --- a/README.md +++ b/README.md @@ -54,8 +54,25 @@ docsify serve docs ## 导出 PDF +### 方式一:浏览器打印 在浏览器中打开任意页面,按 `Ctrl+P` / `Cmd+P`,选择"另存为 PDF"。 +### 方式二:批量导出(推荐) +使用脚本将整个文档导出为单个 PDF 文件: + +```bash +# 安装依赖 +npm install puppeteer pdf-lib + +# 启动 docsify 服务 +docsify serve docs + +# 运行导出脚本 +node scripts/export-pdf.js +``` + +输出文件:`claude-code-complete-guide.pdf` + ## 源码来源 - [Claude Code CLI](https://github.com/huangserva/claude-code-cli) - TypeScript 源码 diff --git a/package.json b/package.json new file mode 100644 index 0000000..5ae1ae5 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "pdf-lib": "^1.17.1", + "puppeteer": "^24.40.0" + } +} diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..31e44e9 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,48 @@ +# PDF 导出脚本 + +## 依赖安装 + +```bash +npm install puppeteer pdf-lib +``` + +> 注意: Puppeteer 需要下载 Chromium(约 150MB),国内可能较慢。可使用镜像: +> ```bash +> PUPPETEER_DOWNLOAD_HOST=https://npmmirror.com/mirrors npm install puppeteer +> ``` + +## 使用方法 + +### 1. 启动 docsify 服务 + +终端 1: +```bash +docsify serve docs +``` + +### 2. 运行导出脚本 + +终端 2: +```bash +node scripts/export-pdf.js +``` + +## 配置修改 + +编辑 `scripts/export-pdf.js` 中的 `CONFIG` 对象: + +```javascript +const CONFIG = { + baseUrl: 'http://localhost:3000', // 修改为你的服务地址 + outputPath: './claude-code-complete-guide.pdf', // 输出路径 + tempDir: './temp-pdfs', + waitTime: 2000, // 页面渲染等待时间(ms) + pageTimeout: 30000, // 单页超时(ms) +}; +``` + +## 输出 + +- 生成单个 PDF 文件,包含所有 114 页内容 +- 自动添加页码 +- 保留原文档样式和图片 diff --git a/scripts/export-pdf.js b/scripts/export-pdf.js new file mode 100644 index 0000000..a559022 --- /dev/null +++ b/scripts/export-pdf.js @@ -0,0 +1,202 @@ +/** + * Claude Code 完全指南 - PDF 导出脚本 + * + * 使用方法: + * 1. 先启动 docsify 服务: docsify serve docs + * 2. 安装依赖: npm i puppeteer pdf-lib + * 3. 运行: node scripts/export-pdf.js + */ + +const puppeteer = require('puppeteer'); +const { PDFDocument } = require('pdf-lib'); +const fs = require('fs'); +const path = require('path'); +const https = require('https'); +const http = require('http'); + +// 等待函数(兼容新版 Puppeteer) +const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms)); + +// ============ 配置 ============ +// 注意: 如果 docsify 不是 3000 端口,请修改 baseUrl +const CONFIG = { + baseUrl: 'http://localhost:52861', // 修改为你的 docsify 端口 + outputPath: './claude-code-complete-guide.pdf', + tempDir: './temp-pdfs', + waitTime: 2000, // 页面渲染等待时间(ms) + pageTimeout: 30000, // 单页超时(ms) +}; + +// ============ 解析侧边栏获取所有页面 ============ +function parseSidebar() { + const sidebarPath = path.join(__dirname, '..', 'docs', '_sidebar.md'); + const content = fs.readFileSync(sidebarPath, 'utf-8'); + const pages = []; + + // 匹配所有 markdown 链接 [标题](路径) + const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g; + let match; + + while ((match = linkRegex.exec(content)) !== null) { + const title = match[1]; + let url = match[2]; + + // 跳过外部链接和特殊路径 + if (url.startsWith('http') || url.startsWith('//') || url === '/') { + continue; + } + + // 转换为 docsify 路由格式 (#/path/to/page) + // 移除 .md 扩展名 + url = url.replace(/\.md$/, ''); + // 添加 #/ 前缀 + const route = '#/' + url; + + pages.push({ title, route }); + } + + return pages; +} + +// ============ 确保目录存在 ============ +function ensureDir(dirPath) { + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } +} + +// ============ 下载单个页面 PDF ============ +async function downloadPagePdf(page, browser, index, total) { + const url = `${CONFIG.baseUrl}/${page.route}`; + console.log(`[${index + 1}/${total}] 正在处理: ${page.title}`); + + try { + const pageObj = await browser.newPage(); + + // 设置视口 + await pageObj.setViewport({ + width: 1200, + height: 800, + deviceScaleFactor: 2, // 2x 分辨率更清晰 + }); + + // 访问页面 + await pageObj.goto(url, { + waitUntil: 'networkidle0', + timeout: CONFIG.pageTimeout, + }); + + // 等待内容加载 + await wait(CONFIG.waitTime); + + // 生成 PDF + const pdfBytes = await pageObj.pdf({ + format: 'A4', + printBackground: true, + margin: { + top: '20mm', + right: '15mm', + bottom: '20mm', + left: '15mm', + }, + displayHeaderFooter: true, + headerTemplate: ` +
+ +
+ `, + footerTemplate: ` +
+ / +
+ `, + }); + + await pageObj.close(); + + return { + title: page.title, + pdfBytes: Buffer.from(pdfBytes), + }; + } catch (error) { + console.error(` ❌ 错误: ${error.message}`); + return null; + } +} + +// ============ 合并 PDF ============ +async function mergePdfs(pdfBuffers) { + console.log('\n正在合并 PDF...'); + + const mergedPdf = await PDFDocument.create(); + + for (const { title, pdfBytes } of pdfBuffers) { + try { + const pdf = await PDFDocument.load(pdfBytes); + const pages = await mergedPdf.copyPages(pdf, pdf.getPageIndices()); + pages.forEach(page => mergedPdf.addPage(page)); + console.log(` ✓ 添加: ${title}`); + } catch (error) { + console.error(` ❌ 合并失败 ${title}: ${error.message}`); + } + } + + const finalBytes = await mergedPdf.save(); + fs.writeFileSync(CONFIG.outputPath, finalBytes); + + console.log(`\n✅ PDF 已导出: ${CONFIG.outputPath}`); + console.log(` 文件大小: ${(finalBytes.length / 1024 / 1024).toFixed(2)} MB`); + console.log(` 总页数: ${mergedPdf.getPageCount()}`); +} + +// ============ 主流程 ============ +async function main() { + console.log('='.repeat(50)); + console.log('Claude Code 完全指南 - PDF 导出工具'); + console.log('='.repeat(50)); + + // 1. 解析页面列表 + console.log('\n[1/4] 解析页面列表...'); + const pages = parseSidebar(); + console.log(`找到 ${pages.length} 个页面`); + + // 2. 清理临时目录 + console.log('\n[2/4] 准备临时目录...'); + ensureDir(CONFIG.tempDir); + fs.readdirSync(CONFIG.tempDir).forEach(file => { + fs.unlinkSync(path.join(CONFIG.tempDir, file)); + }); + + // 3. 启动浏览器并下载所有页面 + console.log('\n[3/4] 启动浏览器...'); + const browser = await puppeteer.launch({ + headless: 'new', + args: ['--no-sandbox', '--disable-setuid-sandbox'], + }); + + const pdfBuffers = []; + + for (let i = 0; i < pages.length; i++) { + const result = await downloadPagePdf(pages[i], browser, i, pages.length); + if (result) { + pdfBuffers.push(result); + } + } + + await browser.close(); + + // 4. 合并 PDF + console.log('\n[4/4] 生成最终 PDF...'); + await mergePdfs(pdfBuffers); + + // 清理临时文件 + fs.rmSync(CONFIG.tempDir, { recursive: true, force: true }); + + console.log('\n完成!'); +} + +// ============ 运行 ============ +main().catch(error => { + console.error('导出失败:', error); + process.exit(1); +});