记录-若依前端集成markdown文档,自动生成文档目录

使用版本: vue 2.6.12 html-loader 1.3.2 markdown-loader 6.0.0 github-markdown-css ^5.5.1 highlight.js 9.18.5 webpack @4.47.x

一.引入loder插件,html-loader和markdown-loader
//安装
pnpm install html-loader --save ;
pnpm install markdown-loader --save;

在vue.config文件中配置webpack配置,主要代码:

// html-loader  markdown-loader
    config.module
      .rule('md')
      //.test(/\.md/)
      .test(/\.md$/)
      .use('html-loader')
      .loader('html-loader')
      .end()
      .use('markdown-loader')
      .loader('markdown-loader')
      .end()

在安装插件时,需注意插件对应的webpack版本,与你项目的webpack版本是否支持,不支持则会报以下错误:
在这里插入图片描述
解决办法:在GitHub上搜插件源码,查找历史版本,卸载原来的插件 pnpm uninstall html-loader,重装历史固定版本来适配webpack版本,安装固定版本: pnpm install html-loader@版本号

二.在页面中使用
<template>
    <div class="app-container1">
        <div class="wrap">
            <div v-if="isMarkDown">
                <div v-highlight id="content" class="markdown-body" v-html="markdownContent"></div>
                <div class="api-tree" id="tree">
                <el-tree
                    highlight-current
                    :expand-on-click-node="false"
                    :data="tree"
                    :default-expand-all="true"
                    @node-click="handleNodeClick"
                ></el-tree>
                </div>
            </div>
            <vue-office-pdf v-if="!isMarkDown" :src="src" @rendered="renderedHandler" @error="errorHandler" />
        </div>
    </div>
</template>

注意:markdown文档容器样式class必须加上’markdown-body’class=“markdown-body” 否则无法识别

三.引用markdown默认样式
// 安装
pnpm install github-markdown-css --save

在页面中引用:
import “github-markdown-css”;

四.引用代码高亮样式_highlight.js
// 安装
pnpm install highlight.js --save

在min.js 中进行全局注册,注册成指令:
min.js:

import hljs from "highlight.js";
Vue.prototype.$hljs = hljs;
// 有多种样式可选,也可以到对应文件中定制化
import "highlight.js/styles/vs.css";
// 自定义命令v-highlight
Vue.directive("highlight", function(el) {
    let blocks = el.querySelectorAll("pre code");
    blocks.forEach(block => {
        hljs.highlightBlock(block);
    });
});

import “highlight.js/styles/vs.css”;
这里引入的样式选择可到官网去查看样式效果:
highlight_Demo
在页面中使用方法:
在Dom元素中加上v-highlight

<div v-highlight id="content" class="markdown-body" v-html="markdownContent"></div>
五.自动生成markdown文档目录

现在,我们需要有目录大纲方便我们查看文档。我的思路是,首先拿到markdown文件的html结构,然后找到所有H1-H5的标题标签,并给这些标签设置id,生成一个新数组,通过这个数组生成目录结构

//html部分
 
<div class="api-tree" id="tree">
    <el-tree
        :data="tree"
        :default-expand-all="true"
        @node-click="handleNodeClick"
    ></el-tree>
</div>
 
 
// js部分
catalogTree() {
    const content = document.getElementById("content").children;
    var arr = [];
    let currentHightestLevel;
    let parentId;
    let index = 0;
    for (let i = 0; i < content.length; i++) {
        let header = content[i].localName;
        if (/\b[h][0-9]\b/.test(header)) {
            let ele = $(content[i]);
            let name = ele.text();
            // 设置id
            ele.attr("id", i);
            let id = i;
            if (index === 0 || header <= currentHightestLevel) {
                currentHightestLevel = header;
                parentId = id;
            }
            arr.push({
                id: id,
                label: name,
                parentId: parentId == id ? "0" : parentId,
            });
            index++;
        }
    }
    const tree = [];
    arr.forEach((item) => {
        if (item.parentId === "0") {
            tree.push(this.convertArrayToTree(arr, item));
        }
    });
    this.tree = tree;
},
convertArrayToTree(arr, node) {
    for (let i = 0; i < arr.length; i++) {
        if (arr[i].parentId === node.id) {
            const res = this.convertArrayToTree(arr, arr[i]);
            if (node.children) {
                node.children.push(res);
            } else {
                node.children = [res];
            }
        }
    }
    return node;
},
handleNodeClick(data) {
    let anchorElement = document.getElementById(data.id);
 
    if (anchorElement) {
         anchorElement.scrollIntoView({
             behavior: "smooth",
             block: "end",
         });
     }
},

