chore: 完成归档页面开发
@ -5,3 +5,4 @@ dist/
|
|||||||
!.vuepress/
|
!.vuepress/
|
||||||
!.*.js
|
!.*.js
|
||||||
scripts/
|
scripts/
|
||||||
|
LICENSE
|
||||||
|
|||||||
@ -11,7 +11,7 @@ export default defineUserConfig<PlumeThemeOptions>({
|
|||||||
themeConfig: {
|
themeConfig: {
|
||||||
logo: 'https://pengzhanbo.cn/g.gif',
|
logo: 'https://pengzhanbo.cn/g.gif',
|
||||||
avatar: {
|
avatar: {
|
||||||
url: 'https://via.placeholder.com/300?text=Profile+Photo',
|
url: '/images/blogger.jpg',
|
||||||
name: 'Plume Theme',
|
name: 'Plume Theme',
|
||||||
description: 'The Theme for Vuepress 2.0',
|
description: 'The Theme for Vuepress 2.0',
|
||||||
},
|
},
|
||||||
@ -37,49 +37,25 @@ export default defineUserConfig<PlumeThemeOptions>({
|
|||||||
},
|
},
|
||||||
darkMode: true,
|
darkMode: true,
|
||||||
navbar: [
|
navbar: [
|
||||||
{ text: '首页', link: '/' },
|
|
||||||
{
|
|
||||||
text: '分类',
|
|
||||||
link: '/category/',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '标签',
|
|
||||||
link: '/tag/',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
text: '笔记',
|
text: '笔记',
|
||||||
children: [
|
children: [
|
||||||
// {
|
|
||||||
// text: '技术',
|
|
||||||
// children: [{ text: '《typescript学习笔记》', link: '/' }],
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// text: '技术',
|
|
||||||
// children: [{ text: '《typescript学习笔记》', link: '/' }],
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
text: 'typescript',
|
text: 'typescript',
|
||||||
link: '/note/typescript/',
|
link: '/note/typescript/',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
text: '标签',
|
|
||||||
link: '/tag/',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
footer: {
|
footer: {
|
||||||
copyright: 'Copyright © 2022-present pengzhanbo',
|
copyright: 'Copyright © 2022-present pengzhanbo',
|
||||||
|
content: '',
|
||||||
},
|
},
|
||||||
themePlugins: {
|
themePlugins: {
|
||||||
caniuse: {
|
caniuse: {
|
||||||
mode: 'embed',
|
mode: 'embed',
|
||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
// hotKeys: ['s', '/'],
|
|
||||||
// maxSuggestions: 5,
|
|
||||||
// isSearchable: (page) => page.path !== '/',
|
|
||||||
// getExtraFields: () => [],
|
|
||||||
locales: {
|
locales: {
|
||||||
'/': {
|
'/': {
|
||||||
placeholder: '搜索',
|
placeholder: '搜索',
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 270 KiB After Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 468 KiB After Width: | Height: | Size: 263 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 62 KiB |
@ -1,3 +1,137 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import BlogInfo from '@theme-plume/BlogInfo.vue'
|
||||||
|
import DropdownTransition from '@theme-plume/DropdownTransition.vue'
|
||||||
|
import { useArchive } from '../composables'
|
||||||
|
|
||||||
|
const archiveList = useArchive()
|
||||||
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div></div>
|
<div class="archive-wrapper">
|
||||||
|
<div class="archive-container">
|
||||||
|
<DropdownTransition>
|
||||||
|
<div class="archive-content">
|
||||||
|
<div
|
||||||
|
v-for="archive in archiveList"
|
||||||
|
:key="archive.year"
|
||||||
|
class="archive-items"
|
||||||
|
>
|
||||||
|
<h2>{{ archive.year }}</h2>
|
||||||
|
<ul class="archive-list">
|
||||||
|
<li v-for="child in archive.children" :key="child.link">
|
||||||
|
<span>{{ child.date }}</span>
|
||||||
|
<RouterLink :to="child.link">{{ child.text }}</RouterLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DropdownTransition>
|
||||||
|
<BlogInfo></BlogInfo>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<style lang="scss">
|
||||||
|
@import '../styles/_mixins';
|
||||||
|
|
||||||
|
.archive-wrapper {
|
||||||
|
@include wrapper;
|
||||||
|
|
||||||
|
.archive-container {
|
||||||
|
@include container_wrapper;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 1.25rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.archive-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.archive-items {
|
||||||
|
h2 {
|
||||||
|
position: relative;
|
||||||
|
margin: 0 0 0 4rem;
|
||||||
|
border-left: solid 4px var(--c-border);
|
||||||
|
border-bottom: none;
|
||||||
|
padding: 1.25rem;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
background-color: var(--c-bg-container);
|
||||||
|
border-radius: 6px;
|
||||||
|
border: solid 2px var(--c-border-dark);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: -2px;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.archive-list {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
position: relative;
|
||||||
|
margin-left: 4rem;
|
||||||
|
border-left: solid 4px var(--c-border);
|
||||||
|
padding: 1.25rem;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background-color: var(--c-bg-container);
|
||||||
|
border-radius: 5px;
|
||||||
|
border: solid 2px var(--c-border-dark);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: -2px;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
transition: border-color var(--t-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
> span {
|
||||||
|
position: absolute;
|
||||||
|
left: -1.25rem;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-100%, -50%);
|
||||||
|
color: var(--c-text-light);
|
||||||
|
font-size: 14px;
|
||||||
|
transition: color var(--t-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
> a {
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem 1.25rem;
|
||||||
|
background-color: var(--c-bg-container);
|
||||||
|
border-radius: var(--p-around);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
color: var(--c-text);
|
||||||
|
transition: color var(--t-color), box-shadow var(--t-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
> span,
|
||||||
|
> a {
|
||||||
|
color: var(--c-text-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
> a {
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
border-color: var(--c-text-accent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -1,40 +1,15 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { onBeforeRouteUpdate, useRouter } from 'vue-router'
|
|
||||||
import { useThemeLocaleData } from '../composables'
|
import { useThemeLocaleData } from '../composables'
|
||||||
|
|
||||||
const themeLocale = useThemeLocaleData()
|
const themeLocale = useThemeLocaleData()
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const footer = computed(() => {
|
const footer = computed(() => {
|
||||||
return themeLocale.value.footer
|
return themeLocale.value.footer
|
||||||
})
|
})
|
||||||
const style = ref({})
|
|
||||||
function setStyle(): void {
|
|
||||||
if (__VUEPRESS_SSR__) return
|
|
||||||
setTimeout(() => {
|
|
||||||
if (
|
|
||||||
document.documentElement.scrollHeight <=
|
|
||||||
document.documentElement.clientHeight
|
|
||||||
) {
|
|
||||||
style.value = {
|
|
||||||
position: 'fixed',
|
|
||||||
bottom: 0,
|
|
||||||
left: 0,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
style.value = {}
|
|
||||||
}
|
|
||||||
}, 30)
|
|
||||||
}
|
|
||||||
router.beforeEach(() => {
|
|
||||||
setStyle()
|
|
||||||
})
|
|
||||||
onMounted(() => setStyle())
|
|
||||||
onBeforeRouteUpdate(() => setStyle())
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<footer v-if="footer" class="theme-plume-footer" :style="style">
|
<footer v-if="footer" class="theme-plume-footer">
|
||||||
<!-- eslint-disable vue/no-v-html -->
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
<div
|
<div
|
||||||
v-if="footer.content"
|
v-if="footer.content"
|
||||||
@ -50,12 +25,14 @@ onBeforeRouteUpdate(() => setStyle())
|
|||||||
</template>
|
</template>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.theme-plume-footer {
|
.theme-plume-footer {
|
||||||
width: 100%;
|
position: absolute;
|
||||||
padding: 1.25rem;
|
left: 0;
|
||||||
margin-top: 4rem;
|
bottom: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
padding: 1.25rem;
|
||||||
background-color: var(--c-bg-container);
|
background-color: var(--c-bg-container);
|
||||||
box-shadow: var(--shadow-footer);
|
box-shadow: var(--shadow-footer);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|||||||
38
packages/theme/src/client/composables/archive.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { ref } from 'vue'
|
||||||
|
import type { Ref } from 'vue'
|
||||||
|
import { usePostAllIndex } from './postIndex'
|
||||||
|
|
||||||
|
export interface ArchiveItem {
|
||||||
|
year: string
|
||||||
|
children: ArchiveChild[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ArchiveChild {
|
||||||
|
date: string
|
||||||
|
text: string
|
||||||
|
link: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ArchiveData = ArchiveItem[]
|
||||||
|
|
||||||
|
export type ArchiveListRef = Ref<ArchiveData>
|
||||||
|
|
||||||
|
export const useArchive = (): ArchiveListRef => {
|
||||||
|
const archiveList: ArchiveData = []
|
||||||
|
const postList = usePostAllIndex().value
|
||||||
|
postList.forEach((post) => {
|
||||||
|
const [year, month, day] = post.createTime.split('-')
|
||||||
|
let current = archiveList.find((arch) => arch.year === year)
|
||||||
|
if (!current) {
|
||||||
|
current = { year, children: [] }
|
||||||
|
archiveList.push(current)
|
||||||
|
}
|
||||||
|
current.children.push({
|
||||||
|
date: `${month}-${day}`,
|
||||||
|
text: post.title,
|
||||||
|
link: post.path,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return ref(archiveList)
|
||||||
|
}
|
||||||
@ -10,3 +10,4 @@ export * from './scrollPromise'
|
|||||||
|
|
||||||
export * from './tag'
|
export * from './tag'
|
||||||
export * from './category'
|
export * from './category'
|
||||||
|
export * from './archive'
|
||||||
|
|||||||
@ -3,7 +3,12 @@ import { isLinkHttp, isString } from '@vuepress/shared'
|
|||||||
import type { ComputedRef } from 'vue'
|
import type { ComputedRef } from 'vue'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import type { NavbarGroup, NavbarItem, ResolveNavbarItem } from '../../shared'
|
import type {
|
||||||
|
NavbarGroup,
|
||||||
|
NavbarItem,
|
||||||
|
NavLink,
|
||||||
|
ResolveNavbarItem,
|
||||||
|
} from '../../shared'
|
||||||
import { resolveRepoType } from '../utils'
|
import { resolveRepoType } from '../utils'
|
||||||
import { useNavLink } from './navLink'
|
import { useNavLink } from './navLink'
|
||||||
import { useThemeLocaleData } from './themeData'
|
import { useThemeLocaleData } from './themeData'
|
||||||
@ -49,7 +54,7 @@ export const useNavbarSelectLanguage = (): ComputedRef<ResolveNavbarItem[]> => {
|
|||||||
) {
|
) {
|
||||||
link = targetLocalePage
|
link = targetLocalePage
|
||||||
} else {
|
} else {
|
||||||
link = targetThemeLocale.home ?? targetLocalPath
|
link = (targetThemeLocale.home as NavLink)?.link ?? targetLocalPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { text, link }
|
return { text, link }
|
||||||
@ -111,5 +116,13 @@ const resolveNavbarItem = (
|
|||||||
|
|
||||||
export const useNavbarConfig = (): ComputedRef<ResolveNavbarItem[]> => {
|
export const useNavbarConfig = (): ComputedRef<ResolveNavbarItem[]> => {
|
||||||
const themeLocale = useThemeLocaleData()
|
const themeLocale = useThemeLocaleData()
|
||||||
return computed(() => (themeLocale.value.navbar || []).map(resolveNavbarItem))
|
const { navbar, home, category, archive, tag } = themeLocale.value
|
||||||
|
const config: NavbarItem[] = [
|
||||||
|
home as NavbarItem,
|
||||||
|
...((navbar || []) as unknown as NavbarItem[]),
|
||||||
|
category as NavbarItem,
|
||||||
|
tag as NavbarItem,
|
||||||
|
archive as NavbarItem,
|
||||||
|
].filter((nav) => nav)
|
||||||
|
return computed(() => config.map(resolveNavbarItem))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,9 +9,11 @@ import Tag from '@theme-plume/Tag.vue'
|
|||||||
import { usePageFrontmatter } from '@vuepress/client'
|
import { usePageFrontmatter } from '@vuepress/client'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
import { useThemeLocaleData } from '../composables'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const frontmatter = usePageFrontmatter()
|
const frontmatter = usePageFrontmatter()
|
||||||
|
const themeLocale = useThemeLocaleData()
|
||||||
const isHome = computed(() => {
|
const isHome = computed(() => {
|
||||||
return route.path === '/' && frontmatter.value.home
|
return route.path === '/' && frontmatter.value.home
|
||||||
})
|
})
|
||||||
@ -20,6 +22,10 @@ const pageType = computed(() => {
|
|||||||
return (frontmatter.value.pageType as string) || ''
|
return (frontmatter.value.pageType as string) || ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const footer = computed(() => {
|
||||||
|
return themeLocale.value.footer
|
||||||
|
})
|
||||||
|
|
||||||
const pageMap = {
|
const pageMap = {
|
||||||
category: Category,
|
category: Category,
|
||||||
archive: Archive,
|
archive: Archive,
|
||||||
@ -27,7 +33,7 @@ const pageMap = {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="plume-theme">
|
<div class="plume-theme" :class="footer ? 'bottom' : ''">
|
||||||
<slot name="navbar">
|
<slot name="navbar">
|
||||||
<Navbar>
|
<Navbar>
|
||||||
<template #before>
|
<template #before>
|
||||||
|
|||||||
@ -6,5 +6,6 @@
|
|||||||
@use 'transition';
|
@use 'transition';
|
||||||
@use 'icons';
|
@use 'icons';
|
||||||
|
|
||||||
|
@use 'layout';
|
||||||
@use 'code';
|
@use 'code';
|
||||||
@use 'toc';
|
@use 'toc';
|
||||||
|
|||||||
9
packages/theme/src/client/styles/layout.scss
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
.plume-theme {
|
||||||
|
position: relative;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding-bottom: 2rem;
|
||||||
|
|
||||||
|
&.bottom {
|
||||||
|
padding-bottom: 6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,10 @@
|
|||||||
import type { PlumeThemeLocaleOptions } from '../shared'
|
import type { PlumeThemeLocaleOptions } from '../shared'
|
||||||
|
|
||||||
export const defaultLocaleOption: Partial<PlumeThemeLocaleOptions> = {
|
export const defaultLocaleOption: Partial<PlumeThemeLocaleOptions> = {
|
||||||
|
home: { text: '首页', link: '/' },
|
||||||
article: '/article',
|
article: '/article',
|
||||||
tag: { text: '标签', link: '/tag' },
|
tag: { text: '标签', link: '/tag' },
|
||||||
category: { text: '分类', link: '/category' },
|
category: { text: '分类', link: '/category' },
|
||||||
notes: { link: '/note', dir: 'notes', notes: [] },
|
notes: { link: '/note', dir: 'notes', notes: [] },
|
||||||
|
archive: { link: '/timeline', text: '归档' },
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,7 +56,7 @@ export interface PlumeThemeLocaleData extends LocaleData {
|
|||||||
/**
|
/**
|
||||||
* 网站站点首页
|
* 网站站点首页
|
||||||
*/
|
*/
|
||||||
home?: string
|
home?: false | NavLink
|
||||||
/**
|
/**
|
||||||
* 网站站点logo
|
* 网站站点logo
|
||||||
*/
|
*/
|
||||||
|
|||||||