由于最近公司需要处理大量官网,且内容不会经常变化,因此选择vuePress,通过只要更改md文件内容,就可以实现不同官网的布置
介绍
vuePress是一个极简静态网站生成器,它包含由vue驱动的主题系统和插件。它有个主题系统,可以设置自定义主题和默认主题,通过服务端渲染,生成对应的HTML文件,只需要设置好需要展示的md文件内容就可以
快速使用
- 创建目录
mkdir vuepress-starter && cd vuepress-starter
- 初始化
yarn init # npm init
- 安装
yarn add -D vuepress # npm install -D vuepress
- 创建md文件
mkdir docs && echo '# Hello VuePress' > docs/README.md
- 设置配置文件
{
"scripts": {
"dev": "vuepress dev docs",
"build": "vuepress build docs"
}
}
- 启动
yarn dev # npm rundev
目录结构
主要遵循“约定大于配置”的原则
目录主要集中在.vuepress中
其中比较重要且常用的目录如下:
docs/.vuepress/theme: 用于存放本地主题。docs/.vuepress/public: 静态资源目录。docs/.vuepress/components: 该目录中的 Vue 组件将会被自动注册为全局组件。docs/.vuepress/config.js: 配置文件的入口文件,也可以是YML或toml。
页面路径规则:
| 文件的相对路径 | 页面路由地址 |
|---|---|
/README.md | / |
/guide/README.md | /guide/ |
/config.md | /config.html |
默认主题
可以访问标题链接,可以查阅默认主题配置,非常详细,基本可以通过配置文件docs/.vuepress/config.js,在里面设置好对应的配置,就可以实现各个功能(导航栏、侧边栏、搜索等等),由于默认主题配置,不是本文主要介绍,所以快速跳过
第三方主题
可以访问标题链接
- 安装
npm install vuepress-theme-simple --save-dev
or
yarn add vuepress-theme-simple --dev
- 引用设置主题
在
docs/.vuepress/config.js设置
// .vuepress/config.js
module.exports = {
theme: 'simple',
themeConfig: {
// 请参考文档来查看所有可用的选项。
}
}
主题继承开发
由于需要快速上线,而且无需全部开发属于自己的主题库,因此需要用高效的方法,可以使用主题继承来实现,只需额外更改属于自己的风格组件就好。
主题继承覆盖原理:
所有的组件都必须使用 @theme 别名来引用其他组件。
<script>
import Navbar from '@theme/components/Navbar.vue'
// ...
</script>
在这样的前提下,当你在子主题中同样的位置创建一个 Navbar 组件时:
@theme/components/Navbar.vue 会自动地映射到子主题中的 Navbar 组件,当你移除这个组件时,@theme/components/Navbar.vue 又会自动恢复为父主题中的 Navbar 组件。
快速入手主题小demo
- 1、
.vuepress/theme/index.js创建
// .vuepress/theme/index.js
module.exports = (themeConfig, ctx) => {
return {
// ...
}
}
- 2、
docs/.vuepress/config.js文件配置
module.exports = {
title: 'hello vuepress',
description: 'vupress',
port: 3334,
theme: require('./theme/index'),
themeConfig: {
copyright: `Copyright © 2018-present`
}
}
- 3、
docs/.vuepress/theme/layouts/Layout.vue创建
<template>
<div class="theme-container">
<SideBar/>
<Home/>
</div>
</template>
<script>
import Home from '../components/Home'
import SideBar from '../components/SideBar'
export default {
components: {
Home,
SideBar
}
}
</script>
- 4、
docs/.vuepress/theme/components/Home.vue
<template>
<div>Home</div>
</template>
- 5、
docs/.vuepress/theme/components/SideBar.vue
<template>
<div>
SideBar {{$site.title}}
<FooterBar/>
</div>
</template>
<script>
import FooterBar from './FooterBar'
export default {
components: {
FooterBar
}
}
</script>
- 6、
docs/.vuepress/theme/components/FooterBar.vue
<template>
<div>FooterBar
<p class="copyright" v-html="$themeConfig.copyright"></p>
</div>
</template>
基于继承,项目应用
继承开发目录结构:
官方默认主题库源码,可以在基础上,提取有用信息,通过继承快速二次开发
其中主要涉及到的theme目录,主要操作如下:
- 1、
docs/.vuepress/config.js文件配置
module.exports = {
port:3334,
title: 'Luckin Game', //需要替换
head: [
['link', { rel: 'icon', href: '/game6.png' }],
],
locales: {
'/': {
lang: 'en-US',
title: 'Luckin Game', //需要替换
description: 'h5 and mobile games publisher', //需要替换
footer:{
copyright:"Copyright © 2018-present",//需要替换
police:""//需要替换
},
header:{
backgroundImage:"/game6.png"//需要替换
}
},
'/zh/': {
lang: 'zh-CN',
title: 'Luckin Game',//需要替换
description: '致力于手游发行,助力游戏开发者',//需要替换
footer:{
copyright:"Copyright © 2018-present",//需要替换
police:""//需要替换
},
header:{
backgroundImage:"/game6.png"//需要替换
}
}
},
themeConfig: {
background:'background:#2F2F2F;',//底部背景颜色,可以替换
gtagId:'xxxxxxx',//谷歌gaid,需要替换
search: false,
smoothScroll: true,
locales: {
'/': {
lang: 'en-US',
title: 'Luckin Game',//需要替换
description: 'Committed to mobile game publishing',//需要替换
nav: [
{ text: 'Home', link: '/' },
{ text: 'Privacy', link: '/privacy/' },
{ text: 'Terms', link: '/terms/' },
]
},
'/zh/': {
lang: 'zh-CN',
title: 'Luckin Game',//需要替换
description: '致力于手游发行,助力游戏开发者',//需要替换
nav: [
{ text: '主页', link: '/zh/' },
{ text: '隐私政策', link: '/zh/privacy/' },
{ text: '服务条款', link: '/zh/terms/' },
]
}
},
},
plugins: ['@vuepress/back-to-top']
}
- 2、
docs/.vuepress/theme/index.js进行配置继承
// .vuepress/theme/index.js
module.exports = {
extend: '@vuepress/theme-default'
}
- 3、layouts 文件夹新增layout.vue文件,主要通过调整官方主题,然后嵌套自己的组件,就可以实现快速修改部分风格布局等效果
<template>
<div
class="theme-container"
:class="pageClasses"
@touchstart="onTouchStart"
@touchend="onTouchEnd"
>
<!-- Google tag (gtag.js) -->
<script async :src="`https://www.googletagmanager.com/gtag/js?id=${$themeConfig.gtagId}`"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '{{$themeConfig.gtagId}}');
</script>
<Navbar
v-if="shouldShowNavbar"
@toggle-sidebar="toggleSidebar"
/>
<div
class="sidebar-mask"
@click="toggleSidebar(false)"
/>
<Sidebar
:items="sidebarItems"
@toggle-sidebar="toggleSidebar"
>
<template #top>
<slot name="sidebar-top" />
</template>
<template #bottom>
<slot name="sidebar-bottom" />
</template>
</Sidebar>
<Home v-if="$page.frontmatter.home" />
<Page
v-else
:sidebar-items="sidebarItems"
>
<template #top>
<slot name="page-top" />
</template>
<template #bottom>
<slot name="page-bottom" />
</template>
</Page>
<Footer/>
</div>
</template>
<script>
import Home from '@theme/components/Home.vue'
import Navbar from '@theme/components/Navbar.vue'
import Page from '@theme/components/Page.vue'
import Sidebar from '@theme/components/Sidebar.vue'
import Footer from '@theme/components/Footer.vue'
import { resolveSidebarItems } from '../util'
export default {
name: 'Layout',
components: {
Home,
Page,
Sidebar,
Navbar,
Footer
},
data () {
return {
isSidebarOpen: false
}
},
computed: {
shouldShowNavbar () {
const { themeConfig } = this.$site
const { frontmatter } = this.$page
if (
frontmatter.navbar === false
|| themeConfig.navbar === false) {
return false
}
return (
this.$title
|| themeConfig.logo
|| themeConfig.repo
|| themeConfig.nav
|| this.$themeLocaleConfig.nav
)
},
shouldShowSidebar () {
const { frontmatter } = this.$page
return (
!frontmatter.home
&& frontmatter.sidebar !== false
&& this.sidebarItems.length
)
},
sidebarItems () {
return resolveSidebarItems(
this.$page,
this.$page.regularPath,
this.$site,
this.$localePath
)
},
pageClasses () {
const userPageClass = this.$page.frontmatter.pageClass
return [
{
'no-navbar': !this.shouldShowNavbar,
'sidebar-open': this.isSidebarOpen,
'no-sidebar': !this.shouldShowSidebar
},
userPageClass
]
}
},
mounted () {
this.$router.afterEach(() => {
this.isSidebarOpen = false
})
},
methods: {
toggleSidebar (to) {
this.isSidebarOpen = typeof to === 'boolean' ? to : !this.isSidebarOpen
this.$emit('toggle-sidebar', this.isSidebarOpen)
},
// side swipe
onTouchStart (e) {
this.touchStart = {
x: e.changedTouches[0].clientX,
y: e.changedTouches[0].clientY
}
},
onTouchEnd (e) {
const dx = e.changedTouches[0].clientX - this.touchStart.x
const dy = e.changedTouches[0].clientY - this.touchStart.y
if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 40) {
if (dx > 0 && this.touchStart.x <= 80) {
this.toggleSidebar(true)
} else {
this.toggleSidebar(false)
}
}
}
}
}
</script>
<style lang="stylus">
.go-to-top{
color:#000 !important;
}
.go-to-top:hover {
color: #FA6400 !important;
}
.header-anchor{
display: none !important;
}
.theme-default-content{
overflow-x: hidden !important;
}
</style>
- 4、通过引用自己的footer.vue和navbar.vue组件,就可以实现替换官方模板这两个组件
navbar.vue:
<template>
<header class="new-navbar" :style="`background:linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)),url(${$localeConfig.header.backgroundImage}) repeat center/ contain;`">
<div class="center">{{$localeConfig.title}}</div>
<div class="center">{{$localeConfig.description}}</div>
</header>
</template>
<style lang="stylus">
.new-navbar
.center
display: flex;
justify-content: center;
align-items: center;
padding:20px 0;
width: 100%;
min-height:200px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
text-align: center;
div
flex: 1;
color: #fff;
width: 100%;
div:first-child
font-size: 80px;
font-weight: 800;
div:last-child
font-size: 26px;
font-weight: 400;
</style>
footer.vue:
<template>
<div class="new_footer" :style="$themeConfig.background">
<div class="header">
<div class="center">{{$localeConfig.title}}</div>
<div>
<DropdownLink v-if="languages" :item="languages" />
</div>
</div>
<div class="end center">
<div>{{$localeConfig.description}}</div>
<div class="center">
<a v-for="(v,i) in links" :key="i" :href="v.link" style="color:#fff; margin: 0 20px;">{{v.text}}</a>
</div>
<div class="center" :style="`${$localeConfig.footer.police ?'':'justify-content: right;'}`">
{{$localeConfig.footer.copyright}}
</div>
<div class="center" v-if="$localeConfig.footer.police">
{{$localeConfig.footer.police}}
</div>
</div>
</div>
</template>
<script>
import DropdownLink from '@theme/components/DropdownLink.vue'
export default {
name: 'Footer',
components: {
DropdownLink
},
computed: {
links () {
return this.$themeLocaleConfig?.nav ||[]
},
languages () {
const { locales } = this.$site
if (locales && Object.keys(locales).length > 1) {
const currentLink = this.$page.path
const routes = this.$router.options.routes
const themeLocales = this.$site.themeConfig.locales || {}
const languageDropdown = {
text: this.$themeLocaleConfig.selectText || 'Languages',
ariaLabel: this.$themeLocaleConfig.ariaLabel || 'Select language',
items: Object.keys(locales).map(path => {
const locale = locales[path]
const text = themeLocales[path] && themeLocales[path].label || locale.lang
let link
// Stay on the current page
if (locale.lang === this.$lang) {
link = currentLink
} else {
// Try to stay on the same page
link = currentLink.replace(this.$localeConfig.path, path)
// fallback to homepage
if (!routes.some(route => route.path === link)) {
link = path
}
}
return { text, link }
})
}
return languageDropdown
}
return null
},
},
}
</script>
<style lang="stylus">
.new_footer
a:hover {
color: #FA6400 !important;
}
.center
display: flex;
justify-content: center;
align-items: center;
min-height 200px;
background #2F2F2F;
display: flex;
flex-direction: column;
padding:30px 150px;
.header
height 40%;
display: flex;
color:#fff;
div:first-child
flex:1;
font-size: 36px;
font-weight: 800;
justify-content: flex-start;
div:last-child
flex:1;
text-align: right;
position: relative;
display: flex;
justify-content: flex-end;
.dropdown-wrapper .nav-dropdown .dropdown-item
font-size: 20px;
.dropdown-wrapper .dropdown-title, .dropdown-wrapper .mobile-dropdown-title
color: #fff;
.dropdown-wrapper .nav-dropdown .dropdown-item a
color:#000;
.dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active
color: #FA6400 !important;
.dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active::after
border-left: 5px solid #FA6400;
.end
margin-top: 20px;
min-height 60%;
color: #fff;
align-items: baseline;
flex-wrap: wrap;
div
margin: 10px 0;
div:nth-child(1)
width: 300px
div:nth-child(2)
flex: 1;
div:nth-child(3)
min-width: 300px
div:nth-child(4)
min-width: 300px
@media (max-width: $MQMobile)
.new_footer
a:hover {
color: #FA6400 !important;
}
.center
display: flex;
justify-content: center;
align-items: center;
min-height 200px;
background #2F2F2F;
display: flex;
flex-direction: column;
padding: 20px 10px;
.header
display: flex;
flex-direction: column;
height: auto;
div:first-child
flex:1;
font-size: 36px;
font-weight: 800;
justify-content: center;
div:last-child
flex:1;
text-align: right;
position: relative;
display: flex;
justify-content: center;
.dropdown-wrapper .nav-dropdown .dropdown-item
font-size: 20px;
.dropdown-wrapper .dropdown-title, .dropdown-wrapper .mobile-dropdown-title
color: #fff;
.dropdown-wrapper .nav-dropdown .dropdown-item a
color:#000;
.dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active
color: #FA6400 !important;
.dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active::after
border-left: 5px solid #FA6400;
.dropdown-wrapper .nav-dropdown .dropdown-item::marker
display: none;
.end
display: flex;
flex-direction: column;
height: auto;
color: #fff;
div
margin: 10px 0;
text-align: center;
div:nth-child(1)
width: 300px
div:nth-child(2)
flex: 1;
div:nth-child(3)
min-width: 300px
div:nth-child(4)
min-width: 300px
</style>
最终效果如图所示
官网