vue进阶04-vue文档生成工具vuepress2

2023-11-04

介绍

VuePress 是一个以 Markdown 为中心的静态网站生成器。你可以使用 Markdown 来书写内容(如文档、博客等),然后 VuePress 会帮助你生成一个静态网站来展示它们。

VuePress 诞生的初衷是为了支持 Vue.js 及其子项目的文档需求,但是现在它已经在帮助大量用户构建他们的文档、博客和其他静态网站

它是如何工作的?

一个 VuePress 站点本质上是一个由 Vue 和 Vue Router 驱动的单页面应用 (SPA)。

路由会根据你的 Markdown 文件的相对路径来自动生成。每个 Markdown 文件都通过 markdown-it 编译为 HTML ,然后将其作为 Vue 组件的模板。因此,你可以在 Markdown 文件中直接使用 Vue 语法,便于你嵌入一些动态内容。

在开发过程中,我们启动一个常规的开发服务器 (dev-server) ,并将 VuePress 站点作为一个常规的 SPA。如果你以前使用过 Vue 的话,你在使用时会感受到非常熟悉的开发体验。

在构建过程中,我们会为 VuePress 站点创建一个服务端渲染 (SSR) 的版本,然后通过虚拟访问每一条路径来渲染对应的 HTML 。这种做法的灵感来源于 Nuxt 的 nuxt generate 命令,以及其他的一些项目,比如 Gatsby。

为什么不是 …?

Nuxt

Nuxt 是一套出色的 Vue SSR 框架, VuePress 能做的事情,Nuxt 实际上也同样能够胜任。但 Nuxt 是为构建应用程序而生的,而 VuePress 则更为轻量化并且专注在以内容为中心的静态网站上。

VitePress

VitePress 是 VuePress 的孪生兄弟,它同样由 Vue.js 团队创建和维护。 VitePress 甚至比 VuePress 要更轻更快,但它在灵活性和可配置性上作出了一些让步,比如它不支持插件系统。当然,如果你没有进阶的定制化需求, VitePress 已经足够支持你将你的内容部署到线上。

这个比喻可能不是很恰当,但是你可以把 VuePress 和 VitePress 的关系看作 Laravel 和 Lumen 。

Docsify / Docute

这两个项目同样都是基于 Vue,然而它们都是完全的运行时驱动,因此对 SEO 不够友好。如果你并不关注 SEO,同时也不想安装大量依赖,它们仍然是非常好的选择!

Hexo

Hexo 一直驱动着 Vue 2.x 的文档。Hexo 最大的问题在于他的主题系统太过于静态以及过度地依赖纯字符串,而我们十分希望能够好好地利用 Vue 来处理我们的布局和交互。同时,Hexo 在配置 Markdown 渲染方面的灵活性也不是最佳的。

GitBook

过去我们的子项目文档一直都在使用 GitBook 。 GitBook 最大的问题在于当文件很多时,每次编辑后的重新加载时间长得令人无法忍受。它的默认主题导航结构也比较有限制性,并且,主题系统也不是 Vue 驱动的。GitBook 背后的团队如今也更专注于将其打造为一个商业产品而不是开源工具。

快速上手

环境要求

Node.js v14.18.0+
Yarn v1 classic (尽量用yarn安装依赖,npm尝试n多次各种兼容问题)

hellworld

步骤1: 创建并进入一个新目录

mkdir vuepress-starter
cd vuepress-starter

步骤2: 初始化项目

yarn init

步骤3: 将 VuePress 安装为本地依赖

yarn add -D vuepress@next

步骤4: 在 package.json 中添加一些 scripts

{
  "scripts": {
    "docs:dev": "vuepress dev docs",
    "docs:build": "vuepress build docs"
  }
}

安装本地vuepress后可使用命令行运行

.\node_modules\.bin\vuepress dev docs
# 分析vuepress.cmd后发现执行的js文件是
node .\node_modules\vuepress\bin\vuepress.js dev docs
# vuepress1.x md文件过多,存内存溢出的增加内存执行方式
node --max_old_space_size=7096 ./node_modules/vuepress/cli.js build
# vuepress2.x md文件过多,内存溢出的增加内存执行方式 vuepress.1.x
node --max_old_space_size=7096 ./node_modules/vuepress\bin\vuepress.js build

步骤5: 将默认的临时目录和缓存目录添加到 .gitignore 文件中

echo 'node_modules' >> .gitignore
echo '.temp' >> .gitignore
echo '.cache' >> .gitignore

步骤6: 创建你的第一篇文档

mkdir docs
echo '# Hello VuePress' > docs/README.md

步骤7: 在本地启动服务器来开发你的文档网站

yarn docs:dev

VuePress 会在 http://localhost:8080 启动一个热重载的开发服务器。当你修改你的 Markdown 文件时,浏览器中的内容也会自动更新。

增强指南

config.js 配置

通用配置

以下是一份大而全的配置,包括国际化,导航栏,侧边栏,插件

