feat(theme): 更新主题样式
1. 新增首页首屏banner;2. 首页首屏个人信息;3. 新增文章列表banner;4. 更新backToTOP 新增首页大图banner 首页首屏首屏个人信息 文章列表新增文章列表banner 更新backToTop按钮样式;
This commit is contained in:
parent
c330ced690
commit
335d273d2c
@ -6,7 +6,7 @@ const packages = fs.readdirSync(path.resolve(__dirname, 'packages'))
|
||||
module.exports = {
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
rules: {
|
||||
'scope-enum': [2, 'always', [...packages]],
|
||||
'scope-enum': [2, 'always', ['docs', ...packages]],
|
||||
'footer-max-line-length': [0],
|
||||
},
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 276 KiB |
BIN
docs/.vuepress/public/images/home-bg.jpg
Normal file
BIN
docs/.vuepress/public/images/home-bg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 275 KiB |
@ -7,8 +7,7 @@ tags:
|
||||
- html
|
||||
- css
|
||||
- develop
|
||||
top: false
|
||||
type: null
|
||||
banner: /images/big-banner.jpg
|
||||
---
|
||||
|
||||
在日常移动端前端应用开发中,经常遇到一个问题就是 1px的线在移动端 Retina屏下的渲染并未达到预期。以下总几种不同场景下的 1px解决方案。
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
---
|
||||
home: true
|
||||
# banner: https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic1.win4000.com%2Fwallpaper%2F2019-08-20%2F5d5bb3ec573e4.jpg&refer=http%3A%2F%2Fpic1.win4000.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1651329706&t=5dac9f133df18a9cdcef5003e33f0b03
|
||||
# mobileBanner: https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg9.51tietu.net%2Fpic%2F2019-091215%2Fg1s4d0voiqog1s4d0voiqo.jpg&refer=http%3A%2F%2Fimg9.51tietu.net&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1651329706&t=b0bc39d9dc448a3e77c6f5f0b544f516
|
||||
banner: /images/big-banner.jpg
|
||||
motto: 世间的美好总是不期而遇,恬静而自然。
|
||||
---
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
import { usePageFrontmatter } from '@vuepress/client'
|
||||
import { debounce } from 'ts-debounce'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { getScrollTop, scrollTo } from '../utils'
|
||||
import { BackTopIcon } from './icons'
|
||||
|
||||
const frontmatter = usePageFrontmatter()
|
||||
|
||||
const opacity = ref<number>(0)
|
||||
const MAX_TOP = 300
|
||||
|
||||
@ -11,8 +14,20 @@ const canShow = debounce((): void => {
|
||||
opacity.value = getScrollTop(document) >= MAX_TOP ? 1 : 0
|
||||
})
|
||||
|
||||
const top = computed(() => {
|
||||
if (__VUEPRESS_SSR__) return 0
|
||||
if (
|
||||
frontmatter.value.home &&
|
||||
(frontmatter.value.banner || frontmatter.value.mobileBanner)
|
||||
) {
|
||||
return document.documentElement.clientHeight - 58
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
})
|
||||
|
||||
const scrollToTop = (): void => {
|
||||
scrollTo(document, 0)
|
||||
scrollTo(document, top.value)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@ -30,7 +45,7 @@ onMounted(() => {
|
||||
.btn-back-top {
|
||||
position: fixed;
|
||||
right: 3rem;
|
||||
bottom: 4.35rem;
|
||||
bottom: 2.1rem;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
text-align: center;
|
||||
|
||||
@ -3,13 +3,16 @@ import DropdownTransition from '@theme-plume/DropdownTransition.vue'
|
||||
import { isLinkHttp, isLinkMailto } from '@vuepress/shared'
|
||||
import type { FunctionalComponent, Ref } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import { useThemeLocaleData } from '../composables'
|
||||
import { usePostStat, useThemeLocaleData } from '../composables'
|
||||
import {
|
||||
EmailIcon,
|
||||
FacebookIcon,
|
||||
FolderIcon,
|
||||
GithubIcon,
|
||||
LinkedinIcon,
|
||||
PostIcon,
|
||||
QQIcon,
|
||||
TagIcon,
|
||||
TwitterIcon,
|
||||
WeiBoIcon,
|
||||
ZhiHuIcon,
|
||||
@ -64,6 +67,10 @@ const useSocialList = (): SocialRef => {
|
||||
return list
|
||||
}
|
||||
const socialList = useSocialList()
|
||||
|
||||
const postStat = usePostStat()
|
||||
|
||||
console.log(postStat)
|
||||
</script>
|
||||
<template>
|
||||
<DropdownTransition>
|
||||
@ -83,6 +90,20 @@ const socialList = useSocialList()
|
||||
<Component :is="item.icon" />
|
||||
</a>
|
||||
</p>
|
||||
<div class="post-stat">
|
||||
<div class="post-stat-item">
|
||||
<PostIcon />
|
||||
<span>{{ postStat.postTotal }}</span>
|
||||
</div>
|
||||
<div class="post-stat-item">
|
||||
<FolderIcon />
|
||||
<span>{{ postStat.categoryTotal }}</span>
|
||||
</div>
|
||||
<div class="post-stat-item">
|
||||
<TagIcon />
|
||||
<span>{{ postStat.tagTotal }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</DropdownTransition>
|
||||
</template>
|
||||
@ -131,5 +152,30 @@ const socialList = useSocialList()
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.post-stat {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
border-top: 1px solid var(--c-border);
|
||||
margin-top: 0.75rem;
|
||||
padding-top: 1rem;
|
||||
|
||||
.post-stat-item {
|
||||
text-align: center;
|
||||
color: var(--c-text-quote);
|
||||
.icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
color: var(--c-text-lightest);
|
||||
}
|
||||
span {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -2,15 +2,19 @@
|
||||
import { usePageFrontmatter, withBase } from '@vuepress/client'
|
||||
import { isLinkHttp } from '@vuepress/shared'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import type { PlumeThemeHomeFrontmatter } from '../../shared'
|
||||
import type {
|
||||
PlumeThemeHomeFrontmatter,
|
||||
PlumeThemeLocaleOptions,
|
||||
} from '../../shared'
|
||||
import { useThemeLocaleData } from '../composables'
|
||||
import { scrollTo } from '../utils'
|
||||
import { ArrowBottomIcon } from './icons'
|
||||
|
||||
const frontmatter = usePageFrontmatter<PlumeThemeHomeFrontmatter>()
|
||||
const MOBILE_WIDTH = 716
|
||||
|
||||
const bannerImg = ref(frontmatter.value.banner || '')
|
||||
const hasBanner = computed(
|
||||
() => !!(frontmatter.value.banner || frontmatter.value.mobileBanner)
|
||||
)
|
||||
const hasBanner = computed(() => !!bannerImg.value)
|
||||
const bannerStyle = computed(() => {
|
||||
if (!hasBanner.value) return ''
|
||||
const url = isLinkHttp(bannerImg.value)
|
||||
@ -37,21 +41,112 @@ onMounted(() => {
|
||||
window.addEventListener('resize', handleResize, false)
|
||||
window.addEventListener('orientationchange', handleResize, false)
|
||||
})
|
||||
let screenHeight = 0
|
||||
const arrowHandle = (): void => {
|
||||
if (!screenHeight) {
|
||||
screenHeight =
|
||||
document.documentElement.clientHeight || document.body.clientHeight
|
||||
screenHeight -=
|
||||
document.querySelector<HTMLElement>('.navbar-wrapper')?.offsetHeight || 0
|
||||
}
|
||||
scrollTo(document, screenHeight)
|
||||
}
|
||||
|
||||
const themeLocale = useThemeLocaleData()
|
||||
const avatar = themeLocale.value.avatar || {}
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
v-if="hasBanner"
|
||||
class="home-big-banner-wrapper"
|
||||
:style="bannerStyle"
|
||||
></div>
|
||||
<div v-if="hasBanner" class="home-big-banner-wrapper" :style="bannerStyle">
|
||||
<ArrowBottomIcon @click="arrowHandle" />
|
||||
<div class="home-blogger-info">
|
||||
<div class="blogger-img">
|
||||
<img :src="avatar.url" :alt="avatar.name" />
|
||||
</div>
|
||||
<h3>{{ avatar.name }}</h3>
|
||||
<p v-if="frontmatter.motto" class="blogger-motto">
|
||||
{{ frontmatter.motto }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
.home-big-banner-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: calc(100vh - var(--navbar-height));
|
||||
background-color: transparent;
|
||||
background-position: 0 0;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
|
||||
.arrow-bottom-icon {
|
||||
position: absolute;
|
||||
bottom: 1.25rem;
|
||||
left: 50%;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
color: var(--c-home-arrow-bottom);
|
||||
animation: home-banner-arrow 1.5s ease 0.3s infinite;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.home-blogger-info {
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
.blogger-img {
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
padding: 1.25rem;
|
||||
background-color: rgba(0, 0, 0, 0.25);
|
||||
margin: auto;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
display: inline-block;
|
||||
font-size: 64px;
|
||||
max-width: var(--content-width);
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
padding: 0 1.25rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.blogger-motto {
|
||||
max-width: var(--content-width);
|
||||
font-size: 32px;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
padding: 0 1.25rem;
|
||||
border-radius: var(--p-around);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes home-banner-arrow {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
|
||||
10% {
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
95% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0.25;
|
||||
transform: translateY(-7px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -3,11 +3,11 @@ import DropdownTransition from '@theme-plume/DropdownTransition.vue'
|
||||
import PostMeta from '@theme-plume/PostMeta.vue'
|
||||
import Sidebar from '@theme-plume/Sidebar.vue'
|
||||
import { usePageData } from '@vuepress/client'
|
||||
import { computed, nextTick, onUnmounted, watchEffect } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
import type { PlumeThemePageData } from '../../shared'
|
||||
import { useDarkMode, useThemeLocaleData } from '../composables'
|
||||
import { getCssValue } from '../utils'
|
||||
import Toc from './Toc'
|
||||
|
||||
const page = usePageData<PlumeThemePageData>()
|
||||
const themeLocale = useThemeLocaleData()
|
||||
const isDarkMode = useDarkMode()
|
||||
@ -29,7 +29,7 @@ const enabledSidebar = computed(() => {
|
||||
<Sidebar v-if="enabledSidebar" />
|
||||
<div class="page-content">
|
||||
<h1>{{ page.title }}</h1>
|
||||
<PostMeta :post="page" type="post" :border="true" />
|
||||
<PostMeta :post="page" type="post" border />
|
||||
<Content />
|
||||
<div class="comment-container">
|
||||
<Comment :darkmode="isDarkMode" />
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import DropdownTransition from '@theme-plume/DropdownTransition.vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import type { PostItem } from '../../shared'
|
||||
import AutoLink from './AutoLink.vue'
|
||||
import { TopIcon } from './icons'
|
||||
@ -16,19 +17,42 @@ defineProps({
|
||||
default: 0,
|
||||
},
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const handlePost = (path: string): void => {
|
||||
router.push({ path })
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<DropdownTransition :delay="index * 0.04">
|
||||
<section :key="post.path" class="post-list-item">
|
||||
<TopIcon v-if="post.sticky" />
|
||||
<h3>
|
||||
<AutoLink :item="{ text: post.title, link: post.path }" />
|
||||
</h3>
|
||||
<PostMeta :post="post" />
|
||||
<!--eslint-disable vue/no-v-html-->
|
||||
<div v-if="post.excerpt" class="post-excerpt" v-html="post.excerpt"></div>
|
||||
<div v-if="post.excerpt" class="post-more">
|
||||
<AutoLink :item="{ text: '阅读全文 >>', link: post.path }" />
|
||||
<div>
|
||||
<TopIcon v-if="post.sticky" />
|
||||
<div
|
||||
v-if="post.banner"
|
||||
class="post-banner"
|
||||
@click="handlePost(post.path)"
|
||||
>
|
||||
<div
|
||||
:style="{
|
||||
'background-image': `url(${post.banner})`,
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
<h3>
|
||||
<AutoLink :item="{ text: post.title, link: post.path }" />
|
||||
</h3>
|
||||
<PostMeta :post="post" />
|
||||
<!--eslint-disable vue/no-v-html-->
|
||||
<div
|
||||
v-if="post.excerpt"
|
||||
class="post-excerpt"
|
||||
v-html="post.excerpt"
|
||||
></div>
|
||||
<div v-if="post.excerpt" class="post-more">
|
||||
<AutoLink :item="{ text: '阅读全文', link: post.path }" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</DropdownTransition>
|
||||
|
||||
@ -81,12 +81,20 @@ const togglePage = (currentPage: number): void => {
|
||||
flex: 1;
|
||||
|
||||
.post-list-item {
|
||||
position: relative;
|
||||
padding: 1.25rem 1.5rem;
|
||||
background-color: var(--c-bg-container);
|
||||
border-radius: var(--p-around);
|
||||
margin-bottom: 1.25rem;
|
||||
box-shadow: var(--shadow);
|
||||
> div {
|
||||
position: relative;
|
||||
padding: 1.25rem 1.5rem;
|
||||
background-color: var(--c-bg-container);
|
||||
border-radius: var(--p-around);
|
||||
margin-bottom: 1.25rem;
|
||||
box-shadow: var(--shadow);
|
||||
transition: box-shadow var(--t-color);
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
}
|
||||
|
||||
.top-icon {
|
||||
position: absolute;
|
||||
@ -98,6 +106,40 @@ const togglePage = (currentPage: number): void => {
|
||||
}
|
||||
}
|
||||
|
||||
.post-banner {
|
||||
position: relative;
|
||||
height: 300px;
|
||||
margin: -1.25rem -1.5rem 1.25rem -1.5rem;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
|
||||
> div {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
transform: scale(100%);
|
||||
transition: transform var(--t-transform);
|
||||
|
||||
&:hover {
|
||||
transform: scale(120%);
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 1.5rem;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: solid 20px;
|
||||
border-color: transparent transparent var(--c-bg-container) transparent;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
width: 100%;
|
||||
margin-top: 0;
|
||||
@ -122,6 +164,14 @@ const togglePage = (currentPage: number): void => {
|
||||
|
||||
.post-more {
|
||||
text-align: right;
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: var(--p-around);
|
||||
background-color: var(--c-bg);
|
||||
color: var(--c-brand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -24,7 +24,11 @@ watchEffect(() => {
|
||||
</script>
|
||||
<template>
|
||||
<aside class="plume-theme-sidebar-wrapper">
|
||||
<SidebarItems class="aside-navbar" :sidebar-list="aside" />
|
||||
<SidebarItems
|
||||
v-if="aside.length"
|
||||
class="aside-navbar"
|
||||
:sidebar-list="aside"
|
||||
/>
|
||||
<SidebarItems :sidebar-list="sidebarList" />
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
@ -65,10 +65,26 @@ export const ArrowRightIcon: FunctionalComponent = () =>
|
||||
)
|
||||
ArrowRightIcon.displayName = 'ArrowRightIcon'
|
||||
|
||||
export const ArrowBottomIcon: FunctionalComponent = () =>
|
||||
h(IconBase, { name: 'arrow-bottom', viewBox: '0 0 1024 1024' }, () =>
|
||||
h('path', {
|
||||
d: 'M150.001 502.111a22.487 22.487 0 0 1 13.185 4.245l348.86 250.152 348.858-250.152a22.577 22.577 0 0 1 26.28 36.665L525.14 802.656a22.577 22.577 0 0 1-26.28 0L136.816 543.02a22.577 22.577 0 0 1 13.185-40.91z m737.183-257.196L525.14 504.55a22.577 22.577 0 0 1-26.28 0L136.816 244.915a22.577 22.577 0 1 1 26.28-36.665l348.859 250.152L860.814 208.25a22.577 22.577 0 1 1 26.28 36.665z',
|
||||
})
|
||||
)
|
||||
ArrowBottomIcon.displayName = 'ArrowBottomIcon'
|
||||
|
||||
export const BackTopIcon: FunctionalComponent = () =>
|
||||
h(IconBase, { name: 'back-top', viewBox: '0 0 1024 1024' }, () =>
|
||||
h('path', {
|
||||
d: 'M832 64H192c-17.6 0-32 14.4-32 32s14.4 32 32 32h640c17.6 0 32-14.4 32-32s-14.4-32-32-32zM852.484 519.469L538.592 205.577a30.79 30.79 0 0 0-3.693-4.476c-6.241-6.241-14.556-9.258-22.899-9.09-8.343-0.168-16.658 2.849-22.899 9.09a30.778 30.778 0 0 0-3.693 4.476L171.419 519.566C164.449 525.448 160 534.228 160 544c0 0.058 0.004 0.115 0.004 0.172-0.124 8.285 2.899 16.529 9.096 22.727 6.202 6.202 14.453 9.224 22.743 9.096 0.066 0 0.131 0.005 0.197 0.005H352v320c0 35.2 28.8 64 64 64h192c35.2 0 64-28.8 64-64V576h160c0.058 0 0.115-0.004 0.172-0.004 8.285 0.124 16.529-2.899 22.727-9.096 6.198-6.198 9.22-14.442 9.096-22.727 0-0.058 0.004-0.115 0.004-0.172 0.001-9.826-4.489-18.65-11.515-24.532z',
|
||||
d: 'M725.902 498.916c18.205-251.45-93.298-410.738-205.369-475.592l-6.257-3.982-6.258 3.414c-111.502 64.853-224.711 224.142-204.8 475.59-55.751 53.476-80.214 116.623-80.214 204.8v15.36l179.2-35.27c11.378 40.39 58.596 69.973 113.21 69.973 54.613 0 101.262-29.582 112.64-68.836l180.337 36.41v-15.36c-0.569-89.885-25.031-153.6-82.489-206.507zM571.733 392.533c-33.564 31.29-87.04 28.445-118.329-5.12s-28.444-87.04 5.12-117.76c33.565-31.289 87.04-28.444 118.33 5.12s28.444 86.471-5.12 117.76z m-56.32 368.64c-35.84 0-64.284 29.014-64.284 64.285 0 35.84 54.044 182.613 64.284 182.613s64.285-146.773 64.285-182.613c0-35.271-29.014-64.285-64.285-64.285z',
|
||||
})
|
||||
)
|
||||
BackTopIcon.displayName = 'BackTopIcon'
|
||||
|
||||
export const PostIcon: FunctionalComponent = () =>
|
||||
h(IconBase, { name: 'post', viewBox: '0 0 1024 1024' }, () =>
|
||||
h('path', {
|
||||
d: 'M805.376 81.0496 188.7232 81.0496c-52.6336 0-94.8736 42.3936-94.8736 94.6176l0 664.576c0 52.2752 42.496 94.6176 94.8736 94.6176L805.376 934.8608c52.6336 0 94.8736-42.3936 94.8736-94.6176L900.2496 175.7184C900.2496 123.392 857.8048 81.0496 805.376 81.0496zM288.768 204.8c39.3216 0 71.168 31.5904 71.168 71.168 0 39.3216-31.5904 71.168-71.168 71.168-39.3216 0-71.168-31.5904-71.168-71.168C217.6 236.6464 249.1904 204.8 288.768 204.8zM506.368 741.0176 217.6 741.0176l0-47.4112L506.368 693.6064 506.368 741.0176zM671.3344 617.2672 217.6 617.2672 217.6 569.856l453.7344 0L671.3344 617.2672zM671.3344 493.568 217.6 493.568 217.6 446.1056l453.7344 0L671.3344 493.568z',
|
||||
})
|
||||
)
|
||||
PostIcon.displayName = 'PostIcon'
|
||||
|
||||
@ -9,6 +9,7 @@ export * from './sidebarIndex'
|
||||
export * from './postList'
|
||||
export * from './scrollPromise'
|
||||
export * from './asideNavbar'
|
||||
export * from './postStat'
|
||||
|
||||
export * from './tag'
|
||||
export * from './category'
|
||||
|
||||
@ -17,6 +17,9 @@ export const usePostIndex = (): PostIndexRef => {
|
||||
return ref(postList)
|
||||
}
|
||||
|
||||
export type PostTotalRef = Ref<number>
|
||||
export const postTotal: PostTotalRef = ref(0)
|
||||
|
||||
if (import.meta.hot) {
|
||||
__VUE_HMR_RUNTIME__.updatePostIndex = (data: PostIndex) => {
|
||||
postIndex.value = data
|
||||
|
||||
31
packages/theme/src/client/composables/postStat.ts
Normal file
31
packages/theme/src/client/composables/postStat.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { reactive } from 'vue'
|
||||
import { usePostAllIndex } from './postIndex'
|
||||
import { useTagList } from './tag'
|
||||
|
||||
export interface PostStatData {
|
||||
postTotal: number
|
||||
tagTotal: number
|
||||
categoryTotal: number
|
||||
}
|
||||
|
||||
export const usePostStat = (): PostStatData => {
|
||||
const data: PostStatData = Object.create(null)
|
||||
|
||||
const postIndex = usePostAllIndex()
|
||||
const tagList = useTagList()
|
||||
|
||||
data.postTotal = postIndex.value.length
|
||||
data.tagTotal = tagList.value.length
|
||||
|
||||
const categorySet = new Set()
|
||||
|
||||
postIndex.value.forEach((post) => {
|
||||
const category = post.category || []
|
||||
category.forEach((cate) => categorySet.add(cate.name))
|
||||
})
|
||||
|
||||
data.categoryTotal = categorySet.size
|
||||
|
||||
const stat = reactive<PostStatData>(data)
|
||||
return stat
|
||||
}
|
||||
@ -5,7 +5,8 @@ html.dark {
|
||||
--c-bg: #22272e;
|
||||
--c-bg-light: #2b313a;
|
||||
--c-bg-lighter: #262c34;
|
||||
--c-bg-container: #262c34;
|
||||
--c-bg-container: rgb(38, 44, 52);
|
||||
--c-bg-navbar: rgba(38, 44, 52, 0.95);
|
||||
|
||||
--c-text: #adbac7;
|
||||
--c-text-light: #96a7b7;
|
||||
@ -27,6 +28,8 @@ html.dark {
|
||||
--c-details-bg: #323843;
|
||||
|
||||
--c-hl-bg-color: #363b46;
|
||||
|
||||
--c-home-arrow-bottom: rgba(196, 205, 216, 0.75);
|
||||
}
|
||||
|
||||
html.dark .DocSearch {
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
--c-bg-light: #e5e7eb;
|
||||
--c-bg-lighter: #d1d5db;
|
||||
--c-bg-container: #fff;
|
||||
--c-bg-navbar: var(--c-bg-container);
|
||||
--c-bg-navbar: rgba(255, 255, 255, 0.9);
|
||||
--c-bg-sidebar: var(--c-bg-container);
|
||||
--c-bg-arrow: #ccc;
|
||||
|
||||
@ -47,6 +47,8 @@
|
||||
--c-badge-warning: var(--c-warning);
|
||||
--c-badge-danger: var(--c-danger);
|
||||
|
||||
--c-home-arrow-bottom: rgba(255, 255, 255, 0.75);
|
||||
|
||||
// transition
|
||||
--t-color: 0.3s ease;
|
||||
--t-transform: 0.3s ease;
|
||||
@ -85,7 +87,7 @@
|
||||
--content-width: 740px;
|
||||
|
||||
// search box vars
|
||||
--search-bg-color: var(--c-bg-container);
|
||||
--search-bg-color: transparent;
|
||||
--search-accent-color: var(--c-text-accent);
|
||||
--search-text-color: var(--c-text);
|
||||
--search-border-color: var(--c-border);
|
||||
|
||||
@ -61,6 +61,7 @@ export const preparedPostIndex = (
|
||||
article: frontmatter.article,
|
||||
category: page.data.category,
|
||||
isNote: page.data.isNote,
|
||||
banner: frontmatter.banner,
|
||||
} as PostItem
|
||||
})
|
||||
postIndex = [
|
||||
|
||||
@ -14,4 +14,5 @@ export interface PlumeThemeHomeFrontmatter extends PlumeThemeNormalFrontmatter {
|
||||
banner?: string
|
||||
mobileBanner?: string
|
||||
productList?: PlumeThemeProductList
|
||||
motto?: string
|
||||
}
|
||||
|
||||
@ -5,4 +5,6 @@ export interface PlumeThemePostFrontmatter {
|
||||
tags?: string[]
|
||||
sticky?: boolean | number
|
||||
article?: boolean
|
||||
banner?: string
|
||||
bgBanner?: string
|
||||
}
|
||||
|
||||
@ -92,21 +92,21 @@ export interface PlumeThemeLocaleData extends LocaleData {
|
||||
/**
|
||||
* 文章链接前缀
|
||||
*
|
||||
* 默认: /article/
|
||||
* @default: /article/
|
||||
*/
|
||||
article?: string
|
||||
|
||||
/**
|
||||
* 标签页链接 与 navbar配置
|
||||
*
|
||||
* 默认:{ text: '标签', link: '/tag/' }
|
||||
* @def:{ text: '标签', link: '/tag/' }
|
||||
*/
|
||||
tag?: false | NavLink
|
||||
|
||||
/**
|
||||
* 文章分类 与 navbar配置
|
||||
*
|
||||
* 默认: { text: '分类', link: '/category/ }
|
||||
* @default: { text: '分类', link: '/category/ }
|
||||
*/
|
||||
category?: false | NavLink
|
||||
|
||||
@ -115,7 +115,7 @@ export interface PlumeThemeLocaleData extends LocaleData {
|
||||
*
|
||||
* (注,由于页面样式为 timeline, 所以默认链接为 timeline )
|
||||
*
|
||||
* 默认: { text: '归档', link: '/timeline/' }
|
||||
* @default: { text: '归档', link: '/timeline/' }
|
||||
*/
|
||||
archive?: false | NavLink
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ export interface PostItem {
|
||||
article?: boolean
|
||||
category: CategoryData
|
||||
isNote?: boolean
|
||||
banner?: string
|
||||
}
|
||||
|
||||
export type PostIndex = PostItem[]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user