这里出现了一个问题,那就是要给父容器一个滚动条,否则点击目录无法自动移动到对应位置,我是在App.vue里面设置了一个样式叫article-list ,其中设置了父容器的滚动条为自适应,id设置为app

.article-list {
  overflow-y: auto;
}
<template>
  <div id="app" class="article-list">
    <router-view />
    <theme-picker />
  </div>
</template>

然后修改handleNodeClick方法:

handleNodeClick(data) {
            let anchorElement = document.getElementById(data.id);

            let scrollPosition = anchorElement.offsetTop-88;

            this.$nextTick(()=>{
                let myElement = document.getElementById("app");
                myElement.scrollTo({
                    left: 0,
                    top: scrollPosition,
                    behavior: "smooth",
                })
            });
        }
六.卡片样式,想要一个像element ui 一样的卡片样式,找到其卡片组件,复制对应的样式到对应的div块上应用即可

部分源码以及效果图:

<template>
    <div class="app-container1">
        <el-button style="margin:8px 9px 8px 11px;" type="primary" size="mini" @click="showUpload"
            v-hasPermi="['system:wiki:upload']">上传<i class="el-icon-upload el-icon--right"></i></el-button>
        <el-row style="display: flex;justify-content: center;margin-bottom:8px;">
            <el-col :span="24">
                <el-card class="box-card" style="width:98%;margin-left: 10px;">
                    <div slot="header" class="clearfix">
                        <span>ems资料</span>
                    </div>
                    <div v-for="(fileName, index) in fileList" :key="index" class="text item">
                        <div :id="'file_' + index" style="display:inline-block;">
                            {{ fileName }}
                        </div>
                        <!-- 下载按钮 -->
                        <div style="float: right;display: flex;justify-content: space-between;"
                        :style="{width:fileName.indexOf('.md') === -1?'155px':'103px',marginRight:fileName.indexOf('.md') === -1?'0':'53px'}">
                            <el-link type="primary" size="mini" icon="el-icon-view"
                                @click="viewFile(fileName, index)">预览</el-link>
                            <el-link type="primary" v-if="fileName.indexOf('.md')===-1" size="mini" icon="el-icon-download" @click="download(fileName)"
                                v-hasPermi="['system:wiki:downFile']">下载</el-link>
                            <!-- 删除 -->
                            <el-link type="danger" v-if="fileName.indexOf('.md')===-1" size="mini" icon="el-icon-delete" @click="clickdeleteFile(fileName)"
                                v-hasPermi="['system:wiki:delete']">删除</el-link>
                        </div>
                    </div>
                </el-card>
            </el-col>
        </el-row>

        <div class="wrap">
            <div v-if="isMarkDown">
                <div v-highlight id="content" class="markdown-body" v-html="markdownContent"></div>
                <div class="api-tree" id="tree">
                <el-tree
                    highlight-current
                    :expand-on-click-node="false"
                    :data="tree"
                    :default-expand-all="true"
                    @node-click="handleNodeClick"
                ></el-tree>
                </div>
            </div>
            <vue-office-pdf v-if="!isMarkDown" :src="src" @rendered="renderedHandler" @error="errorHandler" />
        </div>

        <el-dialog title="提示" :visible.sync="uploadVisible" width="30%">
            <el-upload class="upload-demo" drag action="#" accept=".pdf" multiple :headers="headers" :auto-upload="false"
                :file-list="uploadfileList" :on-change="handleChange">
                <i class="el-icon-upload"></i>
                <div class="el-upload__text">将pdf文件拖到此处,或<em>点击上传</em></div>
            </el-upload>
            <span slot="footer" class="dialog-footer">
                <el-button @click="uploadVisible = false">取 消</el-button>
                <el-button type="primary" @click="confirmUpload">确 定</el-button>
            </span>
        </el-dialog>
    </div>
