mjz update

This commit is contained in:
mjz
2023-05-28 18:15:33 +08:00
parent 9b75e60571
commit ed6e2d81fb
243 changed files with 11730 additions and 6 deletions
+166
View File
@@ -0,0 +1,166 @@
<template>
<form
id="search-form"
class="algolia-search-wrapper search-box"
role="search"
>
<input
id="algolia-search-input"
class="search-query"
:placeholder="placeholder"
/>
</form>
</template>
<script>
export default {
props: ['options'],
data () {
return {
placeholder: undefined
}
},
mounted () {
this.initialize(this.options, this.$lang)
this.placeholder = this.$site.themeConfig.searchPlaceholder || ''
},
methods: {
initialize (userOptions, lang) {
Promise.all([
import(/* webpackChunkName: "docsearch" */ 'docsearch.js/dist/cdn/docsearch.min.js'),
import(/* webpackChunkName: "docsearch" */ 'docsearch.js/dist/cdn/docsearch.min.css')
]).then(([docsearch]) => {
docsearch = docsearch.default
const { algoliaOptions = {} } = userOptions
docsearch(Object.assign(
{},
userOptions,
{
inputSelector: '#algolia-search-input',
// #697 Make docsearch work well at i18n mode.
algoliaOptions: Object.assign({
'facetFilters': [`lang:${lang}`].concat(algoliaOptions.facetFilters || [])
}, algoliaOptions),
handleSelected: (input, event, suggestion) => {
const { pathname, hash } = new URL(suggestion.url)
const routepath = pathname.replace(this.$site.base, '/');
const _hash = decodeURIComponent(hash)
this.$router.push(`${routepath}${_hash}`)
}
}
))
})
},
update (options, lang) {
this.$el.innerHTML = '<input id="algolia-search-input" class="search-query">'
this.initialize(options, lang)
}
},
watch: {
$lang (newValue) {
this.update(this.options, newValue)
},
options (newValue) {
this.update(newValue, this.$lang)
}
}
}
</script>
<style lang="stylus">
.algolia-search-wrapper
& > span
vertical-align middle
.algolia-autocomplete
line-height normal
.ds-dropdown-menu
background-color #fff
border 1px solid #999
border-radius 4px
font-size 16px
margin 6px 0 0
padding 4px
text-align left
&:before
border-color #999
[class*=ds-dataset-]
border none
padding 0
.ds-suggestions
margin-top 0
.ds-suggestion
border-bottom 1px solid var(--borderColor)
.algolia-docsearch-suggestion--highlight
color #2c815b
.algolia-docsearch-suggestion
border-color var(--borderColor)
padding 0
.algolia-docsearch-suggestion--category-header
padding 5px 10px
margin-top 0
background $accentColor
color #fff
font-weight 600
.algolia-docsearch-suggestion--highlight
background rgba(255, 255, 255, 0.6)
.algolia-docsearch-suggestion--wrapper
padding 0
.algolia-docsearch-suggestion--title
font-weight 600
margin-bottom 0
color var(--textColor)
.algolia-docsearch-suggestion--subcategory-column
vertical-align top
padding 5px 7px 5px 5px
border-color var(--borderColor)
background #f1f3f5
&:after
display none
.algolia-docsearch-suggestion--subcategory-column-text
color #555
.algolia-docsearch-footer
border-color var(--borderColor)
.ds-cursor .algolia-docsearch-suggestion--content
background-color #e7edf3 !important
color var(--textColor)
@media (min-width $MQMobile)
.algolia-search-wrapper
.algolia-autocomplete
.algolia-docsearch-suggestion
.algolia-docsearch-suggestion--subcategory-column
float none
width 150px
min-width 150px
display table-cell
.algolia-docsearch-suggestion--content
float none
display table-cell
width 100%
vertical-align top
.ds-dropdown-menu
min-width 515px !important
@media (max-width $MQMobile)
.algolia-search-wrapper
.ds-dropdown-menu
min-width calc(100vw - 4rem) !important
max-width calc(100vw - 4rem) !important
.algolia-docsearch-suggestion--wrapper
padding 5px 7px 5px 5px !important
.algolia-docsearch-suggestion--subcategory-column
padding 0 !important
background white !important
.algolia-docsearch-suggestion--subcategory-column-text:after
content ' > '
font-size 10px
line-height 14.4px
display inline-block
width 5px
margin -3px 3px 0
vertical-align middle
</style>
+207
View File
@@ -0,0 +1,207 @@
<template>
<div class="custom-page archives-page">
<div class="theme-vdoing-wrapper">
<h1>
<img
:src="currentBadge"
v-if="$themeConfig.titleBadge === false ? false : true"
/>
{{ $page.title }}
</h1>
<div class="count">
总共 <i>{{ $sortPostsByDate.length }}</i> 篇文章
</div>
<ul>
<template v-for="(item, index) in postsList">
<li
class="year"
v-if="(year = getYear(index)) !== getYear(index - 1)"
:key="index + $sortPostsByDate.length"
>
<h2>
{{ year }}
<span>
<i>{{ countByYear[year] }}</i>
</span>
</h2>
</li>
<li :key="index">
<router-link :to="item.path">
<span class="date">{{ getDate(item) }}</span>
{{ item.title }}
<span class="title-tag" v-if="item.frontmatter.titleTag">
{{ item.frontmatter.titleTag }}
</span>
</router-link>
</li>
</template>
</ul>
</div>
</div>
</template>
<script>
import debounce from 'lodash.debounce'
import { type } from '../util'
import TitleBadgeMixin from '../mixins/titleBadge'
export default {
mixins: [TitleBadgeMixin],
data() {
return {
postsList: [],
countByYear: {}, // 根据年份统计的文章数
perPage: 80, // 每页长
currentPage: 1// 当前页
}
},
created() {
this.getPageData()
// 根据年份计算出文章数
const { $sortPostsByDate, countByYear } = this
for (let i = 0; i < $sortPostsByDate.length; i++) {
const { frontmatter: { date } } = $sortPostsByDate[i];
if (date && type(date) === 'string') {
const year = date.slice(0, 4)
if (!countByYear[year]) {
countByYear[year] = 0
}
countByYear[year] = countByYear[year] + 1
}
}
this.countByYear = countByYear
},
mounted() {
window.addEventListener('scroll', debounce(() => {
if (this.postsList.length < this.$sortPostsByDate.length) {
const docEl = document.documentElement
const docBody = document.body
const scrollTop = docEl.scrollTop || docBody.scrollTop;
const clientHeight = docEl.clientHeight || docBody.clientHeight;
const scrollHeight = docEl.scrollHeight || docBody.scrollHeight;
if (scrollHeight > clientHeight && scrollTop + clientHeight >= scrollHeight - 250) {
this.loadmore()
}
}
}, 200))
},
methods: {
getPageData() {
const currentPage = this.currentPage
const perPage = this.perPage
this.postsList = this.postsList.concat(this.$sortPostsByDate.slice((currentPage - 1) * perPage, currentPage * perPage))
},
loadmore() {
this.currentPage = this.currentPage + 1
this.getPageData()
},
getYear(index) {
const item = this.postsList[index]
if (!item) {
return
}
const { frontmatter: { date } } = item
if (date && type(date) === 'string') {
return date.slice(0, 4)
}
},
getDate(item) {
const { frontmatter: { date } } = item
if (date && type(date) === 'string') {
return date.slice(5, 10)
}
}
}
}
</script>
<style lang='stylus'>
@require '../styles/wrapper.styl'
.theme-style-line
.archives-page .theme-vdoing-wrapper
box-shadow 0 0
// border 1px solid var(--borderColor)
// border-radius 5px
.archives-page
.theme-vdoing-wrapper
@extend $vdoing-wrapper
position relative
@media (min-width $contentWidth + 80)
margin-top 1.5rem !important
.count
text-align right
margin-top -2.5rem
font-size 0.85rem
opacity 0.8
ul, li
margin 0
padding 0
ul
margin-top 2rem
li
list-style none
&.year
position sticky
top $navbarHeight
background var(--mainBg)
z-index 1
&.year:not(:first-child)
margin-top 3.5rem
h2
margin-bottom 0.8rem
font-weight 400
padding 0.5rem 0
span
font-size 0.85rem
font-weight 300
float right
margin-top 1rem
a
display block
color var(--textColor)
transition padding 0.3s
padding 0.5rem 2rem
line-height 1.2rem
&:hover
padding-left 2.5rem
color $accentColor
background #f9f9f9
@media (max-width $contentWidth + 80)
padding 0.5rem 1rem
font-weight normal
&:hover
padding-left 1.5rem
span.date
opacity 0.6
font-size 0.85rem
font-weight 400
margin-right 0.3rem
.title-tag
// height 1.1rem
// line-height 1.1rem
border 1px solid $activeColor
color $activeColor
font-size 0.8rem
padding 0 0.35rem
border-radius 0.2rem
margin-left 0rem
transform translate(0, -0.05rem)
display inline-block
.loadmore
text-align center
margin-top 1rem
opacity 0.5
.theme-mode-dark .archives-page .theme-vdoing-wrapper li a:hover, .theme-mode-read .archives-page .theme-vdoing-wrapper li a:hover
background var(--customBlockBg)
.hide-navbar
.archives-page
.theme-vdoing-wrapper
li.year
top 0
</style>
+203
View File
@@ -0,0 +1,203 @@
<template>
<div class="articleInfo-wrap">
<div class="articleInfo">
<!-- 面包屑 -->
<ul class="breadcrumbs" v-if="classify1 && classify1 !== '_posts'">
<li>
<router-link to="/" class="iconfont icon-home" title="首页" />
</li>
<li v-for="item in classifyList" :key="item">
<!-- 跳目录页 -->
<router-link v-if="cataloguePermalink" :to="getLink(item)">{{
item
}}</router-link>
<!-- 跳分类页 -->
<router-link
v-else-if="$themeConfig.category !== false"
:to="`/categories/?category=${encodeURIComponent(item)}`"
title="分类"
>{{ item }}</router-link
>
<!-- 没有跳转 -->
<span v-else>{{ item }}</span>
</li>
</ul>
<!-- 作者&日期 -->
<div class="info">
<div class="author iconfont icon-touxiang" title="作者" v-if="author">
<a
:href="author.href || author.link"
v-if="
author.href || (author.link && typeof author.link === 'string')
"
target="_blank"
class="beLink"
title="作者"
>{{ author.name }}</a
>
<a v-else href="javascript:;">{{ author.name || author }}</a>
</div>
<div class="date iconfont icon-riqi" title="创建时间" v-if="date">
<a href="javascript:;">{{ date }}</a>
</div>
<div
class="date iconfont icon-wenjian"
title="分类"
v-if="
$themeConfig.category !== false &&
!(classify1 && classify1 !== '_posts') &&
categories
"
>
<router-link
:to="`/categories/?category=${encodeURIComponent(item)}`"
v-for="(item, index) in categories"
:key="index"
>{{ item + ' ' }}</router-link
>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
date: '',
classify1: '',
classifyList: [],
cataloguePermalink: '',
author: null,
categories: []
}
},
created() {
this.getPageInfo()
},
watch: {
'$route.path'() {
this.classifyList = []
this.getPageInfo()
}
},
methods: {
getPageInfo() {
const pageInfo = this.$page
const { relativePath } = pageInfo
const { sidebar } = this.$themeConfig
// 分类采用解析文件夹地址名称的方式 (即使关闭分类功能也可以正确跳转目录页)
const relativePathArr = relativePath.split('/')
// const classifyArr = relativePathArr[0].split('.')
relativePathArr.forEach((item, index) => {
const nameArr = item.split('.')
if (index !== relativePathArr.length - 1) {
if (nameArr === 1) {
this.classifyList.push(nameArr[0])
} else {
const firstDotIndex = item.indexOf('.');
this.classifyList.push(item.substring(firstDotIndex + 1) || '')
}
}
})
this.classify1 = this.classifyList[0]
const cataloguePermalink = sidebar && sidebar.catalogue ? sidebar.catalogue[this.classify1] : ''// 目录页永久链接
const author = this.$frontmatter.author || this.$themeConfig.author // 作者
let date = (pageInfo.frontmatter.date || '').split(' ')[0] // 文章创建时间
// 获取页面frontmatter的分类(碎片化文章使用)
const { categories } = this.$frontmatter
this.date = date
this.cataloguePermalink = cataloguePermalink
this.author = author
this.categories = categories
},
getLink(item) {
const { cataloguePermalink } = this
if (item === cataloguePermalink) {
return cataloguePermalink
}
return `${cataloguePermalink}${cataloguePermalink.charAt(cataloguePermalink.length - 1) === '/'
? ''
: '/'
}#${item}`
}
}
}
</script>
<style lang='stylus' scoped>
@require '../styles/wrapper.styl'
.theme-style-line
.articleInfo-wrap
.articleInfo
padding-top 0.5rem
.articleInfo-wrap
@extend $wrapper
position relative
z-index 1
color #888
.articleInfo
overflow hidden
font-size 0.92rem
.breadcrumbs
margin 0
padding 0
overflow hidden
display inline-block
line-height 2rem
@media (max-width 960px)
width 100%
li
list-style-type none
float left
padding-right 5px
&:after
content '/'
margin-left 5px
color #999
&:last-child
&:after
content ''
a
color #888
&:before
font-size 0.92rem
&:hover
color $accentColor
.icon-home
text-decoration none
.info
float right
line-height 32px
@media (max-width 960px)
float left
div
float left
margin-left 20px
font-size 0.8rem
@media (max-width 960px)
margin 0 20px 0 0
&:before
margin-right 3px
a
color #888
&:hover
text-decoration none
a.beLink
&:hover
color $accentColor
text-decoration underline
</style>
+76
View File
@@ -0,0 +1,76 @@
<template>
<aside class="blogger-wrapper card-box">
<div class="avatar">
<img :src="blogger.avatar" alt="头像" title="我好看吗" />
</div>
<div class="icons" v-if="social && social.icons && social.icons.length">
<a
v-for="(item, index) in social.icons"
:key="index"
:href="item.link"
:title="item.title"
:class="['iconfont', item.iconClass]"
:style="{ width: 100 / social.icons.length + '%' }"
target="_blank"
/>
</div>
<div class="blogger">
<span class="name">{{ blogger.name }}</span>
<span class="slogan">{{ blogger.slogan }}</span>
</div>
</aside>
</template>
<script>
export default {
computed: {
blogger () {
return this.$themeConfig.blogger
},
social () {
return this.$themeConfig.social
}
}
}
</script>
<style lang='stylus'>
.blogger-wrapper
height auto
display inline-table
padding-top 0 !important
overflow hidden
.avatar
width 100%
// height 235px
overflow hidden
@media (max-width 900px)
// width 205px
// height 205px
img
width 100%
height 100%
.icons
// border 1px solid var(--borderColor)
border-top none
height 35px
line-height 35px
a
font-size 20px
width 33%
color var(--textColor)
display block
float left
text-align center
opacity 0.8
&:hover
color $accentColor
.blogger
padding 0.3rem 0.95rem 0 0.95rem
.name
font-size 1.3rem
display block
margin-bottom 6px
.slogan
color var(--textColor)
</style>
+59
View File
@@ -0,0 +1,59 @@
<template>
<div
class="body-bg"
:style="`background: url(${bgImg}) center center / cover no-repeat;opacity:${opacity}`"
></div>
</template>
<script>
import { type } from '../util'
export default {
data() {
return {
bgImg: '',
opacity: 0.5
}
},
mounted() {
let { bodyBgImg, bodyBgImgOpacity, bodyBgImgInterval = 15 } = this.$themeConfig
if (type(bodyBgImg) === 'string') {
this.bgImg = bodyBgImg
} else if (type(bodyBgImg) === 'array') {
let count = 0
let timer = null
this.bgImg = bodyBgImg[count]
clearInterval(timer)
timer = setInterval(() => {
if (++count >= bodyBgImg.length) {
count = 0
}
this.bgImg = bodyBgImg[count]
// 预加载下一张图片
if (bodyBgImg[count + 1]) {
const img = new Image()
img.src = bodyBgImg[count + 1]
}
}, bodyBgImgInterval * 1000);
}
if (bodyBgImgOpacity !== undefined) {
this.opacity = bodyBgImgOpacity
}
}
}
</script>
<style lang='stylus'>
.body-bg
position fixed
left 0
top 0
z-index -999999
height 100vh
width 100vw
transition background 0.5s
</style>
+263
View File
@@ -0,0 +1,263 @@
<template>
<div class="buttons">
<transition name="fade">
<div
title="返回顶部"
class="button blur go-to-top iconfont icon-fanhuidingbu"
v-show="showToTop"
@click="scrollToTop"
/>
</transition>
<div
title="去评论"
class="button blur go-to-comment iconfont icon-pinglun"
v-show="showCommentBut"
@click="scrollToComment"
/>
<div
title="主题模式"
class="button blur theme-mode-but iconfont icon-zhuti"
@mouseenter="showModeBox = true"
@mouseleave="showModeBox = false"
@click="showModeBox = true"
>
<transition name="mode">
<ul
class="select-box"
ref="modeBox"
v-show="showModeBox"
@click.stop
@touchstart.stop
>
<li
v-for="item in modeList"
:key="item.KEY"
class="iconfont"
:class="[item.icon, { active: item.KEY === currentMode }]"
@click="toggleMode(item.KEY)"
>
{{ item.name }}
</li>
</ul>
</transition>
</div>
</div>
</template>
<script>
import debounce from 'lodash.debounce'
import storage from 'good-storage' // 本地存储
const MOBILE_DESKTOP_BREAKPOINT = 719 // refer to config.styl
export default {
data () {
return {
threshold: 100,
scrollTop: null,
showCommentBut: false,
commentTop: null,
currentMode: '',
showModeBox: false,
modeList: [
{
name: '跟随系统',
icon: 'icon-zidong',
KEY: 'auto'
},
{
name: '浅色模式',
icon: 'icon-rijianmoshi',
KEY: 'light'
},
{
name: '深色模式',
icon: 'icon-yejianmoshi',
KEY: 'dark'
},
{
name: '阅读模式',
icon: 'icon-yuedu',
KEY: 'read'
}
],
_scrollTimer: null,
_textareaEl: null,
_recordScrollTop: null,
COMMENT_SELECTOR_1: '#vuepress-plugin-comment', // 评论区元素的选择器1
COMMENT_SELECTOR_2: '#valine-vuepress-comment', // 评论区元素的选择器2
COMMENT_SELECTOR_3: '.vssue' // 评论区元素的选择器3
}
},
mounted () {
this.currentMode = storage.get('mode') || this.$themeConfig.defaultMode ||'auto'
this.scrollTop = this.getScrollTop()
window.addEventListener('scroll', debounce(() => {
this.scrollTop = this.getScrollTop()
}, 100))
window.addEventListener('load', () => {
this.getCommentTop()
})
// 小屏时选择主题模式后关闭选择框
if (document.documentElement.clientWidth < MOBILE_DESKTOP_BREAKPOINT) {
const modeBox = this.$refs.modeBox
modeBox.onclick = () => {
this.showModeBox = false
}
window.addEventListener('scroll', debounce(() => {
if (this.showModeBox) {
this.showModeBox = false
}
}, 100))
}
// 移动端对类似:hover效果的处理
const buttons = document.querySelectorAll('.buttons .button')
for (let i = 0; i < buttons.length; i++) {
const button = buttons[i]
button.addEventListener('touchstart', function () {
button.classList.add('hover')
})
button.addEventListener('touchend', function () {
setTimeout(() => {
button.classList.remove('hover')
}, 150)
})
}
},
computed: {
showToTop () {
return this.scrollTop > this.threshold
}
},
methods: {
toggleMode (key) {
this.currentMode = key
this.$emit('toggle-theme-mode', key)
},
getScrollTop () {
return window.pageYOffset
|| document.documentElement.scrollTop
|| document.body.scrollTop || 0
},
scrollToTop () {
window.scrollTo({ top: 0, behavior: 'smooth' })
this.scrollTop = 0
},
getCommentTop () {
setTimeout(() => {
let commentEl = document.querySelector(this.COMMENT_SELECTOR_1) || document.querySelector(this.COMMENT_SELECTOR_2) || document.querySelector(this.COMMENT_SELECTOR_3)
if (commentEl) {
this.showCommentBut = this.$frontmatter.comment !== false && this.$frontmatter.home !== true
this.commentTop = commentEl.offsetTop - 58
}
}, 500)
},
scrollToComment () {
window.scrollTo({ top: this.commentTop, behavior: 'smooth' })
this._textareaEl = document.querySelector(this.COMMENT_SELECTOR_1 + ' textarea') || document.querySelector(this.COMMENT_SELECTOR_2 + ' input') || document.querySelector(this.COMMENT_SELECTOR_3 + ' textarea')
if (this._textareaEl && this.getScrollTop() !== this._recordScrollTop) {
document.addEventListener("scroll", this._handleListener)
} else if (this._textareaEl && this.getScrollTop() === this._recordScrollTop) {
this._handleFocus()
}
},
_handleListener () {
clearTimeout(this._scrollTimer)
this._scrollTimer = setTimeout(() => {
document.removeEventListener('scroll', this._handleListener)
this._recordScrollTop = this.getScrollTop()
this._handleFocus()
}, 30)
},
_handleFocus () {
this._textareaEl.focus()
this._textareaEl.classList.add('yellowBorder')
setTimeout(() => {
this._textareaEl.classList.remove('yellowBorder')
}, 500)
}
},
watch: {
'$route.path' () {
this.showCommentBut = false
this.getCommentTop()
}
}
}
</script>
<style lang='stylus'>
.yellowBorder
// border: #FFE089 1px solid!important
border-radius 5px
box-shadow 0 0 15px #FFE089 !important
.buttons
position fixed
right 2rem
bottom 2.5rem
z-index 11
@media (max-width $MQNarrow)
right 1rem
bottom 1.5rem
.button
width 2.2rem
height 2.2rem
line-height 2.2rem
border-radius 50%
box-shadow 0 2px 6px rgba(0, 0, 0, 0.15)
margin-top 0.9rem
text-align center
cursor pointer
transition all 0.5s
background var(--blurBg)
&.hover
background $accentColor
box-shadow 0 0 15px $accentColor
&:before
color #fff
@media (any-hover hover)
&:hover
background $accentColor
box-shadow 0 0 15px $accentColor
&:before
color #fff
.select-box
margin 0
padding 0.8rem 0
position absolute
bottom 0rem
right 1.5rem
background var(--mainBg)
border 1px solid var(--borderColor)
width 120px
border-radius 6px
box-shadow 0 0 15px rgba(255, 255, 255, 0.2)
li
list-style none
line-height 2rem
font-size 0.95rem
&:hover
color $accentColor
&.active
background-color rgba(150, 150, 150, 0.2)
color $accentColor
.mode-enter-active, .mode-leave-active
transition all 0.3s
.mode-enter, .mode-leave-to
opacity 0
transform scale(0.8)
.fade-enter-active, .fade-leave-active
transition opacity 0.2s
.fade-enter, .fade-leave-to
opacity 0
</style>
+232
View File
@@ -0,0 +1,232 @@
<template>
<div class="theme-vdoing-content">
<div class="column-wrapper">
<img v-if="pageData.imgUrl" :src="$withBase(pageData.imgUrl)" />
<dl class="column-info">
<dt class="title">{{ pageData.title }}</dt>
<dd class="description" v-html="pageData.description"></dd>
</dl>
</div>
<div class="catalogue-wrapper" v-if="isStructuring">
<div class="catalogue-title">目录</div>
<div class="catalogue-content">
<template v-for="(item, index) in getCatalogueList()">
<dl v-if="type(item) === 'array'" :key="index" class="inline">
<dt>
<router-link :to="item[2]"
>{{ `${index + 1}. ${item[1]}` }}
<span class="title-tag" v-if="item[3]">
{{ item[3] }}
</span>
</router-link>
</dt>
</dl>
<dl v-else-if="type(item) === 'object'" :key="index">
<!-- 一级目录 -->
<dt :id="(anchorText = item.title)">
<a :href="`#${anchorText}`" class="header-anchor">#</a>
{{ `${index + 1}. ${item.title}` }}
</dt>
<dd>
<!-- 二级目录 -->
<template v-for="(c, i) in item.children">
<template v-if="type(c) === 'array'">
<router-link :to="c[2]" :key="i"
>{{ `${index + 1}-${i + 1}. ${c[1]}` }}
<span class="title-tag" v-if="c[3]">
{{ c[3] }}
</span>
</router-link>
</template>
<!-- 三级目录 -->
<div
v-else-if="type(c) === 'object'"
:key="i"
class="sub-cat-wrap"
>
<div :id="(anchorText = c.title)" class="sub-title">
<a :href="`#${anchorText}`" class="header-anchor">#</a>
{{ `${index + 1}-${i + 1}. ${c.title}` }}
</div>
<router-link
v-for="(cc, ii) in c.children"
:to="cc[2]"
:key="`${index + 1}-${i + 1}-${ii + 1}`"
>
{{ `${index + 1}-${i + 1}-${ii + 1}. ${cc[1]}` }}
<span class="title-tag" v-if="cc[3]">
{{ cc[3] }}
</span>
</router-link>
</div>
</template>
</dd>
</dl>
</template>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
pageData: null,
isStructuring: true,
appointDir: {}
}
},
created() {
this.getPageData()
const sidebar = this.$themeConfig.sidebar
if (!sidebar || sidebar === 'auto') {
this.isStructuring = false
console.error("目录页数据依赖于结构化的侧边栏数据,请在主题设置中将侧边栏字段设置为'structuring',否则无法获取目录数据。")
}
},
methods: {
getPageData() {
const pageComponent = this.$frontmatter.pageComponent
if (pageComponent && pageComponent.data) {
this.pageData = {
...pageComponent.data,
title: this.$frontmatter.title
}
} else {
console.error('请在front matter中设置pageComponent和pageComponent.data数据')
}
},
getCatalogueList() {
const { sidebar } = this.$site.themeConfig
const { data } = this.$frontmatter.pageComponent
const key = data.path || data.key
let keyArray = key.split('/');
let catalogueList = (sidebar[`/${keyArray[0]}/`]);
if (keyArray.length > 1) {
// 删除第一个元素,并修改原数组
keyArray.shift();
catalogueList = this.appointDirDeal(0, keyArray, catalogueList);
}
if (!catalogueList) {
console.error('未获取到目录数据,请查看front matter中设置的path是否正确。')
}
return catalogueList
},
type(o) { // 数据类型检查
return Object.prototype.toString.call(o).match(/\[object (.*?)\]/)[1].toLowerCase()
},
/**
* 指定目录页配置处理
* @param index 目录数组的下标
* @param dirKeyArray 目录名称数组
* @param catalogueList 目录对象列表
* @returns {*}
*/
appointDirDeal(index, dirKeyArray, catalogueList) {
let dirKey = dirKeyArray[index];
if (dirKey !== undefined && dirKey.indexOf(".") !== -1) {
dirKey = dirKey.substring(dirKey.indexOf('.') + 1);
}
for (let i = 0; i < catalogueList.length; i++) {
if (catalogueList[i].title === dirKey) {
this.appointDir = catalogueList[i];
if (index < dirKeyArray.length - 1) {
this.appointDirDeal(index + 1, dirKeyArray, catalogueList[i].children);
}
}
}
return this.appointDir.children;
},
},
watch: {
'$route.path'() {
this.getPageData()
}
}
}
</script>
<style scoped lang="stylus" rel="stylesheet/stylus">
.theme-vdoing-content
margin-bottom $navbarHeight
.title-tag
// height 1.1rem
// line-height 1.1rem
border 1px solid $activeColor
color $activeColor
font-size 0.8rem
padding 0 0.35rem
border-radius 0.2rem
margin-left 0rem
transform translate(0, -0.05rem)
display inline-block
dl, dd
margin 0
.column-wrapper
margin-top 1rem
display flex
padding-bottom 2rem
border-bottom 1px solid var(--borderColor)
img
width 80px
height 80px
border-radius 2px
margin-right 1rem
.column-info
.title
font-size 1.6rem
.description
color var(--textColor)
opacity 0.8
margin 0.5rem 0
.catalogue-wrapper
.catalogue-title
font-size 1.45rem
margin 2rem 0
.catalogue-content
dl
margin-bottom 1.8rem
&.inline
display inline-block
width 50%
margin-bottom 1rem
@media (max-width $MQMobileNarrow)
width 100%
a
width 100%
&:not(.inline)
dt
margin-top -($navbarHeight)
padding-top $navbarHeight
dt
font-size 1.1rem
&:hover .header-anchor
opacity 1
dd
margin-top 0.7rem
margin-left 1rem
a:not(.header-anchor)
margin-bottom 0.5rem
display inline-block
width 50%
&:hover
color $activeColor
text-decoration none
@media (max-width 720px)
width 100%
.sub-cat-wrap
margin 5px 0 8px 0
font-size 0.95rem
&> a
padding-left 1rem
box-sizing border-box
.sub-title
margin-top -($navbarHeight)
padding-top $navbarHeight
margin-bottom 6px
font-size 1rem
&:hover
.header-anchor
opacity 1
</style>
+117
View File
@@ -0,0 +1,117 @@
<template>
<div class="categories-wrapper card-box">
<router-link
to="/categories/"
class="title iconfont icon-wenjianjia"
title="全部分类"
>{{ length === 'all' ? '全部分类' : '文章分类' }}</router-link
>
<div class="categories">
<router-link
:to="`/categories/?category=${encodeURIComponent(item.key)}`"
v-for="(item, index) in categories"
:key="index"
:class="{ active: item.key === category }"
>
{{ item.key }}
<span>{{ item.length }}</span>
</router-link>
<router-link
to="/categories/"
v-if="length !== 'all' && length < categoriesData.length"
class="more"
>更多 ...</router-link
>
</div>
</div>
</template>
<script>
export default {
props: {
category: {
type: String,
default: ''
},
categoriesData: {
type: Array,
default: []
},
length: {
type: [String, Number],
default: 'all'
}
},
computed: {
categories() {
if (this.length === 'all') {
return this.categoriesData
} else {
return this.categoriesData.slice(0, this.length)
}
}
}
}
</script>
<style lang='stylus'>
.categories-wrapper
.title
color var(--textColor)
opacity 0.9
font-size 1.2rem
padding 0 0.95rem
&::before
margin-right 0.3rem
.categories
margin-top 0.6rem
a
display block
padding 8px 2.4rem 7px 0.95rem
color var(--textColor)
opacity 0.8
font-size 0.95rem
line-height 0.95rem
position relative
transition all 0.2s
border-left 2px solid transparent
margin-top -1px
overflow hidden
white-space nowrap
text-overflow ellipsis
@media (max-width $MQMobile)
font-weight 400
&.more
// color $accentColor
&:not(.active):hover
color $accentColor
background #f8f8f8
border-color $accentColor
span
opacity 0.8
span
// float right
background-color var(--textColor)
color var(--mainBg)
border-radius 8px
padding 0 0.13rem
min-width 1rem
height 1rem
line-height 1rem
font-size 12px
text-align center
opacity 0.6
transition opacity 0.3s
position absolute
right 0.95rem
top 8px
&.active
background $accentColor
color var(--mainBg)
padding-left 0.8rem
border-radius 1px
border-color transparent
.theme-mode-dark .categories-wrapper .categories a:not(.active):hover, .theme-mode-read .categories-wrapper .categories a:not(.active):hover
background var(--customBlockBg)
</style>
+144
View File
@@ -0,0 +1,144 @@
<template>
<div class="custom-page categories-page">
<MainLayout>
<template #mainLeft>
<CategoriesBar
v-if="$categoriesAndTags.categories.length"
:categoriesData="$categoriesAndTags.categories"
:category="category"
/>
<PostList
:currentPage="currentPage"
:perPage="perPage"
:category="category"
/>
<Pagination
:total="total"
:perPage="perPage"
:currentPage="currentPage"
@getCurrentPage="handlePagination"
v-show="Math.ceil(total / perPage) > 1"
/>
</template>
<template #mainRight>
<CategoriesBar
v-if="$categoriesAndTags.categories.length"
:categoriesData="$categoriesAndTags.categories"
:category="category"
/>
</template>
</MainLayout>
</div>
</template>
<script>
import MainLayout from '@theme/components/MainLayout'
import PostList from '@theme/components/PostList'
import Pagination from '@theme/components/Pagination'
import CategoriesBar from '@theme/components/CategoriesBar'
export default {
data() {
return {
category: '',
total: 0, // 总长
perPage: 10, // 每页长
currentPage: 1// 当前页
}
},
components: { MainLayout, PostList, Pagination, CategoriesBar },
mounted() {
const queryCategory = this.$route.query.category
if (queryCategory) {
this.category = queryCategory
this.total = this.$groupPosts.categories[queryCategory].length
} else {
this.total = this.$sortPosts.length
}
if (this.$route.query.p) {
this.currentPage = Number(this.$route.query.p)
}
// 滚动条定位到当前分类(增强用户体验)
const cateEl = document.querySelector('.categories')
if (cateEl) {
setTimeout(() => {
const activeEl = cateEl.querySelector('.active')
const topVal = activeEl ? activeEl.offsetTop : 0
cateEl.scrollTo({ top: topVal, behavior: 'smooth' })
}, 300)
}
},
methods: {
handlePagination(i) { // 分页
this.currentPage = i
}
},
watch: {
'$route.query.category'(category) {
this.category = category ? decodeURIComponent(category) : ''
if (this.category) {
this.total = this.$groupPosts.categories[this.category].length
} else {
this.total = this.$sortPosts.length
}
this.currentPage = 1
}
}
}
</script>
<style lang='stylus'>
.categories-page
.categories-wrapper
position sticky
top: ($navbarHeight + 0.9rem)
max-height calc(100vh - 10rem)
min-height 4.2rem
@media (max-width $MQMobile)
display none
.categories
// padding-right 0.3rem
max-height calc(100vh - 14rem)
min-height 2.2rem
overflow-y auto
transition all 0.2s
position relative
a
padding-right 1.8rem
span
right 0.4rem
&::-webkit-scrollbar-track-piece
background-color rgba(0, 0, 0, 0.05)
&::-webkit-scrollbar-thumb:vertical
background-color rgba(0, 0, 0, 0.15)
&:hover
&::-webkit-scrollbar-track-piece
background-color rgba(0, 0, 0, 0.1)
&::-webkit-scrollbar-thumb:vertical
background-color rgba(0, 0, 0, 0.25)
.categories-page
.main-left
.categories-wrapper
position relative
top 0
padding 0.9rem 1.5rem
margin-bottom 0.9rem
max-height 15rem
border-radius 0
display none
@media (max-width $MQMobile)
display block
.categories
max-height 12.3rem
.theme-style-line
.categories-page
.main-left
.categories-wrapper
@media (max-width $MQMobile)
margin-top -0.91rem
margin-bottom -1px
padding 0.9rem 0.2rem
padding-bottom 0.5rem
</style>
+228
View File
@@ -0,0 +1,228 @@
<template>
<div class="dropdown-wrapper" :class="{ open }">
<button
class="dropdown-title"
type="button"
:aria-label="dropdownAriaLabel"
@click="toggle"
>
<router-link v-if="item.link" :to="item.link" class="link-title">{{
item.text
}}</router-link>
<span class="title" v-show="!item.link">{{ item.text }}</span>
<span class="arrow" :class="open ? 'down' : 'right'"></span>
</button>
<DropdownTransition>
<ul class="nav-dropdown" v-show="open">
<li
class="dropdown-item"
:key="subItem.link || index"
v-for="(subItem, index) in item.items"
>
<h4 v-if="subItem.type === 'links'">{{ subItem.text }}</h4>
<ul class="dropdown-subitem-wrapper" v-if="subItem.type === 'links'">
<li
class="dropdown-subitem"
:key="childSubItem.link"
v-for="childSubItem in subItem.items"
>
<NavLink
@focusout="
isLastItemOfArray(childSubItem, subItem.items) &&
isLastItemOfArray(subItem, item.items) &&
toggle()
"
:item="childSubItem"
/>
</li>
</ul>
<NavLink
v-else
@focusout="isLastItemOfArray(subItem, item.items) && toggle()"
:item="subItem"
/>
</li>
</ul>
</DropdownTransition>
</div>
</template>
<script>
import NavLink from '@theme/components/NavLink.vue'
import DropdownTransition from '@theme/components/DropdownTransition.vue'
import last from 'lodash/last'
export default {
components: { NavLink, DropdownTransition },
data () {
return {
open: false,
isMQMobile: false
}
},
props: {
item: {
required: true
}
},
computed: {
dropdownAriaLabel () {
return this.item.ariaLabel || this.item.text
}
},
beforeMount () {
this.isMQMobile = window.innerWidth < 720 ? true : false;
window.addEventListener('resize', () => {
this.isMQMobile = window.innerWidth < 720 ? true : false;
})
},
methods: {
toggle () {
if (this.isMQMobile) {
this.open = !this.open
}
},
isLastItemOfArray (item, array) {
return last(array) === item
}
},
watch: {
$route () {
this.open = false
}
}
}
</script>
<style lang="stylus">
.dropdown-wrapper
cursor pointer
.dropdown-title
display block
font-size 0.9rem
font-family inherit
cursor inherit
padding inherit
line-height 1.4rem
background transparent
border none
font-weight 500
color var(--textColor)
&:hover
border-color transparent
.arrow
vertical-align middle
margin-top -1px
margin-left 0.4rem
.nav-dropdown
.dropdown-item
color inherit
line-height 1.7rem
h4
margin 0.45rem 0 0
border-top 1px solid var(--borderColor)
padding 0.45rem 1.5rem 0 1.25rem
.dropdown-subitem-wrapper
padding 0
list-style none
.dropdown-subitem
font-size 0.9em
a
display block
line-height 1.7rem
position relative
border-bottom none
font-weight 400
margin-bottom 0
padding 0 1.5rem 0 1.25rem
&:hover
color $accentColor
&.router-link-active
color $accentColor
&::after
content ''
width 0
height 0
border-left 5px solid $accentColor
border-top 3px solid transparent
border-bottom 3px solid transparent
position absolute
top calc(50% - 2px)
left 9px
&:first-child h4
margin-top 0
padding-top 0
border-top 0
@media (max-width $MQMobile)
.dropdown-wrapper
&.open .dropdown-title
margin-bottom 0.5rem
.dropdown-title
font-weight 600
font-size inherit
&:hover
color $accentColor
.link-title
display none
.title
display inline-block !important
.nav-dropdown
transition height 0.1s ease-out
overflow hidden
.dropdown-item
h4
border-top 0
margin-top 0
padding-top 0
h4, & > a
font-size 15px
line-height 2rem
.dropdown-subitem
font-size 14px
padding-left 1rem
@media (min-width $MQMobile)
.dropdown-wrapper
height 1.8rem
&:hover .nav-dropdown, &.open .nav-dropdown
// override the inline style.
display block !important
&.open:blur
display none
.dropdown-title .arrow
// make the arrow always down at desktop
border-left 4px solid transparent
border-right 4px solid transparent
border-top 6px solid $arrowBgColor
border-bottom 0
.nav-dropdown
display none
// Avoid height shaked by clicking
height auto !important
box-sizing border-box
max-height calc(100vh - 2.7rem)
overflow-y auto
position absolute
top 100%
right 0
background-color var(--mainBg)
padding 0.6rem 0
border 1px solid var(--borderColor)
border-bottom-color var(--borderColor)
text-align left
border-radius 0.25rem
white-space nowrap
margin 0
.nav-item .dropdown-title a
&:hover, &.router-link-active
margin-bottom -2px
border-bottom 2px solid lighten($accentColor, 8%)
</style>
+32
View File
@@ -0,0 +1,32 @@
<template>
<transition
name="dropdown"
@enter="setHeight"
@after-enter="unsetHeight"
@before-leave="setHeight"
>
<slot />
</transition>
</template>
<script>
export default {
name: 'DropdownTransition',
methods: {
setHeight (items) {
// explicitly set height so that it can be transitioned
items.style.height = items.scrollHeight + 'px'
},
unsetHeight (items) {
items.style.height = ''
}
}
}
</script>
<style lang="stylus">
.dropdown-enter, .dropdown-leave-to
height 0 !important
</style>
+72
View File
@@ -0,0 +1,72 @@
<template>
<div class="footer">
<div class="icons" v-if="social && social.icons">
<a
:href="item.link"
:title="item.title"
:class="['iconfont', item.iconClass]"
v-for="(item, index) in social.icons"
:key="index"
target="_blank"
></a>
</div>
<!--Vdoing主题遵循MIT协议完全开源且免费如果您对主题的修改并不大希望您保留主题的链接-->
Theme by
<a
href="https://github.com/xugaoyi/vuepress-theme-vdoing"
target="_blank"
title="本站主题"
>Vdoing</a
>
<template v-if="footer">
| Copyright © {{ footer.createYear }}-{{ new Date().getFullYear() }}
<span v-html="footer.copyrightInfo"></span>
</template>
</div>
</template>
<script>
export default {
computed: {
social() {
return this.$themeConfig.social
},
footer() {
return this.$themeConfig.footer
}
}
}
</script>
<style lang='stylus'>
// $mobileSidebarWidth = $sidebarWidth * 0.82
.footer
padding 5rem 1.5rem 2.5rem
text-align center
color #666
box-sizing border-box
font-size 0.85rem
transition all 0.2s ease
> span
line-height 1.5rem
.icons
margin-bottom 12px
.iconfont
padding 0 10px
font-size 1.3rem
a
color inherit
&:hover
color $accentColor
@media (min-width ($MQMobile + 1px))
.sidebar-open .footer
width auto
padding-left: ($sidebarWidth + 1.5rem)
@media (min-width 1520px)
.have-rightmenu .footer
padding-right: ($rightMenuWidth + 1.5rem)
.no-sidebar .footer
width auto
padding-left 1.5rem
</style>
+545
View File
@@ -0,0 +1,545 @@
<template>
<div class="home-wrapper">
<!-- banner块 s -->
<div
class="banner"
:class="{ 'hide-banner': !showBanner }"
:style="bannerBgStyle"
>
<div
class="banner-conent"
:style="
!homeData.features && !homeData.heroImage && `padding-top: 7rem`
"
>
<header class="hero">
<img
v-if="homeData.heroImage"
:src="$withBase(homeData.heroImage)"
:alt="homeData.heroAlt"
/>
<h1 v-if="homeData.heroText" id="main-title">
{{ homeData.heroText }}
</h1>
<p v-if="homeData.tagline" class="description">
{{ homeData.tagline }}
</p>
<p class="action" v-if="homeData.actionText && homeData.actionLink">
<NavLink class="action-button" :item="actionLink" />
</p>
</header>
<!-- PC端features块 s -->
<div class="features" v-if="hasFeatures && !isMQMobile">
<div
class="feature"
v-for="(feature, index) in homeData.features"
:key="index"
>
<router-link v-if="feature.link" :to="feature.link">
<img
class="feature-img"
v-if="feature.imgUrl"
:src="$withBase(feature.imgUrl)"
:alt="feature.title"
/>
<h2>{{ feature.title }}</h2>
<p>{{ feature.details }}</p>
</router-link>
<a v-else href="javascript:;">
<img
class="feature-img"
v-if="feature.imgUrl"
:src="$withBase(feature.imgUrl)"
:alt="feature.title"
/>
<h2>{{ feature.title }}</h2>
<p>{{ feature.details }}</p>
</a>
</div>
</div>
<!-- PC端features块 e -->
</div>
<!-- 移动端features块 s -->
<!-- isMQMobile放到v-if上线后会报错 -->
<div class="slide-banner" v-if="hasFeatures" v-show="isMQMobile">
<div class="banner-wrapper">
<div class="slide-banner-scroll" ref="slide">
<div class="slide-banner-wrapper">
<div
class="slide-item"
v-for="(feature, index) in homeData.features"
:key="index"
>
<router-link v-if="feature.link" :to="feature.link">
<img
class="feature-img"
v-if="feature.imgUrl"
:src="$withBase(feature.imgUrl)"
:alt="feature.title"
/>
<h2>{{ feature.title }}</h2>
<p>{{ feature.details }}</p>
</router-link>
<a v-else href="javascript:;">
<img
class="feature-img"
v-if="feature.imgUrl"
:src="$withBase(feature.imgUrl)"
:alt="feature.title"
/>
<h2>{{ feature.title }}</h2>
<p>{{ feature.details }}</p>
</a>
</div>
</div>
</div>
<div class="docs-wrapper">
<span
class="doc"
v-for="(item, index) in homeData.features.length"
:key="index"
:class="{ active: currentPageIndex === index }"
></span>
</div>
</div>
</div>
<!-- 移动端features块 e -->
</div>
<!-- banner块 e -->
<MainLayout>
<template #mainLeft>
<!-- 简约版文章列表 -->
<UpdateArticle
class="card-box"
v-if="homeData.postList === 'simple'"
:length="homeData.simplePostListLength || 10"
:moreArticle="
$themeConfig.updateBar && $themeConfig.updateBar.moreArticle
"
/>
<!-- 详情版文章列表 -->
<template
v-else-if="!homeData.postList || homeData.postList === 'detailed'"
>
<PostList :currentPage="currentPage" :perPage="perPage" />
<Pagination
:total="total"
:perPage="perPage"
:currentPage="currentPage"
@getCurrentPage="handlePagination"
v-show="Math.ceil(total / perPage) > 1"
/>
</template>
<Content class="theme-vdoing-content custom card-box" />
</template>
<template v-if="!homeData.hideRightBar" #mainRight>
<BloggerBar v-if="$themeConfig.blogger" />
<CategoriesBar
v-if="
$themeConfig.category !== false &&
$categoriesAndTags.categories.length
"
:categoriesData="$categoriesAndTags.categories"
:length="10"
/>
<TagsBar
v-if="$themeConfig.tag !== false && $categoriesAndTags.tags.length"
:tagsData="$categoriesAndTags.tags"
:length="30"
/>
<div
class="custom-html-box card-box"
v-if="homeSidebarB"
v-html="homeSidebarB"
></div>
</template>
</MainLayout>
</div>
</template>
<script>
import NavLink from "@theme/components/NavLink";
import BScroll from "@better-scroll/core"
import Slide from "@better-scroll/slide"
import MainLayout from '@theme/components/MainLayout'
import PostList from '@theme/components/PostList'
import UpdateArticle from '@theme/components/UpdateArticle'
import Pagination from '@theme/components/Pagination'
import BloggerBar from '@theme/components/BloggerBar'
import CategoriesBar from '@theme/components/CategoriesBar'
import TagsBar from '@theme/components/TagsBar'
const MOBILE_DESKTOP_BREAKPOINT = 720 // refer to config.styl
BScroll.use(Slide)
export default {
data() {
return {
isMQMobile: false,
slide: null,
currentPageIndex: 0,
playTimer: 0,
mark: 0,
total: 0, // 总长
perPage: 10, // 每页长
currentPage: 1// 当前页
}
},
computed: {
homeData() {
return {
...this.$page.frontmatter
}
},
hasFeatures() {
return !!(this.homeData.features && this.homeData.features.length)
},
homeSidebarB() {
const { htmlModules } = this.$themeConfig
return htmlModules ? htmlModules.homeSidebarB : ''
},
showBanner() { // 当分页不在第一页时隐藏banner栏
return this.$route.query.p
&& this.$route.query.p != 1
&& (!this.homeData.postList || this.homeData.postList === 'detailed')
? false : true
},
bannerBgStyle() {
let bannerBg = this.homeData.bannerBg
if (!bannerBg || bannerBg === 'auto') { // 默认
if (this.$themeConfig.bodyBgImg) { // 当有bodyBgImg时,不显示背景
return ''
} else { // 网格纹背景
return 'background: rgb(40,40,45) url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAjCAYAAAAe2bNZAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABOSURBVFhH7c6xCQAgDAVRR9A6E4hLu4uLiWJ7tSnuQcIvr2TRYsw3/zOGGEOMIcYQY4gxxBhiDDGGGEOMIcYQY4gxxBhiDLkx52W4Gn1tuslCtHJvL54AAAAASUVORK5CYII=)'
}
} else if (bannerBg === 'none') { // 无背景
if (this.$themeConfig.bodyBgImg) {
return ''
} else {
return 'background: var(--mainBg);color: var(--textColor)'
}
} else if (bannerBg.indexOf('background:') > -1) { // 自定义背景样式
return bannerBg
} else if (bannerBg.indexOf('.') > -1) { // 大图
return `background: url(${this.$withBase(bannerBg)}) center center / cover no-repeat`
}
},
actionLink() {
return {
link: this.homeData.actionLink,
text: this.homeData.actionText
};
}
},
components: { NavLink, MainLayout, PostList, UpdateArticle, BloggerBar, CategoriesBar, TagsBar, Pagination },
created() {
this.total = this.$sortPosts.length
},
beforeMount() {
this.isMQMobile = window.innerWidth < MOBILE_DESKTOP_BREAKPOINT ? true : false; // vupress在打包时不能在beforeCreate(),created()访问浏览器api(如window
},
mounted() {
if (this.$route.query.p) {
this.currentPage = Number(this.$route.query.p)
}
if (this.hasFeatures && this.isMQMobile && (!this.$route.query.p || this.$route.query.p == 1)) {
this.init()
}
if (this.hasFeatures) {
window.addEventListener('resize', () => {
this.isMQMobile = window.innerWidth < MOBILE_DESKTOP_BREAKPOINT ? true : false;
if (this.isMQMobile && !this.slide && !this.mark) {
this.mark++
setTimeout(() => {
this.init()
}, 60)
}
})
}
},
beforeDestroy() {
clearTimeout(this.playTimer)
this.slide && this.slide.destroy()
},
watch: {
'$route.query.p'() {
if (!this.$route.query.p) {
this.currentPage = 1
} else {
this.currentPage = Number(this.$route.query.p)
}
if (this.hasFeatures && this.currentPage === 1 && this.isMQMobile) {
setTimeout(() => {
this.slide && this.slide.destroy()
this.init()
}, 0)
}
}
},
methods: {
init() {
clearTimeout(this.playTimer)
this.slide = new BScroll(this.$refs.slide, {
scrollX: true, // x轴滚动
scrollY: false, // y轴滚动
slide: {
loop: true,
threshold: 100
},
useTransition: true, // 使用css3 transition动画
momentum: false,
bounce: false, // 回弹
stopPropagation: false, // 是否阻止事件冒泡
probeType: 2,
preventDefault: false
})
// user touches the slide area
this.slide.on('beforeScrollStart', () => {
clearTimeout(this.playTimer)
})
// user touched the slide done
this.slide.on('scrollEnd', () => {
this.autoGoNext()
})
this.slide.on('slideWillChange', (page) => {
this.currentPageIndex = page.pageX
})
this.autoGoNext()
},
autoGoNext() {
clearTimeout(this.playTimer)
this.playTimer = setTimeout(() => {
this.slide.next()
}, 4000)
},
handlePagination(i) { // 分页
this.currentPage = i
},
getScrollTop() {
return window.pageYOffset
|| document.documentElement.scrollTop
|| document.body.scrollTop
},
},
};
</script>
<style lang="stylus" scoped>
.home-wrapper
.banner
width 100%
min-height 450px
margin-top $navbarHeight
color $bannerTextColor
position relative
overflow hidden
.banner-conent
max-width $homePageWidth
margin 0px auto
position relative
z-index 1
overflow hidden
.hero
text-align center
margin-top 3rem
img
max-width 100%
max-height 240px
display block
margin 2rem auto 1.5rem
h1
margin 0
font-size 3.2rem
.description, .action
margin 1.5rem auto
.description
max-width 40rem
font-size 1.1rem
line-height 1.3
opacity 0.9
.action-button
display inline-block
font-size 1.2rem
background-color $accentColor
padding 0.8rem 1.6rem
border-radius 4px
transition background-color 0.1s ease
box-sizing border-box
border-bottom 1px solid darken($accentColor, 10%)
color #fff
&:hover
background-color lighten($accentColor, 10%)
// pc端features
.features
padding 2rem 0
margin-top 2.5rem
display flex
flex-wrap wrap
align-items flex-start
align-content stretch
justify-content space-between
.feature
flex-grow 1
flex-basis 30%
max-width 30%
text-align center
a
// color lighten($bannerTextColor,10%)
color inherit
.feature-img
width 10rem
height 10rem
animation heart 1.2s ease-in-out 0s infinite alternate
animation-play-state paused
h2
font-weight 500
font-size 1.3rem
border-bottom none
padding-bottom 0
p
opacity 0.8
padding 0 0.8rem
.feature:hover
.feature-img
animation-play-state running
h2, p
color $accentColor
// 移动端滑动图标
.slide-banner
margin-top 2rem
.banner-wrapper
position relative
.slide-banner-scroll
min-height 1px
overflow hidden
.slide-banner-wrapper
height 300px
.slide-item
display inline-block
height 300px
width 100%
text-align center
a
// color lighten($bannerTextColor,10%)
color inherit
.feature-img
width 10rem
height 10rem
h2
font-size 1.1rem
font-weight 500
border-bottom none
padding-bottom 0
p
opacity 0.8
padding 0 0.8rem
.docs-wrapper
position absolute
bottom 25px
left 50%
transform translateX(-50%)
.doc
display inline-block
margin 0 4px
width 8px
height 8px
border-radius 50%
background var(--textColor)
opacity 0.9
&.active
opacity 0.5
// 分页不在第一页时,隐藏banner栏
.banner.hide-banner
display none
& + .main-wrapper
margin-top: ($navbarHeight + 0.9rem)
.main-wrapper
margin-top 2rem
.main-left
.card-box
margin-bottom 2rem
.pagination
margin-bottom 3rem
.theme-vdoing-content
padding 0 2rem
overflow hidden
border none
&>:first-child
padding-top 2rem
&>:last-child
padding-bottom 2rem
.main-right
.custom-html-box
padding 0
overflow hidden
@keyframes heart
from
transform translate(0, 0)
to
transform translate(0, 8px)
// 1025px以下
@media (max-width 1025px)
.home-wrapper
.banner
.banner-conent
.hero
h1
font-size 2.5rem
.description
font-size 1rem
.feature
a
h2
font-size 1.1rem
.feature-img
width 9rem
height 9rem
// 719px以下
@media (max-width $MQMobile)
.home-wrapper
.banner
.banner-conent
.features
display none !important
// 419px以下
@media (max-width $MQMobileNarrow)
.home-wrapper
.banner-conent
padding-left 1.5rem
padding-right 1.5rem
.hero
img
max-height 210px
margin 2rem auto 1.2rem
h1
font-size 2rem
h1, .description, .action
margin 1.2rem auto
.description
font-size 1.2rem
.action-button
font-size 1rem
padding 0.6rem 1.2rem
.feature
h2
font-size 1.25rem
.theme-style-line
.main-wrapper
@media (max-width 719px)
margin-top -1px
</style>
+64
View File
@@ -0,0 +1,64 @@
<template>
<div class="main-wrapper">
<div class="main-left">
<slot name="mainLeft" />
</div>
<div class="main-right">
<slot name="mainRight" />
</div>
</div>
</template>
<style lang="stylus">
.main-wrapper
margin 1.5rem auto 0 auto
max-width $homePageWidth
padding 0 0.9rem
box-sizing border-box
position relative
display flex
.main-left
flex 1
// width 72%
.theme-vdoing-content.card-box
padding 1rem 1.5rem
margin-bottom 0.9rem
.home-content
padding 1rem 1.5rem 0
.main-right
>*
width 245px
box-sizing border-box
@media (max-width 900px)
width 235px
.card-box
margin 0 0 0.8rem 0.8rem
padding-top 0.95rem
padding-bottom 0.95rem
// 719px以下
@media (max-width $MQMobile)
.main-wrapper
margin 0.9rem 0
padding 0
display block
.main-left
width 100%
.post-list
margin-bottom 3rem
.post
border-radius 0
.pagination
margin-bottom 3rem
.main-right
.blogger-wrapper
display none
.card-box
margin 0 0 0.9rem 0
border-radius 0
width 100%
.theme-style-line
.main-wrapper
.main-right
.card-box
margin -1px 0 0 0
</style>
+54
View File
@@ -0,0 +1,54 @@
<template>
<router-link
class="nav-link"
:to="link"
@focusout.native="focusoutAction"
v-if="!isExternal(link)"
:exact="exact"
>{{ item.text }}</router-link>
<a
v-else
:href="link"
@focusout="focusoutAction"
class="nav-link external"
:target="isMailto(link) || isTel(link) ? null : '_blank'"
:rel="isMailto(link) || isTel(link) ? null : 'noopener noreferrer'"
>
{{ item.text }}
<OutboundLink />
</a>
</template>
<script>
import { isExternal, isMailto, isTel, ensureExt } from '../util'
export default {
props: {
item: {
required: true
}
},
computed: {
link () {
return ensureExt(this.item.link)
},
exact () {
if (this.$site.locales) {
return Object.keys(this.$site.locales).some(rootLink => rootLink === this.link)
}
return this.link === '/'
}
},
methods: {
isExternal,
isMailto,
isTel,
focusoutAction () {
this.$emit('focusout')
}
}
}
</script>
+154
View File
@@ -0,0 +1,154 @@
<template>
<nav
class="nav-links"
v-if="userLinks.length || repoLink"
>
<!-- user links -->
<div
class="nav-item"
v-for="item in userLinks"
:key="item.link"
>
<DropdownLink
v-if="item.type === 'links'"
:item="item"
/>
<NavLink
v-else
:item="item"
/>
</div>
<!-- repo link -->
<a
v-if="repoLink"
:href="repoLink"
class="repo-link"
target="_blank"
rel="noopener noreferrer"
>
{{ repoLabel }}
<OutboundLink />
</a>
</nav>
</template>
<script>
import DropdownLink from '@theme/components/DropdownLink.vue'
import { resolveNavLinkItem } from '../util'
import NavLink from '@theme/components/NavLink.vue'
export default {
components: { NavLink, DropdownLink },
computed: {
userNav () {
return this.$themeLocaleConfig.nav || this.$site.themeConfig.nav || []
},
nav () {
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 [...this.userNav, languageDropdown]
}
return this.userNav
},
userLinks () {
return (this.nav || []).map(link => {
return Object.assign(resolveNavLinkItem(link), {
items: (link.items || []).map(resolveNavLinkItem)
})
})
},
repoLink () {
const { repo } = this.$site.themeConfig
if (repo) {
return /^https?:/.test(repo)
? repo
: `https://github.com/${repo}`
}
return null
},
repoLabel () {
if (!this.repoLink) return
if (this.$site.themeConfig.repoLabel) {
return this.$site.themeConfig.repoLabel
}
const repoHost = this.repoLink.match(/^https?:\/\/[^/]+/)[0]
const platforms = ['GitHub', 'GitLab', 'Bitbucket']
for (let i = 0; i < platforms.length; i++) {
const platform = platforms[i]
if (new RegExp(platform, 'i').test(repoHost)) {
return platform
}
}
return 'Source'
}
}
}
</script>
<style lang="stylus">
.nav-links
display inline-block
a
line-height 1.4rem
color inherit
&:hover, &.router-link-active
color $accentColor
.nav-item
position relative
display inline-block
margin-left 1.5rem
line-height 2rem
&:first-child
margin-left 0
.repo-link
margin-left 1.5rem
// 959
@media (max-width $MQNarrow)
.nav-links
.nav-item
margin-left 1.2rem
@media (max-width $MQMobile)
.nav-links
.nav-item, .repo-link
margin-left 0
@media (min-width $MQMobile)
.nav-links a
&:hover, &.router-link-active
color var(--textColor)
.nav-item > a:not(.external)
&:hover, &.router-link-active
margin-bottom -2px
border-bottom 2px solid lighten($accentColor, 8%)
</style>
+141
View File
@@ -0,0 +1,141 @@
<template>
<header class="navbar blur">
<SidebarButton @toggle-sidebar="$emit('toggle-sidebar')" />
<router-link
:to="$localePath"
class="home-link"
>
<img
class="logo"
v-if="$site.themeConfig.logo"
:src="$withBase($site.themeConfig.logo)"
:alt="$siteTitle"
/>
<span
ref="siteName"
class="site-name"
v-if="$siteTitle"
:class="{ 'can-hide': $site.themeConfig.logo }"
>{{ $siteTitle }}</span>
</router-link>
<div
class="links"
:style="linksWrapMaxWidth ? {
'max-width': linksWrapMaxWidth + 'px'
} : {}"
>
<AlgoliaSearchBox
v-if="isAlgoliaSearch"
:options="algolia"
/>
<SearchBox
v-else-if="$site.themeConfig.search !== false && $page.frontmatter.search !== false"
/>
<NavLinks class="can-hide" />
</div>
</header>
</template>
<script>
import AlgoliaSearchBox from '@AlgoliaSearchBox'
import SearchBox from '@SearchBox'
import SidebarButton from '@theme/components/SidebarButton.vue'
import NavLinks from '@theme/components/NavLinks.vue'
export default {
components: { SidebarButton, NavLinks, SearchBox, AlgoliaSearchBox },
data () {
return {
linksWrapMaxWidth: null
}
},
mounted () {
const MOBILE_DESKTOP_BREAKPOINT = 719 // refer to config.styl
const NAVBAR_VERTICAL_PADDING = parseInt(css(this.$el, 'paddingLeft')) + parseInt(css(this.$el, 'paddingRight'))
const handleLinksWrapWidth = () => {
if (document.documentElement.clientWidth < MOBILE_DESKTOP_BREAKPOINT) {
this.linksWrapMaxWidth = null
} else {
this.linksWrapMaxWidth = this.$el.offsetWidth - NAVBAR_VERTICAL_PADDING
- (this.$refs.siteName && this.$refs.siteName.offsetWidth || 0)
}
}
handleLinksWrapWidth()
window.addEventListener('resize', handleLinksWrapWidth, false)
},
computed: {
algolia () {
return this.$themeLocaleConfig.algolia || this.$site.themeConfig.algolia || {}
},
isAlgoliaSearch () {
return this.algolia && this.algolia.apiKey && this.algolia.indexName
}
}
}
function css (el, property) {
// NOTE: Known bug, will return 'auto' if style value is 'auto'
const win = el.ownerDocument.defaultView
// null means not to return pseudo styles
return win.getComputedStyle(el, null)[property]
}
</script>
<style lang="stylus">
$navbar-vertical-padding = 0.7rem
$navbar-horizontal-padding = 1.5rem
.navbar
padding $navbar-vertical-padding $navbar-horizontal-padding
line-height $navbarHeight - 1.4rem
transition transform 0.3s
a, span, img
display inline-block
.logo
height $navbarHeight - 1.4rem
min-width $navbarHeight - 1.4rem
margin-right 0.8rem
vertical-align top
.site-name
font-size 1.3rem
font-weight 600
color var(--textColor)
position relative
.links
padding-left 1.5rem
box-sizing border-box
white-space nowrap
font-size 0.9rem
position absolute
right $navbar-horizontal-padding
top $navbar-vertical-padding
display flex
.search-box
flex 0 0 auto
vertical-align top
.hide-navbar
.navbar
transform translateY(-100%)
// 959
@media (max-width $MQNarrow)
.navbar
.site-name
display none
@media (max-width $MQMobile)
.navbar
padding-left 4rem
.can-hide
display none
.links
padding-left 1.5rem
.site-name
width calc(100vw - 9.4rem)
overflow hidden
white-space nowrap
text-overflow ellipsis
</style>
+218
View File
@@ -0,0 +1,218 @@
<template>
<div>
<main class="page">
<div :class="`theme-vdoing-wrapper ${bgStyle}`">
<ArticleInfo v-if="isArticle()" />
<div v-else class="placeholder" />
<component
class="theme-vdoing-content"
v-if="pageComponent"
:is="pageComponent"
/>
<div class="content-wrapper">
<RightMenu v-if="showRightMenu" />
<h1 v-if="showTitle">
<img
:src="currentBadge"
v-if="$themeConfig.titleBadge === false ? false : true"
/>{{ this.$page.title
}}<span class="title-tag" v-if="$frontmatter.titleTag">{{
$frontmatter.titleTag
}}</span>
</h1>
<slot name="top" v-if="isShowSlotT" />
<Content class="theme-vdoing-content" />
</div>
<slot name="bottom" v-if="isShowSlotB" />
<PageEdit />
<PageNav v-bind="{ sidebarItems }" />
</div>
<UpdateArticle
:length="3"
:moreArticle="updateBarConfig && updateBarConfig.moreArticle"
v-if="isShowUpdateBar"
/>
</main>
</div>
</template>
<script>
import PageEdit from '@theme/components/PageEdit.vue'
import PageNav from '@theme/components/PageNav.vue'
import ArticleInfo from './ArticleInfo.vue'
import Catalogue from './Catalogue.vue'
import UpdateArticle from './UpdateArticle.vue'
import RightMenu from './RightMenu.vue'
import TitleBadgeMixin from '../mixins/titleBadge'
export default {
mixins: [TitleBadgeMixin],
data() {
return {
updateBarConfig: null
}
},
props: ['sidebarItems'],
components: { PageEdit, PageNav, ArticleInfo, Catalogue, UpdateArticle, RightMenu },
created() {
this.updateBarConfig = this.$themeConfig.updateBar
},
computed: {
bgStyle() {
const { contentBgStyle } = this.$themeConfig
return contentBgStyle ? 'bg-style-' + contentBgStyle : ''
},
isShowUpdateBar() {
return this.updateBarConfig && this.updateBarConfig.showToArticle === false ? false : true
},
showTitle() {
return !this.$frontmatter.pageComponent
},
showRightMenu() {
const { $frontmatter, $themeConfig, $page } = this
const { sidebar } = $frontmatter
return (
$themeConfig.rightMenuBar !== false &&
$page.headers &&
($frontmatter && sidebar && sidebar !== false) !== false
)
},
pageComponent() {
return this.$frontmatter.pageComponent ? this.$frontmatter.pageComponent.name : false
},
isShowSlotT() {
return this.getShowStatus('pageTshowMode')
},
isShowSlotB() {
return this.getShowStatus('pageBshowMode')
}
},
methods: {
getShowStatus(prop) {
const { htmlModules } = this.$themeConfig
if (!htmlModules) return false
if (htmlModules[prop] === 'article') { // 仅文章页显示
return this.isArticle()
} else if (htmlModules[prop] === 'custom') { // 仅自定义页显示
return !(this.isArticle())
} else { // 全部显示
return true
}
},
isArticle() {
return this.$frontmatter.article !== false
}
}
}
</script>
<style lang="stylus">
@require '../styles/wrapper.styl'
.page
padding-bottom 2rem
display block
@media (max-width $MQMobile)
padding-top $navbarHeight
@media (min-width $MQMobile)
padding-top: ($navbarHeight + 1.5rem)
>*
@extend $vdoing-wrapper
.theme-style-line
.page
@media (min-width $MQMobile)
padding-top $navbarHeight
&>*
&:not(.footer)
box-shadow 0 0
.placeholder
@media (min-width 720px)
height 1.2rem
.theme-vdoing-wrapper
.content-wrapper
position relative
h1
.title-tag
height 1.5rem
line-height 1.5rem
border 1px solid $activeColor
color $activeColor
font-size 1rem
padding 0 0.4rem
border-radius 0.2rem
margin-left 0.5rem
transform translate(0, -0.25rem)
display inline-block
img
margin-bottom -0.2rem
margin-right 0.2rem
max-width 2.2rem
max-height 2.2rem
.theme-vdoing-wrapper
--linesColor rgba(50, 0, 0, 0.05)
&.bg-style-1 // 方格
background-image linear-gradient(90deg, var(--linesColor) 3%, transparent 3%), linear-gradient(0deg, var(--linesColor) 3%, transparent 3%)
background-position center center
background-size 20px 20px
&.bg-style-2 // 横线
background-image repeating-linear-gradient(0, var(--linesColor) 0, var(--linesColor) 1px, transparent 0, transparent 50%)
background-size 30px 30px
&.bg-style-3 // 竖线
background-image repeating-linear-gradient(90deg, var(--linesColor) 0, var(--linesColor) 1px, transparent 0, transparent 50%)
background-size 30px 30px
&.bg-style-4 // 左斜线
background-image repeating-linear-gradient(-45deg, var(--linesColor) 0, var(--linesColor) 1px, transparent 0, transparent 50%)
background-size 20px 20px
&.bg-style-5 // 右斜线
background-image repeating-linear-gradient(45deg, var(--linesColor) 0, var(--linesColor) 1px, transparent 0, transparent 50%)
background-size 20px 20px
&.bg-style-6 // 点状
background-image radial-gradient(var(--linesColor) 1px, transparent 1px)
background-size 10px 10px
// 背景纹适应深色模式
.theme-mode-dark
.theme-vdoing-wrapper
--linesColor rgba(125, 125, 125, 0.05)
/**
* 右侧菜单的自适应
*/
@media (min-width 720px) and (max-width 1279px)
.have-rightmenu
.page
padding-right 0.8rem !important
@media (max-width 1279px)
.have-rightmenu
.right-menu-wrapper
display none
@media (min-width 1280px)
.have-rightmenu
.sidebar .sidebar-sub-headers
display none
// 左侧边栏只有一项且没有右侧边栏时
.theme-container.only-sidebarItem:not(.have-rightmenu)
.sidebar, .sidebar-button
display none
@media (min-width ($MQMobile + 1px))
.page
padding-left 0.8rem !important
@media (max-width $MQMobile)
.page
padding-left 0rem !important
.sidebar, .sidebar-button
display block
// 左侧边栏只有一项且有右侧边栏时
.theme-container.only-sidebarItem.have-rightmenu
@media (min-width 720px) and (max-width 1279px)
.sidebar, .sidebar-button
display block
@media (min-width 1280px)
.sidebar, .sidebar-button
display none
</style>
+181
View File
@@ -0,0 +1,181 @@
<template>
<div class="page-edit">
<div class="edit-link" v-if="editLink">
<a :href="editLink" target="_blank" rel="noopener noreferrer">{{
editLinkText
}}</a>
<OutboundLink />
</div>
<div class="tags" v-if="$themeConfig.tag !== false && tags && tags[0]">
<router-link
:to="`/tags/?tag=${encodeURIComponent(item)}`"
v-for="(item, index) in tags"
:key="index"
title="标签"
>#{{ item }}</router-link
>
</div>
<div class="last-updated" v-if="lastUpdated">
<span class="prefix">{{ lastUpdatedText }}:</span>
<span class="time">{{ lastUpdated }}</span>
</div>
</div>
</template>
<script>
import isNil from 'lodash/isNil'
import { endingSlashRE, outboundRE } from '../util'
export default {
name: 'PageEdit',
computed: {
tags () {
return this.$frontmatter.tags
},
lastUpdated () {
return this.$page.lastUpdated
},
lastUpdatedText () {
if (typeof this.$themeLocaleConfig.lastUpdated === 'string') {
return this.$themeLocaleConfig.lastUpdated
}
if (typeof this.$site.themeConfig.lastUpdated === 'string') {
return this.$site.themeConfig.lastUpdated
}
return 'Last Updated'
},
editLink () {
const showEditLink = isNil(this.$page.frontmatter.editLink)
? this.$site.themeConfig.editLinks
: this.$page.frontmatter.editLink
const {
repo,
docsDir = '',
docsBranch = 'master',
docsRepo = repo
} = this.$site.themeConfig
if (showEditLink && docsRepo && this.$page.relativePath) {
return this.createEditLink(
repo,
docsRepo,
docsDir,
docsBranch,
this.$page.relativePath
)
}
return null
},
editLinkText () {
return (
this.$themeLocaleConfig.editLinkText
|| this.$site.themeConfig.editLinkText
|| `Edit this page`
)
}
},
methods: {
createEditLink (repo, docsRepo, docsDir, docsBranch, path) {
const bitbucket = /bitbucket.org/
if (bitbucket.test(docsRepo)) {
const base = docsRepo
return (
base.replace(endingSlashRE, '')
+ `/src`
+ `/${docsBranch}/`
+ (docsDir ? docsDir.replace(endingSlashRE, '') + '/' : '')
+ path
+ `?mode=edit&spa=0&at=${docsBranch}&fileviewer=file-view-default`
)
}
const gitlab = /gitlab.com/
if (gitlab.test(docsRepo)) {
const base = docsRepo
return (
base.replace(endingSlashRE, '')
+ `/-/edit`
+ `/${docsBranch}/`
+ (docsDir ? docsDir.replace(endingSlashRE, '') + '/' : '')
+ path
)
}
// https://gitee.com/-/ide/project/xxx/xxx/edit/master/-/xxxx
const gitee = /gitee.com/
if (gitee.test(docsRepo)) {
const base = docsRepo
return (
base.replace(gitee, 'gitee.com/-/ide/project')
+ `/edit`
+ `/${docsBranch}/-/`
+ (docsDir ? docsDir.replace(endingSlashRE, '') + '/' : '')
+ path
)
}
const base = outboundRE.test(docsRepo)
? docsRepo
: `https://github.com/${docsRepo}`
return (
base.replace(endingSlashRE, '')
+ `/edit`
+ `/${docsBranch}/`
+ (docsDir ? docsDir.replace(endingSlashRE, '') + '/' : '')
+ path
)
}
}
}
</script>
<style lang="stylus">
@require '../styles/wrapper.styl'
.page-edit
@extend $wrapper
padding-top 1rem
padding-bottom 1rem
overflow auto
.edit-link
display inline-block
float left
margin 0 2rem 0.5rem 0
a
margin-right 0.25rem
.tags
float left
a
margin 0 0.8rem 0.5rem 0
display inline-block
color var(--textLightenColor)
padding 0.2rem 0.7rem
font-size 0.9em
background-color rgba(128, 128, 128, 0.08)
border-radius 3px
opacity 0.8
.last-updated
float right
font-size 0.9em
.prefix
font-weight 500
color var(--textColor)
opacity 0.8
.time
font-weight 400
color #aaa
@media (max-width $MQMobile)
.page-edit
.edit-link, .tags
margin-bottom 0.5rem
.last-updated
width 100%
font-size 0.8em
text-align left
</style>
+237
View File
@@ -0,0 +1,237 @@
<template>
<div class="page-nav-wapper">
<!-- 页面中间左右翻页 -->
<div
class="page-nav-centre-wrap"
v-if="$themeConfig.pageButton !== false && (prev || next)"
>
<router-link
class="page-nav-centre page-nav-centre-prev"
v-if="prev"
:to="prev.path"
@mouseenter.native="showTooltip($event)"
@mousemove.native="showTooltip($event)"
>
<div class="tooltip">{{ prev.title || prev.path }}</div>
</router-link>
<router-link
class="page-nav-centre page-nav-centre-next"
v-if="next"
:to="next.path"
@mouseenter.native="showTooltip($event)"
@mousemove.native="showTooltip($event)"
>
<div class="tooltip">{{ next.title || next.path }}</div>
</router-link>
</div>
<!-- 底部翻页按钮 -->
<div
class="page-nav"
v-if="prev || next"
>
<p class="inner">
<span
v-if="prev"
class="prev"
>
<router-link
v-if="prev"
class="prev"
:to="prev.path"
>{{ prev.title || prev.path }}</router-link>
</span>
<span
v-if="next"
class="next"
>
<router-link
v-if="next"
:to="next.path"
>{{ next.title || next.path }}</router-link>
</span>
</p>
</div>
</div>
</template>
<script>
import { resolvePage } from '../util'
import isString from 'lodash/isString'
import isNil from 'lodash/isNil'
export default {
name: 'PageNav',
props: ['sidebarItems'],
computed: {
prev () {
return resolvePageLink(LINK_TYPES.PREV, this)
},
next () {
return resolvePageLink(LINK_TYPES.NEXT, this)
}
},
methods: {
showTooltip (e) {
const clientW = document.body.clientWidth
const X = e.clientX
const tooltipEle = e.target.querySelector('.tooltip')
if (!tooltipEle) {
return
}
const tooltipEleStyle = tooltipEle.style
if (X < clientW / 2) {
tooltipEleStyle.right = null
tooltipEleStyle.left = X + 10 + 'px'
} else {
tooltipEleStyle.left = null
tooltipEleStyle.right = clientW - X + 10 + 'px'
}
tooltipEleStyle.top = e.clientY + 10 + 'px'
}
}
}
function resolvePrev (page, items) {
return find(page, items, -1)
}
function resolveNext (page, items) {
return find(page, items, 1)
}
const LINK_TYPES = {
NEXT: {
resolveLink: resolveNext,
getThemeLinkConfig: ({ nextLinks }) => nextLinks,
getPageLinkConfig: ({ frontmatter }) => frontmatter.next
},
PREV: {
resolveLink: resolvePrev,
getThemeLinkConfig: ({ prevLinks }) => prevLinks,
getPageLinkConfig: ({ frontmatter }) => frontmatter.prev
}
}
function resolvePageLink (
linkType,
{ $themeConfig, $page, $route, $site, sidebarItems }
) {
const { resolveLink, getThemeLinkConfig, getPageLinkConfig } = linkType
// Get link config from theme
const themeLinkConfig = getThemeLinkConfig($themeConfig)
// Get link config from current page
const pageLinkConfig = getPageLinkConfig($page)
// Page link config will overwrite global theme link config if defined
const link = isNil(pageLinkConfig) ? themeLinkConfig : pageLinkConfig
if (link === false) {
return
} else if (isString(link)) {
return resolvePage($site.pages, link, $route.path)
} else {
return resolveLink($page, sidebarItems)
}
}
function find (page, items, offset) {
const res = []
flatten(items, res)
for (let i = 0; i < res.length; i++) {
const cur = res[i]
if (cur.type === 'page' && cur.path === decodeURIComponent(page.path)) {
return res[i + offset]
}
}
}
function flatten (items, res) {
for (let i = 0, l = items.length; i < l; i++) {
if (items[i].type === 'group') {
flatten(items[i].children || [], res)
} else {
res.push(items[i])
}
}
}
</script>
<style lang="stylus">
@require '../styles/wrapper.styl'
.page-nav
@extend $wrapper
padding-top 1rem
padding-bottom 0
.inner
min-height 2rem
margin-top 0
border-top 1px solid var(--borderColor)
padding-top 1rem
overflow auto // clear float
.next
float right
.page-nav-centre-wrap
.page-nav-centre
position fixed
top 50%
width 80px
height 70px
margin-top -35px
outline 0
transition all 0.2s
border-radius 3px
opacity 0.55
z-index 99
@media (max-width 1340px)
width 50px
@media (max-width 960px)
display none
&:hover
background rgba(153, 153, 153, 0.15)
opacity 1
.tooltip
display block
&:before
content ''
display block
width 10px
height 10px
border-top 2px solid #999
border-right 2px solid #999
position absolute
top 0
right 0
bottom 0
left 0
margin auto
.tooltip
display none
background rgba(0, 0, 0, 0.5)
color #fff
padding 4px 8px
font-size 13px
border-radius 3px
position fixed
max-width 200px
z-index 99
.page-nav-centre-prev
left 0
&:before
transform rotate(-135deg)
.page-nav-centre-next
right 0
&:before
transform rotate(45deg)
.sidebar-open .page-nav-centre-wrap .page-nav-centre-prev
left $sidebarWidth
.no-sidebar .page-nav-centre-wrap .page-nav-centre-prev
left 0
</style>
+240
View File
@@ -0,0 +1,240 @@
<template>
<div class="pagination">
<span
class="card-box prev iconfont icon-jiantou-zuo"
:class="{ disabled: currentPage === 1 }"
@click="goPrex()"
>
<p>上一页</p>
</span>
<!-- 分页在5页及以下时 -->
<div class="pagination-list" v-if="pages <= 5">
<span
class="card-box"
v-for="item in pages"
:key="item"
:class="{ active: currentPage === item }"
@click="goIndex(item)"
>{{ item }}</span
>
</div>
<!-- 分页在5页以上 -->
<div class="pagination-list" v-else>
<!-- 一号位 -->
<span
class="card-box"
:class="{ active: currentPage === 1 }"
@click="goIndex(1)"
>1</span
>
<!-- 二号位 -->
<span
class="ellipsis ell-two"
v-show="currentPage > 3"
@click="goIndex(currentPage - 2)"
title="上两页"
/>
<!--这里没有使用v-if的原因是因为部署版本在当前页大于3时刷新页面出现了一些bug-->
<span
class="card-box"
v-show="currentPage <= 3"
:class="{ active: currentPage === 2 }"
@click="goIndex(2)"
>2</span
>
<!-- 三号位 -->
<span
class="card-box"
:class="{ active: currentPage >= 3 && currentPage <= pages - 2 }"
@click="goIndex(threeNum())"
>{{ threeNum() }}</span
>
<!-- 四号位 -->
<span
class="ellipsis ell-four"
v-show="currentPage < pages - 2"
@click="goIndex(currentPage + 2)"
title="下两页"
/>
<span
class="card-box"
v-show="currentPage >= pages - 2"
:class="{ active: currentPage === pages - 1 }"
@click="goIndex(pages - 1)"
>{{ pages - 1 }}</span
>
<!-- 五号位 -->
<span
class="card-box"
:class="{ active: currentPage === pages }"
@click="goIndex(pages)"
>{{ pages }}</span
>
</div>
<span
class="card-box next iconfont icon-jiantou-you"
:class="{ disabled: currentPage === pages }"
@click="goNext()"
>
<p>下一页</p>
</span>
</div>
</template>
<script>
export default {
props: {
total: { // 总长度
type: Number,
default: 10
},
perPage: { // 每页长
type: Number,
default: 10
},
currentPage: { // 当前页
type: Number,
default: 1
}
},
computed: {
pages() { // 总页数
return Math.ceil(this.total / this.perPage)
}
},
methods: {
threeNum() { // 三号位页码计算
let num = 3
const currentPage = this.currentPage
const pages = this.pages
if (currentPage < 3) {
num = 3
} else if (currentPage > (pages - 3)) {
num = pages - 2
} else {
num = currentPage
}
return num
},
goPrex() {
let currentPage = this.currentPage
if (currentPage > 1) {
this.handleEmit(--currentPage)
}
},
goNext() {
let currentPage = this.currentPage
if (currentPage < this.pages) {
this.handleEmit(++currentPage)
}
},
goIndex(i) {
if (i !== this.currentPage) {
this.handleEmit(i)
}
},
handleEmit(i) {
this.$emit('getCurrentPage', i)
}
}
}
</script>
<style lang='stylus'>
.pagination
position relative
height 60px
text-align center
@media (max-width 720px)
margin-left 1px
margin-right 1px
span
line-height 1rem
opacity 0.9
cursor pointer
&:hover
color $accentColor
&.ellipsis
opacity 0.5
&::before
content '...'
font-size 1.2rem
@media (any-hover hover)
&.ell-two
&:hover
&::before
content '«'
&.ell-four
&:hover
&::before
content '»'
> span
position absolute
top 0
padding 1rem 1.2rem
font-size 0.95rem
&::before
font-size 0.4rem
&.disabled
color rgba(125, 125, 125, 0.5)
&.prev
left 0
// border-top-right-radius 32px
// border-bottom-right-radius 32px
&::before
margin-right 0.3rem
&.next
right 0
// border-top-left-radius 32px
// border-bottom-left-radius 32px
&::before
float right
margin-left 0.3rem
p
display inline
line-height 0.95rem
.pagination-list
span
display inline-block
width 2.5rem
height 2.5rem
line-height 2.5rem
margin 0.3rem
&.active
background $accentColor
color var(--mainBg)
@media (max-width 800px)
.pagination
> span
padding 1rem 1.5rem
p
display none
// 719px
@media (max-width $MQMobile)
.pagination
> span // 左右按钮
padding 0.9rem 1.5rem
.pagination-list
span
width 2.3rem
height 2.3rem
line-height 2.3rem
margin 0.25rem
@media (max-width 390px)
.pagination
> span // 左右按钮
padding 0.8rem 1.3rem
.pagination-list
span
width 2rem
height 2rem
line-height 2rem
margin 0.1rem
margin-top 0.3rem
</style>
+260
View File
@@ -0,0 +1,260 @@
<template>
<div class="post-list" ref="postList">
<transition-group tag="div" name="post">
<div
class="post card-box"
:class="item.frontmatter.sticky && 'iconfont icon-zhiding'"
v-for="item in sortPosts"
:key="item.key"
>
<div class="title-wrapper">
<h2>
<router-link :to="item.path">
{{ item.title }}
<span class="title-tag" v-if="item.frontmatter.titleTag">{{
item.frontmatter.titleTag
}}</span>
</router-link>
</h2>
<div class="article-info">
<a
title="作者"
class="iconfont icon-touxiang"
target="_blank"
v-if="item.author && item.author.href"
:href="item.author.href"
>{{ item.author.name ? item.author.name : item.author }}</a
>
<span
title="作者"
class="iconfont icon-touxiang"
v-else-if="item.author"
>{{ item.author.name ? item.author.name : item.author }}</span
>
<span
title="创建时间"
class="iconfont icon-riqi"
v-if="item.frontmatter.date"
>{{ item.frontmatter.date.split(' ')[0] }}</span
>
<span
title="分类"
class="iconfont icon-wenjian"
v-if="
$themeConfig.category !== false && item.frontmatter.categories
"
>
<router-link
:to="`/categories/?category=${encodeURIComponent(c)}`"
v-for="(c, index) in item.frontmatter.categories"
:key="index"
>{{ c }}</router-link
>
</span>
<span
title="标签"
class="iconfont icon-biaoqian tags"
v-if="
$themeConfig.tag !== false &&
item.frontmatter.tags &&
item.frontmatter.tags[0]
"
>
<router-link
:to="`/tags/?tag=${encodeURIComponent(t)}`"
v-for="(t, index) in item.frontmatter.tags"
:key="index"
>{{ t }}</router-link
>
</span>
</div>
</div>
<div class="excerpt-wrapper" v-if="item.excerpt">
<div class="excerpt" v-html="item.excerpt"></div>
<router-link
:to="item.path"
class="readmore iconfont icon-jiantou-you"
>阅读全文</router-link
>
</div>
</div>
</transition-group>
</div>
</template>
<script>
export default {
props: {
category: {
type: String,
default: ''
},
tag: {
type: String,
default: ''
},
currentPage: {
type: Number,
default: 1
},
perPage: {
type: Number,
default: 10
}
},
data() {
return {
sortPosts: [],
postListOffsetTop: 0
}
},
created() {
this.setPosts()
},
mounted() {
// this.postListOffsetTop = this.getElementToPageTop(this.$refs.postList) - 240
},
watch: {
currentPage() {
if (this.$route.query.p != this.currentPage) { // 此判断防止添加相同的路由信息(如浏览器回退时触发的)
this.$router.push({
query: {
...this.$route.query,
p: this.currentPage
}
})
}
// setTimeout(() => {
// window.scrollTo({ top: this.postListOffsetTop }) // behavior: 'smooth'
// },0)
this.setPosts()
},
category() {
this.setPosts()
},
tag() {
this.setPosts()
}
},
methods: {
setPosts() {
const currentPage = this.currentPage
const perPage = this.perPage
let posts = []
if (this.category) {
posts = this.$groupPosts.categories[this.category]
} else if (this.tag) {
posts = this.$groupPosts.tags[this.tag]
} else {
posts = this.$sortPosts
}
this.sortPosts = posts.slice((currentPage - 1) * perPage, currentPage * perPage)
},
// getElementToPageTop(el) {
// if(el && el.parentElement) {
// return this.getElementToPageTop(el.parentElement) + el.offsetTop
// }
// return el.offsetTop
// }
}
}
</script>
<style lang='stylus'>
.post-list
margin-bottom 3rem
.post
position relative
padding 1rem 1.5rem
margin-bottom 0.8rem
transition all 0.3s
// border-bottom 1px solid var(--borderColor)
&:last-child
border-bottom none
&.post-leave-active
display none
&.post-enter
opacity 0
transform translateX(-20px)
&::before
position absolute
top -1px
right 0
font-size 2.5rem
color $activeColor
opacity 0.85
.title-wrapper
a
color var(--textColor)
&:hover
color $accentColor
h2
margin 0.5rem 0
font-size 1.4rem
border none
.title-tag
height 1.2rem
line-height 1.2rem
border 1px solid $activeColor
color $activeColor
font-size 0.8rem
padding 0 0.35rem
border-radius 0.2rem
margin-left 0rem
transform translate(0, -0.15rem)
display inline-block
a
display block
@media (max-width $MQMobile)
font-weight 400
.article-info
> a, > span
opacity 0.7
font-size 0.8rem
margin-right 1rem
cursor pointer
&::before
margin-right 0.3rem
a
margin 0
&:not(:first-child)
&::before
content '/'
.tags a:not(:first-child)::before
content '、'
.excerpt-wrapper
border-top 1px solid var(--borderColor)
margin 0.5rem 0
overflow hidden
.excerpt
margin-bottom 0.3rem
font-size 0.92rem
h1, h2, h3
display none
img
max-height 280px
max-width 100% !important
margin 0 auto
.readmore
float right
margin-right 1rem
line-height 1rem
&::before
float right
font-size 0.8rem
margin 0.1rem 0 0 0.2rem
.theme-style-line
.post-list
border 1px solid var(--borderColor)
border-bottom none
border-radius 5px
overflow hidden
.post
margin-bottom 0
border none
border-bottom 1px solid var(--borderColor)
border-radius 0
</style>
+262
View File
@@ -0,0 +1,262 @@
<template>
<div class="right-menu-wrapper">
<div
title="切换视图"
class="views-switch button blur theme-mode-but iconfont icon-yuedu"
@mouseenter="showModeBox = true"
@mouseleave="showModeBox = false"
@click="showModeBox = true"
>
<transition name="mode">
<ul
class="select-box"
ref="modeBox"
v-show="showModeBox"
@click.stop
@touchstart.stop
>
<li
v-for="item in modeList"
:key="item.KEY"
class="iconfont"
:class="[item.icon, { active: item.KEY === modeView }]"
@click="toggleMode(item.KEY)"
>
{{ item.name }}
</li>
</ul>
</transition>
</div>
<div class="docs-box" v-show="modeView === 'h5' ">
<iframe :src="iframeUrl" frameborder="0" ref="iframeId"></iframe>
</div>
<div class="right-menu-margin" v-show="modeView === 'mu' ">
<div class="right-menu-title">目录</div>
<div class="right-menu-content">
<div
:class="[
'right-menu-item',
'level' + item.level,
{ active: item.slug === hashText }
]"
v-for="(item, i) in headers"
:key="i"
>
<a :href="'#' + item.slug">{{ item.title }}</a>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
headers: [],
hashText: '',
iframeUrl: 'https://miren123.gitee.io/colorui-h5/#/',
modeView: 'h5',
showModeBox: false,
modeList: [
{
name: '视图模式',
icon: 'icon-rijianmoshi',
KEY: 'h5'
},
{
name: '目录模式',
icon: 'icon-yejianmoshi',
KEY: 'mu'
}
],
}
},
mounted() {
this.getHeadersData()
this.getHashText()
this.getIframeUrl()
},
watch: {
$route() {
this.headers = this.$page.headers
this.getHashText()
this.getIframeUrl()
},
},
methods: {
toggleMode (key) {
this.modeView = key
},
getIframeUrl() {
const path = this.$page.path.indexOf('base') > -1 ? '/' : this.$page.path
this.iframeUrl = 'https://miren123.gitee.io/colorui-h5/#' + path || ''
},
getHeadersData() {
this.headers = this.$page.headers
},
getHashText() {
this.hashText = decodeURIComponent(window.location.hash.slice(1))
}
}
}
</script>
<style lang='stylus'>
.views-switch
position fixed
top calc(var(--navbar-height) + 50px)
right 31px
width 2.2rem
height 2.2rem
line-height 2.2rem
border-radius 50%
box-shadow 0 2px 6px rgba(0, 0, 0, 0.15)
margin-top 0.9rem
text-align center
cursor pointer
transition all 0.5s
background var(--blurBg)
z-index 9
&:hover
background $accentColor
box-shadow 0 0 15px $accentColor
&:before
color #fff
.select-box
margin 0
padding 0.8rem 0
position absolute
bottom -2rem
right 2rem
background var(--mainBg)
border 1px solid var(--borderColor)
width 120px
border-radius 6px
box-shadow 0 0 15px rgba(255, 255, 255, 0.2)
li
list-style none
line-height 2rem
font-size 0.95rem
&:hover
color $accentColor
&.active
background-color rgba(150, 150, 150, 0.2)
color $accentColor
.docs-box
position fixed
top calc(var(--navbar-height) + 50px)
right 72px
width 320px
height 627px
z-index -1
background-image: url(https://www.uviewui.com/common/iPhone13.png);
background-repeat: no-repeat;
background-size: 100%;
padding: 48px 13px 25px;
iframe
display block
width 100%
height 627px
border-radius: 10px 10px 22px 22px
.theme-style-line
.right-menu-wrapper
.right-menu-margin
border-left 1px solid var(--borderColor)
.right-menu-wrapper
width $rightMenuWidth
float right
margin-right -($rightMenuWidth + 55px)
// margin-top -($navbarHeight *2 + 1.5rem)
position sticky
top 0
font-size 0.8rem
.right-menu-margin
margin-top: ($navbarHeight + 1rem)
border-radius 3px
overflow hidden
.right-menu-title
padding 10px 15px 0 15px
background var(--mainBg)
font-size 1rem
&:after
content ''
display block
width 100%
height 1px
background var(--borderColor)
margin-top 10px
.right-menu-content
max-height 80vh
position relative
overflow hidden
background var(--mainBg)
padding 4px 3px 4px 0
&::-webkit-scrollbar
width 3px
height 3px
&::-webkit-scrollbar-track-piece
background none
&::-webkit-scrollbar-thumb:vertical
background-color hsla(0, 0%, 49%, 0.3)
&:hover
overflow-y auto
padding-right 0
.right-menu-item
padding 4px 15px
// border-left 1px solid var(--borderColor)
overflow hidden
white-space nowrap
text-overflow ellipsis
position relative
&.level2
font-size 0.8rem
&.level3
padding-left 27px
&.level4
padding-left 37px
&.level5
padding-left 47px
&.level6
padding-left 57px
&.active
&:before
content ''
position absolute
top 5px
left 0
width 3px
height 14px
background $accentColor
border-radius 0 4px 4px 0
a
color $accentColor
opacity 1
a
color var(--textColor)
opacity 0.75
display inline-block
width 100%
overflow hidden
white-space nowrap
text-overflow ellipsis
&:hover
opacity 1
&:hover
color $accentColor
.have-body-img
.right-menu-wrapper
.right-menu-margin
// padding 0.3rem 0
// background var(--sidebarBg)
// border-radius 5px
.right-menu-item
// border-color transparent
// &.active
// border-left 0.2rem solid $accentColor
// &:hover
// border-left 0.2rem solid $accentColor
</style>
+113
View File
@@ -0,0 +1,113 @@
<template>
<aside class="sidebar">
<div class="blogger" v-if="blogger">
<img :src="blogger.avatar" />
<div class="blogger-info">
<h3>{{ blogger.name }}</h3>
<div class="icons" v-if="blogger.social">
<a
:href="item.link"
:title="item.title"
:class="['iconfont', item.iconClass]"
v-for="(item, index) in blogger.social.icons"
:key="index"
target="_blank"
></a>
</div>
<span v-else>{{ blogger.slogan }}</span>
</div>
</div>
<!-- 移动端Nav -->
<NavLinks />
<slot name="top" />
<SidebarLinks :depth="0" :items="items" />
<slot name="bottom" />
</aside>
</template>
<script>
import SidebarLinks from '@theme/components/SidebarLinks.vue'
import NavLinks from '@theme/components/NavLinks.vue'
export default {
name: 'Sidebar',
components: { SidebarLinks, NavLinks },
props: ['items'],
computed: {
blogger() {
return this.$themeConfig.blogger
}
}
}
</script>
<style lang="stylus">
.sidebar
ul
padding 0
margin 0
list-style-type none
a
display inline-block
.nav-links
display none
border-bottom 1px solid var(--borderColor)
padding 0.5rem 0 0.75rem 0
a
font-weight 600
.nav-item, .repo-link
display block
line-height 1.25rem
font-size 1.1em
padding 0.5rem 0 0.5rem 1.5rem
& > .sidebar-links
padding 1.5rem 0
& > li > a.sidebar-link
font-size 1.1em
line-height 1.7
font-weight bold
& > li:not(:first-child)
margin-top 0.75rem
.blogger
display none
border-bottom 1px solid var(--borderColor)
img
width 60px
height 60px
border-radius 5px
margin 0.75rem 1rem
.blogger-info
flex 1
padding 0 0.3rem 0.3rem 0
h3
margin 0.95rem 0 0.6rem
font-size 1.1rem
.icons .iconfont
font-size 1.2rem
padding-right 0.6rem
color #777
.sidebar-slot
margin-bottom -0.5rem
font-size 0.85rem
&.sidebar-slot-top
padding 1.5rem 1.5rem 0
&.sidebar-slot-bottom
padding 0 1.5rem 1.5rem
@media (max-width $MQMobile)
.sidebar
.blogger
display flex
.nav-links
display block
.dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active::after
top calc(1rem - 2px)
& > .sidebar-links
padding 1rem 0
</style>
+64
View File
@@ -0,0 +1,64 @@
<template>
<div
class="sidebar-button"
@click="$emit('toggle-sidebar')"
title="目录"
>
<svg
class="icon"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
role="img"
viewBox="0 0 448 512"
>
<path
fill="currentColor"
d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"
class
/>
</svg>
</div>
</template>
<style lang="stylus">
.sidebar-button
cursor pointer
display none
width 1.25rem
height 1.25rem
position absolute
padding 0.6rem
top 0.6rem
left 1rem
@media (max-width $MQMobile)
display block
.icon
display block
width 1.25rem
height 1.25rem
@media (min-width ($MQMobile + 1px))
$mobileSidebarWidth = $sidebarWidth * 0.82
.sidebar-button
width 40px
height 40px
display inline-block
position fixed
left 0
top ($navbarHeight + 1rem)
text-align center
line-height 44px
margin 5px 8px
color #888
border-radius 50%
padding 0
// transition left 0.2s ease
transition all .2s
&:hover
background $accentColor
color #fff
box-shadow 0 0 6px $accentColor
.icon
display inline
width 1rem
height 1rem
</style>
+128
View File
@@ -0,0 +1,128 @@
<template>
<section
class="sidebar-group"
:class="[
{
collapsable,
'is-sub-group': depth !== 0
},
`depth-${depth}`
]"
>
<router-link
v-if="item.path"
class="sidebar-heading clickable"
:class="{
open,
active: isActive($route, item.path)
}"
:to="item.path"
@click.native="$emit('toggle')"
>
<span>{{ item.title }}</span>
<span
class="arrow"
v-if="collapsable"
:class="open ? 'down' : 'right'"
></span>
</router-link>
<p
v-else
class="sidebar-heading"
:class="{ open }"
@click="$emit('toggle')"
>
<span>{{ item.title }}</span>
<span
class="arrow"
v-if="collapsable"
:class="open ? 'down' : 'right'"
></span>
</p>
<DropdownTransition>
<SidebarLinks
class="sidebar-group-items"
:items="item.children"
v-if="open || !collapsable"
:sidebar-depth="item.sidebarDepth"
:initial-open-group-index="item.initialOpenGroupIndex"
:depth="depth + 1"
/>
</DropdownTransition>
</section>
</template>
<script>
import { isActive } from '../util'
import DropdownTransition from '@theme/components/DropdownTransition.vue'
export default {
name: 'SidebarGroup',
props: ['item', 'open', 'collapsable', 'depth'],
components: { DropdownTransition },
// ref: https://vuejs.org/v2/guide/components-edge-cases.html#Circular-References-Between-Components
beforeCreate () {
this.$options.components.SidebarLinks = require('./SidebarLinks.vue').default
},
methods: { isActive }
}
</script>
<style lang="stylus">
.sidebar-group
.sidebar-group
padding-left 0.5em
&:not(.collapsable)
.sidebar-heading:not(.clickable)
cursor auto
color inherit
// refine styles of nested sidebar groups
&.is-sub-group
padding-left 0
& > .sidebar-heading
font-size 1.01em
line-height 1.4
font-weight bold
padding-left 2rem
&:not(.clickable)
// opacity 0.9
& > .sidebar-group-items
padding-left 1rem
& > li > .sidebar-link
font-size 0.98em
border-left none
&.depth-2
& > .sidebar-heading
border-left none
.sidebar-heading
color var(--textColor)
transition color 0.15s ease
cursor pointer
font-size 1.1em
font-weight bold
// text-transform uppercase
padding 0.35rem 1.5rem 0.35rem 1.25rem
width 100%
box-sizing border-box
margin 0
border-left 0.25rem solid transparent
&.open, &:hover
color inherit
.arrow
position relative
top -0.12em
left 0.5em
&.clickable
&.active
font-weight 600
color $accentColor
border-left-color $accentColor
&:hover
color $accentColor
.sidebar-group-items
transition height 0.1s ease-out
font-size 0.95em
overflow hidden
</style>
+130
View File
@@ -0,0 +1,130 @@
<script>
import { isActive, hashRE, groupHeaders } from '../util'
export default {
functional: true,
props: ['item', 'sidebarDepth'],
render(h,
{
parent: {
$page,
$site,
$route,
$themeConfig,
$themeLocaleConfig
},
props: {
item,
sidebarDepth
}
}) {
// use custom active class matching logic
// due to edge case of paths ending with / + hash
const selfActive = isActive($route, item.path)
// for sidebar: auto pages, a hash link should be active if one of its child
// matches
const active = item.type === 'auto'
? selfActive || item.children.some(c => isActive($route, item.basePath + '#' + c.slug))
: selfActive
const link = item.type === 'external'
? renderExternal(h, item.path, item.title || item.path)
: renderLink(h, item.path, item.title || item.path, active)
const maxDepth = [
$page.frontmatter.sidebarDepth,
sidebarDepth,
$themeLocaleConfig.sidebarDepth,
$themeConfig.sidebarDepth,
1
].find(depth => depth !== undefined)
const displayAllHeaders = $themeLocaleConfig.displayAllHeaders
|| $themeConfig.displayAllHeaders
if (item.type === 'auto') {
return [link, renderChildren(h, item.children, item.basePath, $route, maxDepth)]
} else if ((active || displayAllHeaders) && item.headers && !hashRE.test(item.path)) {
const children = groupHeaders(item.headers)
return [link, renderChildren(h, children, item.path, $route, maxDepth)]
} else {
return link
}
}
}
function renderLink(h, to, text, active) {
return h('router-link', {
props: {
to,
activeClass: '',
exactActiveClass: ''
},
class: {
active,
'sidebar-link': true
}
}, text)
}
function renderChildren(h, children, path, route, maxDepth, depth = 1) {
if (!children || depth > maxDepth) return null
return h('ul', { class: 'sidebar-sub-headers' }, children.map(c => {
const active = isActive(route, path + '#' + c.slug)
return h('li', { class: 'sidebar-sub-header level' + c.level }, [
renderLink(h, path + '#' + c.slug, c.title, active),
renderChildren(h, c.children, path, route, maxDepth, depth + 1)
])
}))
}
function renderExternal(h, to, text) {
return h('a', {
attrs: {
href: to,
target: '_blank',
rel: 'noopener noreferrer'
},
class: {
'sidebar-link': true
}
}, [text, h('OutboundLink')])
}
</script>
<style lang="stylus">
.sidebar .sidebar-sub-headers
padding-left 1rem
font-size 0.95em
.level4
padding-left 0.2rem
.level5
padding-left 0.4rem
.level6
padding-left 0.6rem
a.sidebar-link
font-size 1em
font-weight 400
display inline-block
color var(--textColor)
border-left 0.25rem solid transparent
padding 0.35rem 1rem 0.35rem 1.25rem
line-height 1.4
width 100%
box-sizing border-box
&:hover
color $accentColor
&.active
font-weight 600
color $accentColor
border-left-color $accentColor
.sidebar-group &
padding-left 2rem
.sidebar-sub-headers &
padding-top 0.25rem
padding-bottom 0.25rem
border-left none
&.active
font-weight 500
</style>
+93
View File
@@ -0,0 +1,93 @@
<template>
<ul class="sidebar-links" v-if="items.length">
<li v-for="(item, i) in items" :key="i">
<SidebarGroup
v-if="item.type === 'group'"
:item="item"
:open="i === openGroupIndex"
:collapsable="item.collapsable || item.collapsible"
:depth="depth"
@toggle="toggleGroup(i)"
/>
<SidebarLink v-else :sidebarDepth="sidebarDepth" :item="item" />
</li>
</ul>
</template>
<script>
import SidebarGroup from '@theme/components/SidebarGroup.vue'
import SidebarLink from '@theme/components/SidebarLink.vue'
import { isActive } from '../util'
export default {
name: 'SidebarLinks',
components: { SidebarGroup, SidebarLink },
props: [
'items',
'depth', // depth of current sidebar links
'sidebarDepth', // depth of headers to be extracted
'initialOpenGroupIndex'
],
data () {
return {
openGroupIndex: this.initialOpenGroupIndex || 0
}
},
created () {
this.refreshIndex()
},
watch: {
'$route' () {
this.refreshIndex()
}
},
methods: {
refreshIndex () {
const index = resolveOpenGroupIndex(
this.$route,
this.items
)
if (index > -1) {
this.openGroupIndex = index
}
},
toggleGroup (index) {
this.openGroupIndex = index === this.openGroupIndex ? -1 : index
},
isActive (page) {
return isActive(this.$route, page.regularPath)
}
}
}
function resolveOpenGroupIndex (route, items) {
for (let i = 0; i < items.length; i++) {
const item = items[i]
if (descendantIsActive(route, item)) {
return i
}
}
return -1
}
function descendantIsActive (route, item) {
if (item.type === 'group') {
return item.children.some(child => {
if (child.type === 'group') {
return descendantIsActive(route, child)
} else {
return child.type === 'page' && isActive(route, child.path)
}
})
}
return false
}
</script>
+110
View File
@@ -0,0 +1,110 @@
<template>
<div class="tags-wrapper card-box">
<router-link
to="/tags/"
class="title iconfont icon-biaoqian1"
title="全部标签"
>{{ length === 'all' ? '全部标签' : '热门标签' }}</router-link>
<div class="tags">
<template v-for="(item, index) in tags">
<router-link
:to="`/tags/?tag=${encodeURIComponent(item.key)}`"
:key="index"
:style="tagStyleList[index]"
:class="{active: item.key === tag}"
>{{item.key}}</router-link>
<span :key="index+tags.length" />
</template>
<router-link
to="/tags/"
v-if="length !== 'all' && tagsData.length > length"
>更多...</router-link>
</div>
</div>
</template>
<script>
export default {
props: {
tag: {
type: String,
default: ''
},
tagsData: {
type: Array,
default: []
},
length: {
type: [String, Number],
default: 'all'
}
},
data () {
return {
tagBgColor: ['#11a8cd', '#F8B26A', '#67CC86', '#E15B64', '#F47E60', '#849B87'],
tagStyleList: []
}
},
created () {
for (let i = 0, tagH = this.tags.length; i < tagH; i++) {
this.tagStyleList.push(this.getTagStyle())
}
},
computed: {
tags () {
if (this.length === 'all') {
return this.tagsData
} else {
return this.tagsData.slice(0, this.length)
}
}
},
methods: {
getTagStyle () {
const tagBgColor = this.tagBgColor
const randomColor = tagBgColor[Math.floor(Math.random() * tagBgColor.length)]
return `background: ${randomColor};--randomColor:${randomColor};`
}
}
}
</script>
<style lang='stylus'>
.tags-wrapper
padding 0 .95rem
.title
color var(--textColor)
opacity 0.9
font-size 1.2rem
&::before
margin-right 0.3rem
.tags
text-align justify
padding 0.8rem 0.5rem 0.5rem 0.5rem
margin 0 -0.5rem -0.5rem -0.5rem
a
opacity 0.8
display inline-block
padding 0.2rem 0.4rem
transition all 0.4s
background-color var(--textColor)
color var(--mainBg)
border-radius 3px
margin 0 0.3rem 0.5rem 0
min-width 2rem
height 1rem
line-height 1rem
font-size 0.8rem
text-align center
@media (max-width $MQMobile)
font-weight 400
&:hover
opacity 1
transform scale(1.1)
&.active
box-shadow 0 5px 10px -5px var(--randomColor, rgba(0, 0, 0, 0.15))
transform scale(1.22)
opacity 1
&:hover
text-decoration none
</style>
+125
View File
@@ -0,0 +1,125 @@
<template>
<div class="custom-page tags-page">
<MainLayout>
<template #mainLeft>
<TagsBar
v-if="$categoriesAndTags.tags.length"
:tagsData="$categoriesAndTags.tags"
:tag="tag"
/>
<PostList :currentPage="currentPage" :perPage="perPage" :tag="tag" />
<Pagination
:total="total"
:perPage="perPage"
:currentPage="currentPage"
@getCurrentPage="handlePagination"
v-show="Math.ceil(total / perPage) > 1"
/>
</template>
<template #mainRight>
<TagsBar
v-if="$categoriesAndTags.tags.length"
:tagsData="$categoriesAndTags.tags"
:tag="tag"
/>
</template>
</MainLayout>
</div>
</template>
<script>
import MainLayout from '@theme/components/MainLayout'
import PostList from '@theme/components/PostList'
import Pagination from '@theme/components/Pagination'
import TagsBar from '@theme/components/TagsBar'
export default {
data() {
return {
tag: '',
total: 0, // 总长
perPage: 10, // 每页长
currentPage: 1// 当前页
}
},
components: { MainLayout, PostList, Pagination, TagsBar },
mounted() {
const queryTag = this.$route.query.tag
if (queryTag) {
this.tag = queryTag
this.total = this.$groupPosts.tags[queryTag].length
} else {
this.total = this.$sortPosts.length
}
if (this.$route.query.p) {
this.currentPage = Number(this.$route.query.p)
}
},
methods: {
handlePagination(i) { // 分页
this.currentPage = i
}
},
watch: {
'$route.query.tag'(tag) {
this.tag = tag ? decodeURIComponent(tag) : ''
if (this.tag) {
this.total = this.$groupPosts.tags[this.tag].length
} else {
this.total = this.$sortPosts.length
}
this.currentPage = 1
}
}
}
</script>
<style lang='stylus'>
.tags-page
.tags-wrapper
position sticky
top: ($navbarHeight + 0.9rem)
max-height calc(100vh - 10rem)
min-height 4.2rem
@media (max-width $MQMobile)
display none
.tags
max-height calc(100vh - 14rem)
min-height 2.2rem
overflow-x hidden
overflow-y auto
transition all 0.2s
&::-webkit-scrollbar-track-piece
background-color rgba(0, 0, 0, 0.05)
&::-webkit-scrollbar-thumb:vertical
background-color rgba(0, 0, 0, 0.15)
&:hover
&::-webkit-scrollbar-track-piece
background-color rgba(0, 0, 0, 0.1)
&::-webkit-scrollbar-thumb:vertical
background-color rgba(0, 0, 0, 0.25)
.tags-page
.main-left
.tags-wrapper
position relative
top 0
padding 0.9rem 1.5rem
margin-bottom 0.9rem
max-height 15rem
border-radius 0
display none
@media (max-width $MQMobile)
display block
.tags
max-height 11.5rem
.theme-style-line
.tags-page
.main-left
.tags-wrapper
@media (max-width $MQMobile)
margin-top -0.91rem
margin-bottom -1px
</style>
+162
View File
@@ -0,0 +1,162 @@
<template>
<div :class="['article-list', { 'no-article-list': isShowArticle }]">
<div class="article-title">
<router-link :to="moreArticle || '/archives/'" class="iconfont icon-bi"
>最近更新</router-link
>
</div>
<div class="article-wrapper">
<dl v-for="(item, index) in topPublishPosts" :key="index">
<dd>{{ getNum(index) }}</dd>
<dt>
<router-link :to="item.path">
<div>
{{ item.title }}
<span class="title-tag" v-if="item.frontmatter.titleTag">
{{ item.frontmatter.titleTag }}
</span>
</div>
</router-link>
<span class="date">{{ getDate(item) }}</span>
</dt>
</dl>
<dl>
<dd></dd>
<dt>
<router-link :to="moreArticle || '/archives/'" class="more"
>更多文章></router-link
>
</dt>
</dl>
</div>
</div>
</template>
<script>
export default {
name: 'UpdateArticle',
props: {
length: {
type: [String, Number],
default: 3
},
moreArticle: String
},
data() {
return {
posts: [],
currentPath: ''
}
},
created() {
this.posts = this.$site.pages
this.currentPath = this.$page.path
},
computed: {
topPublishPosts() {
return this.$sortPostsByDate ? this.$sortPostsByDate.filter(post => {
const { path } = post
return path !== this.currentPath
}).slice(0, this.length) : []
},
isShowArticle() {
const { frontmatter } = this.$page
return !(frontmatter.article !== false)
}
},
methods: {
getNum(index) {
return index < 9 ? '0' + (index + 1) : index + 1
},
getDate(item) {
return item.frontmatter.date ? item.frontmatter.date.split(" ")[0].slice(5, 10) : ''
}
},
watch: {
$route() {
this.currentPath = this.$page.path
}
}
}
</script>
<style lang='stylus'>
// @require '../styles/wrapper.styl'
.article-list
// @extend $wrapper
padding 1rem 2rem
@media (max-width $MQNarrow)
padding 1rem 1.5rem
&.no-article-list
display none
.article-title
border-bottom 1px solid var(--borderColor)
font-size 1.3rem
padding 1rem
a
font-size 1.2rem
color var(--textColor)
opacity 0.9
&:before
margin-right 0.4rem
font-size 1.1rem
.article-wrapper
overflow hidden
dl
border-bottom 1px dotted var(--borderColor)
float left
display flex
padding 8px 0
margin 0
height 45px
width 100%
dd
font-size 1.1rem
color #F17229
width 50px
text-align center
margin 0
line-height 45px
dt
flex 1
display flex
a
color var(--textColor)
flex 1
display flex
height 45px
align-items center
font-weight normal
div
overflow hidden
white-space normal
text-overflow ellipsis
display -webkit-box
-webkit-line-clamp 2
-webkit-box-orient vertical
.title-tag
// height 1.1rem
// line-height 1.1rem
border 1px solid $activeColor
color $activeColor
font-size 0.8rem
padding 0 0.35rem
border-radius 0.2rem
margin-left 0rem
transform translate(0, -0.05rem)
display inline-block
&:hover
text-decoration underline
&.more
color $accentColor
.date
width 50px
margin-right 15px
color #999
text-align right
font-size 0.9rem
line-height 45px
</style>