VuePress 是一个以 Markdown 为中心的静态网站生成器。你可以使用 Markdown 来书写内容(如文档、博客等),然后 VuePress 会帮助你生成一个静态网站来展示它们。
本文是使用 vuepress 搭建类似于 vant 文档,右侧带有移动端展示。先看下成果:shop-m使用文档 ,左侧是文档,右侧带有移动端展示,且会根据不同的文档页面,移动端跳转到对应的页面展示。
VuePress 提供的默认主题就挺好的,我们使用 布局插槽 来完成我们的功能。
默认主题的 Layout
布局提供了一些插槽:
navbar
navbar-before
navbar-after
sidebar
sidebar-top
sidebar-bottom
page
page-top
page-bottom
page-content-top
page-content-bottom
在它们的帮助下,你可以很容易地添加或替换内容。下面通过一个示例来介绍一下如何使用布局插槽来继承默认主题。
首先,创建你的本地主题 docs/.vuepress/theme/index.js
:
const { defaultTheme } = require('vuepress')
const { path } = require('@vuepress/utils')
module.exports = {
localTheme: (options) => {
return {
name: 'vuepress-theme-local',
extends: defaultTheme(options),
layouts: {
Layout: path.resolve(__dirname, 'layouts/Layout.vue'),
},
}
}
}
这样你的本地主题将会继承默认主题,并且覆盖 Layout 布局。
接下来,创建 docs/.vuepress/theme/layouts/Layout.vue
,并使用由默认主题的 Layout 布局提供的插槽:
<script setup>
import { watch, ref, nextTick } from 'vue'
import ParentLayout from '@vuepress/theme-default/lib/client/layouts/Layout.vue'
import { useRouter, useRoute } from 'vue-router'
import pathList from './../pathList.js'
const route = useRoute()
const iframeId = ref(null)
const iframeBaseUrl = import.meta.env.MODE === 'development' ? 'http://localhost:3000/shop-m/#' : 'https://shop-template.github.io/shop-m/#'
const iframeUrl = ref(iframeBaseUrl)
// 根据父 path 拿到 子 path
function parentPathToChildrenPath(parentPath) {
const cur = pathList.find(x => x.parentPath === parentPath)
return cur ? cur.childrenPath : ''
}
// 首次设置 iframe 的链接
iframeUrl.value = `${iframeBaseUrl}${parentPathToChildrenPath(route.path)}`
let osEnd = ref('pc')
// 获取是移动端还是PC,摘自:https://tim.qq.com/
const OS = function() {
var a = navigator.userAgent,
b = /(?:Android)/.test(a),
d = /(?:Firefox)/.test(a),
e = /(?:Mobile)/.test(a),
f = b && e,
g = b && !f,
c = /(?:iPad.*OS)/.test(a),
h = !c && /(?:iPhone\sOS)/.test(a),
k = c || g || /(?:PlayBook)/.test(a) || d && /(?:Tablet)/.test(a),
a = !k && (b || h || /(?:(webOS|hpwOS)[\s\/]|BlackBerry.*Version\/|BB10.*Version\/|CriOS\/)/.test(a) || d && e);
return {
android: b,
androidPad: g,
androidPhone: f,
ipad: c,
iphone: h,
tablet: k,
phone: a
}
}();
if (OS.phone || OS.ipad) {
osEnd.value = 'phone'
}
let oldPath = route.path
watch(
route,
async (val) => {
await nextTick()
if (val.path !== oldPath) {
oldPath = val.path
const childrenPath = parentPathToChildrenPath(val.path)
if (childrenPath) {
if (osEnd.value === 'pc') {
iframeId.value && iframeId.value.contentWindow.location.replace(`${iframeBaseUrl}${childrenPath}`)
} else {
iframeUrl.value = `${iframeBaseUrl}${childrenPath}`
}
}
}
},
{
deep: true,
immediate: true
}
)
script>
<template>
<ParentLayout>
<template #navbar-after>
<div v-if="osEnd === 'pc'" class="docs-box">
<iframe :src="iframeUrl" frameborder="0" ref="iframeId">iframe>
div>
template>
<template #page-content-bottom>
<div v-if="osEnd === 'phone'" class="docs-box-wrap">
<div class="docs-box">
<iframe :src="iframeUrl" frameborder="0" ref="iframeId">iframe>
div>
div>
template>
ParentLayout>
template>
<style lang="css">
.docs-box {
position: fixed;
top: calc(var(--navbar-height) + 50px);
right: 68px;
width: 360px;
height: 640px;
z-index: 1000;
background-color: #fff;
border: 1px solid var(--c-border);
}
.docs-box iframe {
display: block;
width: 100%;
height: 640px;
}
.page {
position: relative;
padding-right: 450px;
}
@media (max-width: 1344px) {
.page {
padding-right: 380px;
}
.docs-box {
right: 20px;
}
}
@media (max-width: 419px) {
.page {
padding-right: 0;
}
.docs-box-wrap {
width: 100vw;
margin-left: calc(calc(100% - 100vw) / 2);
}
.docs-box {
position: inherit;
top: 0;
right: 0;
margin: 0 auto;
z-index: 5;
}
}
.theme-container.no-sidebar .docs-box {
display: none;
}
:root {
--sidebar-width: 15rem;
--content-width: auto;
}
style>
文档页面的路由和移动端展示页面的路由关系配置在pathList.js
文件中,一一对应关系:
// 配置父子 path
export default [
{
parentPath: '/guide/',
childrenPath: '/'
},
{
parentPath: '/guide/getting-started.html',
childrenPath: '/'
},
{
parentPath: '/guide/cssVar.html',
childrenPath: '/demo/cssVar'
},
{
parentPath: '/guide/navBar.html',
childrenPath: '/demo/navBar'
},
{
parentPath: '/guide/tabbar.html',
childrenPath: '/demo/tabbar'
},
{
parentPath: '/guide/network.html',
childrenPath: '/demo/network'
},
{
parentPath: '/guide/vconsole.html',
childrenPath: '/demo/vconsole'
},
{
parentPath: '/guide/404.html',
childrenPath: '/aaaa'
},
{
parentPath: '/guide/permission.html',
childrenPath: '/user'
},
{
parentPath: '/guide/login.html',
childrenPath: '/login'
},
{
parentPath: '/guide/userInfo.html',
childrenPath: '/userInfo'
},
{
parentPath: '/plugins/compressorjs.html',
childrenPath: '/userInfo'
}
]
其中需要注意的是 iframe 的链接赋值之后不能再次赋值,否则会在
history
中增加一个记录,导致浏览器点击返回按钮出问题,解决方案如下:
iframeId.value && iframeId.value.contentWindow.location.replace(`${iframeBaseUrl}${childrenPath}`)
PC端中移动端展示放在
navbar-after
插槽内,是为了防止每次路由变更之后 iframe 都重新创建的问题。移动端中移动端展示放在page-content-bottom
插槽内,因为其他位置的插槽会导致摆放位置不太符合。所以针对PC端、移动端做不同的逻辑处理:
let osEnd = ref('pc')
// 获取是移动端还是PC,摘自:https://tim.qq.com/
const OS = function() {
var a = navigator.userAgent,
b = /(?:Android)/.test(a),
d = /(?:Firefox)/.test(a),
e = /(?:Mobile)/.test(a),
f = b && e,
g = b && !f,
c = /(?:iPad.*OS)/.test(a),
h = !c && /(?:iPhone\sOS)/.test(a),
k = c || g || /(?:PlayBook)/.test(a) || d && /(?:Tablet)/.test(a),
a = !k && (b || h || /(?:(webOS|hpwOS)[\s\/]|BlackBerry.*Version\/|BB10.*Version\/|CriOS\/)/.test(a) || d && e);
return {
android: b,
androidPad: g,
androidPhone: f,
ipad: c,
iphone: h,
tablet: k,
phone: a
}
}();
if (OS.phone || OS.ipad) {
osEnd.value = 'phone'
}
观测路由变化,让移动端展示显示不同的路径页面:
// 根据父 path 拿到 子 path
function parentPathToChildrenPath(parentPath) {
const cur = pathList.find(x => x.parentPath === parentPath)
return cur ? cur.childrenPath : ''
}
...
watch(
route,
async (val) => {
await nextTick()
if (val.path !== oldPath) {
oldPath = val.path
const childrenPath = parentPathToChildrenPath(val.path)
if (childrenPath) {
if (osEnd.value === 'pc') {
iframeId.value && iframeId.value.contentWindow.location.replace(`${iframeBaseUrl}${childrenPath}`)
} else {
iframeUrl.value = `${iframeBaseUrl}${childrenPath}`
}
}
}
},
{
deep: true,
immediate: true
}
)
注意:iframe 链接是全连接,所以需要自行修改
docs/.vuepress/theme/layouts/Layout.vue
中的iframeBaseUrl
变量。
至此就可以实现一个带移动端展示的 shop-m使用文档 。