Vue3实现markdown-it支持chartjs、Echartjs、mermaid的渲染

4,774 阅读3分钟

近日接到一个小的需求,目标是实现一个AI问答的页面,该页面使用markdown语法输出内容,但是领导希望能够支持图表和流程图的内容输出。这里实现了echarts、chartjs、mermaid的语法支持。

1. 安装markdown-it

npm install markdown-it chart.js echarts mermaid

2. 使用

首先初始化markdown-it实例,并配置基本选项:

javascript

const md = new MarkdownIt({
    html: true// 启用HTML标签
    breaks: true// 转换换行符为<br>
    linkify: true, // 自动转换URL为链接
    typographer: true // 启用语言中性的替换
})

自定义fence渲染规则

通过扩展markdown-it的fence规则,我们可以支持自定义的代码块渲染: 在Markdown中,fence(围栏)代码块是指使用三个反引号 (```) 包裹的代码块

md.use(function (md) {// 重写fence渲染规则
    md.renderer.rules.fence = function (tokens, idx, options, env, slf) {
    // 渲染逻辑
}
- `tokens`: 包含所有解析后的标记的数组
- `idx`: 当前处理的标记索引
- `options`: markdown-it的配置选项
- `env`: 环境变量
- `slf`: 渲染器自身的引用
基本结构:
// 测试数据
const answer = ref(`
# Hello\n## Hi\n> hello world
\`\`\`mermaidChart
sequenceDiagram
    participant Alice
    participant Bob
    Alice->>John: Hello John, how are you?
    loop HealthCheck
        John->>John: Fight against hypochondria
    end
    Note right of John: Rational thoughts <br/>prevail!
    John-->>Alice: Great!
    John->>Bob: How about you?
    Bob-->>John: Jolly good!

`)
const eChartOption = ref(null) // 存储 ECharts 配置
md.use(function (md) {
  md.renderer.rules.fence = function (tokens, idx, options, env, slf) {
    console.log('tokens', tokens)
    // 此处判断是否为 echarts 代码块
    if (tokens[idx].info === "echarts") {
      eChartOption.value = JSON.parse(tokens[idx].content) //此处表示将内容存起来,存到当前页面的变量去
      return `<div id="echarts-id" style="width: 600px;height:400px;"></div>`
    } else if (tokens[idx].info === "chartjs") {// 此处判断是否为 chartjs 代码块
      eChartOption.value = JSON.parse(tokens[idx].content)
      return `<canvas id="chartjs-id"></canvas>`
    } else if (tokens[idx].info === "mermaidChart") {// 此处判断是否为 mermaidChart 代码块
      const code = tokens[idx].content.trim()
      eChartOption.value = code
      return `<div id="mermaid-id" class="mermaid">${code}</div>`
    } else { // 其他代码块
      return `<pre><code class='languge-${tokens[idx].info}'>${tokens[idx].content}</code></pre>`;
    }
  }
})

3. 图表渲染实现

在组件挂载后,我们需要初始化各种图表:

// 在组件挂载时加载设置
onMounted(function () {
  nextTick(() => {
  // 渲染ECharts
    if (document.getElementById('echarts-id')) {
      const chartDom = document.getElementById('echarts-id')
      const myChart = echarts.init(chartDom)
      myChart.setOption(eChartOption.value)
    }
    // 渲染Chart.js
    if (document.getElementById('chartjs-id')) {
      const ctx = document.getElementById('chartjs-id');
      console.log('chartjs-id', ctx)
      new Chart(ctx, eChartOption.value);
    }
    // 渲染Mermaid
    if (document.getElementById('mermaid-id')) {
      mermaid.initialize({ startOnLoad: true });
      // 通过 id 获取元素并渲染图表
      const mermaidChart = document.getElementById('mermaid-id');
      if (mermaidChart) {
        mermaid.init(undefined, mermaidChart);
      }
    }
  })
  loadSettingsFromStorage()
})
  <div class="answer-area">
    <div v-if="renderedAnswer" class="markdown-body" v-html="renderedAnswer" />
    <div v-else class="placeholder">
      <template v-if="loading">
        <div class="thinking">
          <span>思考中</span>
          <span class="thinking-spinner"></span>
        </div>
      </template>
      <template v-else>
        你好!很高兴见到你。有什么我可以帮忙的吗?
      </template>
    </div>
  </div>
  
  renderedAnswer.value = md.render(answer.value)// 渲染语句

4. 使用示例

4.1 ECharts示例

// echarts 测试数据
const answer = ref(`
# Hello\n## Hi\n> hello world
\`\`\`echarts
    {
        "title": {
            "text": "第一个 ECharts 实例"
        },
        "tooltip": {},
        "legend": {
            "data":["小红", "小明", "小黑"]
        },
        "xAxis": {
            "data": ["语文","数学","英语"]
        },
        "yAxis": {},
        "series": [
        {
            "name": "小红",
            "type": "bar",
            "data": [45, 15, 32]
        },
        {
            "name": "小明",
            "type": "bar",
            "data": [44, 14, 33]
        },
        {
            "name": "小黑",
            "type": "bar",
            "data": [38, 10, 35]
        }
        ]
    }
`)

image.png

4.2 Chart.js示例

// chartjs 测试数据
const answer = ref(`
# Hello\n## Hi\n> hello world
\`\`\`chartjs
{
  "type": "line",
  "data": {
    "labels": ["一月", "二月", "三月", "四月", "五月", "六月", "七月"],
    "datasets": [
      {
        "label": "我的第一个数据集",
        "data": [65, 59, 80, 81, 56, 55, 40],
        "fill": false,
        "borderColor": "rgb(75, 192, 192)",
        "tension": 0.1
      }
    ]
  }
}
`)

image.png

4.3 Mermaid示例

// mermaid 测试数据
const answer = ref(`
# Hello\n## Hi\n> hello world
\`\`\`mermaidChart
sequenceDiagram
    participant Alice
    participant Bob
    Alice->>John: Hello John, how are you?
    loop HealthCheck
        John->>John: Fight against hypochondria
    end
    Note right of John: Rational thoughts <br/>prevail!
    John-->>Alice: Great!
    John->>Bob: How about you?
    Bob-->>John: Jolly good!

`)

image.png

单纯记录一下解决方案笔记,若有写的不好的地方烦请各位大佬指教一下~

参考这位大佬的文章:记一次为markdown-it添加echart支持