1. 总览:同一个入口,不同解析器
当用户上传文件时,技术链路通常是:
#mermaid-svg-EXUIk8R5msHwbgOx{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-EXUIk8R5msHwbgOx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EXUIk8R5msHwbgOx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EXUIk8R5msHwbgOx .error-icon{fill:#552222;}#mermaid-svg-EXUIk8R5msHwbgOx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EXUIk8R5msHwbgOx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EXUIk8R5msHwbgOx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EXUIk8R5msHwbgOx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EXUIk8R5msHwbgOx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EXUIk8R5msHwbgOx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EXUIk8R5msHwbgOx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EXUIk8R5msHwbgOx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EXUIk8R5msHwbgOx .marker.cross{stroke:#333333;}#mermaid-svg-EXUIk8R5msHwbgOx svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EXUIk8R5msHwbgOx p{margin:0;}#mermaid-svg-EXUIk8R5msHwbgOx .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-EXUIk8R5msHwbgOx .cluster-label text{fill:#333;}#mermaid-svg-EXUIk8R5msHwbgOx .cluster-label span{color:#333;}#mermaid-svg-EXUIk8R5msHwbgOx .cluster-label span p{background-color:transparent;}#mermaid-svg-EXUIk8R5msHwbgOx .label text,#mermaid-svg-EXUIk8R5msHwbgOx span{fill:#333;color:#333;}#mermaid-svg-EXUIk8R5msHwbgOx .node rect,#mermaid-svg-EXUIk8R5msHwbgOx .node circle,#mermaid-svg-EXUIk8R5msHwbgOx .node ellipse,#mermaid-svg-EXUIk8R5msHwbgOx .node polygon,#mermaid-svg-EXUIk8R5msHwbgOx .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-EXUIk8R5msHwbgOx .rough-node .label text,#mermaid-svg-EXUIk8R5msHwbgOx .node .label text,#mermaid-svg-EXUIk8R5msHwbgOx .image-shape .label,#mermaid-svg-EXUIk8R5msHwbgOx .icon-shape .label{text-anchor:middle;}#mermaid-svg-EXUIk8R5msHwbgOx .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-EXUIk8R5msHwbgOx .rough-node .label,#mermaid-svg-EXUIk8R5msHwbgOx .node .label,#mermaid-svg-EXUIk8R5msHwbgOx .image-shape .label,#mermaid-svg-EXUIk8R5msHwbgOx .icon-shape .label{text-align:center;}#mermaid-svg-EXUIk8R5msHwbgOx .node.clickable{cursor:pointer;}#mermaid-svg-EXUIk8R5msHwbgOx .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-EXUIk8R5msHwbgOx .arrowheadPath{fill:#333333;}#mermaid-svg-EXUIk8R5msHwbgOx .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-EXUIk8R5msHwbgOx .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-EXUIk8R5msHwbgOx .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EXUIk8R5msHwbgOx .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-EXUIk8R5msHwbgOx .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EXUIk8R5msHwbgOx .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-EXUIk8R5msHwbgOx .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-EXUIk8R5msHwbgOx .cluster text{fill:#333;}#mermaid-svg-EXUIk8R5msHwbgOx .cluster span{color:#333;}#mermaid-svg-EXUIk8R5msHwbgOx div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-EXUIk8R5msHwbgOx .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-EXUIk8R5msHwbgOx rect.text{fill:none;stroke-width:0;}#mermaid-svg-EXUIk8R5msHwbgOx .icon-shape,#mermaid-svg-EXUIk8R5msHwbgOx .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EXUIk8R5msHwbgOx .icon-shape p,#mermaid-svg-EXUIk8R5msHwbgOx .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-EXUIk8R5msHwbgOx .icon-shape rect,#mermaid-svg-EXUIk8R5msHwbgOx .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EXUIk8R5msHwbgOx .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-EXUIk8R5msHwbgOx .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-EXUIk8R5msHwbgOx :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
xlsx
docx
用户上传文件
Upload API
保存原件
按扩展名路由
Excel 处理器
Word 处理器
PDF 处理器
预览数据
预览数据
预览数据
前端预览与编辑
保存编辑结果
下载原件 / 下载编辑结果
2. 插件与依赖清单
先统一约定:本文里“插件”指第三方依赖包(Python 包 / 前端 npm 包)。
2.1 Python 端插件(后端)
| fastapi | 0.111.0 | 提供上传、预览、保存、下载 API | 必须 |
| uvicorn | 0.29.0 | 启动 FastAPI 服务 | 必须 |
| python-multipart | 0.0.9 | 支持 multipart/form-data 文件上传 | 必须 |
| openpyxl | 3.1.2 | 解析/回写 Excel(xlsx) | Excel 必须 |
| pandas | 2.2.2 | Excel/表格辅助清洗(可选但常用) | 推荐 |
| python-docx | 1.1.0 | 解析/生成 Word(docx) | Word 必须 |
| pymupdf (fitz) | 1.24.9 | PDF 文本、表格、分页信息提取 | PDF 必须 |
对应 import 写法(便于直接复制代码):
2.2 前端插件(预览与编辑)
| xlsx | ^0.18.5 | 浏览器端 Excel 预览(按 sheet 渲染) | Excel 预览必须 |
| mammoth | ^1.11.0 | 浏览器端 Word 转 HTML 预览 | Word 预览必须 |
| react-data-grid | 7.0.0-beta.59 | 表格编辑组件(Excel/Word 表格/PDF 表格修订) | 需要编辑时必须 |
2.3 可选插件(PDF OCR 兜底)
| pytesseract + Pillow | PDF/图片 OCR 兜底 | 可选 |
| paddleocr | 中文 OCR 兜底(效果通常更好) | 可选 |
2.4 可选插件(兼容旧格式)
| xlrd | 仅当你要直接读取 .xls 时使用 | 可选 |
| libreoffice (soffice) | 把 .doc 转成 .docx,再交给 python-docx 解析 | 可选 |
2.5 安装命令示例
# Backend
pip install fastapi uvicorn python-multipart openpyxl pandas python-docx pymupdf
# Frontend
npm install xlsx mammoth react-data-grid
# Optional OCR fallback
pip install pytesseract pillow
# or
pip install paddleocr
# Optional legacy-format support
pip install xlrd
3. 通用基础:上传、存储、路由
3.1 上传接口示例
from pathlib import Path
from fastapi import APIRouter, UploadFile, File
router = APIRouter()
@router.post("/api/files/upload")
async def upload(files: list[UploadFile] = File(...)):
items = []
for f in files:
suffix = Path(f.filename).suffix.lower()
raw_path = save_raw_file(f) # 保存原件
if suffix == ".xlsx":
file_id = process_excel(raw_path)
file_type = "excel"
elif suffix == ".docx":
file_id = process_word(raw_path)
file_type = "word"
elif suffix == ".pdf":
file_id = process_pdf(raw_path)
file_type = "pdf"
else:
items.append({"filename": f.filename, "error": "unsupported file type"})
continue
items.append({"file_id": file_id, "type": file_type, "filename": f.filename})
return {"items": items}
说明:openpyxl 主流场景是 .xlsx,python-docx 主流场景是 .docx。
如果必须支持 .xls / .doc,建议先做“格式转换”再进入本文解析流水线。
3.2 原件下载接口(通用)
from fastapi.responses import FileResponse
@router.get("/api/files/{file_id}/download/raw")
def download_raw(file_id: str):
path = locate_raw_file(file_id)
return FileResponse(path, filename=path.name)
4. Excel:解析、处理、预览、编辑保存、下载
本章用到的插件:openpyxl、pandas(可选)、xlsx、react-data-grid。
Excel 的特点是天然二维网格,所以处理策略是“保留 sheet + 保留行列”。
4.1 解析(Python)
from openpyxl import load_workbook
def parse_excel(path: str) –> dict:
wb = load_workbook(path, data_only=True)
sheets: dict[str, list[list[str]]] = {}
for sheet_name in wb.sheetnames:
ws = wb[sheet_name]
rows: list[list[str]] = []
for row in ws.iter_rows(values_only=True):
rows.append(["" if c is None else str(c) for c in row])
sheets[sheet_name] = rows
return {
"sheet_names": wb.sheetnames,
"sheets": sheets,
}
4.2 处理(可选)
常见的通用处理:
def normalize_excel_rows(rows: list[list[str]]) –> list[list[str]]:
if not rows:
return [[""]]
width = max(len(r) for r in rows)
normalized = []
for r in rows:
row = ["" if c is None else str(c) for c in r]
if len(row) < width:
row += [""] * (width – len(row))
normalized.append(row[:width])
return normalized
4.3 前端预览
前端展示方式:
4.4 编辑保存
PUT /api/excel/{file_id}/edit
{
"sheet_name": "Sheet1",
"rows": [["A1", "B1"], ["A2", "B2"]]
}
后端可把编辑结果保存成:
4.5 下载
@router.get("/api/excel/{file_id}/download/edited")
def download_excel_edited(file_id: str):
edited_path = build_edited_excel(file_id) # 根据保存的 rows 重建 xlsx
return FileResponse(edited_path, filename=edited_path.name)
存储后端前端用户存储后端前端用户#mermaid-svg-MwyRjjL0o67weGPg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MwyRjjL0o67weGPg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MwyRjjL0o67weGPg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MwyRjjL0o67weGPg .error-icon{fill:#552222;}#mermaid-svg-MwyRjjL0o67weGPg .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MwyRjjL0o67weGPg .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MwyRjjL0o67weGPg .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MwyRjjL0o67weGPg .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MwyRjjL0o67weGPg .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MwyRjjL0o67weGPg .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MwyRjjL0o67weGPg .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MwyRjjL0o67weGPg .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MwyRjjL0o67weGPg .marker.cross{stroke:#333333;}#mermaid-svg-MwyRjjL0o67weGPg svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MwyRjjL0o67weGPg p{margin:0;}#mermaid-svg-MwyRjjL0o67weGPg .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-MwyRjjL0o67weGPg text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-MwyRjjL0o67weGPg .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-MwyRjjL0o67weGPg .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-MwyRjjL0o67weGPg .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-MwyRjjL0o67weGPg .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-MwyRjjL0o67weGPg #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-MwyRjjL0o67weGPg .sequenceNumber{fill:white;}#mermaid-svg-MwyRjjL0o67weGPg #sequencenumber{fill:#333;}#mermaid-svg-MwyRjjL0o67weGPg #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-MwyRjjL0o67weGPg .messageText{fill:#333;stroke:none;}#mermaid-svg-MwyRjjL0o67weGPg .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-MwyRjjL0o67weGPg .labelText,#mermaid-svg-MwyRjjL0o67weGPg .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-MwyRjjL0o67weGPg .loopText,#mermaid-svg-MwyRjjL0o67weGPg .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-MwyRjjL0o67weGPg .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-MwyRjjL0o67weGPg .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-MwyRjjL0o67weGPg .noteText,#mermaid-svg-MwyRjjL0o67weGPg .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-MwyRjjL0o67weGPg .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-MwyRjjL0o67weGPg .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-MwyRjjL0o67weGPg .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-MwyRjjL0o67weGPg .actorPopupMenu{position:absolute;}#mermaid-svg-MwyRjjL0o67weGPg .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-MwyRjjL0o67weGPg .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-MwyRjjL0o67weGPg .actor-man circle,#mermaid-svg-MwyRjjL0o67weGPg line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-MwyRjjL0o67weGPg :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}上传 ExcelPOST /upload保存 raw.xlsxparse_excelsheet_names + sheets编辑单元格PUT /excel/{file_id}/edit保存 excel_edits.json / edited.xlsx下载编辑版GET /excel/{file_id}/download/editededited.xlsx
5. Word:解析、处理、预览、编辑保存、下载
本章用到的插件:python-docx、mammoth、react-data-grid。
Word 的天然结构是“段落 + 表格”,不建议简单压平为纯二维表。
5.1 解析(Python)
from docx import Document
def parse_word(path: str) –> dict:
doc = Document(path)
blocks: list[dict] = []
# 段落块
for p in doc.paragraphs:
text = p.text.strip()
if text:
blocks.append({"type": "paragraph", "text": text})
# 表格块
for table_index, table in enumerate(doc.tables):
rows = []
for row in table.rows:
rows.append([cell.text.strip() for cell in row.cells])
blocks.append({"type": "table", "table_index": table_index, "rows": rows})
return {"blocks": blocks}
5.2 处理(可选)
5.3 前端预览
Word 常见做法是双视图:
5.4 编辑保存
PUT /api/word/{file_id}/edit
{
"blocks": [
{"type": "paragraph", "text": "Updated paragraph"},
{"type": "table", "table_index": 0, "rows": [["Header1", "Header2"], ["V1", "V2"]]}
]
}
后端可以:
5.5 下载
@router.get("/api/word/{file_id}/download/edited")
def download_word_edited(file_id: str):
edited_docx = build_edited_word_docx(file_id)
return FileResponse(edited_docx, filename=edited_docx.name)
6. PDF:解析、处理、预览、编辑保存、下载
本章用到的插件:pymupdf(可选 OCR:pytesseract/paddleocr)、react-data-grid。
PDF 的关键是“按页处理”,因为页面是它的天然单位。
6.1 解析(Python)
import fitz
def parse_pdf(path: str) –> dict:
pages = []
with fitz.open(path) as doc:
for i in range(doc.page_count):
page = doc.load_page(i)
text = page.get_text("text") or ""
tables = []
finder = page.find_tables()
if finder and finder.tables:
for t in finder.tables:
tables.append([[str(c or "").strip() for c in row] for row in t.extract()])
pages.append({
"page_no": i + 1,
"text": text,
"tables": tables,
"width": float(page.rect.width),
"height": float(page.rect.height),
})
return {"page_count": len(pages), "pages": pages}
6.2 处理(可选)
6.3 前端预览
建议双层:
6.4 编辑保存
PUT /api/pdf/{file_id}/edit
{
"page_no": 1,
"table_index": 0,
"rows": [["Col1", "Col2"], ["A", "B"]],
"notes": "manual correction"
}
后端可保存:
6.5 下载
存储后端前端用户存储后端前端用户#mermaid-svg-fJ1lmYaePWYry9p8{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fJ1lmYaePWYry9p8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fJ1lmYaePWYry9p8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fJ1lmYaePWYry9p8 .error-icon{fill:#552222;}#mermaid-svg-fJ1lmYaePWYry9p8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fJ1lmYaePWYry9p8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fJ1lmYaePWYry9p8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fJ1lmYaePWYry9p8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fJ1lmYaePWYry9p8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fJ1lmYaePWYry9p8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fJ1lmYaePWYry9p8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fJ1lmYaePWYry9p8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fJ1lmYaePWYry9p8 .marker.cross{stroke:#333333;}#mermaid-svg-fJ1lmYaePWYry9p8 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fJ1lmYaePWYry9p8 p{margin:0;}#mermaid-svg-fJ1lmYaePWYry9p8 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-fJ1lmYaePWYry9p8 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-fJ1lmYaePWYry9p8 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-fJ1lmYaePWYry9p8 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-fJ1lmYaePWYry9p8 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-fJ1lmYaePWYry9p8 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-fJ1lmYaePWYry9p8 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-fJ1lmYaePWYry9p8 .sequenceNumber{fill:white;}#mermaid-svg-fJ1lmYaePWYry9p8 #sequencenumber{fill:#333;}#mermaid-svg-fJ1lmYaePWYry9p8 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-fJ1lmYaePWYry9p8 .messageText{fill:#333;stroke:none;}#mermaid-svg-fJ1lmYaePWYry9p8 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-fJ1lmYaePWYry9p8 .labelText,#mermaid-svg-fJ1lmYaePWYry9p8 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-fJ1lmYaePWYry9p8 .loopText,#mermaid-svg-fJ1lmYaePWYry9p8 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-fJ1lmYaePWYry9p8 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-fJ1lmYaePWYry9p8 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-fJ1lmYaePWYry9p8 .noteText,#mermaid-svg-fJ1lmYaePWYry9p8 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-fJ1lmYaePWYry9p8 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-fJ1lmYaePWYry9p8 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-fJ1lmYaePWYry9p8 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-fJ1lmYaePWYry9p8 .actorPopupMenu{position:absolute;}#mermaid-svg-fJ1lmYaePWYry9p8 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-fJ1lmYaePWYry9p8 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-fJ1lmYaePWYry9p8 .actor-man circle,#mermaid-svg-fJ1lmYaePWYry9p8 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-fJ1lmYaePWYry9p8 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}上传 PDFPOST /upload保存 raw.pdfparse_pdf(分页提取)pages/meta修订表格PUT /pdf/{file_id}/edit保存 pdf_edits.json / edited.pdf下载编辑版GET /pdf/{file_id}/download/editededited.pdf
7. API 清单(示例)
| Excel | GET /api/excel/{file_id}/preview | PUT /api/excel/{file_id}/edit | download/raw / download/edited |
| Word | GET /api/word/{file_id}/preview | PUT /api/word/{file_id}/edit | download/raw / download/edited |
| GET /api/pdf/{file_id}/preview | PUT /api/pdf/{file_id}/edit | download/raw / download/edited |
说明:这三组接口可以由同一个服务实现。本文拆开写,仅用于按文件格式独立说明技术实现细节。
8. 工程落地建议(纯技术)
9. 效果
excel预览效果

word预览效果

pdf 预览效果




