Skip to content

Commit 1068db8

Browse files
author
wuyifan
committed
fix: 🐛 tocbot render error
1 parent 703bffc commit 1068db8

6 files changed

Lines changed: 151 additions & 89 deletions

File tree

src/components/ArticleCatalog.vue

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
<!--
22
* @Author: wuyifan0203 1208097313@qq.com
33
* @Date: 2024-03-05 14:33:33
4-
* @LastEditors: wuyifan0203 1208097313@qq.com
5-
* @LastEditTime: 2024-05-28 10:02:03
6-
* @FilePath: /vitepress-theme-sakurairo/src/components/ArticleCatalog.vue
4+
* @LastEditors: wuyifan wuyifan@udschina.com
5+
* @LastEditTime: 2026-04-27 11:45:49
6+
* @FilePath: \vitepress-theme-sakurairo\src\components\ArticleCatalog.vue
77
* Copyright (c) 2024 by wuyifan0203 email: 1208097313@qq.com, All Rights Reserved.
88
-->
99
<template>
1010
<div :style="{
11-
height:tocHeight
11+
height: tocHeight
1212
}">
1313
<div id="toc-container">
1414
</div>
@@ -26,20 +26,24 @@ onMounted(() => {
2626
tocSelector: '#toc-container',
2727
contentSelector: '.article-body',
2828
headingSelector: 'h1, h2, h3, h4, h5, h6',
29+
// For headings inside relative or absolute positioned containers within content.
30+
hasInnerContainers: true,
2931
headingsOffset: 100,
3032
scrollSmoothOffset: -100,
3133
collapseDepth: 0,
3234
scrollSmooth: true,
35+
// 启用滚动时更新 URL hash
36+
enableUrlHashUpdateOnScroll: true,
3337
})
3438
})
3539
3640
defineExpose({
37-
refresh(){
38-
nextTick(()=>{
41+
refresh() {
42+
nextTick(() => {
3943
tocbot.refresh();
4044
})
4145
},
42-
updateHeight(height:number){
46+
updateHeight(height: number) {
4347
tocHeight.value = height + 'px';
4448
}
4549
})
@@ -56,9 +60,34 @@ onUnmounted(() => {
5660
#toc-container {
5761
position: sticky;
5862
top: 100px;
63+
max-height: calc(100vh - 120px);
5964
60-
&>.toc-list li {
61-
list-style: none;
65+
&>.toc-list {
66+
li {
67+
list-style: none;
68+
}
69+
70+
// tocbot 生成的列表样式
71+
.toc-list-item {
72+
margin: 5px 0;
73+
}
74+
75+
.toc-link {
76+
text-decoration: none;
77+
color: #666;
78+
transition: all 0.3s ease;
79+
display: block;
80+
padding: 4px 0;
81+
82+
&:hover {
83+
color: #ff7875;
84+
}
85+
}
86+
87+
.is-active-link {
88+
color: #ff7875;
89+
font-weight: bold;
90+
}
6291
}
6392
}
6493
</style>

src/composables/useRouter.ts

Lines changed: 72 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,64 +7,92 @@
77
* Copyright (c) 2024 by wuyifan0203 email: 1208097313@qq.com, All Rights Reserved.
88
*/
99
import { useRouter } from 'vitepress';
10+
import { watch, onMounted } from 'vue';
11+
12+
const beforeCallbackList: Function[] = [];
13+
let beforeHookInitialized = false;
1014

11-
const beforeCallbackMap: { [key: string]: Function } = {};
1215
const useBeforeRouterChange = (callback: Function) => {
13-
if (Object.keys(beforeCallbackMap).length === 0) {
16+
beforeCallbackList.push(callback);
17+
18+
if (!beforeHookInitialized) {
19+
beforeHookInitialized = true;
1420
const router = useRouter();
1521
const cacheBeforeChange = router.onBeforeRouteChange;
22+
1623
router.onBeforeRouteChange = async (to: string) => {
17-
for (const key in beforeCallbackMap) {
18-
await beforeCallbackMap[key](to);
24+
// 执行所有注册的回调
25+
for (const callback of beforeCallbackList) {
26+
try {
27+
await callback(to);
28+
} catch (error) {
29+
console.error('Error in beforeRouteChange callback:', error);
30+
}
1931
}
32+
// 执行原始钩子
2033
cacheBeforeChange && cacheBeforeChange(to);
2134
}
2235
}
23-
// 不用数组因为没法去重
24-
beforeCallbackMap[callback.toString()] = callback;
25-
26-
console.log(beforeCallbackMap, 'callbackMap');
27-
2836
}
2937

30-
const afterCallbackMap: { [key: string]: Function } = {};
31-
const useAfterRouterChange = (callback: Function) => {
32-
if (Object.keys(afterCallbackMap).length === 0) {
33-
const router = useRouter();
34-
const cacheAfterChange = router.onAfterRouteChanged;
35-
router.onAfterRouteChanged = async (to: string) => {
36-
for (const key in afterCallbackMap) {
37-
await afterCallbackMap[key](to);
38+
const afterCallbackList: Function[] = [];
39+
let afterWatchInitialized = false;
40+
let routerInstance: any = null;
41+
42+
const initRouteWatcher = () => {
43+
if (afterWatchInitialized || !routerInstance) return;
44+
45+
console.log('Initializing route watcher...');
46+
afterWatchInitialized = true;
47+
48+
// 使用 watch 监听 route.path 变化
49+
watch(
50+
() => routerInstance.route.path,
51+
async (newPath, oldPath) => {
52+
// 跳过初始加载(oldPath 为空字符串)
53+
if (!oldPath) {
54+
console.log('Initial load, skip callback execution');
55+
return;
3856
}
39-
cacheAfterChange && cacheAfterChange(to);
57+
58+
console.log('=== Route changed ===');
59+
console.log('From:', oldPath, 'To:', newPath);
60+
console.log('Callbacks count:', afterCallbackList.length);
61+
62+
// 执行所有注册的回调
63+
for (let i = 0; i < afterCallbackList.length; i++) {
64+
const cb = afterCallbackList[i];
65+
try {
66+
console.log(`Executing callback [${i}]:`, cb.name || 'anonymous');
67+
await cb(newPath);
68+
console.log(`Callback [${i}] completed`);
69+
} catch (error) {
70+
console.error(`Error in callback [${i}]:`, error);
71+
}
72+
}
73+
74+
console.log('All callbacks executed');
4075
}
76+
);
77+
78+
console.log('Route watcher initialized successfully');
79+
};
80+
81+
const useAfterRouterChange = (callback: Function) => {
82+
afterCallbackList.push(callback);
83+
console.log('Registered after route change callback, total:', afterCallbackList.length);
84+
85+
// 获取 router 实例
86+
if (!routerInstance) {
87+
routerInstance = useRouter();
88+
}
89+
90+
// 在 onMounted 中初始化 watch,确保在正确的 Vue 上下文中
91+
if (typeof window !== 'undefined') {
92+
onMounted(() => {
93+
initRouteWatcher();
94+
});
4195
}
42-
// 不用数组因为没法去重
43-
afterCallbackMap[callback.toString()] = callback;
4496
}
4597

46-
47-
4898
export { useBeforeRouterChange, useAfterRouterChange }
49-
50-
// 另一种写法,
51-
52-
// const a = {
53-
// useBeforeRouterChange(callback: Function) {
54-
// const router = useRouter();
55-
// const cacheBeforeChange = router.onBeforeRouteChange;
56-
// router.onBeforeRouteChange = async (to: string) => {
57-
// for (const func of callbackList) {
58-
// await func(to);
59-
// }
60-
// cacheBeforeChange && cacheBeforeChange(to);
61-
// }
62-
// const fn = (callback: Function) => {
63-
// callbackList.push(callback);
64-
// };
65-
// fn(callback);
66-
// this.useBeforeRouterChange = fn;
67-
// }
68-
// }
69-
70-
// export { a }

src/layout/Article.vue

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<!--
22
* @Author: wuyifan0203 1208097313@qq.com
33
* @Date: 2024-02-21 15:06:46
4-
* @LastEditors: wuyifan0203 1208097313@qq.com
5-
* @LastEditTime: 2024-05-28 11:06:53
6-
* @FilePath: /vitepress-theme-sakurairo/src/layout/Article.vue
4+
* @LastEditors: wuyifan wuyifan@udschina.com
5+
* @LastEditTime: 2026-04-27 11:41:48
6+
* @FilePath: \vitepress-theme-sakurairo\src\layout\Article.vue
77
* Copyright (c) 2024 by wuyifan0203 email: 1208097313@qq.com, All Rights Reserved.
88
-->
99
<template>
@@ -49,7 +49,7 @@
4949

5050
<script setup lang="ts">
5151
import { Content, useData, withBase, } from 'vitepress';
52-
import { ComputedRef, computed, onMounted, ref } from 'vue';
52+
import { ComputedRef, computed, nextTick, onMounted, ref } from 'vue';
5353
import { DefaultPageFormatter, Theme } from '../types';
5454
import Pagination from '../components/Pagination.vue';
5555
import ArticleFooter from '../components/ArticleFooter.vue';
@@ -89,14 +89,28 @@ const updatePageViews = async () => {
8989
9090
const updateTocHeight = () => {
9191
if (layout.value === 'page') {
92-
if (articleRef.value && catalogRef.value) {
93-
let height = articleRef.value.clientHeight;
94-
catalogRef.value.updateHeight(height);
95-
console.log(height);
96-
catalogRef.value.refresh();
97-
} else {
98-
console.warn('set tocbot height fail!');
99-
}
92+
nextTick(() => {
93+
if (articleRef.value && catalogRef.value) {
94+
let height = articleRef.value.clientHeight;
95+
96+
if (typeof catalogRef.value.updateHeight === 'function') {
97+
catalogRef.value.updateHeight(height);
98+
} else {
99+
console.error('catalogRef.updateHeight is not a function');
100+
}
101+
102+
if (typeof catalogRef.value.refresh === 'function') {
103+
catalogRef.value.refresh();
104+
console.log('refresh called');
105+
} else {
106+
console.error('catalogRef.refresh is not a function');
107+
}
108+
} else {
109+
console.warn('set tocbot height fail!');
110+
}
111+
});
112+
} else {
113+
console.log('Layout is not page, skip updateTocHeight');
100114
}
101115
}
102116
@@ -219,7 +233,7 @@ const useComment = computed(() => {
219233
position: absolute !important;
220234
top: 480px;
221235
right: calc((100% - 950px - 250px) / 2);
222-
z-index: 0;
236+
z-index: 10;
223237
padding-top: 10px;
224238
padding-bottom: 10px;
225239
width: 200px;

src/layout/index.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,10 @@ const global = useStore('global');
4646
provide('global', global);
4747
global.setData(data);
4848
49+
// 在脚本顶层立即初始化插件,确保路由钩子在应用启动时就设置好
50+
installThemePlugin();
4951
5052
onMounted(() => {
51-
installThemePlugin()
5253
document.body.style.backgroundImage = `url(${theme.global.background?.src()})`;
5354
})
5455

src/plugin/nprogress.ts

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,21 @@
88
* Copyright (c) 2024 by wuyifan0203 email: 1208097313@qq.com, All Rights Reserved.
99
*/
1010

11-
import { useRouter } from 'vitepress'
11+
import { useBeforeRouterChange, useAfterRouterChange } from '../composables/useRouter';
1212
import nprogress from "nprogress";
1313
import "nprogress/nprogress.css";
1414

1515
export function useNProgress() {
1616
if (typeof window === 'undefined') return;
1717

18-
const router = useRouter();
19-
2018
nprogress.configure({ showSpinner: false });
21-
const cacheOnBeforeRouteChange = router.onBeforeRouteChange;
22-
const cacheAfterRouteChange = router.onAfterRouteChange
23-
24-
router.onBeforeRouteChange = (to) => {
19+
20+
// 使用统一的回调管理系统
21+
useBeforeRouterChange(() => {
2522
nprogress.start();
26-
cacheOnBeforeRouteChange && cacheOnBeforeRouteChange(to);
27-
}
28-
29-
router.onAfterRouteChange = (to) => {
23+
});
24+
25+
useAfterRouterChange(() => {
3026
nprogress.done();
31-
cacheAfterRouteChange && cacheAfterRouteChange(to);
32-
}
33-
27+
});
3428
}

src/plugin/yiyan.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* @FilePath: /vitepress-theme-sakurairo/src/plugin/yiyan.ts
77
* Copyright (c) 2024 by wuyifan0203 email: 1208097313@qq.com, All Rights Reserved.
88
*/
9-
import { useRouter } from 'vitepress';
9+
import { useAfterRouterChange } from '../composables/useRouter';
1010
const apis = [
1111
"https://v1.hitokoto.cn/",
1212
"https://api.nmxc.ltd/yiyan/",
@@ -18,24 +18,20 @@ let dom: null | HTMLElement = null;
1818
function useYiYan() {
1919
dom = dom || document.querySelector('#yiyan');
2020

21-
const router = useRouter();
22-
const cacheAfterRouteChange = router.onAfterRouteChanged;
23-
2421
if (dom) {
2522
updateYiYan(dom);
2623
} else {
2724
console.log('cannot find #yiyan domElement when init!');
2825
}
2926

30-
router.onAfterRouteChanged = async (to) => {
27+
// 使用统一的回调管理系统
28+
useAfterRouterChange(async () => {
3129
if (dom) {
3230
updateYiYan(dom);
3331
} else {
3432
console.log('cannot find #yiyan domElement');
3533
}
36-
37-
cacheAfterRouteChange && cacheAfterRouteChange(to);
38-
}
34+
});
3935
}
4036

4137
async function request(api: string) {

0 commit comments

Comments
 (0)