完整实现方案:Flask 3.0 + mistune 2.0 + mermaid 10.9 渲染 Markdown 中的 mermaid 图表

以下是基于 Flask 3.0、mistune 2.0 和 mermaid 10.9 的完整可运行方案,逻辑参考 server.js,支持:

  1. 上传 .md/.txt 文件(.txt 需符合 CSV 格式自动转换为 mermaid 流程图)
  2. Markdown 语法渲染(含代码块、链接、排版等)
  3. mermaid 图表渲染(支持横向/纵向流程图)
  4. 完整的错误处理和页面交互

一、项目结构

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 渲染原理
  1. Markdown 中的 Mermaid 代码块(mermaid ... )被 MermaidRenderer 识别为 <div class="mermaid">...</div>
  2. 前端页面引入 mermaid.min.js 后,mermaid.initialize() 会自动扫描页面中所有 class="mermaid" 的元素
  3. 将 Mermaid 语法转换为 SVG 图表并渲染到页面

五、运行和测试步骤

  1. 安装依赖:执行 pip install flask==3.0.3 mistune==2.0.5 python-multipart==0.0.6
  2. 下载 mermaid.min.js 10.9 放入 static 目录
  3. 启动服务:python app.py
  4. 访问页面:浏览器打开 http://localhost:8000
  5. 测试场景:
    • 上传 test1.md:直接渲染 Markdown 和 Mermaid 图表
    • 上传 test1.txt:自动转换为 Mermaid 流程图并渲染
    • 上传其他格式文件:提示“仅支持 .md 或 .txt 格式”
    • 上传格式错误的 .txt:提示格式错误信息

六、扩展功能(可选)

  1. 支持更多 Mermaid 图表类型(如饼图、时序图):只需在 Markdown 中编写对应 Mermaid 语法
  2. 自定义 Mermaid 主题:修改 mermaid.initialize() 中的 themethemeVariables
  3. 支持文件下载:在渲染结果页面添加“下载 Markdown”按钮
  4. 代码块高亮:引入 Prism.js 或 Highlight.js 对代码块进行语法高亮

该方案完全基于指定版本实现,逻辑与 server.js 一致,可直接运行并满足 Markdown + mermaid 渲染需求。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