前端vue进行markdown渲染,表格,Echarts,文本,图片等……

image.png
image.png
image.png
image.png

我下载的版本

  • npm install katex@0.16.8
  • npm install marked@4.2.12 --save
  • npm install highlight.js@10.7.3 --save
<template>
  <div style="margin-top: 100px;width:100%;overflow-x: hidden;">
    <div v-html="renderedContent" ref="markdownContent"></div>
  </div>
</template>
 
<script>
import { marked } from "marked";
import hljs from "highlight.js";
// import 'highlight.js/styles/github.css';
import "highlight.js/styles/dracula.css"; // 引入Dracula深色主题
// import { renderMathInElement } from "katex"; 引入方式根据版本不同有区别,使用方法也有区别,请注意版本
import { default as autoRender } from "katex/dist/contrib/auto-render.mjs"; // 注意路径
import "katex/dist/katex.min.css";

export default {
  data() {
    return {
      renderedContent: `# Markdown渲染示例

## 文本样式

这是**粗体文本**,这是*斜体文本*,这是***粗斜体文本***,这是~~删除线文本~~。

## 图片

![示例图片](https://picsum.photos/800/400?random=1)

## 代码块

\`\`\`javascript
function helloWorld() {
  console.log('Hello, world!');
}
\`\`\`

## 表格

| 姓名 | 年龄 | 职业 |
|------|------|------|
| 张三 | 28   | 工程师 |
| 李四 | 32   | 设计师 |
| 王五 | 45   | 产品经理 |

## 数学公式

当 $a \\ne 0$ 时,方程 $ax^2 + bx + c = 0$ 有两个解,它们是:

$$x = {-b \\pm \\sqrt{b^2-4ac} \\over 2a}$$

## ECharts图表

[chart:bar]
{
  "xAxis": ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"],
  "yAxis": {},
  "series": [{
    "name": "销量",
    "type": "bar",
    "data": [5, 20, 36, 10, 10, 20]
  }]
}
[/chart]

[chart:line]
{
  "xAxis": {
    "type": "category",
    "data": ["1月", "2月", "3月", "4月", "5月", "6月"]
  },
  "yAxis": {
    "type": "value"
  },
  "series": [{
    "data": [820, 932, 901, 934, 1290, 1330],
    "type": "line"
  }]
}
[/chart]

[chart:pie]
{
  "legend": ["直接访问", "邮件营销", "联盟广告", "视频广告", "搜索引擎"],
  "series": [{
    "name": "访问来源",
    "type": "pie",
    "radius": "50%",
    "data": [
      {"value": 335, "name": "直接访问"}, 
      {"value": 310, "name": "邮件营销"},
      {"value": 234, "name": "联盟广告"},
      {"value": 135, "name": "视频广告"},
      {"value": 1548, "name": "搜索引擎"}
    ]
  }]
}
[/chart]`,
    };
  },
  watch: {},
  mounted() {
  },
  created() {
    this.renderMarkdown(this.renderedContent);
  },
  methods: {
    renderMarkdown(content) {
      if (!content) return;

      // 配置marked解析器
      marked.setOptions({
        highlight: (code, lang) => {
          if (lang && hljs.getLanguage(lang)) {
            return hljs.highlight(code, { language: lang }).value;
          }
          return hljs.highlightAuto(code).value;
        },
        breaks: true,
        gfm: true,
      });

      // 自定义解析器处理特殊语法
      const renderer = new marked.Renderer();
      // 处理图片
      renderer.image = (href, title, text) => {
        // 基础图片标签
        let imgTag = `<img src="${href}" alt="${text}"`;
        if (title) imgTag += ` title="${title}"`;

        // 检查是否有自定义属性 (例如: width=300)
        const widthMatch = href.match(/width=(\d+)/);
        const heightMatch = href.match(/height=(\d+)/);

        if (widthMatch) imgTag += ` width="${widthMatch[1]}"`;
        if (heightMatch) imgTag += ` height="${heightMatch[1]}"`;

        imgTag += ' class="img-fluid rounded" loading="lazy">';

        // 处理图片懒加载和错误处理
        return `
          <div class="markdown-image-container">
            ${imgTag}
            <noscript>${imgTag}</noscript>
          </div>
        `;
      };
      // 处理代码块
      renderer.code = (code, language) => {
        return `<div><div>复制</div><pre><code class="hljs ${language}">${marked.parse(
          code
        )}</code></pre></div>`;
      };
      //处理表格
      renderer.table = function (header, body) {
        // 为表格添加样式类
        return `<table class="markdown-table">
                  <thead>${header}</thead>
                  <tbody>${body}</tbody>
                </table>`;
      };
      // 处理ECharts图表 (使用自定义语法 [chart:type] {...} [/chart])
      const chartRegex = /\[chart:(\w+)\]([\s\S]*?)\[\/chart\]/g;
      content = content.replace(chartRegex, (match, type, options) => {
        const chartId = `chart-${Date.now()}-${Math.floor(
          Math.random() * 1000
        )}`;
        return `<div class="markdown-chart" id="${chartId}" data-chart-type="${type}" data-chart-options="${encodeURIComponent(
          options
        )}"></div>`;
      });
      // 渲染Markdown为HTML
      let htmlContent = marked(content, { renderer });
      // 净化HTML防止XSS攻击
      // htmlContent = DOMPurify.sanitize(htmlContent);
      // 设置渲染内容
      this.renderedContent = htmlContent;
      // 异步渲染数学公式
      this.$nextTick(() => {
        const markdownElement = this.$refs.markdownContent;
        if (markdownElement) {
          autoRender(markdownElement, {
            delimiters: [
              { left: "$$", right: "$$", display: true },
              { left: "$", right: "$", display: false },
              { left: "\\(", right: "\\)", display: false },
              { left: "\\[", right: "\\]", display: true },
            ],
          });
          //     // 渲染ECharts图表
          this.renderCharts();
        }
      });
    },
    renderCharts() {
      const chartElements =
        this.$refs.markdownContent.querySelectorAll(".markdown-chart");

      chartElements.forEach((element) => {
        const chartType = element.getAttribute("data-chart-type");
        let optionsStr = element.getAttribute("data-chart-options");
 

        try {
          // 解码并解析图表配置
          optionsStr = decodeURIComponent(optionsStr);

          const chartOptions = JSON.parse(optionsStr);
          // const chartOptions = optionsStr
             
          // 创建ECharts实例
          const chart = this.$echarts.init(element);
          // // 根据图表类型设置配置
          let finalOptions = {
            tooltip: {
              trigger: "axis",
            },
            legend: {
              data: chartOptions?.legend?.data ?? [],
            },
            xAxis: {
              type: "category",
              data: chartOptions?.xAxis?.data ?? [],
            },
            yAxis: {
              type: "value",
            },
            series: chartOptions?.series ?? [],
          };

   
          // // 根据不同图表类型调整配置
          if (chartType === "pie") {
            finalOptions = {
              tooltip: {
                trigger: "item",
              },
              legend: {
                orient: "vertical",
                left: "left",
                data: chartOptions?.legend?.data ?? [],
              },
              series: [
                {
                  type: "pie",
                  radius: "50%",
                  data: chartOptions?.series?.[0]?.data ?? [],
                },
              ],
            };
          }
          // 设置图表配置
          chart.setOption(finalOptions);

          // 监听窗口大小变化,调整图表
          window.addEventListener("resize", () => {
            chart.resize();
          });
        } catch (error) {
          element.innerHTML =
            '<div class="text-danger">图表渲染失败,请检查配置格式</div>';
        }
      });
    }
  },
};
</script>