import { defineUserConfig } from 'vuepress'
import { defaultTheme } from '@vuepress/theme-default'
import { searchPlugin } from '@vuepress/plugin-search'
//自定义插件
import sideBarPlugin  from './plugins/sidebar'
export default {
    base: '/jiedoc/',
    cmd: 'node ./node_modules/vuepress/bin/vuepress.js build',
    plugins:[
        sideBarPlugin, //自定义的插件
        searchPlugin({
            // 搜索插件
          }),
        'vuepress-plugin-mermaidjs',  //支持各种图形(流程图)等的插件
        '@maginapp/vuepress-plugin-flowchart', //支持各种图形(mermaid流程图)等的插件
        {
            openMarker:'```mermaid',
            closeMarker:'```',
            scondMarker:'flowchat',
            ignoreSecondLine:false
        }
    ],
    markdown:{
        anchor:{ permalink:false },
        toc:{ includeLevel:[1, 2] },
        extendMarkdown:md => {
            md.use(require('markdown-it-katex'))
                .use(require('markdown-it-footnote'))
                .use(require('markdown-it-ins'))
                .use(require('markdown-it-mark'))
                .use(require('markdown-it-sub'))
                .use(require('markdown-it-sup'))
                .use(require('markdown-it-abbr'))
        },
        lineNumbers: true, // 显示代码行号
    },
    head: [
        //引用一些静态的资源
        ['link', { rel: 'shortcut icon', type: "image/x-icon", href: "/assets/img/favicon.ico" }],
        ['link', { rel: 'apple-touch-icon', type: "image/x-icon", href: "/assets/img/apple-touch-icon.png" }],
        ['link', { rel: 'icon', type: "image/png", sizes: "32x32", href: "/assets/img/favicon-32x32.png" }],
        ['link', { rel: 'icon', type: "image/png", sizes: "16x16", href: "/assets/img/favicon-16x16.png" }],
        ['link', { rel: 'manifest', href: "/assets/img/site.webmanifest" }],
        ['script', { src: '/assets/js/jquery/3.3.1/jquery.slim.min.js' }],
        ['script', { src: '/assets/js/fancybox/3.5.2/jquery.fancybox.min.js' }],
        ['link', { rel: 'stylesheet', type: 'text/css', href: '/assets/css/fancybox/3.5.2/jquery.fancybox.min.css' }]
    ],
    theme:defaultTheme({
        locales: {
            '/':
            {
                selectText: '选择语言',
                selectLanguageName: '简体中文',
                logo: '/assets/img/logo.png',
                lastUpdated: '上次更新',
                smoothScroll: true,
                navbar: 
              [
                   { text: 使用指引', 
                    ariaLabel: 'Apos micro-mall operation guidelines',
                    children:
                        [
                          { text: '基本功能流程指引', link: '/zh-cn/ttttttt.html'},
                          { text: '商城', link: '/zh-cn/t.html'},
                            ]
                         },
                   { text: '业务系统使用指引', 
                    ariaLabel: 'Apos cashier operation guidelines',
                    children:
                        [
                          { text:'商品管理',link: '/zh-cn/gg.html'},
                          { text: '库存管理', link: '/zh-cn/gg1.html'}
                            ]
                         }
                    ],
                sidebar: 'auto'
            },
            '/en-us/':
            {
                selectText: 'Languages',
                selectLanguageName: 'English',
                logo: '/assets/img/logo.png',
                lastUpdated: 'Last Updated',
                smoothScroll: true,
                navbar:
                    [
                        { text: 'Doc Guide', link: '/en-us/product/guide/' }
                    ],
                sidebar: 'auto'
            },
        }
    }),
    locales:  //注意一定要在最外层定义了locales才会显示出选择语言
    {
        '/':
        {
            lang: 'zh-CN',
            title: '测试帮助中心',
            description: ''
        },
        '/en-us/':
        {
            lang: 'en-US',
            title: 'Helper Center',
            description: ' '
        },
    }
}

sidebar

全局sidebar

在config.js中配置sidebar:auto将自动将markdown的2级和3级标题显示在sidebar上,如果需要自定义sidebar可配置为(config.js):

sidebar:[
						{
							text:'新手入门',
							sidebarDepth:1, 
							collapsible:true,
							children:[
								{text:'新手入门指导', link:'/hello/first.md'},
							]
						},
						{
							text:'店铺认证',
							sidebarDepth:1, 
							collapsible:true,
							children:[
								{text:'主题认证教程', link:'/auth/body'},
								{text:'品牌认证教程', link:'/auth/pp'},
								{text:'入驻资质教程', link:'/auth/rz'},
								{text:'店铺命名指引', link:'/auth/dp'},
							]
						},
					],

link表示跳转的markdown文件

注意如果跳转到其他的markdown了,其他markdown没有配置页面级别sidebar将引用全局config.js的sidebar。

页面sidebar

也可以在单独的markdown使用yaml配置

---
 sidebar: 
  - text: "Group"
    children: 
    - text: "SubGroup"
      children: 
        - text: "hello1"
          link: "/group/a/"
        - text: "hello2"
          link: "/group/b/"
  - text: "Group 2"
    children: 
    - text: "gg1"
      link: "/group/a/"
    - text: "gg2"
      link: "/hello/second/"
---
# ttt
## aaa


## bbb

当浏览器访问该页面时自动引用当前页面的sidebar,同时也可以指定sidebarDepth来当前页面sidebar激活的标题显示深度,0表示不显示markdown文件的标题,1表示显示2级标题,2表示显示3级标题,1级标题永远不显示。

navbar

集成ui框架elementplus

vuepress2使用vue3.0开发,这里选择集成elementplus使用他的一些菜单和按钮组件。
vue3.0建议使用组合式编程提高复用性。
效果演示:
在这里插入图片描述
引入elemetplus

npm install element-plus --save

在.vuepress/client.js中,use,关注enhance即可


import { defineClientConfig } from '@vuepress/client'
import Layout from './layouts/Layout.vue'
import * as Icons from '@element-plus/icons-vue'
import ElementPlus from 'element-plus'
import 'element-plus/theme-chalk/index.css'

export default defineClientConfig({
  enhance({ app, router, siteData }){
    app.use(ElementPlus)
    // icon
    for (const icon in Icons) {
      // eslint-disable-next-line import/namespace
      app.component('ElIcon' + icon, Icons[icon])
    }
  },
  setup(){
    
  },
  layouts: {
    Layout
  }
})

新增一个配置导航栏菜单的json文件,新增.vuepress/navbar.json

