«

代码分享-Markmap 自定义导出图片按钮

时间:2025-11-7 13:42     作者:wanzi     分类: vue


一、什么是 Markmap

Markmap 是一个将 Markdown 转换为交互式思维导图的开源工具。
核心库是 markmap-lib,可用于网页端(包括 Vue / React / 原生 HTML)渲染 Markdown。

常用库:


<script type="module">
    const {createApp, ref, onMounted, onUpdated} = Vue;
    createApp({
        setup() {
            const svgRef = ref(null);
            const markdown = ref('');
            let mm = null;

            const update = async () => {
                const transformer = new markmap.Transformer()
                const {root} = transformer.transform(markdown.value);
                await mm.setData(root);
                mm.fit();
            };

            const renderFile = async () => {
                const urlParams = new URLSearchParams(window.location.search);
                const fileName = urlParams.get('file');
                const taskId = urlParams.get('task_id');

                if (!fileName) {
                    layui.layer.closeAll()
                    return layui.layer.msg('访问出错');
                }

                try {
                    const mdStr = await http.get(`/api/tasks/video/${taskId}/files/${fileName}`, {
                        responseType: 'text'
                    });
                    markdown.value = mdStr

                    mm = markmap.Markmap.create(svgRef.value)
                    update()

                    markmap.Toolbar.defaultItems = ["zoomIn", "zoomOut", "fit", "recurse", "dark", "saveAsImage"];
                    const toolbar = new markmap.Toolbar()
                    toolbar.register({
                        id: 'saveAsImage',
                        title: '保存为图片',
                        content: markmap.Toolbar.icon(
                            'M4 7h3l2-3h6l2 3h3a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2zm8 3a4 4 0 1 1 0 8 4 4 0 0 1 0-8z',
                        ),
                        onClick: async () => {
                            if (!mm?.svg) {
                                console.warn('[markmap] SVG not available');
                                return;
                            }

                            const svgEl = mm.svg._groups[0][0];
                            const {width, height} = svgEl.getBoundingClientRect();

                            const canvas = document.createElement('canvas');
                            const ctx = canvas.getContext('2d');
                            canvas.width = width * 2;
                            canvas.height = height * 2;
                            ctx.scale(2, 2);

                            const svgData = new XMLSerializer().serializeToString(svgEl);
                            const svgBase64 = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svgData);

                            const img = new Image();
                            img.crossOrigin = 'anonymous';
                            img.onload = () => {
                                ctx.fillStyle = '#ffffff';
                                ctx.fillRect(0, 0, width, height);
                                ctx.drawImage(img, 0, 0, width, height);

                                canvas.toBlob((blob) => {
                                    const link = document.createElement('a');
                                    link.download = 'markmap.png';
                                    link.href = URL.createObjectURL(blob);
                                    link.click();
                                    URL.revokeObjectURL(link.href);
                                }, 'image/png');

                                canvas.remove();
                            };

                            img.src = svgBase64;
                        }
                    });
                    toolbar.render();
                    toolbar.attach(mm);

                    const el = toolbar.el;
                    // const {el} =markmap.Toolbar.create(mm);
                    el.style.position = 'absolute';
                    el.style.bottom = '0.5rem';
                    el.style.right = '0.5rem';

                    document.getElementById('toolbar').append(el);
                } catch (error) {
                    layui.layer.msg('查看文件失败: ' + error.message, {icon: 2});
                }
            };

            onMounted(() => {
                renderFile();
            });

            onUpdated(update);

            return {
                svgRef
            }
        }
    }).mount('#app');

</script>