python:用 Flask 3 , mistune 2 和 mermaid.min.js 10.9 来实现 Markdown 中 mermaid 图表的渲染
一个完整实现方案:Flask 3.0.3 + mistune 2.0.5 + mermaid 10.9 渲染 Markdown 中的 Mermaid 图表
·
完整实现方案:Flask 3.0 + mistune 2.0 + mermaid 10.9 渲染 Markdown 中的 mermaid 图表
以下是基于 Flask 3.0、mistune 2.0 和 mermaid 10.9 的完整可运行方案,逻辑参考 server.js,支持:
- 上传
.md/.txt文件(.txt需符合 CSV 格式自动转换为 mermaid 流程图) - Markdown 语法渲染(含代码块、链接、排版等)
- mermaid 图表渲染(支持横向/纵向流程图)
- 完整的错误处理和页面交互
一、项目结构
mermaid-markdown-render/
├── app.py # Flask 后端核心代码
├── static/
│ ├── index.html # 前端上传页面
│ └── mermaid.min.js # mermaid 10.9 核心库(需手动下载)
├── test1.md # 测试用 Markdown 文件(含 Mermaid)
└── test1.txt # 测试用 CSV 格式文本文件
二、核心依赖安装
pip install flask==3.0.3 mistune==2.0.5 python-multipart==0.0.6
三、文件内容实现
1. 后端核心:app.py
# -*- coding: utf-8 -*-
from flask import Flask, request, send_file
import mistune
import os
from datetime import datetime
from werkzeug.utils import secure_filename
# ====================== 修复核心:显式配置静态目录 ======================
# static_folder:指定静态文件存放目录(相对路径)
# static_url_path:前端访问静态文件的 URL 路径(默认 /static,可自定义)
app = Flask(
__name__,
static_folder='static', # 显式指定静态文件夹为 static
static_url_path='/static' # 前端通过 /static/xxx 访问文件(如 /static/mermaid.min.js)
)
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024 # 5MB 限制
app.config['ALLOWED_EXTENSIONS'] = {'md', 'txt'}
# 创建上传文件夹(若不存在)
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
# ====================== 配置 mistune 渲染器(支持 Mermaid)======================
class MermaidRenderer(mistune.HTMLRenderer):
"""自定义渲染器,处理 Mermaid 代码块"""
def block_code(self, code, lang=None):
# 识别 mermaid 代码块
if lang and lang.lower() == 'mermaid':
return f'<div class="mermaid">{code}</div>'
# 其他代码块使用默认渲染
return super().block_code(code, lang)
# 初始化 mistune 解析器(仅保留 2.0.5 兼容的内置插件)
markdown_parser = mistune.create_markdown(
renderer=MermaidRenderer(),
plugins=[
'strikethrough', # 支持 ~~删除线~~(2.0.5 内置兼容)
'footnotes', # 支持脚注(2.0.5 内置兼容)
'table', # 支持表格(2.0.5 内置兼容)
]
)
# ====================== 核心工具函数:CSV → Mermaid 转换(参考 server.js)======================
def validate_and_convert_csv_to_mermaid(file_name, content):
"""
校验 .txt 文件(CSV 格式)并转换为 Mermaid 流程图
:param file_name: 文件名
:param content: 文件内容
:return: dict {valid: 布尔值, content: 转换后内容/错误信息}
"""
# 统一换行符 + 拆分行(过滤空行)
normalized_content = content.replace('\r\n', '\n').replace('\r', '\n')
lines = [line.strip() for line in normalized_content.split('\n') if line.strip()]
# 校验空文件
if not lines:
return {"valid": False, "content": ".txt 文本文件格式错误(空文件)"}
# 校验首行格式(必须是 source,target,weight)
first_line = lines[0]
if first_line != 'source,target,weight':
return {"valid": False, "content": ".txt 文本文件格式错误(首行需为 source,target,weight)"}
# 生成 Mermaid 横向流程图(graph LR),可改为 graph TD 纵向
mermaid_content = "graph LR\n"
# 处理数据行(跳过首行)
for line_num, line in enumerate(lines[1:], start=2):
parts = [item.strip() for item in line.split(',')]
if len(parts) < 2: # 至少需要 source 和 target
continue
source, target = parts[0], parts[1]
weight = parts[2] if len(parts) >= 3 and parts[2] else ""
# 节点名加前缀避免冲突,显示时用原始名称
source_node = f"node_{source}"
target_node = f"node_{target}"
# 权重标签(有则显示,无则不显示)
weight_label = f"|{weight}|" if weight else ""
# 拼接 Mermaid 语法
mermaid_content += f" {source_node}[\"{source}\"] -->{weight_label} {target_node}[\"{target}\"]\n"
# 包装为 Markdown 格式(含标题和转换时间)
final_md = f"# {file_name} 转换为 Mermaid 关系图\n\n" \
f"```mermaid\n{mermaid_content}\n```\n\n" \
f"> 转换时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
return {"valid": True, "content": final_md}
# ====================== 路由定义 ======================
@app.route('/')
def index():
"""首页:返回文件上传页面(使用静态文件路由)"""
# 修复:使用 url_for 生成静态文件的绝对路径,避免路径错误
return send_file(os.path.join(app.static_folder, 'index.html'))
@app.route('/upload-md', methods=['POST'])
def upload_and_render():
"""处理文件上传、转换、渲染"""
try:
# 1. 校验是否上传文件
if 'mdFile' not in request.files:
return render_error("上传失败", "请选择要上传的文件!")
file = request.files['mdFile']
filename = secure_filename(file.filename)
# 2. 校验文件名(避免空文件名)
if filename == '':
return render_error("上传失败", "文件名不能为空!")
# 3. 校验文件类型
file_ext = os.path.splitext(filename)[1].lower()[1:] # 获取扩展名(不含.)
if file_ext not in app.config['ALLOWED_EXTENSIONS']:
return render_error("上传失败", "仅支持 .md 或 .txt 格式的文件!")
# 4. 读取文件内容(UTF-8 编码)
file_content = file.read().decode('utf-8')
render_content = file_content
# 5. 处理 .txt 文件(CSV 格式转换为 Mermaid)
if file_ext == 'txt':
convert_result = validate_and_convert_csv_to_mermaid(filename, file_content)
if not convert_result['valid']:
return render_error("处理失败", convert_result['content'])
render_content = convert_result['content']
# 6. 渲染 Markdown(含 Mermaid 代码块)
rendered_html = markdown_parser(render_content)
# 7. 读取前端模板并替换内容(使用 app.static_folder 确保路径正确)
with open(os.path.join(app.static_folder, 'index.html'), 'r', encoding='utf-8') as f:
index_template = f.read()
final_html = index_template.replace('<div id="markdown-content"></div>', rendered_html)
return final_html
except Exception as e:
print(f"服务器异常:{str(e)}")
return render_error("服务器出错", "文件处理出错,请稍后重试!")
def render_error(title, message):
"""生成错误页面"""
error_html = f"""
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{title}</title>
<style>
body {{ font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 0 20px; }}
.error-box {{ border: 1px solid #dc3545; border-radius: 8px; padding: 20px; background-color: #f8d7da; }}
h1 {{ color: #dc3545; }}
a {{ color: #007bff; text-decoration: none; }}
a:hover {{ text-decoration: underline; }}
</style>
</head>
<body>
<div class="error-box">
<h1>{title}</h1>
<p>{message}</p>
<a href="/">返回上传页面</a>
</div>
</body>
</html>
"""
return error_html, 400 if "格式" in message or "上传" in message else 500
# ====================== 启动服务 ======================
if __name__ == '__main__':
app.run(debug=True, port=8000)
2. 前端页面:static/index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Markdown + Mermaid 渲染工具</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; }
.upload-container { margin: 30px 0; padding: 20px; border: 2px dashed #ccc; border-radius: 8px; }
.upload-container h2 { margin-bottom: 15px; color: #333; }
.btn-upload { padding: 10px 20px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
.btn-upload:hover { background-color: #0056b3; }
#markdown-content { margin-top: 30px; line-height: 1.8; }
#markdown-content h1, h2, h3 { margin: 20px 0 10px; color: #2c3e50; }
#markdown-content code { background-color: #f8f9fa; padding: 2px 4px; border-radius: 4px; }
#markdown-content pre { background-color: #f8f9fa; padding: 15px; border-radius: 8px; overflow-x: auto; margin: 10px 0; }
#markdown-content table { border-collapse: collapse; width: 100%; margin: 15px 0; }
#markdown-content th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
#markdown-content th { background-color: #f2f2f2; }
#markdown-content blockquote { border-left: 4px solid #ccc; padding: 0 15px; color: #666; margin: 10px 0; }
.mermaid { margin: 20px 0; } /* Mermaid 图表间距 */
</style>
<!-- 引入 Mermaid 10.9 核心库(本地文件,需手动下载) -->
<script src="/static/mermaid.min.js"></script>
<script>
// 初始化 Mermaid(配置渲染主题和样式)
mermaid.initialize({
startOnLoad: true, // 页面加载完成后自动渲染
theme: 'default', // 可选:default/neutral/dark/forest
themeVariables: {
primaryColor: '#e2f0ff',
lineColor: '#007bff',
fontSize: '14px'
}
});
</script>
</head>
<body>
<h1>📊 Markdown + Mermaid 渲染工具</h1>
<div class="upload-container">
<h2>上传文件</h2>
<form action="/upload-md" method="post" enctype="multipart/form-data">
<input type="file" name="mdFile" accept=".md,.txt" required>
<button type="submit" class="btn-upload">上传并渲染</button>
</form>
<p style="margin-top: 10px; color: #666;">
支持格式:.md(Markdown 文件,可直接包含 Mermaid 代码块)、.txt(CSV 格式,需符合 source,target,weight 表头)
</p>
</div>
<!-- Markdown 渲染结果将插入到这里 -->
<div id="markdown-content"></div>
</body>
</html>
3. 测试文件:test1.md(含 Mermaid 代码块)
### 测试 Markdown 中的 Mermaid 图表
这是一个直接在 Markdown 中编写的 Mermaid 流程图:
```mermaid
graph LR
A[用户] --> B[上传文件]
B --> C{文件类型}
C -->|.md| D[直接渲染 Markdown]
C -->|.txt| E[转换为 Mermaid]
D --> F[显示结果]
E --> F
表格支持测试
| 功能 | 支持情况 |
|---|---|
| Markdown 渲染 | ✅ |
| Mermaid 图表 | ✅ |
| CSV → Mermaid | ✅ |
| 代码块高亮 | ✅ |
备注:本文件可直接上传测试 Mermaid 渲染功能
#### 4. 测试文件:test1.txt(CSV 格式,自动转换)
```txt
source,target,weight
张三,李四,10000
李四,王五,9000
王五,刘大哥,8000
刘大哥,张三,5000
刘大哥,李四,3000
刘大哥,王五,2000
四、关键步骤说明
1. Mermaid 库下载
需手动下载 mermaid.min.js 10.9 并放入 static 目录:
- 下载地址:https://cdn.jsdelivr.net/npm/mermaid@10.9.5/dist/mermaid.min.js
- 直接右键保存文件到
static文件夹即可
2. 核心逻辑对应(参考 server.js)
| server.js 功能 | Flask 实现对应 |
|---|---|
| Express 静态文件目录 | Flask 自动识别 static 目录 |
| multer 文件上传 | Flask request.files + secure_filename |
| markdown-it 渲染 + Mermaid 插件 | mistune 自定义渲染器(MermaidRenderer) |
| CSV → Mermaid 转换函数 | validate_and_convert_csv_to_mermaid(逻辑完全对齐) |
| 错误处理 | 自定义 render_error 函数返回错误页面 |
| 端口监听 | app.run(port=8000) |
3. Mermaid 渲染原理
- Markdown 中的 Mermaid 代码块(
mermaid ...)被MermaidRenderer识别为<div class="mermaid">...</div> - 前端页面引入
mermaid.min.js后,mermaid.initialize()会自动扫描页面中所有class="mermaid"的元素 - 将 Mermaid 语法转换为 SVG 图表并渲染到页面
五、运行和测试步骤
- 安装依赖:执行
pip install flask==3.0.3 mistune==2.0.5 python-multipart==0.0.6 - 下载
mermaid.min.js 10.9放入static目录 - 启动服务:
python app.py - 访问页面:浏览器打开
http://localhost:8000 - 测试场景:
- 上传
test1.md:直接渲染 Markdown 和 Mermaid 图表 - 上传
test1.txt:自动转换为 Mermaid 流程图并渲染 - 上传其他格式文件:提示“仅支持 .md 或 .txt 格式”
- 上传格式错误的 .txt:提示格式错误信息
- 上传
六、扩展功能(可选)
- 支持更多 Mermaid 图表类型(如饼图、时序图):只需在 Markdown 中编写对应 Mermaid 语法
- 自定义 Mermaid 主题:修改
mermaid.initialize()中的theme和themeVariables - 支持文件下载:在渲染结果页面添加“下载 Markdown”按钮
- 代码块高亮:引入 Prism.js 或 Highlight.js 对代码块进行语法高亮
该方案完全基于指定版本实现,逻辑与 server.js 一致,可直接运行并满足 Markdown + mermaid 渲染需求。
更多推荐



所有评论(0)