{
   "tabList":[
    {
        "title":"首页",
        "href":"/"
    },
    {
        "title":"帮助中心",
        "href":"/zh-cn/Open-API/APOS开放平台1210统一对接方案.html",
        "children":[{
                "title":"发货流程",
                "href":"/zh-cn/Help-Document/Apos_cashier/commodity/Commodity_delivery_warehouse.html"
            },
            {
                "title":"商城分销",
                "href":"/zh-cn/Help-Document/Micro_mall/商城分销业务/"
            }
        ]
    },
    {
        "title":"常见问题",
        "href":"/zh-cn/Help-Document/Micro_mall/商城分销业务/independent-distribution.html"
    },
    {
        "title":"技术帮助中心",
        "href":"/zh-cn/Help-Document/Micro_mall/商城分销业务/直接分销.html"
    }
   ]
}

薪资TopHeader.vue替换navbar

<template>
  <div class="navbar">
    <div class="topHeader">
      <img class="jie_logo" src="/assets/img/newlog.png" />
      <el-menu
        :default-active="activeIndex+''"
        class="jie_tab"
        mode="horizontal"
        @select="handleSelect"
      >
       <template v-for="(item,index) in tabList" :key="index">
        <el-menu-item v-if="!item.children || item.children.length==0" :index="''+index">{{item.title}}</el-menu-item>
        <template v-if="item.children && item.children.length>0">
          <el-sub-menu :index="''+index">
            <template #title>{{item.title}}</template>
            <el-menu-item v-for="(citem,index1) in item.children" :key="index+''+index1" :index="index+'-'+index1">{{citem.title}}</el-menu-item>
          </el-sub-menu>
        </template>
       </template>
      </el-menu>

      <div class="jie_empty"></div>
      <div class="jie_searchBox">
        <SearchBox />
      </div>
      <div class="jie_rightButton">
        <el-button class="jie_button"  type="primary" @click="testUse('https://apos.jieztech.com/#/login')">登录</el-button>
        <el-button class="jie_button" type="primary" @click="testUse('https://apos.jieztech.com/umsf/index.html#/sign-up?step=1')">免费试用</el-button>
      </div>
    </div>
  </div>
</template>
<script setup>
import { usePageData, useSiteData } from "@vuepress/client";
import { ref,onMounted,computed  } from 'vue'
import navbar from "../navbar.json";
//创建一个vue变量activeIndex值为0
const activeIndex = ref('0')
const tabList = ref()
tabList.value=navbar.tabList
const siteData = useSiteData();
let contextPath = siteData._rawValue.base;
function matchPage(url) {
      return getPath() == encodeURI(url);
    }
function  testUse(url) {
    window.open(url);
  }
function getPath() {
    //"/jiedoc/zh-cn/Help-Document/Micro_mall/%E5%95%86%E5%9F%8E%E5%88%86%E9%94%80%E4%B8%9A%E5%8A%A1/%E7%9B%B4%E6%8E%A5%E5%88%86%E9%94%80.html"
    let pathname = window.location.pathname.substring(1);
    let pathNameIndex = pathname.indexOf("/");
    return pathname.substring(pathNameIndex);
}
// 定义点击事件 myFn
function handleSelect(index,path,argu){
  let firstIndex=index;
  let secondIndex=-1;
  if(index.indexOf("-")>=0){
    firstIndex=index.split("-")[0];
    secondIndex=index.split("-")[1];
  }
  let curTab = tabList.value[parseInt(firstIndex)];
  if(secondIndex>=0){
    curTab=curTab.children[parseInt(secondIndex)];
  }
  let goHref = curTab.href.startsWith("/")
      ? curTab.href.substring(1)
      : curTab.href;
  window.location = contextPath + goHref;
}
onMounted(() => {
    tabList.value.forEach((v,i)=>{
        if(matchPage(v.href)){
          activeIndex.value=i
        }
        if(v.children && v.children.length>0){
          v.children.forEach((v1,i1)=>{
            if(matchPage(v1.href)){
              activeIndex.value=i+"-"+i1
            }
          })
        }
    })
})
let noChildTabList = computed(()=>{
   return tabList.value.filter((v,i)=>{
      if(!v.children || v.children.length==0){
        return v;
      }
   })
  })
let haveChildTabList = computed(()=>{
   return tabList.value.filter((v,i)=>{
      if(v.children && v.children.length>0){
        return v;
      }
   })
  })
</script>
<script>
import "element-plus/theme-chalk/index.css";


</script>
<style>
.search-box input {
  width: 80% !important;
}
:root {
  --c-brand: rgb(133, 162, 213);
}
.el-menu--horizontal{
  border-bottom: solid 0px white;
}
</style>
<style scoped>
.navbar {
  padding: 0px;
}
.theme-container {
  background-color: rgb(242, 242, 242);
}
.topHeader {
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: row;
  background-color: white;
  align-items: center;
  width: 100vw;
  height: var(--navbar-height);
  border-bottom: 1px solid rgb(238, 238, 238);
}
.jie_logo {
  width: 12vw;
  height: 100%;
}
.jie_tab {
  width: 38vw;
  margin-left: 2vw;
  background: white;
  color: black;
  /*border-bottom: 1px solid rgb(238, 238, 238);*/
}
.jie_empty {
  width: 25vw;
}
.jie_rightButton {
  width: 15vw;
  text-align: right;
  vertical-align: middle;
}
.jie_button {
  vertical-align: middle;
  margin-right: 10px;
  background-color: rgb(53, 111, 212);
  color: white;
}
.search-box {
  text-align: right;
  vertical-align: middle;
  width: 30rem;
}
@media screen and (orientation: portrait) {
  .jie_rightButton {
    display: none;
  }
  .search-box {
    display: none;
  }
  .jie_tab {
    width: 88vw;
  }
}
</style>

