vue进阶04-vue文档生成工具vuepress2
VuePress 是一个以 Markdown 为中心的静态网站生成器。你可以使用 Markdown 来书写内容(如文档、博客等),然后 VuePress 会帮助你生成一个静态网站来展示它们。VuePress 诞生的初衷是为了支持 Vue.js 及其子项目的文档需求,但是现在它已经在帮助大量用户构建他们的文档、博客和其他静态网站。
介绍
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> <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>
<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>
插件开发
需求:
需要开发一个插件,该插件可以定义一批页面的侧边栏菜单都是同一个,支持设置页面扫描标题深度.
- group1下自定义两个标题,这两个标题自动扫描2级别标题。
- group3直接指向一个markdown扫描2-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
],
更多推荐
所有评论(0)