当前位置:首页 » 《关注互联网》 » 正文

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

29 人参与  2024年04月21日 16:10  分类 : 《关注互联网》  评论

点击全文阅读


使用版本: 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-highlightVue.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-代码高亮
在这里插入图片描述


点击全文阅读


本文链接:http://zhangshiyu.com/post/98674.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1