config.js设置别名替换

export default {
    alias: {
        '@theme/Navbar.vue': path.resolve(__dirname, './layouts/TopHeader.vue'),
    }
 }

布局继承

需求:
所有的markdown页面拓展底部新增一个好用和不好用的评价按钮
效果:
请添加图片描述
vuepress在markdown布局时提供了一些插槽可以用于新增内容,比如page-content-bottom就是内容底部:https://v2.vuepress.vuejs.org/zh/reference/default-theme/extending.html#%E5%B8%83%E5%B1%80%E6%8F%92%E6%A7%BD
.vuepress下新建目录和文件layouts/Layout.vue

<template>
  <ParentLayout>
    <template #page-content-bottom>

    <div class="dialog_backdrop" v-show="dialogTableVisible">
    </div>
    <div class="dialog-wrap" v-show="dialogTableVisible">
      <div class="dialog" style="min-width: 450px; max-width: 75%; transform-origin: 712.5px 465px 0px;">
        您接的没用的原因是:<br/>
        <textarea style="width:80%;height:100px" v-model="useMsg"></textarea><br/>
        <button @click="submitIssue">提交</button>&nbsp;&nbsp;<button @click="dialogTableVisible=false">关闭</button>
      </div>
    </div>

    <div class="divlast">
      <button @click="usefullFun" class="btn" style="border:1px solid red;color:red">
           <span class="btnicon" style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAu9JREFUWAnFV81LG0EU/43JpodqjWigiOClFBSpBwkUvVgwNKYg9ODF/gXtSYu30oIfBU+Kl/Yv0IuHQKAaUMhFUMitBaGligexQhClUqTxY/vezG6yxsyukbX7YDKbfb95v7fvzccbgRuKOTwcwdHRM4IPUeuk1mo16rBvtS3qM2hqyomlpSIrvER4AcxU6iGKxQ8Q4hVM84EXXuqF+E3YBUQik2J5+cBtjNYBc3DwHs7P39Hgt2TsvpsRrU6IP6SbRTj8Uays/K2Gq+qA/OqzszQRP602qOZ3QmzCMF5Wi8Y1B8xk8gkuLr4QeVvNRG4DhNgjmymxtvbNCbvigPXled/JbUZ2wjDizkjU2TqZcxV2f7/cJuCeo0ockst6X3JATji/cu4krXxmDjW5pUamwAr9T/LwdrO9ksTrP68Ow3jEqVAR4HX+v8jZOeZiThIhd7jj4wK9rL7JxOPA2Bhjgbk5IJ9Xz7rfm+J5s4pGY8IcGHhOtrI6e1hcBGIxpS4UgJERLVQqasMnOQW8twclQ+wAHyx64bDzl3PjZy+pDd/JKfhONh972b0j/Q+OAB+rQUmrWoZB0dOCZAe4mAhKfgXtwD47wGWUu0xPqxXQ0OCOY20oBExMAPPzQJ1nhrcYkfG0Wl8PdHUBMzNAS4sebhjA+DjQ2wuqC4HLSz1WaTLeWzEDo1EVgTY6qU9OgHQaWF8HDqjcM02guRno6aEtjfa09nbg9BQYHQV2dvQO2FsxI2gv+ETdaz2aNI2N6kzo63OFYXdXRWp72x0HfKbq6E3tx3F3N5BIAB0d5XQcHkIS53LAxgbovHcndxzHpZLMTCQmKZzv3Uf6pBViSqyuyuO4PE2pdKbaf9MnCr0Z5mAuS0oR4P+BFqXsgKxWQ6EXFIk9/u+r2GV5xU2pnAKLTWSzX7l09jUd6mISr7wTMOU1B/iljEQ43E9OTFHj69XthMeyDbLlvAs4jV2ZA06F/SznRRCXU9sBu7+r6/k/b6kihzk07qQAAAAASUVORK5CYII=)"></span>
           有用
      </button>&nbsp;&nbsp;&nbsp;
      <button @click="noGoodFun" class="btn" style="border:1px solid black;margin-left:50px">
        <span class="btnicon" style="background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAA0JJREFUWAnFV01IG1EQnt00bUGIB4mWKKSB0kPRCmpMEaEGlDYGCj3oQe9ii3gI9FRooZJDLzm21yD0JBhpCenN60rMpYVASsEEJakpIkhySMSk3zyyIdrdzQ+uebD7fubnm52Z996sRC22hYWF25lMxntxcfFCkqRHEHNUq1UHi2OeRZfFPGmxWL46nc7dra2tMtOaNakZg9vtvlepVN4BZBkAtmb8TAfvGXi/yLL8IR6P/zGS0TXA5/PdyefzbyEcwNNjpMSAVgQt1N/fH4zFYiUtPk0Dal8dgcATLaEO1hR446WWN/4zwOPxPEaco3DhUAdAuiIIyxF0zicSiZ+NTJcM4C8HU/y6wVVANgKPu9ETskrkmCPZImaBMw7rZgzGUnEt6qC3t/c9xsvq3MR+qFgsVnO53C5jiBDUku435p1me7v2FpGUDzgUIgS8z28QnI3tqWGSzCccEkPX9VNTUxSNRsXD42atVX7GFNiTk5PPsO2+6ylm8IGBAUE+Pj4mv9+vxyrW2+HHsf1c5rPdUKOJRMa2DA4OvgHGfT2cdDpNY2NjhMylYDBIh4eHeqxivR1+hKEoTUxMpLA/HxpqNYkIA37JABdXqkkYhmoZu34SGnKaRIQHqjJeXEx0q+XYA900IHsLcUjCiJlWXIB9S16vl6anp2lkZIT6+vrIarXS6ekppVIpUhSFUHhQoVBoRR1fTkmp2UGkapqdnaX19XVyOIxzlrfr5uYmhcNhwj5XxTV7PogkPg4PDg7+whrdei8QCNDS0pJQAl7a3t4mXCSEG43Oz8/JbrfT6OgoLS4u0vDwsOBjb6ytrWkC8yJy78zlctnFbTg+Pv4Ja6/0uHd2doS7Q6EQRSJcqem3ubk5WllZoZOTE1pdXdVnJPqM6uh1S9exzWYj3F4tx9YItUarX8eiIMlmswXE9i6IT7WES6USlctlLVKnax/39/e/sXD9IOLSGXOlU41tyCk1LCEiQqAKd7UoZSO4RMLW8CNDj1Sjrqtnndhp840VMeuuh0AF2tvb+wFmN+bXGQ6FdV79J9A0gBfZSsRpBsMNPPx71Wlj2Q3WdfXLVYWXckBdbOxrFfPN/5w2GsFjs37P/wFZa428Xb42JwAAAABJRU5ErkJggg==)"/>
        没用</button>
    </div>
    </template>
  </ParentLayout>
