前言
本篇文章是对上篇文章 详解:使用 vuepress 实现项目文档 的扩展,主要介绍 VuePress
主题的编辑、移动端组件库文档类型的左侧菜单与右侧 iframe 组件示例页面交互的实现。
一、 效果预览
注:以下效果图是从我的微信小程序组件库 Mind UI WeApp 文档中截取。
1. 首页样式
首页内容与样式重写,并隐藏顶部导航栏。
2. 菜单联动
左侧菜单样式优化,右侧增加预览功能。
二、VuePress
默认主题
1. 默认主题介绍
在实现自定义首页与增加 iframe
页面前,我们先了解下 VuePress
的 主题功能。
所谓主题,我们可以简单理解为一套皮肤,比如掘金文章编辑器中的 markdown
可以选择多种主题,其主要是通过模板语法与样式的搭配,将同样的内容以不同的形式展现出来。
网上有许多小伙伴开发了不同的三方主题,可以直接安装使用。本篇文章侧重点不在如何开发一套主题,而是如何修改默认主题,以适配自己的需求。
2. 默认主题结构
我们在项目的 /.vuepress
文件夹下,新建 /theme
文件夹,接下来找到 npm
包中默认主题,路径为 node_modules\vuepress\node_modules\@vuepress\theme-default
,将文件夹中除 /node_modules
、package.json
以外的文件复制到 /theme
文件夹下,其目录结构为:
├─ theme
│ ├─ components // 主题相关组件
│ │ ├─ FooterBar.vue // 页底,比如写入版权信息
│ │ ├─ Home.vue // 首页 (接下来会重写的页面)
│ │ ├─ Navbar.vue // 顶部菜单栏
│ │ ├─ Page.vue // 文档页面 (在此组件中增加 `iframe` 页面,并实现消息发送与接收)
│ │ ├─ Sidebar.vue // 侧边栏导航菜单 (会调整其样式)
│ │ └─ ... // 其它组件
│ ├─ global-components // 会被自动注册为全局组件,在 `.md` 中可使用
│ │ └─ Badge.vue // 角标组件
│ ├─ layouts
│ │ ├─ 404.vue // 404页面
│ │ └─ Layout.vue // 布局组件
│ ├─ styles // 样式文件
│ │ ├─ index.styl // 全局样式
│ │ ├─ mobile.styl // 移动端样式
│ │ ├─ markdown.styl // markdown 主题
│ │ └─ ... // 其它样式文件
│ ├─ utils
│ │ └─ index.js // 默认工具函数
│ ├─ index.js // 主题文件的入口文件
│ └─ noopModule.js
└─ ...
三、页面优化
1. 文档首页
我们打开 .vuepress/theme/components/Home.vue
文件,该文件对应的就是文档首页的结构与样式,我们删除 <template>
中的内容,根据自己的需求,重写结构与样式。
注意点:
-
模板中使用
/public
中的图片,需要放在$withBase
方法中,该方法对应的路径为config.js
中base
属性的值,该方法支持Vue
变量。<img :src="$withBase('/logo.png')" />
-
为了支持移动端的展示,需要对首页的样式通过媒体查询做好对应的适配:
@media (max-width: $MQMobile) { // 移动端样式 }
$MQMobile
为框架提供的移动端宽度变量。 -
隐藏首页顶部导航栏
我们打开/layouts/Layout.vue
文件,其中的<Navbar>
组件即为顶部导航栏,我们找到计算属性中的shouldShowNavbar
属性,其中的frontmatter
存放着当前页面的一些属性信息,我们用其中的home
属性来判断是否为首页,代码如下:// 43行,是否显示左侧导航栏判断,在判断条件中增加 frontmatter.home 条件,如果是首页,则返回 false shouldShowNavbar() { const { themeConfig } = this.$site; const { frontmatter } = this.$page; if ( frontmatter.navbar === false || themeConfig.navbar === false || frontmatter.home ) { return false; } // ... },
2. 左侧菜单
左侧菜单对应文件为 /theme/components/
下的 Sidebar.vue
与 SidebarXxx.vue
文件,我们可以根据需求改写相关文件结构,或者重写其样式。
3. 右侧内容
文档内容区域对应组件为 /theme/components/Page.vue
文件,我们增加一个 iframe
内容区域:
<template>
<div class="wrapper">
<main class="page">
<slot name="top" />
<Content class="theme-default-content" />
<PageEdit />
<PageNav v-bind="{ sidebarItems }" />
<slot name="bottom" />
</main>
<!-- iframe 内容区域 -->
<div class="preview">
<iframe
class="iframe"
width="100%"
height="100%"
:src="iframeUrl"
ref="iframe"
></iframe>
</div>
</div>
</template>
对应的 Page.vue
组件的 class="page"
样式需要调整,以给右侧留出展示 iframe
的空间。主要涉及 /theme/styles
文件夹下的 index.styl
与 mobile.styl
文件。
// index.styl 中增加的部分样式
.page
padding-left 18rem
// mobile.styl 中增加的部分样式
@media (max-width: $MQMobile)
.page
padding-right 0 // 移动端文档为全屏,右侧边距置为0
.preview
display none // 移动端时隐藏 iframe 预览
新增的 preview
标签样式如下:
.preview
z-index 1
position fixed
right 15px
top: 70px
min-width 340px
height calc(100vh - 90px) // 高度设置为去除顶部导航栏与底部留白高度
border-radius 6px
box-shadow 0 0 6px #ccc
background-color #fff
以上只是部分样式的代码,主要是项目的样式做了多种媒体查询适配,我们需要对其一一处理。
四、文档联动
1. iframe
通信
通过 window.postMessage('message', e.origin);
与 window.addEveantListener('message',(event)=>{})
函数来发送与监听消息,他们是支持跨域传递消息的,其更多的介绍与使用方式可在 mdn
上查看,在此不做赘述。
2. 左右联动实现
左侧菜单与右侧内容联动,主要分为以下两种情况:
-
文档菜单控制
iframe
页面路由
思路: 点击文档菜单后,watch
监听文档的路由变化,将页面跳转后的路由信息发送给iframe
页面,iframe
页面接收后,根据发送来的信息,跳转到对应的页面。- 文档页面发送消息代码如下:
// ./vuepress/theme/components/Page.vue 组件 export default { // ... 其它代码 watch: { // 监听路由变化 $route: "routerChange", }, methods: { routerChange(route) { // 由于未找到设置当前页面路由名称的方法,所以以截取路由中 .html 名称作为当前路由名称 let paths = route.path.split("/"); let lastPath = paths[paths.length - 1]; // pathUrl 为接收页面需要跳转的地址 let pathUrl = lastPath.replace(/.html/, ""); // 设置传空值给接受页的白名单 const ignorePathList = ['', 'install', 'started']; if(ignorePathList.includes(pathUrl)) { pathUrl = '' } // 获取 iframe 实例,通过该实例去发送消息 this.iframeWindow = this.$refs.iframe.contentWindow; // 将路径名称发送给 iframe 页面 this.iframeWindow.postMessage( { url: pathUrl }, "*" ); } } }
iframe
接收消息代码:
// App.vue export default { mounted() { // 当前网页被放入 iframe 时才执行此监听 if (window.parent !== window.self) { window.addEventListener("message", this.handleMessage); } }, methods: { handleMessage(ev) { if (ev.data) { let url = ev.data.url; // 当前页面与跳转页面路由一致时不跳转 const currentPath = this.$route.path; if (currentPath === "/" + url) return; if (pathList.includes(url)) { this.$router.push(url); } else { if (currentPath === "/") return; this.$router.push("/"); } } } } }
由于我的文档网站菜单与示例的网站路由是能对应上的,所以只做了简单的地址对应。
-
iframe
页面控制文档页面
与上面方式一样,只是改由iframe
页面向文档页面发送消息,文档页面接收消息后做对应路由跳转,差异处会在代码中注明。
以下为iframe
页面部分代码:// App.vue export default { watch: { // 监听路由变化 $route: "routerChange", }, methods: { routerChange(route) { // 如果当前页面不是在 iframe 内,则不执行以下操作 if (window.parent === window.self) return; if (!route.path) return; // 注意此处为 window.parent 指向父级页面 window.parent.postMessage( { url: route.path, }, "*" ); } } }
以下为文档页面的
Page.vue
消息接收页面部分代码:// ./vuepress/theme/components/Page.vue export default { mounted() { // 全局监听消息 window.addEventListener("message", this.handleMessage); }, methods: { handleMessage(ev) { // 根据传入的消息内容,拼接处理出需要跳转的文档地址。 let url = ev.data.url; // ... 处理代码略 let path = `/component/${prefix}/${url}.html`; // 如果当前页面地址与传入地址一致,则不跳转 if (path === this.$route.path) return; this.$router.push(path); } } }
结束语
以上即为我在使用 VuePress
的过程中感觉有用些的知识,希望能给需要人带来帮助。