</template>

<script>
import VueOfficePdf from '@vue-office/pdf'
// markdown样式
import "github-markdown-css";  // markDown默认样式
import $ from "jquery";

import { getFile, uploads, getFileName, downFile, deleteFile } from '@/api/ems/common';
export default {

    components: {
        VueOfficePdf
    },
    data() {
        return {
            headers: {
                'Content-Type': 'multipart/form-data'
            },
            uploadfileList: [],
            fileList: [], // 文件列表数据
            src: '',
            uploadVisible: false, // 控制上传组件的显示状态
            curreSelectFileIndex: -1,
            markdownContent: '',
            key: 0,
            tree: [],
            isMarkDown: false,
        };
    },
    watch: {
    },
    methods: {
        clickdeleteFile(fileName) {
            console.log(fileName + '删除文件')
            //把fileName类如test - 副本 (2).pdf查看文件转为string
            fileName = fileName.toString()
            deleteFile(fileName).then(response => {
                console.log(response);
                this.$message({
                    message: '删除成功',
                    type: 'success'
                });
                this.loadFileList();
            })
        },
        handleChange(file, fileList) { //文件数量改变
            console.log(fileList)
            //判断file类型如果不是pdf格式,提示用户,并且将fileList中的file删除
            fileList.forEach((item, index) => {
                if (item.name.indexOf('.pdf') == -1) {
                    this.$message.error('文件格式不正确,请上传pdf格式文件');
                    fileList.splice(index, 1);
                }
            })

            this.uploadfileList = fileList;
        },
        confirmUpload() {
            var formData = new FormData();
            this.uploadfileList.forEach(file => {
                formData.append('files', file.raw);
            });
            uploads(formData).then(response => {
                console.log(response);
                this.$message({
                    message: '上传成功',
                    type: 'success'
                });
            }).catch(error => {
                console.error('上传失败:', error);
                this.$message.error('上传失败');
            });
            setTimeout(() => {
                this.loadFileList();
            }, 1000); // 延迟时间,单位为毫秒
            this.uploadfileList = [];
            this.uploadVisible = false;
        },
        uploadFile(event) {
            console.log('上传文件');
            const files = event.target.files;
            console.log(files);
            uploads(files).then(response => {
                console.log(response);
            })
        },
        loadFileList() {
            const context = require.context('@/assets/document', false, /\.md/);  //  查找pdf或md /[\.pdf$][\.md$]/
            const modulePaths = context.keys();
            //遍历modulePaths去掉路径前缀'./'然后赋值给fileList
            this.fileList = modulePaths.map(item => item.replace('./', ''));
            getFileName().then(response => {
                console.log(response.data + '文件列表');
                response.data.forEach(x=>{
                    this.fileList.push(x);
                })
            })
        },
        showUpload() {
            this.uploadVisible = true
        },
        renderedHandler() {
            console.log("渲染完成")
        },
        errorHandler() {
            console.log("渲染失败")
        },
        viewFile(fileName, index) {
            const loading = this.$loading({
                lock: true,
                text: 'Loading',
                spinner: 'el-icon-loading',
                background: 'rgba(0, 0, 0, 0.7)'
                });
            this.changeLineColor(index);
            // 收起菜单
            // hamburger-container
            if(this.$store.getters.sidebar.opened === true)
            {
                this.$store.dispatch('app/toggleSideBar');
            }
            console.log(fileName + '查看文件')
            //把fileName类如test - 副本 (2).pdf查看文件转为string
            fileName = fileName.toString()
            if(fileName.endsWith('.md'))
            {
                this.isMarkDown=true;
                this.loadMd(fileName);
                setTimeout(() => {
                    loading.close();
                }, 300);
                return;
            }
            this.isMarkDown=false;
            //调用后台接口,获取文件地址
            getFile(fileName).then(response => {
                const byteArray = atob(response.data);
                const uint8Array = new Uint8Array(byteArray.length);
                for (let i = 0; i < byteArray.length; i++) {
                    uint8Array[i] = byteArray.charCodeAt(i);
                }
                const blob = new Blob([uint8Array]);
                let fileBytes = response.data;
                let fileReader = new FileReader();
                fileReader.readAsArrayBuffer(blob);
                fileReader.onload = () => {
                    this.src = fileReader.result
                }
                setTimeout(() => {
                    loading.close();
                }, 300);
            })
        },
        changeLineColor(index)
        {
            let current = document.getElementById('file_' + this.curreSelectFileIndex);
            if (current != null) {
                    current.className = '';
                }
            this.curreSelectFileIndex = index;
            let ele = document.getElementById('file_' + index);
            ele.className = "viewLine";
        },
        download(fileName) {
            console.log(fileName + '下载文件')
            //把fileName类如test - 副本 (2).pdf查看文件转为string
            fileName = fileName.toString()
            downFile(fileName).then(response => {
                const byteArray = atob(response.data);
                const uint8Array = new Uint8Array(byteArray.length);
                for (let i = 0; i < byteArray.length; i++) {
                    uint8Array[i] = byteArray.charCodeAt(i);
                }
                const blob = new Blob([uint8Array]);
                // 创建 URL 对象
                const url = URL.createObjectURL(blob);
                // 创建 <a> 元素
                const link = document.createElement('a');
                link.href = url;
                link.download = fileName; // 设置下载文件的名称
                link.click();
                // 释放 URL 对象
                URL.revokeObjectURL(url);
            })
        },
        catalogTree()
        {
            const content = document.getElementById("content").children;
            var arr = [];
            let currentHightestLevel;
            let parentId;
            let index = 0;
            for (let i = 0; i < content.length; i++) {
                let header = content[i].localName;
                if (/\b[h][0-9]\b/.test(header)) {
                    let ele = $(content[i]);
                    let name = ele.text();
                    // 设置id
                    ele.attr("id", i);
                    // let id = ele.children("a").attr("id");
                    let id = i;
                    if (index === 0 || header <= currentHightestLevel) {
                        currentHightestLevel = header;
                        parentId = id;
                    }
                    arr.push({
                        id: id,
                        label: name,
                        parentId: parentId == id ? "0" : parentId,
                    });
                    index++;
                }
            }
            const tree = [];
            arr.forEach((item) => {
                if (item.parentId === "0") {
                    tree.push(this.convertArrayToTree(arr, item));
                }
            });
            this.tree = tree;
        },
        convertArrayToTree(arr, node) {
            for (let i = 0; i < arr.length; i++) {
                if (arr[i].parentId === node.id) {
                    const res = this.convertArrayToTree(arr, arr[i]);
                    if (node.children) {
                        node.children.push(res);
                    } else {
                        node.children = [res];
                    }
                }
            }
            return node;
        },
        handleNodeClick(data) {
            let anchorElement = document.getElementById(data.id);

            let scrollPosition = anchorElement.offsetTop-88;

            this.$nextTick(()=>{
                let myElement = document.getElementById("app");
                myElement.scrollTo({
                    left: 0,
                    top: scrollPosition,
                    behavior: "smooth",
                })
            });
        },
        loadMd(fileName)
        {
            const conTxt = require(`@/assets/document/${fileName}`);
            if(conTxt)
            {
                this.markdownContent = conTxt;
                this.$nextTick(() => {
                        this.catalogTree();
                    });
            }
            this.key += 1;
        }
    },
    mounted() {
        console.log('mounted');
        this.loadFileList();
    }
};
</script>