</template>
<script>
import ParentLayout from '@vuepress/theme-default/layouts/Layout.vue'
import { usePageData } from '@vuepress/client'

export default {
  name:"layout",
  data(){
    return {
      dialogTableVisible:false,
      usePreKey:"use_",
      useMsg:"",
    }
  },
  components: {
    ParentLayout
  },
  setup() {
    const page = usePageData()
    const frontmatter=page.frontmatter;//这里就可以获取到页面定义的frontmatter
  },
  methods:{
    //如果用后台存储只需要实现重写ifClick和storeKey方法即可
    //判断用户是否点击过
    ifClick(key){
      if(localStorage.getItem(key)!=null){
         alert("您已经赞过了")
         return true;
      }
      return false;
    },
    //用于点击后存储的key
    storeKey(key,msg){  
       localStorage.setItem(key,msg)
    },

    usefullFun(){
      let key=this.usePreKey+window.location.href;
      if(!this.ifClick(key)){
        this.storeKey(key,"")
      }
    },
    noGoodFun(){
      let key=this.usePreKey+window.location.href;
      if(!this.ifClick(key)){
         this.dialogTableVisible = true
      }
    },
    submitIssue(){
      let key=this.usePreKey+window.location.href;
      if(!this.ifClick(key)){
        if(this.useMsg=="" ||this.useMsg.trim()==""){
          alert("请输入您的意见")
          return;
        }
        this.storeKey(key,this.useMsg)
        this.dialogTableVisible = false;
      }
    }
  }
}
</script>
<style scoped>
  .divlast{
    margin-top: 50px;
  }
  .spanbg{
      
  }
  .btnicon{
    display: inline-block;
    width:16px;
    height:16px;
    background-size: 16px 16px;
  }
  .btn{
     vertical-align: middle;
     width:120px;
     height:60px;
     cursor: pointer;
     background-color:white;
     border-radius:15px;
  }
  .dialog_backdrop{
    position: fixed;
    top: 0;
    right: 0;
    left: 0;
    bottom: 0;
    background-color: rgba(0, 0, 0, 0.6);
    height: 100%;
    z-index: 1050;
  }
  .dialog-wrap {
    position: fixed;
    overflow: auto;
    top: 30%;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 1050;
    -webkit-overflow-scrolling: touch;
    outline: 0;
    text-align: center;
    font-size: 0;
    white-space: nowrap;
}
.dialog {
    position: relative;
    display: inline-block;
    vertical-align: middle;
    text-align: initial;
    background-color: #fff;
    border-radius: 4px;
    padding: 20px;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
    font-size: 14px;
    white-space: normal;
}

</style>

新增.vuepress/client.js

import { defineClientConfig } from '@vuepress/client'
import Layout from './layouts/Layout.vue'
export default defineClientConfig({
  enhance({ app, router, siteData }){
    
  },
  setup(){
    
  },
  layouts: {
    Layout
  }
})

ssr渲染

布局页面使用ssr将整个vue在服务器上解析,不能出现常用的浏览器对象(window,navigator等),他会将所有的前端页面markdown调用Layout.vue进行渲染。
需求:在layout.vue中排除掉部分页面,不出现有用和无用的按钮。
methods中添加ifExcept方法

   //引用页面数据
  import { usePageData } from '@vuepress/client'
  //method添加方法
  methods:{
    ifExcept(){
      const page = usePageData()//这里就可以获取到页面定义的frontmatter和页面数据
      const compilePath=page._value.path;//获取渲染的页面路径
      let footExceptVar=footExcept?.except||[];
      return footExceptVar.indexOf(decodeURI(compilePath))<0
    }
    }

在#page-content-bottom中添加v-if即可

    <template v-if="ifExcept()" #page-content-bottom>

插件开发

需求:
需要开发一个插件,该插件可以定义一批页面的侧边栏菜单都是同一个,支持设置页面扫描标题深度.

  1. group1下自定义两个标题,这两个标题自动扫描2级别标题。
  2. group3直接指向一个markdown扫描2-3级别标题
  3. 中文文件名这个兼容中文,能扫描到2级别标题。

注意在页面设置sidebar点击有link的地址是会跳转的,如果浏览器的访问路径和你导航栏的link一致的事情,会自动高亮,才会显示2-3级别标题。

效果如下:
请添加图片描述

vuepress架构

