[{"data":1,"prerenderedAt":1455},["ShallowReactive",2],{"page-/post/nuxt/content/nuxt-content-toc":3,"surrounding-page":1446},{"id":4,"title":5,"author":6,"body":7,"date":1432,"description":1433,"extension":1434,"group":6,"lastmod":1435,"meta":1436,"navigation":1069,"path":1437,"rawbody":1438,"seo":1439,"showTitle":6,"stem":1440,"tags":1441,"versions":1442,"__hash__":1445},"content/post/nuxt/content/nuxt-content-toc.md","Nuxt Content 实现 TOC 组件",null,{"type":8,"value":9,"toc":1430},"minimark",[10,33,47,65,76,706,733,751,766,776,790,797,871,880,897,903,908,1245,1248,1284,1291,1297,1426],[11,12,13,17,18,21,22,25,26,25,29,32],"p",{},[14,15,16],"code",{},"TOC"," 全称 ",[14,19,20],{},"table of contents"," ，指的是一篇文章内的 ",[14,23,24],{},"h1","、",[14,27,28],{},"h2",[14,30,31],{},"h3"," 等标题的导航，用于快速跳转到对应的标题处。",[11,34,35,38,39,42,43,46],{},[14,36,37],{},"Nuxt Content"," 会为 ",[14,40,41],{},"markdown"," 内容生成 ",[14,44,45],{},"toc"," 数据。",[11,48,49,50,53,54,57,58,61,62,64],{},"通过 ",[14,51,52],{},"queryCollection"," 获取的 ",[14,55,56],{},"page"," 数据中，通过 ",[14,59,60],{},"page?.body?.toc?.links"," 拿到当前文章的 ",[14,63,45],{}," 数据",[11,66,67,68,75],{},"以",[69,70,74],"a",{"href":71,"rel":72},"https://zzao.club/post/nuxt/nuxt-3.17-release",[73],"nofollow","《Nuxt 3.17 发布，对比3.16有一个重大改变》","这篇文章为例，数据是这样的",[77,78,83],"pre",{"className":79,"code":80,"language":81,"meta":82,"style":82},"language-typescript shiki shiki-themes github-light","[\n    {\n        \"id\": \"更新日志\",\n        \"depth\": 2,\n        \"text\": \"更新日志\",\n        \"children\": [\n            {\n                \"id\": \"数据获取改进\",\n                \"depth\": 3,\n                \"text\": \"数据获取改进\"\n            },\n            {\n                \"id\": \"新增内置组件\",\n                \"depth\": 3,\n                \"text\": \"新增内置组件\"\n            },\n            {\n                \"id\": \"路由改进\",\n                \"depth\": 3,\n                \"text\": \"路由改进\"\n            },\n            {\n                \"id\": \"加载指示器自定义\",\n                \"depth\": 3,\n                \"text\": \"加载指示器自定义\"\n            },\n            {\n                \"id\": \"文档作为包\",\n                \"depth\": 3,\n                \"text\": \"文档作为包\"\n            },\n            {\n                \"id\": \"开发体验改进\",\n                \"depth\": 3,\n                \"text\": \"开发体验改进\"\n            },\n            {\n                \"id\": \"模块开发增强\",\n                \"depth\": 3,\n                \"text\": \"模块开发增强\"\n            },\n            {\n                \"id\": \"性能改进\",\n                \"depth\": 3,\n                \"text\": \"性能改进\"\n            },\n            {\n                \"id\": \"其他改进\",\n                \"depth\": 3,\n                \"text\": \"其他改进\"\n            }\n        ]\n    },\n    {\n        \"id\": \"主要影响点\",\n        \"depth\": 2,\n        \"text\": \"主要影响点\",\n        \"children\": [\n            {\n                \"id\": \"useasyncdatausefetch\",\n                \"depth\": 3,\n                \"text\": \"useAsyncData、useFetch\"\n            },\n            {\n                \"id\": \"_2025年05月06日093334-更新\",\n                \"depth\": 3,\n                \"text\": \"2025年05月06日09:33:34 更新\"\n            }\n        ]\n    }\n]\n","typescript","",[14,84,85,94,100,116,130,142,151,157,170,183,194,200,205,217,228,238,243,248,260,271,281,286,291,303,314,324,329,334,346,357,367,372,377,389,400,410,415,420,432,443,453,458,463,475,486,496,501,506,518,529,539,545,551,557,562,574,585,596,603,608,620,631,641,646,651,663,674,684,689,694,700],{"__ignoreMap":82},[86,87,90],"span",{"class":88,"line":89},"line",1,[86,91,93],{"class":92},"sgsFI","[\n",[86,95,97],{"class":88,"line":96},2,[86,98,99],{"class":92},"    {\n",[86,101,103,107,110,113],{"class":88,"line":102},3,[86,104,106],{"class":105},"sYBdl","        \"id\"",[86,108,109],{"class":92},": ",[86,111,112],{"class":105},"\"更新日志\"",[86,114,115],{"class":92},",\n",[86,117,119,122,124,128],{"class":88,"line":118},4,[86,120,121],{"class":105},"        \"depth\"",[86,123,109],{"class":92},[86,125,127],{"class":126},"sYu0t","2",[86,129,115],{"class":92},[86,131,133,136,138,140],{"class":88,"line":132},5,[86,134,135],{"class":105},"        \"text\"",[86,137,109],{"class":92},[86,139,112],{"class":105},[86,141,115],{"class":92},[86,143,145,148],{"class":88,"line":144},6,[86,146,147],{"class":105},"        \"children\"",[86,149,150],{"class":92},": [\n",[86,152,154],{"class":88,"line":153},7,[86,155,156],{"class":92},"            {\n",[86,158,160,163,165,168],{"class":88,"line":159},8,[86,161,162],{"class":105},"                \"id\"",[86,164,109],{"class":92},[86,166,167],{"class":105},"\"数据获取改进\"",[86,169,115],{"class":92},[86,171,173,176,178,181],{"class":88,"line":172},9,[86,174,175],{"class":105},"                \"depth\"",[86,177,109],{"class":92},[86,179,180],{"class":126},"3",[86,182,115],{"class":92},[86,184,186,189,191],{"class":88,"line":185},10,[86,187,188],{"class":105},"                \"text\"",[86,190,109],{"class":92},[86,192,193],{"class":105},"\"数据获取改进\"\n",[86,195,197],{"class":88,"line":196},11,[86,198,199],{"class":92},"            },\n",[86,201,203],{"class":88,"line":202},12,[86,204,156],{"class":92},[86,206,208,210,212,215],{"class":88,"line":207},13,[86,209,162],{"class":105},[86,211,109],{"class":92},[86,213,214],{"class":105},"\"新增内置组件\"",[86,216,115],{"class":92},[86,218,220,222,224,226],{"class":88,"line":219},14,[86,221,175],{"class":105},[86,223,109],{"class":92},[86,225,180],{"class":126},[86,227,115],{"class":92},[86,229,231,233,235],{"class":88,"line":230},15,[86,232,188],{"class":105},[86,234,109],{"class":92},[86,236,237],{"class":105},"\"新增内置组件\"\n",[86,239,241],{"class":88,"line":240},16,[86,242,199],{"class":92},[86,244,246],{"class":88,"line":245},17,[86,247,156],{"class":92},[86,249,251,253,255,258],{"class":88,"line":250},18,[86,252,162],{"class":105},[86,254,109],{"class":92},[86,256,257],{"class":105},"\"路由改进\"",[86,259,115],{"class":92},[86,261,263,265,267,269],{"class":88,"line":262},19,[86,264,175],{"class":105},[86,266,109],{"class":92},[86,268,180],{"class":126},[86,270,115],{"class":92},[86,272,274,276,278],{"class":88,"line":273},20,[86,275,188],{"class":105},[86,277,109],{"class":92},[86,279,280],{"class":105},"\"路由改进\"\n",[86,282,284],{"class":88,"line":283},21,[86,285,199],{"class":92},[86,287,289],{"class":88,"line":288},22,[86,290,156],{"class":92},[86,292,294,296,298,301],{"class":88,"line":293},23,[86,295,162],{"class":105},[86,297,109],{"class":92},[86,299,300],{"class":105},"\"加载指示器自定义\"",[86,302,115],{"class":92},[86,304,306,308,310,312],{"class":88,"line":305},24,[86,307,175],{"class":105},[86,309,109],{"class":92},[86,311,180],{"class":126},[86,313,115],{"class":92},[86,315,317,319,321],{"class":88,"line":316},25,[86,318,188],{"class":105},[86,320,109],{"class":92},[86,322,323],{"class":105},"\"加载指示器自定义\"\n",[86,325,327],{"class":88,"line":326},26,[86,328,199],{"class":92},[86,330,332],{"class":88,"line":331},27,[86,333,156],{"class":92},[86,335,337,339,341,344],{"class":88,"line":336},28,[86,338,162],{"class":105},[86,340,109],{"class":92},[86,342,343],{"class":105},"\"文档作为包\"",[86,345,115],{"class":92},[86,347,349,351,353,355],{"class":88,"line":348},29,[86,350,175],{"class":105},[86,352,109],{"class":92},[86,354,180],{"class":126},[86,356,115],{"class":92},[86,358,360,362,364],{"class":88,"line":359},30,[86,361,188],{"class":105},[86,363,109],{"class":92},[86,365,366],{"class":105},"\"文档作为包\"\n",[86,368,370],{"class":88,"line":369},31,[86,371,199],{"class":92},[86,373,375],{"class":88,"line":374},32,[86,376,156],{"class":92},[86,378,380,382,384,387],{"class":88,"line":379},33,[86,381,162],{"class":105},[86,383,109],{"class":92},[86,385,386],{"class":105},"\"开发体验改进\"",[86,388,115],{"class":92},[86,390,392,394,396,398],{"class":88,"line":391},34,[86,393,175],{"class":105},[86,395,109],{"class":92},[86,397,180],{"class":126},[86,399,115],{"class":92},[86,401,403,405,407],{"class":88,"line":402},35,[86,404,188],{"class":105},[86,406,109],{"class":92},[86,408,409],{"class":105},"\"开发体验改进\"\n",[86,411,413],{"class":88,"line":412},36,[86,414,199],{"class":92},[86,416,418],{"class":88,"line":417},37,[86,419,156],{"class":92},[86,421,423,425,427,430],{"class":88,"line":422},38,[86,424,162],{"class":105},[86,426,109],{"class":92},[86,428,429],{"class":105},"\"模块开发增强\"",[86,431,115],{"class":92},[86,433,435,437,439,441],{"class":88,"line":434},39,[86,436,175],{"class":105},[86,438,109],{"class":92},[86,440,180],{"class":126},[86,442,115],{"class":92},[86,444,446,448,450],{"class":88,"line":445},40,[86,447,188],{"class":105},[86,449,109],{"class":92},[86,451,452],{"class":105},"\"模块开发增强\"\n",[86,454,456],{"class":88,"line":455},41,[86,457,199],{"class":92},[86,459,461],{"class":88,"line":460},42,[86,462,156],{"class":92},[86,464,466,468,470,473],{"class":88,"line":465},43,[86,467,162],{"class":105},[86,469,109],{"class":92},[86,471,472],{"class":105},"\"性能改进\"",[86,474,115],{"class":92},[86,476,478,480,482,484],{"class":88,"line":477},44,[86,479,175],{"class":105},[86,481,109],{"class":92},[86,483,180],{"class":126},[86,485,115],{"class":92},[86,487,489,491,493],{"class":88,"line":488},45,[86,490,188],{"class":105},[86,492,109],{"class":92},[86,494,495],{"class":105},"\"性能改进\"\n",[86,497,499],{"class":88,"line":498},46,[86,500,199],{"class":92},[86,502,504],{"class":88,"line":503},47,[86,505,156],{"class":92},[86,507,509,511,513,516],{"class":88,"line":508},48,[86,510,162],{"class":105},[86,512,109],{"class":92},[86,514,515],{"class":105},"\"其他改进\"",[86,517,115],{"class":92},[86,519,521,523,525,527],{"class":88,"line":520},49,[86,522,175],{"class":105},[86,524,109],{"class":92},[86,526,180],{"class":126},[86,528,115],{"class":92},[86,530,532,534,536],{"class":88,"line":531},50,[86,533,188],{"class":105},[86,535,109],{"class":92},[86,537,538],{"class":105},"\"其他改进\"\n",[86,540,542],{"class":88,"line":541},51,[86,543,544],{"class":92},"            }\n",[86,546,548],{"class":88,"line":547},52,[86,549,550],{"class":92},"        ]\n",[86,552,554],{"class":88,"line":553},53,[86,555,556],{"class":92},"    },\n",[86,558,560],{"class":88,"line":559},54,[86,561,99],{"class":92},[86,563,565,567,569,572],{"class":88,"line":564},55,[86,566,106],{"class":105},[86,568,109],{"class":92},[86,570,571],{"class":105},"\"主要影响点\"",[86,573,115],{"class":92},[86,575,577,579,581,583],{"class":88,"line":576},56,[86,578,121],{"class":105},[86,580,109],{"class":92},[86,582,127],{"class":126},[86,584,115],{"class":92},[86,586,588,590,592,594],{"class":88,"line":587},57,[86,589,135],{"class":105},[86,591,109],{"class":92},[86,593,571],{"class":105},[86,595,115],{"class":92},[86,597,599,601],{"class":88,"line":598},58,[86,600,147],{"class":105},[86,602,150],{"class":92},[86,604,606],{"class":88,"line":605},59,[86,607,156],{"class":92},[86,609,611,613,615,618],{"class":88,"line":610},60,[86,612,162],{"class":105},[86,614,109],{"class":92},[86,616,617],{"class":105},"\"useasyncdatausefetch\"",[86,619,115],{"class":92},[86,621,623,625,627,629],{"class":88,"line":622},61,[86,624,175],{"class":105},[86,626,109],{"class":92},[86,628,180],{"class":126},[86,630,115],{"class":92},[86,632,634,636,638],{"class":88,"line":633},62,[86,635,188],{"class":105},[86,637,109],{"class":92},[86,639,640],{"class":105},"\"useAsyncData、useFetch\"\n",[86,642,644],{"class":88,"line":643},63,[86,645,199],{"class":92},[86,647,649],{"class":88,"line":648},64,[86,650,156],{"class":92},[86,652,654,656,658,661],{"class":88,"line":653},65,[86,655,162],{"class":105},[86,657,109],{"class":92},[86,659,660],{"class":105},"\"_2025年05月06日093334-更新\"",[86,662,115],{"class":92},[86,664,666,668,670,672],{"class":88,"line":665},66,[86,667,175],{"class":105},[86,669,109],{"class":92},[86,671,180],{"class":126},[86,673,115],{"class":92},[86,675,677,679,681],{"class":88,"line":676},67,[86,678,188],{"class":105},[86,680,109],{"class":92},[86,682,683],{"class":105},"\"2025年05月06日09:33:34 更新\"\n",[86,685,687],{"class":88,"line":686},68,[86,688,544],{"class":92},[86,690,692],{"class":88,"line":691},69,[86,693,550],{"class":92},[86,695,697],{"class":88,"line":696},70,[86,698,699],{"class":92},"    }\n",[86,701,703],{"class":88,"line":702},71,[86,704,705],{"class":92},"]\n",[11,707,708,709,711,712,714,715,718,719,25,721,723,724,727,728,730,731],{},"一般一篇文章里，",[14,710,24],{}," 表示的是文章标题，在一个页面中通常只会存在一个 ",[14,713,24],{}," 标题，所以在写文章时，要注意不要乱用 ",[14,716,717],{},"# 标题","这个语法。 ",[14,720,28],{},[14,722,31],{},"、 就是文章里常用的二级和三级标题，在数据中就是 ",[14,725,726],{},"depth"," 为 ",[14,729,127],{}," 或 ",[14,732,180],{},[11,734,735,736,739,740,743,744,730,747,750],{},"组件本身使用 ",[14,737,738],{},"ul"," 、",[14,741,742],{},"li"," 来渲染即可，再配合 ",[14,745,746],{},"fixed",[14,748,749],{},"sticky","，使其国定在文章的一侧。",[11,752,753,754,757,758,760,761,765],{},"对于 ",[14,755,756],{},"Nuxt"," 来说，",[14,759,16],{}," 组件可以完全是一个",[762,763,764],"strong",{},"客户端组件","，因为不需要被爬虫抓取，也不是初次渲染需要的重要信息。而且如果要做一些简单的交互，也需要等前端环境加载出来之后才能做到。",[11,767,768,769,772,773,775],{},"所以只需要使用一个 ",[14,770,771],{},"computed"," 拿到 ",[14,774,45],{}," 数据，然后把数据传递给组件即可。",[11,777,778,779,784,785],{},"我观察了一圈，感觉",[69,780,783],{"href":781,"rel":782},"https://sspai.com/",[73],"少数派","的 TOC 组件是比较美观的，于是我仿照他们的思路封装了自己的 ",[69,786,789],{"href":787,"rel":788},"https://github.com/aatrooox/blog.zzao.club/blob/main/app/components/common/AppToc.vue",[73],"TOC 组件",[11,791,792,793,796],{},"toc 的配置位于 ",[14,794,795],{},"nuxt.config.ts"," ：",[77,798,800],{"className":79,"code":799,"language":81,"meta":82,"style":82},"content: {\n    build: {\n        markdown: {\n            toc: {\n              depth: 2,\n              searchDepth: 2\n            }\n        }\n    }\n}\n",[14,801,802,811,818,825,832,843,853,857,862,866],{"__ignoreMap":82},[86,803,804,808],{"class":88,"line":89},[86,805,807],{"class":806},"s7eDp","content",[86,809,810],{"class":92},": {\n",[86,812,813,816],{"class":88,"line":96},[86,814,815],{"class":806},"    build",[86,817,810],{"class":92},[86,819,820,823],{"class":88,"line":102},[86,821,822],{"class":806},"        markdown",[86,824,810],{"class":92},[86,826,827,830],{"class":88,"line":118},[86,828,829],{"class":806},"            toc",[86,831,810],{"class":92},[86,833,834,837,839,841],{"class":88,"line":132},[86,835,836],{"class":806},"              depth",[86,838,109],{"class":92},[86,840,127],{"class":126},[86,842,115],{"class":92},[86,844,845,848,850],{"class":88,"line":144},[86,846,847],{"class":806},"              searchDepth",[86,849,109],{"class":92},[86,851,852],{"class":126},"2\n",[86,854,855],{"class":88,"line":153},[86,856,544],{"class":92},[86,858,859],{"class":88,"line":159},[86,860,861],{"class":92},"        }\n",[86,863,864],{"class":88,"line":172},[86,865,699],{"class":92},[86,867,868],{"class":88,"line":185},[86,869,870],{"class":92},"}\n",[11,872,873,874,876,877,879],{},"默认深度是 ",[14,875,127],{},"，我一般会用到 ",[14,878,180],{},"。",[11,881,882,883,886,887,25,890,25,893,896],{},"同时，如果要想自己定义 h1、h2、h3 标题的样式，需要在 ",[14,884,885],{},"app/components/content"," 目录下新建 ",[14,888,889],{},"ProseH1.vue",[14,891,892],{},"ProseH2.vue",[14,894,895],{},"ProseH3.vue"," 组件。",[11,898,899,900],{},"写样式时，不管如何封装，",[762,901,902],{},"记得不要丢掉 id 属性",[11,904,905],{},[762,906,907],{},"ProseH3.vue 为例",[77,909,913],{"className":910,"code":911,"language":912,"meta":82,"style":82},"language-vue shiki shiki-themes github-light","\u003Ctemplate>\n  \u003Cdiv :id=\"props.id\" class=\"heading my-4 cursor-pointer scroll-mt-14\">\n    \u003Cspan class=\"px-2 py-1 text-xl font-bold bg-zinc-800 text-white dark:bg-zinc-200 \">\n      \u003Ca v-if=\"props.id && generate\" :href=\"`#${props.id}`\" class=\"!text-zinc-200 dark:!text-zinc-800\">\n        \u003Cslot />\n      \u003C/a>\n      \u003Cslot v-else />\n    \u003C/span>\n  \u003C/div>\n\u003C/template>\n\n\u003Cscript setup lang=\"ts\">\nimport { computed, useRuntimeConfig } from '#imports'\n\nconst props = defineProps\u003C{ id?: string }>()\n\nconst { headings } = useRuntimeConfig().public.mdc\nconst generate = computed(() => props.id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h1)))\n\u003C/script>\n","vue",[14,914,915,927,954,970,1002,1016,1025,1038,1047,1056,1065,1071,1091,1106,1110,1140,1144,1165,1237],{"__ignoreMap":82},[86,916,917,920,924],{"class":88,"line":89},[86,918,919],{"class":92},"\u003C",[86,921,923],{"class":922},"shJU0","template",[86,925,926],{"class":92},">\n",[86,928,929,932,935,938,941,944,947,949,952],{"class":88,"line":96},[86,930,931],{"class":92},"  \u003C",[86,933,934],{"class":922},"div",[86,936,937],{"class":806}," :id",[86,939,940],{"class":92},"=",[86,942,943],{"class":105},"\"props.id\"",[86,945,946],{"class":806}," class",[86,948,940],{"class":92},[86,950,951],{"class":105},"\"heading my-4 cursor-pointer scroll-mt-14\"",[86,953,926],{"class":92},[86,955,956,959,961,963,965,968],{"class":88,"line":102},[86,957,958],{"class":92},"    \u003C",[86,960,86],{"class":922},[86,962,946],{"class":806},[86,964,940],{"class":92},[86,966,967],{"class":105},"\"px-2 py-1 text-xl font-bold bg-zinc-800 text-white dark:bg-zinc-200 \"",[86,969,926],{"class":92},[86,971,972,975,977,980,982,985,988,990,993,995,997,1000],{"class":88,"line":118},[86,973,974],{"class":92},"      \u003C",[86,976,69],{"class":922},[86,978,979],{"class":806}," v-if",[86,981,940],{"class":92},[86,983,984],{"class":105},"\"props.id && generate\"",[86,986,987],{"class":806}," :href",[86,989,940],{"class":92},[86,991,992],{"class":105},"\"`#${props.id}`\"",[86,994,946],{"class":806},[86,996,940],{"class":92},[86,998,999],{"class":105},"\"!text-zinc-200 dark:!text-zinc-800\"",[86,1001,926],{"class":92},[86,1003,1004,1007,1010,1014],{"class":88,"line":132},[86,1005,1006],{"class":92},"        \u003C",[86,1008,1009],{"class":922},"slot",[86,1011,1013],{"class":1012},"sB1qb"," /",[86,1015,926],{"class":92},[86,1017,1018,1021,1023],{"class":88,"line":144},[86,1019,1020],{"class":92},"      \u003C/",[86,1022,69],{"class":922},[86,1024,926],{"class":92},[86,1026,1027,1029,1031,1034,1036],{"class":88,"line":153},[86,1028,974],{"class":92},[86,1030,1009],{"class":922},[86,1032,1033],{"class":806}," v-else",[86,1035,1013],{"class":1012},[86,1037,926],{"class":92},[86,1039,1040,1043,1045],{"class":88,"line":159},[86,1041,1042],{"class":92},"    \u003C/",[86,1044,86],{"class":922},[86,1046,926],{"class":92},[86,1048,1049,1052,1054],{"class":88,"line":172},[86,1050,1051],{"class":92},"  \u003C/",[86,1053,934],{"class":922},[86,1055,926],{"class":92},[86,1057,1058,1061,1063],{"class":88,"line":185},[86,1059,1060],{"class":92},"\u003C/",[86,1062,923],{"class":922},[86,1064,926],{"class":92},[86,1066,1067],{"class":88,"line":196},[86,1068,1070],{"emptyLinePlaceholder":1069},true,"\n",[86,1072,1073,1075,1078,1081,1084,1086,1089],{"class":88,"line":202},[86,1074,919],{"class":92},[86,1076,1077],{"class":922},"script",[86,1079,1080],{"class":806}," setup",[86,1082,1083],{"class":806}," lang",[86,1085,940],{"class":92},[86,1087,1088],{"class":105},"\"ts\"",[86,1090,926],{"class":92},[86,1092,1093,1097,1100,1103],{"class":88,"line":207},[86,1094,1096],{"class":1095},"sD7c4","import",[86,1098,1099],{"class":92}," { computed, useRuntimeConfig } ",[86,1101,1102],{"class":1095},"from",[86,1104,1105],{"class":105}," '#imports'\n",[86,1107,1108],{"class":88,"line":219},[86,1109,1070],{"emptyLinePlaceholder":1069},[86,1111,1112,1115,1118,1121,1124,1127,1131,1134,1137],{"class":88,"line":230},[86,1113,1114],{"class":1095},"const",[86,1116,1117],{"class":126}," props",[86,1119,1120],{"class":1095}," =",[86,1122,1123],{"class":806}," defineProps",[86,1125,1126],{"class":92},"\u003C{ ",[86,1128,1130],{"class":1129},"sqxcx","id",[86,1132,1133],{"class":1095},"?:",[86,1135,1136],{"class":126}," string",[86,1138,1139],{"class":92}," }>()\n",[86,1141,1142],{"class":88,"line":240},[86,1143,1070],{"emptyLinePlaceholder":1069},[86,1145,1146,1148,1151,1154,1157,1159,1162],{"class":88,"line":245},[86,1147,1114],{"class":1095},[86,1149,1150],{"class":92}," { ",[86,1152,1153],{"class":126},"headings",[86,1155,1156],{"class":92}," } ",[86,1158,940],{"class":1095},[86,1160,1161],{"class":806}," useRuntimeConfig",[86,1163,1164],{"class":92},"().public.mdc\n",[86,1166,1167,1169,1172,1174,1177,1180,1183,1186,1189,1192,1195,1198,1201,1204,1207,1209,1211,1214,1217,1220,1223,1225,1227,1229,1232,1234],{"class":88,"line":250},[86,1168,1114],{"class":1095},[86,1170,1171],{"class":126}," generate",[86,1173,1120],{"class":1095},[86,1175,1176],{"class":806}," computed",[86,1178,1179],{"class":92},"(() ",[86,1181,1182],{"class":1095},"=>",[86,1184,1185],{"class":92}," props.id ",[86,1187,1188],{"class":1095},"&&",[86,1190,1191],{"class":92}," ((",[86,1193,1194],{"class":1095},"typeof",[86,1196,1197],{"class":92}," headings?.anchorLinks ",[86,1199,1200],{"class":1095},"===",[86,1202,1203],{"class":105}," 'boolean'",[86,1205,1206],{"class":1095}," &&",[86,1208,1197],{"class":92},[86,1210,1200],{"class":1095},[86,1212,1213],{"class":126}," true",[86,1215,1216],{"class":92},") ",[86,1218,1219],{"class":1095},"||",[86,1221,1222],{"class":92}," (",[86,1224,1194],{"class":1095},[86,1226,1197],{"class":92},[86,1228,1200],{"class":1095},[86,1230,1231],{"class":105}," 'object'",[86,1233,1206],{"class":1095},[86,1235,1236],{"class":92}," headings?.anchorLinks?.h1)))\n",[86,1238,1239,1241,1243],{"class":88,"line":262},[86,1240,1060],{"class":92},[86,1242,1077],{"class":922},[86,1244,926],{"class":92},[11,1246,1247],{},"对应 TOC 组件中：",[77,1249,1251],{"className":910,"code":1250,"language":912,"meta":82,"style":82},"# template v-for child in link.children\n\u003Cli>\n    \u003Cspan>#\u003C/span>\n    \u003CNuxtLink :href=\"`#${child.id}`\"> {{ child.text }} \u003C/NuxtLink>\n\u003C/li>\n",[14,1252,1253,1258,1266,1271,1276],{"__ignoreMap":82},[86,1254,1255],{"class":88,"line":89},[86,1256,1257],{"class":92},"# template v-for child in link.children\n",[86,1259,1260,1262,1264],{"class":88,"line":96},[86,1261,919],{"class":92},[86,1263,742],{"class":922},[86,1265,926],{"class":92},[86,1267,1268],{"class":88,"line":102},[86,1269,1270],{"class":92},"    \u003Cspan>#\u003C/span>\n",[86,1272,1273],{"class":88,"line":118},[86,1274,1275],{"class":92},"    \u003CNuxtLink :href=\"`#${child.id}`\"> {{ child.text }} \u003C/NuxtLink>\n",[86,1277,1278,1280,1282],{"class":88,"line":132},[86,1279,1060],{"class":92},[86,1281,742],{"class":922},[86,1283,926],{"class":92},[11,1285,1286,1287,1290],{},"也可以像我的组件一样，配合 ",[14,1288,1289],{},"IntersectionObserver"," ，做到 TOC 组件的导航根据滚动的区域使其高亮或是显示其他标识",[11,1292,1293],{},[1294,1295],"img",{"alt":82,"src":1296},"https://img.zzao.club/article/202505131613386.png",[77,1298,1300],{"className":79,"code":1299,"language":81,"meta":82,"style":82}," const headings = document.querySelectorAll('.heading')\n    observer.value = new IntersectionObserver((entries) => {\n      entries.forEach(entry => {\n        if (entry.isIntersecting) {\n          activeId.value = entry.target.id\n        }\n      })\n    })\n    headings.forEach(heading => observer.value.observe(heading))\n",[14,1301,1302,1327,1353,1371,1379,1389,1393,1398,1403],{"__ignoreMap":82},[86,1303,1304,1307,1310,1312,1315,1318,1321,1324],{"class":88,"line":89},[86,1305,1306],{"class":1095}," const",[86,1308,1309],{"class":126}," headings",[86,1311,1120],{"class":1095},[86,1313,1314],{"class":92}," document.",[86,1316,1317],{"class":806},"querySelectorAll",[86,1319,1320],{"class":92},"(",[86,1322,1323],{"class":105},"'.heading'",[86,1325,1326],{"class":92},")\n",[86,1328,1329,1332,1334,1337,1340,1343,1346,1348,1350],{"class":88,"line":96},[86,1330,1331],{"class":92},"    observer.value ",[86,1333,940],{"class":1095},[86,1335,1336],{"class":1095}," new",[86,1338,1339],{"class":806}," IntersectionObserver",[86,1341,1342],{"class":92},"((",[86,1344,1345],{"class":1129},"entries",[86,1347,1216],{"class":92},[86,1349,1182],{"class":1095},[86,1351,1352],{"class":92}," {\n",[86,1354,1355,1358,1361,1363,1366,1369],{"class":88,"line":102},[86,1356,1357],{"class":92},"      entries.",[86,1359,1360],{"class":806},"forEach",[86,1362,1320],{"class":92},[86,1364,1365],{"class":1129},"entry",[86,1367,1368],{"class":1095}," =>",[86,1370,1352],{"class":92},[86,1372,1373,1376],{"class":88,"line":118},[86,1374,1375],{"class":1095},"        if",[86,1377,1378],{"class":92}," (entry.isIntersecting) {\n",[86,1380,1381,1384,1386],{"class":88,"line":132},[86,1382,1383],{"class":92},"          activeId.value ",[86,1385,940],{"class":1095},[86,1387,1388],{"class":92}," entry.target.id\n",[86,1390,1391],{"class":88,"line":144},[86,1392,861],{"class":92},[86,1394,1395],{"class":88,"line":153},[86,1396,1397],{"class":92},"      })\n",[86,1399,1400],{"class":88,"line":159},[86,1401,1402],{"class":92},"    })\n",[86,1404,1405,1408,1410,1412,1415,1417,1420,1423],{"class":88,"line":172},[86,1406,1407],{"class":92},"    headings.",[86,1409,1360],{"class":806},[86,1411,1320],{"class":92},[86,1413,1414],{"class":1129},"heading",[86,1416,1368],{"class":1095},[86,1418,1419],{"class":92}," observer.value.",[86,1421,1422],{"class":806},"observe",[86,1424,1425],{"class":92},"(heading))\n",[1427,1428,1429],"style",{},"html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .shJU0, html code.shiki .shJU0{--shiki-default:#22863A}html pre.shiki code .sB1qb, html code.shiki .sB1qb{--shiki-default:#B31D28;--shiki-default-font-style:italic}html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .sqxcx, html code.shiki .sqxcx{--shiki-default:#E36209}",{"title":82,"searchDepth":96,"depth":96,"links":1431},[],"2025-05-13T00:00:00.000Z","Nuxt Content 中配置和使用 toc","md","2025-08-21T00:00:00.000Z",{},"/post/nuxt/content/nuxt-content-toc","---\ntitle: Nuxt Content 实现 TOC 组件\ndate: 2025-05-13\nlastmod: 2025-08-21\nversions: [\"@nuxt/content@3.4.0\", \"nuxt@3.17.2\"]\ntags: [\"Nuxt\"]\ndescription: Nuxt Content 中配置和使用 toc\n---\n`TOC` 全称 `table of contents` ，指的是一篇文章内的 `h1`、`h2`、`h3` 等标题的导航，用于快速跳转到对应的标题处。\n\n`Nuxt Content` 会为 `markdown` 内容生成 `toc` 数据。\n\n通过 `queryCollection` 获取的 `page` 数据中，通过 `page?.body?.toc?.links` 拿到当前文章的 `toc` 数据\n\n以[《Nuxt 3.17 发布，对比3.16有一个重大改变》](https://zzao.club/post/nuxt/nuxt-3.17-release)这篇文章为例，数据是这样的\n\n```typescript\n[\n    {\n        \"id\": \"更新日志\",\n        \"depth\": 2,\n        \"text\": \"更新日志\",\n        \"children\": [\n            {\n                \"id\": \"数据获取改进\",\n                \"depth\": 3,\n                \"text\": \"数据获取改进\"\n            },\n            {\n                \"id\": \"新增内置组件\",\n                \"depth\": 3,\n                \"text\": \"新增内置组件\"\n            },\n            {\n                \"id\": \"路由改进\",\n                \"depth\": 3,\n                \"text\": \"路由改进\"\n            },\n            {\n                \"id\": \"加载指示器自定义\",\n                \"depth\": 3,\n                \"text\": \"加载指示器自定义\"\n            },\n            {\n                \"id\": \"文档作为包\",\n                \"depth\": 3,\n                \"text\": \"文档作为包\"\n            },\n            {\n                \"id\": \"开发体验改进\",\n                \"depth\": 3,\n                \"text\": \"开发体验改进\"\n            },\n            {\n                \"id\": \"模块开发增强\",\n                \"depth\": 3,\n                \"text\": \"模块开发增强\"\n            },\n            {\n                \"id\": \"性能改进\",\n                \"depth\": 3,\n                \"text\": \"性能改进\"\n            },\n            {\n                \"id\": \"其他改进\",\n                \"depth\": 3,\n                \"text\": \"其他改进\"\n            }\n        ]\n    },\n    {\n        \"id\": \"主要影响点\",\n        \"depth\": 2,\n        \"text\": \"主要影响点\",\n        \"children\": [\n            {\n                \"id\": \"useasyncdatausefetch\",\n                \"depth\": 3,\n                \"text\": \"useAsyncData、useFetch\"\n            },\n            {\n                \"id\": \"_2025年05月06日093334-更新\",\n                \"depth\": 3,\n                \"text\": \"2025年05月06日09:33:34 更新\"\n            }\n        ]\n    }\n]\n```\n\n一般一篇文章里，`h1` 表示的是文章标题，在一个页面中通常只会存在一个 `h1` 标题，所以在写文章时，要注意不要乱用 `# 标题`这个语法。 `h2`、`h3`、 就是文章里常用的二级和三级标题，在数据中就是 `depth` 为 `2` 或 `3` \n\n组件本身使用 `ul` 、`li` 来渲染即可，再配合 `fixed` 或 `sticky`，使其国定在文章的一侧。\n\n对于 `Nuxt` 来说，`TOC` 组件可以完全是一个**客户端组件**，因为不需要被爬虫抓取，也不是初次渲染需要的重要信息。而且如果要做一些简单的交互，也需要等前端环境加载出来之后才能做到。\n\n所以只需要使用一个 `computed` 拿到 `toc` 数据，然后把数据传递给组件即可。\n\n我观察了一圈，感觉[少数派](https://sspai.com/)的 TOC 组件是比较美观的，于是我仿照他们的思路封装了自己的 [TOC 组件](https://github.com/aatrooox/blog.zzao.club/blob/main/app/components/common/AppToc.vue)\n\ntoc 的配置位于 `nuxt.config.ts` ：\n\n```typescript\ncontent: {\n\tbuild: {\n\t\tmarkdown: {\n\t\t\ttoc: {\n\t\t\t  depth: 2,\n\t\t\t  searchDepth: 2\n\t\t\t}\n\t\t}\n\t}\n}\n```\n\n默认深度是 `2`，我一般会用到 `3`。\n\n同时，如果要想自己定义 h1、h2、h3 标题的样式，需要在 `app/components/content` 目录下新建 `ProseH1.vue`、`ProseH2.vue`、`ProseH3.vue` 组件。\n\n写样式时，不管如何封装，**记得不要丢掉 id 属性**\n\n**ProseH3.vue 为例**\n\n```vue\n\u003Ctemplate>\n  \u003Cdiv :id=\"props.id\" class=\"heading my-4 cursor-pointer scroll-mt-14\">\n    \u003Cspan class=\"px-2 py-1 text-xl font-bold bg-zinc-800 text-white dark:bg-zinc-200 \">\n      \u003Ca v-if=\"props.id && generate\" :href=\"`#${props.id}`\" class=\"!text-zinc-200 dark:!text-zinc-800\">\n        \u003Cslot />\n      \u003C/a>\n      \u003Cslot v-else />\n    \u003C/span>\n  \u003C/div>\n\u003C/template>\n\n\u003Cscript setup lang=\"ts\">\nimport { computed, useRuntimeConfig } from '#imports'\n\nconst props = defineProps\u003C{ id?: string }>()\n\nconst { headings } = useRuntimeConfig().public.mdc\nconst generate = computed(() => props.id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h1)))\n\u003C/script>\n```\n\n对应 TOC 组件中：\n\n```vue\n# template v-for child in link.children\n\u003Cli>\n    \u003Cspan>#\u003C/span>\n    \u003CNuxtLink :href=\"`#${child.id}`\"> {{ child.text }} \u003C/NuxtLink>\n\u003C/li>\n```\n\n也可以像我的组件一样，配合 `IntersectionObserver` ，做到 TOC 组件的导航根据滚动的区域使其高亮或是显示其他标识\n\n![](https://img.zzao.club/article/202505131613386.png)\n\n```typescript\n const headings = document.querySelectorAll('.heading')\n\tobserver.value = new IntersectionObserver((entries) => {\n\t  entries.forEach(entry => {\n\t\tif (entry.isIntersecting) {\n\t\t  activeId.value = entry.target.id\n\t\t}\n\t  })\n\t})\n\theadings.forEach(heading => observer.value.observe(heading))\n```\n\n",{"title":5,"description":1433},"post/nuxt/content/nuxt-content-toc",[756],[1443,1444],"@nuxt/content@3.4.0","nuxt@3.17.2","UChgvi1e7Dk4YpU_d_FhDo0SwHGNDvIEFDKOu_WE74Q",[1447,1451],{"title":1448,"path":1449,"stem":1450},"OpenClaw 安装入门（Windows）","/post/zzao/openclaw/openclaw-install-windows","post/zzao/openclaw/openclaw-install-windows",{"title":1452,"path":1453,"stem":1454},"假设你是AI，你的Skill应该是什么样的","/post/zzao/ai-skill-structure","post/zzao/ai-skill-structure",1779005085847]