426 lines
10 KiB
Markdown
426 lines
10 KiB
Markdown
# Word/Markdown 转 PDF API 使用指南
|
||
|
||
## API 端点
|
||
|
||
```
|
||
POST /api/pdf/convert
|
||
```
|
||
|
||
## 支持的输入方式
|
||
|
||
### 1. 上传文件
|
||
|
||
支持上传 `.doc`, `.docx`, `.md` 文件:
|
||
|
||
```javascript
|
||
// 上传 Word 文件
|
||
const formData = new FormData();
|
||
formData.append('file', fileInput.files[0]); // .doc 或 .docx
|
||
|
||
// 可选参数
|
||
formData.append('toc', 'true'); // 生成目录
|
||
formData.append('header_text', '文档标题|页码'); // 页眉
|
||
formData.append('footer_text', '版权信息'); // 页脚
|
||
formData.append('filename_text', '我的文档'); // 文件名
|
||
|
||
const response = await fetch('/api/pdf/convert', {
|
||
method: 'POST',
|
||
body: formData
|
||
});
|
||
|
||
const blob = await response.blob();
|
||
const url = URL.createObjectURL(blob);
|
||
```
|
||
|
||
### 2. 指定本地文件路径
|
||
|
||
```javascript
|
||
const formData = new FormData();
|
||
formData.append('file_path', '/path/to/document.docx');
|
||
formData.append('toc', 'true');
|
||
|
||
const response = await fetch('/api/pdf/convert', {
|
||
method: 'POST',
|
||
body: formData
|
||
});
|
||
```
|
||
|
||
### 3. 直接提交 Markdown 内容
|
||
|
||
```javascript
|
||
const formData = new FormData();
|
||
formData.append('markdown_content', '# 标题\n\n这是内容');
|
||
formData.append('filename_text', '我的文档');
|
||
|
||
const response = await fetch('/api/pdf/convert', {
|
||
method: 'POST',
|
||
body: formData
|
||
});
|
||
```
|
||
|
||
## 完整参数列表
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| file | File | 否* | 上传的文件 |
|
||
| file_path | string | 否* | 本地文件路径 |
|
||
| markdown_content | string | 否* | Markdown 内容 |
|
||
| toc | boolean | 否 | 是否生成目录,默认 false |
|
||
| header_text | string | 否 | 页眉文本,可用 `\|` 分隔左右 |
|
||
| footer_text | string | 否 | 页脚文本 |
|
||
| logo_url | string | 否 | Logo 图片 URL |
|
||
| copyright_text | string | 否 | 版权声明 |
|
||
| filename_text | string | 否 | 显示的文件名 |
|
||
| cover_src | string | 否 | 封面图片 URL |
|
||
| product_name | string | 否 | 产品名称(封面) |
|
||
| document_name | string | 否 | 文档名称(封面) |
|
||
| product_version | string | 否 | 产品版本 |
|
||
| document_version | string | 否 | 文档版本 |
|
||
| css_name | string | 否 | CSS 样式名称 |
|
||
| css_text | string | 否 | 自定义 CSS |
|
||
| download | boolean | 否 | 是否直接下载,默认 true |
|
||
|
||
*注:file、file_path、markdown_content 三者必选其一
|
||
|
||
## 完整示例代码
|
||
|
||
### React 示例
|
||
|
||
```jsx
|
||
import { useState, useRef } from 'react';
|
||
|
||
function PdfConverter() {
|
||
const [loading, setLoading] = useState(false);
|
||
const fileInput = useRef(null);
|
||
|
||
const convertToPdf = async () => {
|
||
const file = fileInput.current.files[0];
|
||
if (!file) return;
|
||
|
||
setLoading(true);
|
||
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
formData.append('toc', 'true');
|
||
formData.append('header_text', '我的文档|第 {page} 页');
|
||
formData.append('footer_text', '© 2024 公司名称');
|
||
formData.append('filename_text', file.name.replace(/\.[^/.]+$/, ''));
|
||
|
||
try {
|
||
const response = await fetch('/api/pdf/convert', {
|
||
method: 'POST',
|
||
body: formData
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const error = await response.json();
|
||
throw new Error(error.detail || '转换失败');
|
||
}
|
||
|
||
const blob = await response.blob();
|
||
const url = URL.createObjectURL(blob);
|
||
|
||
// 下载 PDF
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = file.name.replace(/\.[^/.]+$/, '') + '.pdf';
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
document.body.removeChild(a);
|
||
URL.revokeObjectURL(url);
|
||
} catch (error) {
|
||
console.error('转换失败:', error);
|
||
alert('转换失败: ' + error.message);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div>
|
||
<input
|
||
type="file"
|
||
ref={fileInput}
|
||
accept=".doc,.docx,.md"
|
||
/>
|
||
<button
|
||
onClick={convertToPdf}
|
||
disabled={loading}
|
||
>
|
||
{loading ? '转换中...' : '转换为 PDF'}
|
||
</button>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
### Vue 3 示例
|
||
|
||
```vue
|
||
<template>
|
||
<div>
|
||
<input
|
||
ref="fileInput"
|
||
type="file"
|
||
accept=".doc,.docx,.md"
|
||
/>
|
||
<button
|
||
@click="convertToPdf"
|
||
:disabled="loading"
|
||
>
|
||
{{ loading ? '转换中...' : '转换为 PDF' }}
|
||
</button>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref } from 'vue';
|
||
|
||
const fileInput = ref(null);
|
||
const loading = ref(false);
|
||
|
||
const convertToPdf = async () => {
|
||
const file = fileInput.value.files[0];
|
||
if (!file) return;
|
||
|
||
loading.value = true;
|
||
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
formData.append('toc', 'true');
|
||
formData.append('header_text', '我的文档');
|
||
formData.append('filename_text', file.name.replace(/\.[^/.]+$/, ''));
|
||
|
||
try {
|
||
const response = await fetch('/api/pdf/convert', {
|
||
method: 'POST',
|
||
body: formData
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const error = await response.json();
|
||
throw new Error(error.detail || '转换失败');
|
||
}
|
||
|
||
const blob = await response.blob();
|
||
const url = URL.createObjectURL(blob);
|
||
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = file.name.replace(/\.[^/.]+$/, '') + '.pdf';
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
document.body.removeChild(a);
|
||
URL.revokeObjectURL(url);
|
||
} catch (error) {
|
||
console.error('转换失败:', error);
|
||
alert('转换失败: ' + error.message);
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
};
|
||
</script>
|
||
```
|
||
|
||
### 原生 JavaScript 示例
|
||
|
||
```html
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title>Word/Markdown 转 PDF</title>
|
||
</head>
|
||
<body>
|
||
<h1>文档转 PDF</h1>
|
||
|
||
<input type="file" id="fileInput" accept=".doc,.docx,.md">
|
||
<button id="convertBtn">转换为 PDF</button>
|
||
|
||
<div id="status" style="margin-top: 10px;"></div>
|
||
|
||
<script>
|
||
document.getElementById('convertBtn').addEventListener('click', async () => {
|
||
const fileInput = document.getElementById('fileInput');
|
||
const status = document.getElementById('status');
|
||
const file = fileInput.files[0];
|
||
|
||
if (!file) {
|
||
status.textContent = '请选择文件';
|
||
return;
|
||
}
|
||
|
||
status.textContent = '转换中...';
|
||
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
formData.append('toc', 'true');
|
||
formData.append('header_text', '我的文档|{page}');
|
||
formData.append('footer_text', '© 2024');
|
||
formData.append('filename_text', file.name.replace(/\.[^/.]+$/, ''));
|
||
|
||
try {
|
||
const response = await fetch('/api/pdf/convert', {
|
||
method: 'POST',
|
||
body: formData
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const error = await response.json();
|
||
throw new Error(error.detail || '转换失败');
|
||
}
|
||
|
||
const blob = await response.blob();
|
||
const url = URL.createObjectURL(blob);
|
||
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = file.name.replace(/\.[^/.]+$/, '') + '.pdf';
|
||
document.body.appendChild(a);
|
||
a.click();
|
||
document.body.removeChild(a);
|
||
URL.revokeObjectURL(url);
|
||
|
||
status.textContent = '转换成功!';
|
||
} catch (error) {
|
||
console.error(error);
|
||
status.textContent = '转换失败: ' + error.message;
|
||
}
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
### Markdown 内容转 PDF 示例
|
||
|
||
```javascript
|
||
async function markdownToPdf() {
|
||
const markdownContent = `
|
||
# 我的文档
|
||
|
||
## 第一章
|
||
|
||
这是第一章的内容。
|
||
|
||
### 小节
|
||
|
||
- 列表项 1
|
||
- 列表项 2
|
||
|
||
| 列1 | 列2 |
|
||
|-----|-----|
|
||
| A | B |
|
||
`;
|
||
|
||
const formData = new FormData();
|
||
formData.append('markdown_content', markdownContent);
|
||
formData.append('filename_text', '我的Markdown文档');
|
||
formData.append('toc', 'true');
|
||
formData.append('header_text', 'Markdown文档');
|
||
formData.append('footer_text', '© 2024');
|
||
|
||
const response = await fetch('/api/pdf/convert', {
|
||
method: 'POST',
|
||
body: formData
|
||
});
|
||
|
||
const blob = await response.blob();
|
||
// 保存 PDF
|
||
saveAs(blob, 'document.pdf');
|
||
}
|
||
```
|
||
|
||
## Python 调用示例
|
||
|
||
```python
|
||
import requests
|
||
|
||
def convert_word_to_pdf(file_path, output_path):
|
||
"""将 Word 文件转换为 PDF"""
|
||
with open(file_path, 'rb') as f:
|
||
files = {'file': f}
|
||
data = {
|
||
'toc': 'true',
|
||
'header_text': '我的文档',
|
||
'footer_text': '© 2024',
|
||
'filename_text': '文档名称'
|
||
}
|
||
|
||
response = requests.post(
|
||
'http://localhost:8000/api/pdf/convert',
|
||
files=files,
|
||
data=data
|
||
)
|
||
|
||
if response.status_code == 200:
|
||
with open(output_path, 'wb') as out:
|
||
out.write(response.content)
|
||
print(f"PDF 已保存到: {output_path}")
|
||
else:
|
||
print(f"转换失败: {response.text}")
|
||
|
||
# 使用示例
|
||
convert_word_to_pdf('document.docx', 'output.pdf')
|
||
```
|
||
|
||
## cURL 示例
|
||
|
||
```bash
|
||
# 上传 Word 文件转 PDF
|
||
curl -X POST http://localhost:8000/api/pdf/convert \
|
||
-F "file=@document.docx" \
|
||
-F "toc=true" \
|
||
-F "header_text=我的文档" \
|
||
-F "footer_text=© 2024" \
|
||
-o output.pdf
|
||
|
||
# Markdown 内容转 PDF
|
||
curl -X POST http://localhost:8000/api/pdf/convert \
|
||
-F "markdown_content=# 标题\n\n这是内容" \
|
||
-F "filename_text=文档" \
|
||
-o output.pdf
|
||
```
|
||
|
||
## 错误处理
|
||
|
||
API 返回的错误格式:
|
||
|
||
```json
|
||
{
|
||
"detail": "错误信息"
|
||
}
|
||
```
|
||
|
||
常见错误:
|
||
|
||
| 错误信息 | 原因 | 解决方法 |
|
||
|---------|------|----------|
|
||
| 必须提供 file、file_path 或 markdown_content 中的一个 | 未提供输入 | 检查请求参数 |
|
||
| 不支持的文件格式 | 文件格式错误 | 确保是 .doc/.docx/.md |
|
||
| 文件不存在 | 本地文件路径无效 | 检查 file_path 参数 |
|
||
| PDF 转换失败 | 转换过程出错 | 查看服务器日志 |
|
||
|
||
## 返回格式
|
||
|
||
### download=true (默认)
|
||
|
||
直接返回 PDF 文件流:
|
||
|
||
```
|
||
Content-Type: application/pdf
|
||
Content-Disposition: attachment; filename="document.pdf"
|
||
```
|
||
|
||
### download=false
|
||
|
||
返回 JSON,包含 base64 编码的 PDF:
|
||
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"pdf_base64": "JVBERi0xLjQK...",
|
||
"filename": "document.pdf",
|
||
"size": 12345
|
||
}
|
||
```
|