在开发插件前需要了解vuepress整个构建的生命周期
在这里插入图片描述
上图展示了 VuePress 的简要架构:

  • Node App 会生成临时文件,包括页面、路由等。

  • Bundler 会将 Client App 和临时文件一起进行打包,就像处理一个普通的 Vue SPA 一样。
    作为开发者,你必须要意识到 VuePress 分为两个主要部分: Node App 和 Client App ,这一点对于开发插件和主题来说都十分重要。

  • 插件或者主题的入口文件会在 Node App 中被加载。

  • 客户端文件会在 Client App 中被加载,也就是会被 Bundler 处理。比如组件、客户端配置文件等。

核心流程与 Hooks

在这里插入图片描述
上图展示了 VuePress 的核心流程以及 插件 API 的 Hooks :

在 init 阶段:

主题和插件会被加载。这意味着插件需要在初始化之前使用。
由于我们要使用 markdown-it 来解析 Markdown 文件,因此需要在加载页面文件之前创建 markdown-it 实例:

  • extendsMarkdownOptions Hook 会被调用,用以创建 markdown-it 实例。
  • extendsMarkdown Hook 会被调用,用以扩展 markdown-it 实例。
    页面文件会被加载:
  • extendsPageOptions Hook 会被调用,用以创建页面。
  • extendsPage Hook 会被调用,用以扩展页面对象。

在 prepare 阶段:

  • 临时文件会被生成,因此所有和客户端文件相关的 Hooks 会在此处调用。

在 dev / build 阶段:

  • Bundler 会被加载:
  • extendsBundlerOptions Hook 会被调用,用以生成 Bundler 的配置。
  • alias Hook 和 define Hook 会被用在 Bundler 的配置中,所以它们会在此处调用。

具体参见api请参考官网:
https://v2.vuepress.vuejs.org/zh/reference/plugin-api.html
因为我们需要在编译文件之前需要插入sidebar,所以可以在onInitialized注入
在.vuepress下新建plugins目录新建文件sidebar.js

//自定义了一个.vuepress/sidebar.json,用来定义sidebar
import sidebar from "../sidebar.json"
/**
 * 对配置文件中文链接进行转码,否则有匹配问题
 * @param {*} sidebar 
 */
function encodeLink(sidebar){
    for(let sb of sidebar){
        if(sb.link){
            console.log(sb.link+"--->")
            sb.link=encodeURI(sb.link)
            console.log(sb.link)
        }
        if(sb.children && sb.children.length>0){
            encodeLink(sb.children)
        }
    }
}
/**
 * 将sidebar的所有链接加入到matchPage(所有的link页面应用相同的sidebar)
 * @param {} sidebar 
 * @param {*} list 
 */
function getAllLink(sidebar,list){
    for(let sb of sidebar){
        if(sb.link && list.indexOf(sb.link)<0){
            list.push(sb.link)
        }
        if(sb.children && sb.children.length>0){
            getAllLink(sb.children,list)
        }
    }
}
/**
 * 获取某个link对应的某个属性,主要是为了获取某个页面的sidebarDepth
 * @param {*} sidebar 
 * @param {*} link 
 * @param {*} proName 
 * @returns 
 */
function getLinkPro(sidebar,link,proName){
    for(let sb of sidebar){
        if(link==sb.link && sb[proName]!=null){
            return sb[proName]
        }else if(sb.children && sb.children.length>0){
            let s=getLinkPro(sb.children,link,proName)
            if(s!=null){
                return s;
            }
        }
    }
    return null;
}
export default  {
    name: 'vuepress-plugin-usermenu',
    onInitialized(app) {
      for(let menu of sidebar.sidebarList){
            encodeLink(menu.sidebar)
      }
      app.pages.forEach((item) => {
        let itemPath=item.path
        for(let menu of sidebar.sidebarList){
            //将所有sidebar下的link的sidebar都设置为相同。
            if(menu.matchAllSideBarLink){
                menu.matchPage=menu.matchPage||[]
                getAllLink(menu.sidebar,menu.matchPage)
            }
            if(menu.matchPage.indexOf(itemPath)>=0){
                //查询是否有sibar对应link的sidebarDepth,存在应用,不存在获取全局
                let sidebarDepth=getLinkPro(menu.sidebar,itemPath,"sidebarDepth")
                if(sidebarDepth!=null){
                    item.frontmatter.sidebarDepth=sidebarDepth;
                }else if(menu.sidebarDepth!=null){
                    item.frontmatter.sidebarDepth=menu.sidebarDepth;
                }
                item.frontmatter.sidebar=menu.sidebar;
            }
        }
      });
    }
  }

.vuepress/sidebar.json内容:

{
    "sidebarList":[{
        "matchPage":["/zh-cn/t.html"],
        "matchAllSideBarLink":true,
        "sidebarDepth":1,
        "sidebar":[
            {
                "text": "Group 1",
                "collapsible": true,
                "children": [
                {
                    "text": "Active on /foo/",
                    "link": "/zh-cn/sidebar/tt.html"
                },
                {
                    "text": "Always active",
                    "link": "/zh-cn/sidebar/tt1.html"
                }
              ]
            },
            {
                "text": "Group 2",
                "collapsible": true,
                "children": [
                {
                    "text": "Active on /foo/",
                    "link": "/zh-cn/sidebar/tt2.html",
                    "sidebarDepth":1
                }
              ]
            },
            {
                "text": "Group 3",
                "collapsible": true,
                "link": "/zh-cn/sidebar/tt3.html",
                "sidebarDepth":2
            },
            {
                "text": "中文文件名",
                "collapsible": true,
                "link": "/zh-cn/sidebar/中文测试.html",
                "sidebarDepth":1
            }
          ]
    }]
}

config.js中引用插件

