Teek 主题自定义 原创
本博客在默认的 Teek 主题上进行了一些自定义。本文将长期更新,尽可能地记录自定义的细节。
样式
对于样式的修改较为简单,我将其分为两部分:自定义样式和覆写样式。
- 自定义样式:存放在
.vitepress/theme/styles/custom.css,自定义样式类,独立于 Teek - 覆写样式:存放在
.vitepress/theme/styles/override.css,覆写 Teek 的样式
加载
根据 VitePress 的要求,为使样式生效,需要将样式文件导入 .vitepress/theme/index.ts 中。
ts
import "./styles/custom.css"
import "./styles/override.css"清单
参考源文件(已详细注释):
配置
在 teekConfig.mts 中,有一些配置是函数回调的形式,其中可以配置自定义组件 / 数据。
清单
⭐articleTopTip
- 功能:文章页顶部添加提示
- 来源:基于 Teek 官方项目二次开发
- 备注:二次开发修改了一些逻辑,以支持通过 frontmatter 自定义提示内容。
ts
// .vitepress/teekConfig.mts
import { defineTeekConfig } from "vitepress-theme-teek/config";
export const teekConfig = defineTeekConfig({
// 文章页顶部使用 VitePress 容器添加提示
articleTopTip: (frontmatter) => {
// 在每个文章页顶部显示 VitePress 容器添加提示,使用场景如超过半年的文章自动提示文章内容可能已过时
const tip: Record<string, string> = {
type: frontmatter.topTip?.type ?? "warning",
title: frontmatter.topTip?.title ?? "",
text: frontmatter.topTip?.text ?? "文章发布较早,内容可能过时,阅读注意甄别。",
};
// 大于半年,添加提示
const longTime = 6 * 30 * 24 * 60 * 60 * 1000;
if (frontmatter.date && Date.now() - new Date(frontmatter.date).getTime() > longTime)
return tip;
},
});⭐articleBottomTip
- 功能:文章页底部添加版权声明
- 来源:自制
- 备注:版权声明需要读取 teekConfig.mts 中配置的默认作者,但回调函数中无法使用外部变量,且无法使用 import 导入 useData,从而无法读取 teekConfig。通过查看 Teek 源码后,将此处配置为传递空函数,从而启用原底部提示的前后插槽,同时由于空函数返回了空内容,原底部提示不会显示。最后,将逻辑转到原底部提示的前后插槽中实现。插槽组件实现参考 ArticleBottomTipBefore。
组件
组件的自定义有两种方式:DOM 插入和插槽组件。
加载
两种方式使用统一的加载方法(参考 Teek 官方项目)。
首先,在 .vitepress/theme/components/ 中创建入口组件 TeekLayoutProvider.vue,并写入代码。
vue
<template>
<Teek.Layout>
此行留空,后续向指定插槽添加组件时使用。
</Teek.Layout>
</template>
<script setup lang="ts">
import Teek from "vitepress-theme-teek";
// 后续向 DOM 插入元素时在这里编写代码。
</script>
<style scoped lang="scss">
</style>然后,在 .vitepress/theme/index.ts 中导入入口组件。
ts
import Teek from "vitepress-theme-teek";
import TeekLayoutProvider from "./components/TeekLayoutProvider.vue";
export default {
extends: Teek,
Layout: TeekLayoutProvider,
};清单
⭐useRibbon.ts
- 功能:彩带背景
- 方式:DOM 插入
- 来源:基于 Teek 官方项目二次开发
- 使用:复制 useRibbon.ts 到
.vitepress/theme/composables/中,然后在TeekLayoutProvider.vue中使用。 - 备注:二次开发对透明度方面的代码进行了微调。
ts
import { useData } from "vitepress";
import { nextTick, watch } from "vue";
import { useRibbon } from "../composables/useRibbon";
const { frontmatter } = useData();
// 彩带背景
const { start: startRibbon, stop: stopRibbon } = useRibbon({ alpha: 0.4, immediate: false, clickReRender: true, });
const watchRibbon = async (layout: string) => {
const isHome = layout === "home";
const isDoc = [undefined, "doc"].includes(layout);
await nextTick();
// 博客类风格的首页显示彩带 & 文章页显示彩带
if (isHome || isDoc) startRibbon();
else stopRibbon();
};
watch(frontmatter, newVal => setTimeout(() => watchRibbon(newVal.layout), 700), { immediate: true, flush: "post", });⭐useRuntime.ts
- 功能:首页页脚显示网站运行时间
- 方式:DOM 插入
- 来源:Teek 官方项目
- 使用:复制 useRuntime.ts 到
.vitepress/theme/composables/中,然后在TeekLayoutProvider.vue中使用。
ts
import { useData } from "vitepress";
import { nextTick, watch } from "vue";
import { useRuntime } from "../composables/useRuntime";
const { theme, frontmatter } = useData();
// 页脚运行时间
const { start: startRuntime, stop: stopRuntime } = useRuntime(theme.value.docAnalysis.createTime, {
prefix: `<span style="width: 16px; display: inline-block; vertical-align: -3px; margin-right: 3px;">${clockIcon}</span>小破站已运行 `,
});
const watchRuntime = async (layout: string) => {
const isHome = layout === "home";
await nextTick();
// 博客类风格的首页显示运行时间
if (isHome) startRuntime();
else stopRuntime();
};
watch(frontmatter, newVal => setTimeout(() => watchRuntime(newVal.layout), 700), { immediate: true, flush: "post", });另外,还需要在 teekConfig.mts 中配置运行时间的挂载点。
ts
import { defineTeekConfig } from "vitepress-theme-teek/config";
export const teekConfig = defineTeekConfig({
// 页脚配置
footerInfo: {
// 自定义 HTML 片段
customHtml: `<span id="runtime"></span>`,
}
});⭐useDocBgImage.ts
- 功能:文章页背景图
- 方式:DOM 插入
- 来源:自制
- 使用:复制 useDocBgImage.ts 到
.vitepress/theme/composables/中,然后在TeekLayoutProvider.vue中使用。
ts
import { useData } from "vitepress";
import { nextTick, watch } from "vue";
import { useRuntime } from "../composables/useRuntime";
const { frontmatter } = useData();
// 文档页背景图片
const { switchDocBgImage } = useDocBgImage({});
const watchDocBgImage = async (layout: string) => {
const isDoc = [undefined, "doc"].includes(layout); // 首页 & 文章页
const showAside = frontmatter.value.aside !== false; // 文档风文章页 & 博客风文章页
await nextTick();
// 切换文档页背景图片
switchDocBgImage(isDoc && showAside && frontmatter.value.coverImg ? frontmatter.value.coverImg : '');
};
watch(frontmatter, newVal => setTimeout(() => watchDocBgImage(newVal.layout), 0), { immediate: true, flush: "post", });⭐HomeBannerContentAfter
- 功能:标注首页 Banner 背景图来源
- 方式:插槽组件
- 来源:自制
- 使用:复制 HomeBannerContentAfter.vue 到
.vitepress/theme/components/中,然后在TeekLayoutProvider.vue中使用。
html
<template>
<Teek.Layout>
<template #teek-home-banner-content-after>
<HomeBannerContentAfter />
</template>
</Teek.Layout>
</template>
<script setup lang="ts">
import HomeBannerContentAfter from "./HomeBannerContentAfter.vue";
</script>⭐ArticleBannerInfoBottom
- 功能:标注文章页 Banner 背景图来源
- 方式:插槽组件
- 来源:自制
- 使用:复制 ArticleBannerInfoBottom.vue 到
.vitepress/theme/components/中,然后在TeekLayoutProvider.vue中使用。导入方式同 HomeBannerContentAfter。
⭐ArticleBottomTipBefore
- 功能:文章页底部标注文章版权及背景图来源
- 方式:插槽组件
- 来源:自制
- 使用:复制 ArticleBottomTipBefore.vue 到
.vitepress/theme/components/中,然后在TeekLayoutProvider.vue中使用。导入方式同 HomeBannerContentAfter。 - 备注:此组件需要考虑文章切换后的参数刷新;另如果直接读取 URL,会存在编译时 SSR 错误。均已解决。
⭐HomeCardMyAvatarBefore
- 功能:标注首页个人卡片背景图来源
- 方式:插槽组件
- 来源:自制
- 使用:复制 HomeCardMyAvatarBefore.vue 到
.vitepress/theme/components/中,然后在TeekLayoutProvider.vue中使用。导入方式同 HomeBannerContentAfter。
⭐ContributeHeatmapChart
- 功能:贡献热力图
- 方式:插槽组件
- 来源:基于 Teek 官方项目二次开发
- 使用:
- 安装 Echarts 依赖:
pnpm add -D echarts - 复制 ContributeHeatmapChart.vue 到
.vitepress/theme/components/ - 使用方式参考 ArchivesTopBefore 或 HomeCardTagAfter
- 安装 Echarts 依赖:
- 备注:二次开发增加了以下特性:
- 根据屏幕宽度动态显示最合适宽度的日期范围(多端适配)
- 热力图 Tooltip 适配暗色模式
- 组件化,可复用
- 其他样式优化
⭐ArchivesTopBefore
- 功能:归档页贡献热力图
- 方式:插槽组件
- 来源:自制
- 使用:复制 ArchivesTopBefore.vue 到
.vitepress/theme/components/中,然后在TeekLayoutProvider.vue中使用。 - 备注:该插槽所用的贡献热力图组件参考 ContributeHeatmapChart。
⭐HomeCardTagAfter
- 功能:首页贡献热力图卡片
- 方式:插槽组件
- 来源:自制
- 使用:复制 HomeCardTagAfter.vue 到
.vitepress/theme/components/中,然后在TeekLayoutProvider.vue中使用。 - 备注:该插槽所用的贡献热力图组件参考 ContributeHeatmapChart。
⭐NotFound
- 功能:自定义 404 页面
- 方式:插槽组件
- 来源:基于 VitePress 官方项目二次开发
- 使用:复制 NotFound.vue 到
.vitepress/theme/components/中,然后在TeekLayoutProvider.vue中使用。导入方式同 HomeBannerContentAfter。