markdown转html在项目中运用

3,241 阅读3分钟

起因

  • 在一个产品需求中,协议是后台模版管理的,模版是markdown语法编写的,并且协议中存在一些需要填充的信息。协议内容是在后台模版管理的markdown语法编辑器中编辑转义好后,发送到后端,再由后端进行安全相关的转义处理,最后返回给前端用户界面一个带变量的markdown协议字符串。

方案

方案一 marked 配合 highlight.js

  1. 安装
npm install marked highlight.js --save-dev
# OR
yarn add marked highlight.js --save-dev
  1. 使用
  • 将变量替换后,把协议字符串通过getHtml方法转换为html,然后使用v-html进行渲染。
<template>
    <div v-html="getHtml"></div>
</template>
<script>
    let marked = require('marked');
    let hljs = require('highlight.js');
    import 'highlight.js/styles/default.css';
    marked.setOptions({
        renderer: new marked.Renderer(),
        gfm: true,
        tables: true,
        breaks: false,
        pedantic: false,
        sanitize: false,
        smartLists: true,
        smartypants: false,
        highlight: function (code, lang) {
            if (lang && hljs.getLanguage(lang)) {    
                return hljs.highlight(lang, code, true).value;
            } else {
                return hljs.highlightAuto(code).value;
            }
        }
    });
 
   export default{
     name: 'protocol', 
     props: {
        protocol: {
            type: String,
            default: ''
        }
    }
    methods: {
        getHtml() {
            return marked(this.protocol || '', {
                sanitize: true
            });
        }
    }
  }
  1. 问题
  • 协议中的变量被替换后,会存在一些特殊的符号,例如脱敏字段的**,以及-1.#等等markdown相关的语法,会导致最后生成的HTML与后台创建的协议有出入,并且格式错乱等问题。

方案二 mavon-editor 配合 highlight.js

  1. 安装
npm install mavon-editor --save-dev
# OR
yarn add mavon-editor --save-dev
  1. 使用
  • 将变量替换后,把协议字符串直接赋值给组件的value进行渲染。
<template>
    <mavon-editor
      class="md"
      :value="protocol"
      :subfield = "false"
      :defaultOpen = "'preview'"
      :toolbarsFlag = "false"
      :editable="false"
      :scrollStyle="true"
      :ishljs = "true"
    ></mavon-editor>

</template>
<script>
     import mavonEditor from 'mavon-editor'
     import 'mavon-editor/dist/css/index.css'
   export default{
     name: 'protocol', 
     props: {
        protocol: {
            type: String,
            default: ''
        }
    }
  }
  1. 问题
  • 与方案一问题相同

最终解决方案 vue-element-admin markdown

MarkdownEditor-gitlab markdown-官网

  1. 安装
npm install tui-editor --save-dev
# OR
yarn add tui-editor --save-dev
  1. 使用
  • 将后端返回的协议字符串不做任何处理,直接通过组件的setValue方法传给组件作为编辑器的初始markdown语法的编辑值,赋值完成后通过组件的getHtml方法将markdown转换为HTML字符串,拿到HTML字符串后替换字符串中的所有变量。替换完成后调用动态生成iframe的方法显示协议内容。
<template>
    <div class="protocol-popup">
        <div id="self-iframe" class="popup-main">
            <div class="close-btn" @click="hideDialog('isShowAbsProtocol')"><img src="../../assets/images/loan/btn_popup_close@2x.png"></div>
            <div style="display:none;" :id="id" />
        </div>
    </div>
</template>
<script>
    import { protocolVarsReplace } from '../../filter/index'
    import 'codemirror/lib/codemirror.css' // codemirror
    import 'tui-editor/dist/tui-editor.css' // editor ui
    import 'tui-editor/dist/tui-editor-contents.css' // editor content
    import Editor from 'tui-editor'
    const defaultOptions = {
        ... // 参照官网
    }

    export default {
        name: 'MarddownEditor',
        props: {
            ...,
            protocol_name: {
                type: String,
                default: '测试'
            },
            protocol_object: {
                type: Object,
                default: {}
            },
            protocol: {
                type: String,
                default: ''
            }
    },
    data() {
        return {
            editor: null,
            content: ''
        }
    },
    computed: {
        editorOptions() {
            const options = Object.assign({}, defaultOptions, this.options)
            options.initialEditType = this.mode
            options.height = this.height
            options.language = this.language
            return options
        }
    },
    watch: {
        ...
    },
    mounted() {
        this.initEditor()
        this.editor.setValue(this.protocol) // 设置初始值
        let content =  `<h3 style="text-align: center;">${this.protocol_name}</h3>${protocolVarsReplace(this.getHtml(),this.protocol_object)}` // 生成HTML字符串并所有protocolVarsReplace方法替换变量
        this.createFrame(1, content) // 动态生成iframe并显示
    },
    destroyed() {
        this.destroyEditor()
    },
    methods: {
        createFrame( data, child) {
            const that = this
            const iframe = document.createElement("iframe");
            iframe.id = 'my-iframe'
            iframe.style.width = '100%';
            iframe.style.height = '100%';
            iframe.style.border = 'none';
            if (data) {
                if (navigator.userAgent.indexOf("MSIE") > -1 && !window.opera) {
                    iframe.onreadystatechange = function () {
                        if (iframe.readyState === "complete") { iframe.contentWindow.postMessage(JSON.stringify(data), '*')
                        }
                    };
                } else {
                    iframe.onload = function () { iframe.contentWindow.postMessage(JSON.stringify(data), '*')
                    };
                }
            }
            document.getElementById('self-iframe').appendChild(iframe);
            const iframeDom = document.getElementById('my-iframe').contentWindow.document
            iframeDom.getElementsByTagName('body')[0].innerHTML = child
            document.getElementById('my-iframe').onload = function() {
                that.loading = false
            }
        },
        hideDialog(name){
            this.$parent.hideDialog(name);
        },
        initEditor() {
            ...
        },
        destroyEditor() {
            if (!this.editor) return
            this.editor.off('change')
            this.editor.remove()
        },
        setValue(value) {
            this.editor.setValue(value)
        },
        getValue() {
            return this.editor.getValue()
        },
        setHtml(value) {
            this.editor.setHtml(value)
        },
        getHtml() {
            return this.editor.getHtml()
        }
    }
}
</script>