chore: 完成归档页面开发
@ -5,3 +5,4 @@ dist/
|
||||
!.vuepress/
|
||||
!.*.js
|
||||
scripts/
|
||||
LICENSE
|
||||
|
||||
@ -11,7 +11,7 @@ export default defineUserConfig<PlumeThemeOptions>({
|
||||
themeConfig: {
|
||||
logo: 'https://pengzhanbo.cn/g.gif',
|
||||
avatar: {
|
||||
url: 'https://via.placeholder.com/300?text=Profile+Photo',
|
||||
url: '/images/blogger.jpg',
|
||||
name: 'Plume Theme',
|
||||
description: 'The Theme for Vuepress 2.0',
|
||||
},
|
||||
@ -37,49 +37,25 @@ export default defineUserConfig<PlumeThemeOptions>({
|
||||
},
|
||||
darkMode: true,
|
||||
navbar: [
|
||||
{ text: '首页', link: '/' },
|
||||
{
|
||||
text: '分类',
|
||||
link: '/category/',
|
||||
},
|
||||
{
|
||||
text: '标签',
|
||||
link: '/tag/',
|
||||
},
|
||||
{
|
||||
text: '笔记',
|
||||
children: [
|
||||
// {
|
||||
// text: '技术',
|
||||
// children: [{ text: '《typescript学习笔记》', link: '/' }],
|
||||
// },
|
||||
// {
|
||||
// text: '技术',
|
||||
// children: [{ text: '《typescript学习笔记》', link: '/' }],
|
||||
// },
|
||||
{
|
||||
text: 'typescript',
|
||||
link: '/note/typescript/',
|
||||
},
|
||||
{
|
||||
text: '标签',
|
||||
link: '/tag/',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
footer: {
|
||||
copyright: 'Copyright © 2022-present pengzhanbo',
|
||||
content: '',
|
||||
},
|
||||
themePlugins: {
|
||||
caniuse: {
|
||||
mode: 'embed',
|
||||
},
|
||||
search: {
|
||||
// hotKeys: ['s', '/'],
|
||||
// maxSuggestions: 5,
|
||||
// isSearchable: (page) => page.path !== '/',
|
||||
// getExtraFields: () => [],
|
||||
locales: {
|
||||
'/': {
|
||||
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>
|
||||
<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>
|
||||
<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>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { onBeforeRouteUpdate, useRouter } from 'vue-router'
|
||||
import { computed } from 'vue'
|
||||
import { useThemeLocaleData } from '../composables'
|
||||
|
||||
const themeLocale = useThemeLocaleData()
|
||||
const router = useRouter()
|
||||
|
||||
const footer = computed(() => {
|
||||
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>
|
||||
<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 -->
|
||||
<div
|
||||
v-if="footer.content"
|
||||
@ -50,12 +25,14 @@ onBeforeRouteUpdate(() => setStyle())
|
||||
</template>
|
||||
<style lang="scss">
|
||||
.theme-plume-footer {
|
||||
width: 100%;
|
||||
padding: 1.25rem;
|
||||
margin-top: 4rem;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
padding: 1.25rem;
|
||||
background-color: var(--c-bg-container);
|
||||
box-shadow: var(--shadow-footer);
|
||||
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 './category'
|
||||
export * from './archive'
|
||||
|
||||
@ -3,7 +3,12 @@ import { isLinkHttp, isString } from '@vuepress/shared'
|
||||
import type { ComputedRef } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
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 { useNavLink } from './navLink'
|
||||
import { useThemeLocaleData } from './themeData'
|
||||
@ -49,7 +54,7 @@ export const useNavbarSelectLanguage = (): ComputedRef<ResolveNavbarItem[]> => {
|
||||
) {
|
||||
link = targetLocalePage
|
||||
} else {
|
||||
link = targetThemeLocale.home ?? targetLocalPath
|
||||
link = (targetThemeLocale.home as NavLink)?.link ?? targetLocalPath
|
||||
}
|
||||
}
|
||||
return { text, link }
|
||||
@ -111,5 +116,13 @@ const resolveNavbarItem = (
|
||||
|
||||
export const useNavbarConfig = (): ComputedRef<ResolveNavbarItem[]> => {
|
||||
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 { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useThemeLocaleData } from '../composables'
|
||||
|
||||
const route = useRoute()
|
||||
const frontmatter = usePageFrontmatter()
|
||||
const themeLocale = useThemeLocaleData()
|
||||
const isHome = computed(() => {
|
||||
return route.path === '/' && frontmatter.value.home
|
||||
})
|
||||
@ -20,6 +22,10 @@ const pageType = computed(() => {
|
||||
return (frontmatter.value.pageType as string) || ''
|
||||
})
|
||||
|
||||
const footer = computed(() => {
|
||||
return themeLocale.value.footer
|
||||
})
|
||||
|
||||
const pageMap = {
|
||||
category: Category,
|
||||
archive: Archive,
|
||||
@ -27,7 +33,7 @@ const pageMap = {
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="plume-theme">
|
||||
<div class="plume-theme" :class="footer ? 'bottom' : ''">
|
||||
<slot name="navbar">
|
||||
<Navbar>
|
||||
<template #before>
|
||||
|
||||
@ -6,5 +6,6 @@
|
||||
@use 'transition';
|
||||
@use 'icons';
|
||||
|
||||
@use 'layout';
|
||||
@use 'code';
|
||||
@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'
|
||||
|
||||
export const defaultLocaleOption: Partial<PlumeThemeLocaleOptions> = {
|
||||
home: { text: '首页', link: '/' },
|
||||
article: '/article',
|
||||
tag: { text: '标签', link: '/tag' },
|
||||
category: { text: '分类', link: '/category' },
|
||||
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
|
||||
*/
|
||||
|
||||