<style scoped lang="scss">
.text {
    font-size: 14px;
    font-weight: bold;
}

.item {
    margin-bottom: 16px;
}

.clearfix:before,
.clearfix:after {
    display: table;
    content: "";
}

.clearfix:after {
    clear: both
}

::v-deep .el-card__body {
    padding: 15px 15px 10px 13px;
}

.viewLine {
    color: rgb(54, 152, 126);
}
// 修改第三方组件样式  +  ::v-deep  和  !important  和 父容器  wrap
.wrap ::v-deep .vue-office-pdf .vue-office-pdf-wrapper {
    padding: 10px 10px !important;
    background: rgb(44, 30, 71, 0.86) !important;
}

// 修改第三方组件样式 +  ::v-deep  和  !important  和 父容器  wrap
.wrap ::v-deep .vue-office-pdf .vue-office-pdf-wrapper canvas {
    width: 100% !important;
    border-radius: 4px !important;
}

.markdown-body {
    display: inline-block;
   //float: right;
   min-width: 200px;
   max-width: 83%;
   margin-left: 10px;
   font-size: 18px;
   // 卡片样式部分
   box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
   border-radius: 4px;
   border: 1px solid #e6ebf5;
    background-color: #FFFFFF;
    overflow: hidden;
    color: #303133;
    -webkit-transition: 0.3s;
    transition: 0.3s;
    // 卡片样式结束
   padding-left: 20px;
    padding-right: 15px;
    padding-top: 40px;
    padding-bottom: 25px;
    margin-right: 25px;
    margin-bottom: 30px;
   h2 {
      font-size: 18px;
      margin: 1em 0 15px;
      padding-top: 0.8em;
      padding-bottom: 0.8em;
   }
   h3 {
      font-size: 14px;
      margin: 22px 0 16px;
   }
   h4 {
      font-size: 13px;
      margin: 20px 0 16px;
   }
   h5 {
      font-size: 12px;
      margin: 16px 0 16px;
      font-weight: 700;
   }
   p {
      font-size: 12px;
      line-height: 24px;
      color: #666666;
      margin-top: 0px;
      margin: 8px 0;
      margin: 14px 0 14px;
   }
   pre {
      background-color: #eee;
      margin-bottom: 8px;
      margin-top: 8px;
      margin: 12px 0 12px;
   }
   blockquote {
      margin-bottom: 8px;
      margin-top: 8px;
      margin: 14px 0 14px;
      background-color: #eee;
      padding: 16px 16px;
   }
   tr {
      background-color: #f5f5f5;
   }
   code {
      background-color: #eee;
   }
   ul,
   ol,
   li {
      list-style: unset;
      font-size: 12px;
      line-height: 20px;
      color: #666666;
      margin-top: 0px;
      margin: 8px 0;
   }
   blockquote {
      border-color: #48b6e2;
   }
   table {
      display: table;
      width: 100%;
      max-width: 100%;
      margin-bottom: 20px;
   }
}

.api-tree {
        padding-left: 7px;
        display: inline-block;
        position: fixed;
        box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
        border-radius: 4px;
        border: 1px solid #e6ebf5;
        background-color: #FFFFFF;
        overflow: hidden;
        color: #303133;
        -webkit-transition: 0.3s;
        transition: 0.3s;

        top: 56%;
        right: 2%;
        transform: translate(2%, 20%);
        width: 200px;
        height: 265px; // 265px
        overflow-y: auto;
        font-weight: bold;
        //z-index: 99;
        .el-tree {
            background: none;
            color: rebeccapurple;
            /* 改变被点击节点背景颜色,字体颜色 */
        }
    }
    ::v-deep .el-tree-node:focus > .el-tree-node__content,
            .el-tree-node__content:hover {
                background: rgb(0, 125, 12,0.4) ;
                color: rgb(255, 24, 93,0.6);
            }
    ::v-deep  .el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content{
                background: rgb(0, 125, 12,0.4) ;
                color: rgb(255, 24, 93,0.6);
    }
    ::v-deep .el-tree-node__expand-icon{
        display: none;
    }
</style>

图1-显示markdown文档,自动生成目录:
在这里插入图片描述
图2-代码高亮
在这里插入图片描述

  • 24
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值