<style scoped>
::v-deep .markdown-chart {
  height: 400px;
  width:600px;
  margin: 16px auto 16px auto;
  border: 1px solid #eee;
  border-radius: 4px;
  padding: 10px;
  background-color: #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,0.12);
}
/* 基础样式 */
.markdown-container {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
    Ubuntu, sans-serif;
  line-height: 1.6;
  color: #333;
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

/* 代码块样式 */
::v-deep .markdown-body pre {
  background-color: #282c34;
  padding: 16px;
  border-radius: 6px;
  overflow-x: auto;
  margin: 1em 0;
}

::v-deep .markdown-body code {
  font-family: Consolas, Monaco, "Andale Mono", monospace;
  font-size: 0.95em;
}

/* 表格样式 */
::v-deep .markdown-body table {
  width: 100%;
  border-collapse: collapse;
  margin: 1em 0;
  overflow: hidden;
  border-radius: 4px;
}
::v-deep .markdown-table {
  border-collapse: collapse;
  border: 1px solid black;
}
::v-deep .markdown-table tr,
::v-deep .markdown-table td,
::v-deep .markdown-table th {
  border: 1px solid black;
}
::v-deep .markdown-body th,
::v-deep .markdown-body td {
  border: 1px solid #3e4452;
  padding: 10px 16px;
  text-align: left;
}

::v-deep .markdown-body th {
  background-color: #21252b;
  font-weight: 600;
  color: #e06c75;
}

::v-deep .markdown-body tr:nth-child(even) {
  background-color: rgba(255, 255, 255, 0.05);
}

::v-deep .markdown-body tr:hover {
  background-color: rgba(255, 255, 255, 0.1);
}

/* 链接样式 */
::v-deep .markdown-body a {
  color: #61afef;
  text-decoration: none;
}

::v-deep .markdown-body a:hover {
  text-decoration: underline;
}

/* 标题样式 */
::v-deep .markdown-body h1,
::v-deep .markdown-body h2,
::v-deep .markdown-body h3 {
  color: #e06c75;
  margin-top: 1.5em;
  margin-bottom: 0.5em;
}

/* 列表样式 */
::v-deep .markdown-body ul,
::v-deep .markdown-body ol {
  padding-left: 1.5em;
  margin: 1em 0;
}

::v-deep .markdown-body li {
  margin-bottom: 0.5em;
}

.katex,
.katex * {
  font-family: "KaTeX_Main", "Times New Roman", serif;
}
</style>    

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容