import sideBarPlugin  from './plugins/sidebar'
export default {
    base: '/jiedoc/',
    cmd: 'node ./node_modules/vuepress/bin/vuepress.js build',
    plugins:[
        sideBarPlugin
    ],
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

vue进阶04-vue文档生成工具vuepress2 的相关文章

  • Angular 4 过滤器搜索自定义管道

    所以我试图构建一个自定义管道来在 ngFor 循环中执行多个值的搜索过滤器 我花了几个小时寻找一个好的工作示例 其中大多数都是基于以前的版本 并且似乎不起作用 所以我正在构建管道并使用控制台为我提供值 但是 我似乎无法显示输入文本 以下是我
  • Angular UI select:从远程服务获取数据

    我正在使用角度用户界面选择 https github com angular ui ui select https github com angular ui ui select 我查看了演示的可用位置这个笨蛋 http plnkr co
  • 如果没有“new”,则无法调用类构造函数

    感谢这个问题已经被问过几次了 但是我遇到的几乎所有情况都是有人试图扩展非本地类的情况 我的情况有所不同 我有一个非常简单的基类 名为CObject如下 export class CObject extends BaseObject cons
  • Mapbox GL 中的 MaxBounds 和自定义非对称填充

    我有一个 Mapbox GL JS 应用程序 在地图上显示一些小部件 为了确保地图上的任何内容都不会被它们隐藏 我使用以下命令添加了一些填充map setPadding 这是一个不对称的 在我的例子中左边比右边大 它按预期工作 例如fitB
  • 如何立即启动setInterval循环? [复制]

    这个问题在这里已经有答案了 在一个简单的setInterval setInterval function Do something every 9 seconds 9000 第一个动作将在 9 秒后发生 t 9s 如何强制循环立即执行第一个
  • 在javascript中访问函数内的实例变量?

    如何以最简单的方式访问函数内的实例变量 function MyObject Instance variables this handler Methods this enableHandler function var button doc
  • 此页面上的脚本导致 ie 运行缓慢

    问题就在标题中 IE 行为异常 并说有一个脚本运行缓慢 FF 和 Chrome 没有这个问题 我怎样才能找到问题所在 那个页面有很多JS 手动检查不是一个好主意 EDIT 这是我正在处理的一个项目的页面 但我需要一个工具来查找问题 End
  • 检测 Google 验证码的挑战窗口何时关闭

    我正在使用谷歌隐形验证码 有没有办法检测挑战窗口何时关闭 我所说的挑战窗口是指您必须选择一些图像进行验证的窗口 目前 我在按钮上放置了一个旋转器 一旦单击按钮 就会呈现验证码挑战 无法向用户提示另一个质询窗口 我以编程方式调用渲染函数 gr
  • API 使用令牌向 odoo 进行身份验证

    我想使用令牌从 Express 应用程序向 Odoo 进行身份验证 我在用odoo xmlrpc https www npmjs com package odoo xmlrpc连接 Odoo 的节点模块 我的快递应用程序 Odoo 要求 A
  • 访问 nuxt 配置文件中的存储

    我想添加通过 Nuxt 静态生成的动态路由 我定义了一个客户端 服务器端存储asyncData方法 我想将这个存储值 一个数组 映射到我的nuxt config js文件使其成为 动态 静态 路线图nuxt generate命令 但如何访问
  • 如何在 select 和 option 标签中添加 JSON 数据?

    我有这个html代码 div class searchfilter div class searchwrapper div div
  • Web组件中嵌套槽的内容不可见

    我有一个 Web 组件 它应该接受任意元素来包装其内容 虽然我可以在 Chrome 开发工具中看到插槽已正确分配 但 DOM 中什么也没有出现 以前有人见过这个问题吗 定义 class ExampleParent extends HTMLE
  • 呃!尝试将包发布到 npm 时出现 403

    我正在尝试将包发布到 npm 您可以在此处查看存储库 https github com biowaffeln mdx state https github com biowaffeln mdx state 我登录到 npmnpm login
  • 如何在画布上所有其他内容后面绘制图像? [复制]

    这个问题在这里已经有答案了 我有一块画布 我想用drawImage在画布上当前内容后面绘制图像 由于画布上已经有内容 我正在使用字面上的画布来创建包含图像的画布 因此我无法真正先绘制图像 所以我无法使用drawImage在我呈现其余内容之前
  • 我可以使用 ASP.NET WebForms 母版页在每个内容页中包含不同的 javascript/css 文件吗?

    我有几个使用相同母版页的内容页 它们并不都需要包含在相同的 javascript 和 css 文件中 tag 是否可以更改内容来自内容页面的标签 确实如此 但我建议采取一些不同的做法 我在关闭正文标签的正上方放置了一个内容占位符 然后我填充
  • 尝试使用 Javascript 解决对称差异

    我正在尝试找出对称的解决方案 使用 javascript 完成以下任务的差异 目标 接受未指定数量的数组作为参数 保留数组中数字的原始顺序 不删除单个数组中数字的重复项 删除数组中出现的重复项 因此 例如 如果输入是 1 1 2 6 2 3
  • PDF 在 Safari 中隐藏 Jquery Modal

    这是与我有关的事情this https stackoverflow com questions 24052681 pdf hide jquery modal in ie问题 在 IE 中 我在对话框中使用 iframe 解决了问题 所以它工
  • 为什么 Node.js 应用程序只能从 127.0.0.1/localhost 访问?

    我本来打算教我的朋友介绍 Node 但是后来 我想知道为什么这个代码来自nodejs org var http require http http createServer function req res res writeHead 20
  • d3.event.translate 在触摸设备的缩放上包含 NaN

    我使用 d3 为我的 svg 编写了一个自定义缩放函数 如下所示 Zoom behavior function myzoom xpos d3 event translate 0 ypos d3 event translate 1 vis a
  • 如何在 Jquery/Javascript 中绑定模糊和更改,但只触发一次函数?

    我试图在选择元素更改时触发函数 由于 Ipad 在 on change 方面遇到问题 我还想绑定到 blur 这在 Ipad 上工作得很好 但是我不希望两个事件都触发该函数两次 所以我需要某种挂钩来确保两个事件是否都触发change and

随机推荐

  • python-docx常用方法总结

    由于最近有任务需要自动生成word报告 因此学习了一些python docx的使用方法 在此总结 目前网上相关的资料不算太多 且大多数都很简单 有一些稍微复杂的需求往往找不到答案 很多想要的方法这个库似乎并没有直接提供 在git上看 这个包
  • java中equals方法重写详解(彻底搞定)

    首先上案例 public static void main String args String str1 abc String str2 abc String str3 new String abc new出来的新地址 System ou
  • 计算机网络中的应用层和传输层(http/tcp)

    目录 1 协议的通俗理解 1 1 理解协议 2 应用层 2 1 http协议 2 2 HTTP的方法 2 3 HTTP的状态码 2 4 HTTP常见Header 3 传输层 3 1 端口号 3 1 1 端口号范围划分 3 1 2 netst
  • ESP32 Arduino安装和烧录程序

    学习ESP32前先必需了解一下Arduino Arduino是指开源硬件 在以前开源一般指的是软件 源码公开 后来随着发展出现了开源硬件 开源硬件有了以后大家就可以在开源硬件上做出一些兼容 官方学习参考网址 https www arduin
  • 使用docker 数据卷怎么查看数据卷对应的容器内部目录

    docker inspect redis7703 grep Mounts A 20 说明 redis7703 是容器名称
  • Mysql计算相邻两两记录某个字段的最大差值

    计算相邻两两记录某个字段的最大差值 需求一个患者有多条病程记录 查询该患者的 最大检查间隔 即求两两记录的最大检查间隔 天数 注1 其中检查时间是 case Record表中的create time字段 其中user id是患者编号 注2
  • GRE隧道协议

    一 GRE协议简介 GRE General Routing Encapsulation 通用路由封装 是对某些网络层协议 如IP和IPX 的数据报文进行封装 使这些被封装的报文能够在另一网络层协议 如IP 中传输 此外 GRE协议也可以作为
  • matlab怎么处理非平衡数据处理,处理非平衡数据的七个技巧

    原标题 处理非平衡数据的七个技巧 摘要 本文介绍了在入侵检测 实时出价等数据集非常不平衡的领域应用的数据处理技术 关键字 平衡数据 数据准备 数据科学 原文 7 Techniques to Handle Imbalanced Data ht
  • 深度学习归一化方法总结(BN、LN、IN、GN)

    目录 一 批量归一化 BatchNorm 二 层归一化 Layer Normalization 三 Instance Normalization 四 Group Normalization 一般在神经网络中会用到数据的归一化 比如在卷积层后
  • C++栈初步认识和范围for循环

    C 栈初步认识 1 使用栈实现字符串反转 2 范围for循环 1 使用栈实现字符串反转 当我们需要在程序中实现一个 先进后出 的数据结构时 栈就是一个很好的选择 在C 中 我们可以通过STL提供的stack类来使用栈 stack类模板定义在
  • 分段函数求值1

    Copyright c 烟台大学计算机与控制工程学院 Author 刘慧艳 Created 2014 07 16 Edition V1 0 Describe 分段函数求值 include
  • 电网电压的三相静止对称坐标系和三相电网电压的相量表示法

    电网电压的空间电压矢量和电网电压的相量表示这两个概念需要区分清楚 分别参考邱关源的 电路 和张兴的 PWM整流 相关章节 图2 三相电网电压的相量表示法 电网电压的相量表示 三相相差120度 整体逆时针50HZ旋转 这里的120度是指三分之
  • ceph -s分析

    1 源码跟踪 1 1 get cluster status https github com ceph ceph blob 2a724a2ff313701fd7f6278ce8ed7f440bb355e0 src mon Monitor c
  • Jenkins pipeline拉取代码超时

    拉取代码报错 using GIT ASKPASS to set credentials gt git fetch tags progress http 192 168 1 8 1234 bi web xxxxxx git refs head
  • Linux学习笔记:win10安装虚拟机

    第一步 打开win10自带的虚拟机 第一步 打开win10自带的虚拟机Hyper V 需要 win10系统 1 点击windows键 e键打开文件资源管理器 右击此电脑 gt 选择属性 gt 打开控制面板 2 选择控制面板主页 gt 选择程
  • 机器人识别抓取笔记(基于视觉的机器人抓取——从物体定位、物体姿态估计到平行抓取器抓取估计:综述)

    Real Time Deep Learning Approach to Visual Servo Control and Grasp Detection for Autonomous Robotic Manipulation 基于视觉的机器
  • el-upload的多文件上传

    el upload实现多文件上传的方法
  • 【C++】第一章:多数据输入时的分隔问题

    在键盘输入多数据时 必须用空格键 Tab键或enter键隔开 不同数据类型的输入 按数据类型读取数据 当用enter键分隔数据时 当用空格键分隔时结果是一样的 多数据错误输入情况 当有与数据类型不匹配的数据时 用空格键分隔 按变量数据类型依
  • vue.js使用props在父子组件之间传参

    prop 组件实例的作用域是孤立的 这意味着不能 也不应该 在子组件的模板内直接引用父组件的数据 要让子组件使用父组件的数据 我们需要通过子组件的 props 选项 子组件要使用 props选项声明它期待获得的数据 官方的解释非常清晰了 两
  • vue进阶04-vue文档生成工具vuepress2

    介绍 VuePress 是一个以 Markdown 为中心的静态网站生成器 你可以使用 Markdown 来书写内容 如文档 博客等 然后 VuePress 会帮助你生成一个静态网站来展示它们 VuePress 诞生的初衷是为了支持 Vue