[{"data":1,"prerenderedAt":2476},["ShallowReactive",2],{"page-/post/nuxt/nuxt3-full-stack-config":3,"surrounding-page":2467},{"id":4,"title":5,"author":6,"body":7,"date":2450,"description":2451,"extension":2452,"group":6,"lastmod":2453,"meta":2454,"navigation":358,"path":2455,"rawbody":2456,"seo":2457,"showTitle":6,"stem":2458,"tags":2459,"versions":2462,"__hash__":2466},"content/post/nuxt/Nuxt3-full-stack-config.md","Nuxt3全栈开发 · 配置篇",null,{"type":8,"value":9,"toc":2441},"minimark",[10,14,18,26,120,123,130,137,146,149,258,275,278,451,458,497,503,710,727,731,923,932,956,967,970,979,986,1007,1013,1020,1050,1056,1065,1068,1074,1077,1082,1115,1126,1380,1392,1399,1819,1830,1898,1905,1909,1919,1966,1969,1976,1983,2006,2015,2067,2080,2085,2089,2097,2104,2107,2116,2122,2128,2147,2150,2176,2179,2210,2218,2260,2265,2272,2277,2287,2303,2310,2400,2404,2407,2410,2417,2431,2434,2437],[11,12,13],"p",{},"最近在用Nuxt3全栈开发个人博客，踩了不少小坑，这篇文章总结一下。",[15,16,17],"h2",{"id":17},"依赖库及博客主要功能",[11,19,20,21,25],{},"先来介绍一下我用到了哪些 ",[22,23,24],"code",{},"Nuxt3"," 的相关生态及对应的功能。",[27,28,29,36,53,63,69,75,88,94,104,110],"ul",{},[30,31,32,35],"li",{},[22,33,34],{},"@nuxtjs/color-mode"," 颜色模式：白天（light）、黑夜（dark）、系统（system）三者切换",[30,37,38,41,42,45,46,49,50],{},[22,39,40],{},"@nuxt/content"," 展示文章，基于",[22,43,44],{},"mdc","，可以使用自定义组件渲染",[22,47,48],{},"markdown","，支持 ",[22,51,52],{},"front matter",[30,54,55,58,59,62],{},[22,56,57],{},"@nuxtjs/tailwindcss"," 样式。以及配合 ",[22,60,61],{},"@tailwindcss/typography"," 自定义markdown主题",[30,64,65,68],{},[22,66,67],{},"@primevue/nuxt-module"," 组件库。",[30,70,71,74],{},[22,72,73],{},"@nuxt/image"," 图片",[30,76,77,80,81,84,85],{},[22,78,79],{},"@nuxt/icon"," 图标。配合 ",[22,82,83],{},"iconify"," ，我目前用的图标主要是 ",[22,86,87],{},"@iconify-json/icon-park-outline",[30,89,90,93],{},[22,91,92],{},"@nuxt/robots"," SEO",[30,95,96,99,100,103],{},[22,97,98],{},"@nuxt/mdc"," 解析动态（类型Memos/朋友圈/X）展示。和文章有一致的表现，也可以通过",[22,101,102],{},"tailwindcss","自定义样式",[30,105,106,109],{},[22,107,108],{},"prisma"," 管理数据库（sqlite3）",[30,111,112,115,116,119],{},[22,113,114],{},"gitea"," 管理代码仓库（私有）。以及使用",[22,117,118],{},"workflows","自动部署",[11,121,122],{},"基于这些库逐步使用和功能的逐渐实现，分享一下使用经验。",[11,124,125,126,129],{},"如果没有刻意提到的安装方式，则默认都是用 ",[22,127,128],{},"npx nuxi@latest module add xxxx"," 进行安装。",[11,131,132,133,136],{},"如果没有表明在何处配置，则默认是在 ",[22,134,135],{},"nuxt.config.ts"," 的顶级",[11,138,139,140,142,143],{},"如果代码中变量明显没有引入，则是使用了 ",[22,141,24],{}," 的 ",[22,144,145],{},"auto imports",[15,147,148],{"id":148},"颜色模式",[150,151,156],"pre",{"className":152,"code":153,"language":154,"meta":155,"style":155},"language-typescript shiki shiki-themes github-light","colorMode: {\n    preference: 'system', // default value of $colorMode.preference\n    fallback: 'light', // fallback value if not system preference found\n    // hid: 'nuxt-color-mode-script',\n    // globalName: '__NUXT_COLOR_MODE__',\n    // componentName: 'ColorScheme',\n    // classPrefix: '',\n    // classSuffix: '-mode',\n    // storage: 'localStorage', // or 'sessionStorage' or 'cookie'\n    // storageKey: 'nuxt-color-mode'\n  },\n","typescript","",[22,157,158,171,191,207,213,219,225,231,237,246,252],{"__ignoreMap":155},[159,160,163,167],"span",{"class":161,"line":162},"line",1,[159,164,166],{"class":165},"s7eDp","colorMode",[159,168,170],{"class":169},"sgsFI",": {\n",[159,172,174,177,180,184,187],{"class":161,"line":173},2,[159,175,176],{"class":165},"    preference",[159,178,179],{"class":169},": ",[159,181,183],{"class":182},"sYBdl","'system'",[159,185,186],{"class":169},", ",[159,188,190],{"class":189},"sAwPA","// default value of $colorMode.preference\n",[159,192,194,197,199,202,204],{"class":161,"line":193},3,[159,195,196],{"class":165},"    fallback",[159,198,179],{"class":169},[159,200,201],{"class":182},"'light'",[159,203,186],{"class":169},[159,205,206],{"class":189},"// fallback value if not system preference found\n",[159,208,210],{"class":161,"line":209},4,[159,211,212],{"class":189},"    // hid: 'nuxt-color-mode-script',\n",[159,214,216],{"class":161,"line":215},5,[159,217,218],{"class":189},"    // globalName: '__NUXT_COLOR_MODE__',\n",[159,220,222],{"class":161,"line":221},6,[159,223,224],{"class":189},"    // componentName: 'ColorScheme',\n",[159,226,228],{"class":161,"line":227},7,[159,229,230],{"class":189},"    // classPrefix: '',\n",[159,232,234],{"class":161,"line":233},8,[159,235,236],{"class":189},"    // classSuffix: '-mode',\n",[159,238,240,243],{"class":161,"line":239},9,[159,241,242],{"class":189},"    // storage: 'localStorage',",[159,244,245],{"class":189}," // or 'sessionStorage' or 'cookie'\n",[159,247,249],{"class":161,"line":248},10,[159,250,251],{"class":189},"    // storageKey: 'nuxt-color-mode'\n",[159,253,255],{"class":161,"line":254},11,[159,256,257],{"class":169},"  },\n",[11,259,260,261,264,265,264,268,271,272,274],{},"有三种模式：",[22,262,263],{},"light"," ",[22,266,267],{},"dark",[22,269,270],{},"system"," ，默认为 ",[22,273,270],{}," 根据系统模式来自动设置浅色或深色",[11,276,277],{},"切换模式时：",[150,279,281],{"className":152,"code":280,"language":154,"meta":155,"style":155},"const colorMode = useColorMode()\nconst index = ref(modes.indexOf(colorMode.preference))\n// 用来显示不同图标\nconst modes = ['system', 'light', 'dark']\n\nconst modeIcon = computed( () => {\n    switch ...\n    case ...\n})\n\nfunction toggleColorMode() {\n  colorMode.preference = modes[(++index.value) % modes.length]\n}\n",[22,282,283,302,323,328,354,360,381,389,396,401,405,416,445],{"__ignoreMap":155},[159,284,285,289,293,296,299],{"class":161,"line":162},[159,286,288],{"class":287},"sD7c4","const",[159,290,292],{"class":291},"sYu0t"," colorMode",[159,294,295],{"class":287}," =",[159,297,298],{"class":165}," useColorMode",[159,300,301],{"class":169},"()\n",[159,303,304,306,309,311,314,317,320],{"class":161,"line":173},[159,305,288],{"class":287},[159,307,308],{"class":291}," index",[159,310,295],{"class":287},[159,312,313],{"class":165}," ref",[159,315,316],{"class":169},"(modes.",[159,318,319],{"class":165},"indexOf",[159,321,322],{"class":169},"(colorMode.preference))\n",[159,324,325],{"class":161,"line":193},[159,326,327],{"class":189},"// 用来显示不同图标\n",[159,329,330,332,335,337,340,342,344,346,348,351],{"class":161,"line":209},[159,331,288],{"class":287},[159,333,334],{"class":291}," modes",[159,336,295],{"class":287},[159,338,339],{"class":169}," [",[159,341,183],{"class":182},[159,343,186],{"class":169},[159,345,201],{"class":182},[159,347,186],{"class":169},[159,349,350],{"class":182},"'dark'",[159,352,353],{"class":169},"]\n",[159,355,356],{"class":161,"line":215},[159,357,359],{"emptyLinePlaceholder":358},true,"\n",[159,361,362,364,367,369,372,375,378],{"class":161,"line":221},[159,363,288],{"class":287},[159,365,366],{"class":291}," modeIcon",[159,368,295],{"class":287},[159,370,371],{"class":165}," computed",[159,373,374],{"class":169},"( () ",[159,376,377],{"class":287},"=>",[159,379,380],{"class":169}," {\n",[159,382,383,386],{"class":161,"line":227},[159,384,385],{"class":287},"    switch",[159,387,388],{"class":287}," ...\n",[159,390,391,394],{"class":161,"line":233},[159,392,393],{"class":287},"    case",[159,395,388],{"class":287},[159,397,398],{"class":161,"line":239},[159,399,400],{"class":169},"})\n",[159,402,403],{"class":161,"line":248},[159,404,359],{"emptyLinePlaceholder":358},[159,406,407,410,413],{"class":161,"line":254},[159,408,409],{"class":287},"function",[159,411,412],{"class":165}," toggleColorMode",[159,414,415],{"class":169},"() {\n",[159,417,419,422,425,428,431,434,437,440,443],{"class":161,"line":418},12,[159,420,421],{"class":169},"  colorMode.preference ",[159,423,424],{"class":287},"=",[159,426,427],{"class":169}," modes[(",[159,429,430],{"class":287},"++",[159,432,433],{"class":169},"index.value) ",[159,435,436],{"class":287},"%",[159,438,439],{"class":169}," modes.",[159,441,442],{"class":291},"length",[159,444,353],{"class":169},[159,446,448],{"class":161,"line":447},13,[159,449,450],{"class":169},"}\n",[11,452,453,454,457],{},"配合组件库 ",[22,455,456],{},"primevue"," 的配置",[150,459,461],{"className":152,"code":460,"language":154,"meta":155,"style":155},"primevue: {\n    importTheme: { from: '~/primevue/theme.ts' },\n    // usePrimeVue: false\n  },\n",[22,462,463,469,488,493],{"__ignoreMap":155},[159,464,465,467],{"class":161,"line":162},[159,466,456],{"class":165},[159,468,170],{"class":169},[159,470,471,474,477,480,482,485],{"class":161,"line":173},[159,472,473],{"class":165},"    importTheme",[159,475,476],{"class":169},": { ",[159,478,479],{"class":165},"from",[159,481,179],{"class":169},[159,483,484],{"class":182},"'~/primevue/theme.ts'",[159,486,487],{"class":169}," },\n",[159,489,490],{"class":161,"line":193},[159,491,492],{"class":189},"    // usePrimeVue: false\n",[159,494,495],{"class":161,"line":209},[159,496,257],{"class":169},[11,498,499,502],{},[22,500,501],{},"theme.ts"," 如下",[150,504,506],{"className":152,"code":505,"language":154,"meta":155,"style":155},"\nimport { definePreset } from '@primevue/themes';\nimport Aura from '@primevue/themes/aura';\n\n\nconst Noir = definePreset(Aura, {\n  semantic: {\n      primary: {\n          ...\n      },\n      colorScheme: {\n        light: { ... }\n        dark: { ... }\n      }\n  },\n  components: {\n    button: {\n      ...\n    }\n  }\n});\n\n\nexport default {\n    preset: Noir,\n    options: {\n        darkModeSelector:'.dark-mode'\n    }\n};\n\n",[22,507,508,512,528,542,546,550,565,570,575,580,585,590,601,610,616,621,627,633,639,645,651,657,662,667,678,684,690,699,704],{"__ignoreMap":155},[159,509,510],{"class":161,"line":162},[159,511,359],{"emptyLinePlaceholder":358},[159,513,514,517,520,522,525],{"class":161,"line":173},[159,515,516],{"class":287},"import",[159,518,519],{"class":169}," { definePreset } ",[159,521,479],{"class":287},[159,523,524],{"class":182}," '@primevue/themes'",[159,526,527],{"class":169},";\n",[159,529,530,532,535,537,540],{"class":161,"line":193},[159,531,516],{"class":287},[159,533,534],{"class":169}," Aura ",[159,536,479],{"class":287},[159,538,539],{"class":182}," '@primevue/themes/aura'",[159,541,527],{"class":169},[159,543,544],{"class":161,"line":209},[159,545,359],{"emptyLinePlaceholder":358},[159,547,548],{"class":161,"line":215},[159,549,359],{"emptyLinePlaceholder":358},[159,551,552,554,557,559,562],{"class":161,"line":221},[159,553,288],{"class":287},[159,555,556],{"class":291}," Noir",[159,558,295],{"class":287},[159,560,561],{"class":165}," definePreset",[159,563,564],{"class":169},"(Aura, {\n",[159,566,567],{"class":161,"line":227},[159,568,569],{"class":169},"  semantic: {\n",[159,571,572],{"class":161,"line":233},[159,573,574],{"class":169},"      primary: {\n",[159,576,577],{"class":161,"line":239},[159,578,579],{"class":287},"          ...\n",[159,581,582],{"class":161,"line":248},[159,583,584],{"class":169},"      },\n",[159,586,587],{"class":161,"line":254},[159,588,589],{"class":169},"      colorScheme: {\n",[159,591,592,595,598],{"class":161,"line":418},[159,593,594],{"class":169},"        light: { ",[159,596,597],{"class":287},"...",[159,599,600],{"class":169}," }\n",[159,602,603,606,608],{"class":161,"line":447},[159,604,605],{"class":169},"        dark: { ",[159,607,597],{"class":287},[159,609,600],{"class":169},[159,611,613],{"class":161,"line":612},14,[159,614,615],{"class":169},"      }\n",[159,617,619],{"class":161,"line":618},15,[159,620,257],{"class":169},[159,622,624],{"class":161,"line":623},16,[159,625,626],{"class":169},"  components: {\n",[159,628,630],{"class":161,"line":629},17,[159,631,632],{"class":169},"    button: {\n",[159,634,636],{"class":161,"line":635},18,[159,637,638],{"class":287},"      ...\n",[159,640,642],{"class":161,"line":641},19,[159,643,644],{"class":169},"    }\n",[159,646,648],{"class":161,"line":647},20,[159,649,650],{"class":169},"  }\n",[159,652,654],{"class":161,"line":653},21,[159,655,656],{"class":169},"});\n",[159,658,660],{"class":161,"line":659},22,[159,661,359],{"emptyLinePlaceholder":358},[159,663,665],{"class":161,"line":664},23,[159,666,359],{"emptyLinePlaceholder":358},[159,668,670,673,676],{"class":161,"line":669},24,[159,671,672],{"class":287},"export",[159,674,675],{"class":287}," default",[159,677,380],{"class":169},[159,679,681],{"class":161,"line":680},25,[159,682,683],{"class":169},"    preset: Noir,\n",[159,685,687],{"class":161,"line":686},26,[159,688,689],{"class":169},"    options: {\n",[159,691,693,696],{"class":161,"line":692},27,[159,694,695],{"class":169},"        darkModeSelector:",[159,697,698],{"class":182},"'.dark-mode'\n",[159,700,702],{"class":161,"line":701},28,[159,703,644],{"class":169},[159,705,707],{"class":161,"line":706},29,[159,708,709],{"class":169},"};\n",[11,711,712,713,716,717,720,721,142,724],{},"设置 ",[22,714,715],{},"darkModeSelector"," 为 ",[22,718,719],{},".dark-mode","。 使用colorMode切换时，会自动切换 ",[22,722,723],{},"html",[22,725,726],{},"class",[15,728,730],{"id":729},"解析markdown文件","解析Markdown文件",[150,732,734],{"className":152,"code":733,"language":154,"meta":155,"style":155},"content: {\n    documentDriven: {\n      injectPage: false\n    },\n    highlight: {\n      theme: 'github-light',\n      langs: ['typescript', 'vue', 'javascript', 'go', 'shell', 'bash', 'yaml', 'markdown', 'json', 'html', 'ts', 'js']\n    },\n    sources: {\n      obsidian: {\n        prefix: '/obsidian', // All contents inside this source will be prefixed with `/fa`\n        driver: 'fs',\n        base: `/Users/your_name/code/notion/blog` // Path for source directory\n      },\n    }\n  },\n",[22,735,736,743,750,760,765,772,785,853,857,864,871,886,898,911,915,919],{"__ignoreMap":155},[159,737,738,741],{"class":161,"line":162},[159,739,740],{"class":165},"content",[159,742,170],{"class":169},[159,744,745,748],{"class":161,"line":173},[159,746,747],{"class":165},"    documentDriven",[159,749,170],{"class":169},[159,751,752,755,757],{"class":161,"line":193},[159,753,754],{"class":165},"      injectPage",[159,756,179],{"class":169},[159,758,759],{"class":291},"false\n",[159,761,762],{"class":161,"line":209},[159,763,764],{"class":169},"    },\n",[159,766,767,770],{"class":161,"line":215},[159,768,769],{"class":165},"    highlight",[159,771,170],{"class":169},[159,773,774,777,779,782],{"class":161,"line":221},[159,775,776],{"class":165},"      theme",[159,778,179],{"class":169},[159,780,781],{"class":182},"'github-light'",[159,783,784],{"class":169},",\n",[159,786,787,790,793,796,798,801,803,806,808,811,813,816,818,821,823,826,828,831,833,836,838,841,843,846,848,851],{"class":161,"line":227},[159,788,789],{"class":165},"      langs",[159,791,792],{"class":169},": [",[159,794,795],{"class":182},"'typescript'",[159,797,186],{"class":169},[159,799,800],{"class":182},"'vue'",[159,802,186],{"class":169},[159,804,805],{"class":182},"'javascript'",[159,807,186],{"class":169},[159,809,810],{"class":182},"'go'",[159,812,186],{"class":169},[159,814,815],{"class":182},"'shell'",[159,817,186],{"class":169},[159,819,820],{"class":182},"'bash'",[159,822,186],{"class":169},[159,824,825],{"class":182},"'yaml'",[159,827,186],{"class":169},[159,829,830],{"class":182},"'markdown'",[159,832,186],{"class":169},[159,834,835],{"class":182},"'json'",[159,837,186],{"class":169},[159,839,840],{"class":182},"'html'",[159,842,186],{"class":169},[159,844,845],{"class":182},"'ts'",[159,847,186],{"class":169},[159,849,850],{"class":182},"'js'",[159,852,353],{"class":169},[159,854,855],{"class":161,"line":233},[159,856,764],{"class":169},[159,858,859,862],{"class":161,"line":239},[159,860,861],{"class":165},"    sources",[159,863,170],{"class":169},[159,865,866,869],{"class":161,"line":248},[159,867,868],{"class":165},"      obsidian",[159,870,170],{"class":169},[159,872,873,876,878,881,883],{"class":161,"line":254},[159,874,875],{"class":165},"        prefix",[159,877,179],{"class":169},[159,879,880],{"class":182},"'/obsidian'",[159,882,186],{"class":169},[159,884,885],{"class":189},"// All contents inside this source will be prefixed with `/fa`\n",[159,887,888,891,893,896],{"class":161,"line":418},[159,889,890],{"class":165},"        driver",[159,892,179],{"class":169},[159,894,895],{"class":182},"'fs'",[159,897,784],{"class":169},[159,899,900,903,905,908],{"class":161,"line":447},[159,901,902],{"class":165},"        base",[159,904,179],{"class":169},[159,906,907],{"class":182},"`/Users/your_name/code/notion/blog`",[159,909,910],{"class":189}," // Path for source directory\n",[159,912,913],{"class":161,"line":612},[159,914,584],{"class":169},[159,916,917],{"class":161,"line":618},[159,918,644],{"class":169},[159,920,921],{"class":161,"line":623},[159,922,257],{"class":169},[11,924,925,142,928,931],{},[22,926,927],{},"documentDriven",[22,929,930],{},"injectPage"," 是为了解决一个警告信息",[150,933,937],{"className":934,"code":935,"language":936,"meta":155,"style":155},"language-shell shiki shiki-themes github-light","[@nuxt/content 09:52:13] Using \u003CNuxtLayout> inside app.vue will cause unwanted layout shifting in your application.\n","shell",[22,938,939],{"__ignoreMap":155},[159,940,941,944,947,950,953],{"class":161,"line":162},[159,942,943],{"class":169},"[@nuxt/content 09:52:13] Using ",[159,945,946],{"class":287},"\u003C",[159,948,949],{"class":169},"NuxtLayout",[159,951,952],{"class":287},">",[159,954,955],{"class":169}," inside app.vue will cause unwanted layout shifting in your application.\n",[11,957,958,959,962,963,966],{},"原因是，原代码从 ",[22,960,961],{},"pages/[slug].vue"," 改为 ",[22,964,965],{},"pages/post/[slug].vue"," 导致报错。",[11,968,969],{},"以下是搜索时找到的相关issue",[11,971,972],{},[973,974,978],"a",{"href":975,"rel":976},"https://github.com/nuxt/nuxt/issues/15240",[977],"nofollow","NuxtLayout warn vs documentation #15240",[11,980,981],{},[973,982,985],{"href":983,"rel":984},"https://github.com/dan-bowen/nuxt-blog-starter/pull/9",[977],"nuxt-blog-starter",[11,987,988,991,992,997,998,1001,1002],{},[22,989,990],{},"highlight"," 是配置代码块高亮的，内部使用的是 ",[973,993,996],{"href":994,"rel":995},"https://github.com/shikijs/shiki",[977],"Shiki","，同时和 ",[22,999,1000],{},"color-mode"," 兼容，可以查看 ",[973,1003,1006],{"href":1004,"rel":1005},"https://content.nuxt.com/get-started/configuration#highlight",[977],"更多官方文档",[11,1008,1009,1012],{},[22,1010,1011],{},"sources"," 是核心配置，",[11,1014,1015,1016,1019],{},"官方的默认配置是 ",[22,1017,1018],{},"base: resolve(__dirname, 'content')"," , 即从当前项目下的content内读取md文件，我直接改成了自己本地的一个目录。",[11,1021,1022,1023,1027,1028,1031,1032,1035,1036,1039,1040,1043,1044,1046,1047,1049],{},"启动项目时，会",[1024,1025,1026],"strong",{},"读取并监听","该目录下的所有md文件，并有一个忽略规则（开头为 ",[22,1029,1030],{},"."," 或 ",[22,1033,1034],{},"-"," 的 ），然后会解析并缓存到 ",[22,1037,1038],{},".nuxt"," 内，",[22,1041,1042],{},"dev"," 模式下就是从 ",[22,1045,1038],{}," 中直接拿缓存数据，所以有一些奇怪的问题可以通过删除 ",[22,1048,1038],{}," 并重新运行可以解决。",[11,1051,1052,1053,1055],{},"当然这个配置也决定了必须带着 ",[22,1054,1038],{}," 目录才能正常打包。",[11,1057,1058,1059,1061,1062,1064],{},"只靠 ",[22,1060,40],{}," 解析出的文章还没眼看，需要借助 ",[22,1063,61],{},"。",[11,1066,1067],{},"使用前：",[11,1069,1070],{},[1071,1072],"img",{"alt":155,"src":1073},"https://img.zzao.club/1-img-20241106171191.png",[11,1075,1076],{},"使用（并自定义）后：",[11,1078,1079],{},[1071,1080],{"alt":155,"src":1081},"https://img.zzao.club/1-img-20241119101171.png",[11,1083,1084,1086,1087,1089,1090,1089,1092,1089,1094,1089,1097,1099,1100,1099,1102,1104,1105,1107,1108,1099,1111,1114],{},[22,1085,48],{}," 被解析为 ",[22,1088,11],{}," 、",[22,1091,973],{},[22,1093,22],{},[22,1095,1096],{},"h1",[22,1098,15],{},"、",[22,1101,1071],{},[22,1103,1024],{}," 等这些标签，而在 ",[22,1106,40],{}," 中，使用对应的 ",[22,1109,1110],{},"ProseA",[22,1112,1113],{},"ProseH1"," 组件进行渲染。",[11,1116,1117,1118,1121,1122,1125],{},"并且支持自己编写然后覆盖这些组件预设，在 ",[22,1119,1120],{},"components/content"," 目录下新建一个同名的组件，如 ",[22,1123,1124],{},"ProseA.vue"," ：",[150,1127,1129],{"className":152,"code":1128,"language":154,"meta":155,"style":155},"\u003Ctemplate>\n  \u003CNuxtLink :href=\"props.href\" :target=\"props.target\"\n    class=\"font-bold border-b-2 border-dashed border-zinc-600 hover:border-solid hover:border-zinc-900 dark:border-zinc-300 dark:hover:border-zinc-100\">\n    \u003Cslot />\n  \u003C/NuxtLink>\n\u003C/template>\n\n\u003Cscript setup lang=\"ts\">\nimport type { PropType } from 'vue'\n\nconst props = defineProps({\n  href: {\n    type: String,\n    default: ''\n  },\n  target: {\n    type: String as PropType\u003C'_blank' | '_parent' | '_self' | '_top' | (string & object) | null | undefined>,\n    default: '_blank',\n    required: false\n  }\n})\n\u003C/script>\n",[22,1130,1131,1141,1165,1177,1188,1197,1206,1210,1224,1239,1243,1258,1263,1268,1276,1280,1285,1348,1356,1363,1367,1371],{"__ignoreMap":155},[159,1132,1133,1135,1138],{"class":161,"line":162},[159,1134,946],{"class":169},[159,1136,1137],{"class":165},"template",[159,1139,1140],{"class":169},">\n",[159,1142,1143,1146,1149,1152,1154,1157,1160,1162],{"class":161,"line":173},[159,1144,1145],{"class":287},"  \u003C",[159,1147,1148],{"class":165},"NuxtLink",[159,1150,1151],{"class":169}," :href",[159,1153,424],{"class":287},[159,1155,1156],{"class":182},"\"props.href\"",[159,1158,1159],{"class":169}," :target",[159,1161,424],{"class":287},[159,1163,1164],{"class":182},"\"props.target\"\n",[159,1166,1167,1170,1172,1175],{"class":161,"line":193},[159,1168,1169],{"class":169},"    class",[159,1171,424],{"class":287},[159,1173,1174],{"class":182},"\"font-bold border-b-2 border-dashed border-zinc-600 hover:border-solid hover:border-zinc-900 dark:border-zinc-300 dark:hover:border-zinc-100\"",[159,1176,1140],{"class":287},[159,1178,1179,1182,1185],{"class":161,"line":209},[159,1180,1181],{"class":287},"    \u003C",[159,1183,1184],{"class":169},"slot ",[159,1186,1187],{"class":287},"/>\n",[159,1189,1190,1193,1195],{"class":161,"line":215},[159,1191,1192],{"class":287},"  \u003C/",[159,1194,1148],{"class":169},[159,1196,1140],{"class":287},[159,1198,1199,1202,1204],{"class":161,"line":221},[159,1200,1201],{"class":287},"\u003C/",[159,1203,1137],{"class":169},[159,1205,1140],{"class":287},[159,1207,1208],{"class":161,"line":227},[159,1209,359],{"emptyLinePlaceholder":358},[159,1211,1212,1214,1217,1219,1222],{"class":161,"line":233},[159,1213,946],{"class":287},[159,1215,1216],{"class":169},"script setup lang",[159,1218,424],{"class":287},[159,1220,1221],{"class":182},"\"ts\"",[159,1223,1140],{"class":287},[159,1225,1226,1228,1231,1234,1236],{"class":161,"line":239},[159,1227,516],{"class":287},[159,1229,1230],{"class":287}," type",[159,1232,1233],{"class":169}," { PropType } ",[159,1235,479],{"class":287},[159,1237,1238],{"class":182}," 'vue'\n",[159,1240,1241],{"class":161,"line":248},[159,1242,359],{"emptyLinePlaceholder":358},[159,1244,1245,1247,1250,1252,1255],{"class":161,"line":254},[159,1246,288],{"class":287},[159,1248,1249],{"class":291}," props",[159,1251,295],{"class":287},[159,1253,1254],{"class":165}," defineProps",[159,1256,1257],{"class":169},"({\n",[159,1259,1260],{"class":161,"line":418},[159,1261,1262],{"class":169},"  href: {\n",[159,1264,1265],{"class":161,"line":447},[159,1266,1267],{"class":169},"    type: String,\n",[159,1269,1270,1273],{"class":161,"line":612},[159,1271,1272],{"class":169},"    default: ",[159,1274,1275],{"class":182},"''\n",[159,1277,1278],{"class":161,"line":618},[159,1279,257],{"class":169},[159,1281,1282],{"class":161,"line":623},[159,1283,1284],{"class":169},"  target: {\n",[159,1286,1287,1290,1293,1296,1298,1301,1304,1307,1309,1312,1314,1317,1319,1322,1325,1328,1331,1334,1337,1340,1342,1345],{"class":161,"line":629},[159,1288,1289],{"class":169},"    type: String ",[159,1291,1292],{"class":287},"as",[159,1294,1295],{"class":165}," PropType",[159,1297,946],{"class":169},[159,1299,1300],{"class":182},"'_blank'",[159,1302,1303],{"class":287}," |",[159,1305,1306],{"class":182}," '_parent'",[159,1308,1303],{"class":287},[159,1310,1311],{"class":182}," '_self'",[159,1313,1303],{"class":287},[159,1315,1316],{"class":182}," '_top'",[159,1318,1303],{"class":287},[159,1320,1321],{"class":169}," (",[159,1323,1324],{"class":291},"string",[159,1326,1327],{"class":287}," &",[159,1329,1330],{"class":291}," object",[159,1332,1333],{"class":169},") ",[159,1335,1336],{"class":287},"|",[159,1338,1339],{"class":291}," null",[159,1341,1303],{"class":287},[159,1343,1344],{"class":291}," undefined",[159,1346,1347],{"class":169},">,\n",[159,1349,1350,1352,1354],{"class":161,"line":635},[159,1351,1272],{"class":169},[159,1353,1300],{"class":182},[159,1355,784],{"class":169},[159,1357,1358,1361],{"class":161,"line":641},[159,1359,1360],{"class":169},"    required: ",[159,1362,759],{"class":291},[159,1364,1365],{"class":161,"line":647},[159,1366,650],{"class":169},[159,1368,1369],{"class":161,"line":653},[159,1370,400],{"class":169},[159,1372,1373,1375,1378],{"class":161,"line":659},[159,1374,1201],{"class":287},[159,1376,1377],{"class":169},"script",[159,1379,1140],{"class":287},[11,1381,1382,1383,1386,1387],{},"这里我把他的默认打开方式改为了 ",[22,1384,1385],{},"_blank"," ，并自定义了功能和样式。其他组件同理，都是可以自定义的。 ",[973,1388,1391],{"href":1389,"rel":1390},"https://content.nuxt.com/components/prose#prosea",[977],"查看NuxtContent中支持的组件",[11,1393,1394,1395,1398],{},"同样的可以基于 ",[22,1396,1397],{},"typography"," 在顶层修改其样式。",[150,1400,1404],{"className":1401,"code":1402,"language":1403,"meta":155,"style":155},"language-js shiki shiki-themes github-light","import typography from '@tailwindcss/typography'\n/** @type {import('tailwindcss').Config} */\nexport default {\n  content: [],\n  plugins: [typography()],\n  theme: {\n    extend: {\n      typography: (theme) => ({\n        DEFAULT: {\n          css: {\n            code: {\n              // backgroundColor: theme('colors.gray.100'),\n              // color: theme('colors.orange.400'),\n              fontWeight: 'normal',\n              marginLeft: theme('spacing.1'),\n              marginRight: theme('spacing.1'),\n              paddingLeft: theme('spacing.2'),\n              paddingRight: theme('spacing.2'),\n              paddingTop: '1px',\n              paddingBottom: '1px',\n              borderRadius: '2px',\n              '&::before': {\n                content: `''!important`\n              },\n              '&::after': {\n                content: `''!important`\n              }\n            },\n            p: {\n              lineHeight: theme('lineHeight.loose')\n            },\n            pre: {\n              // paddingBottom: 0,\n              // paddingTop: 0,\n              '& > code': {\n                color: theme('colors.gray.900'),\n                backgroundColor: 'transparent'\n              }\n            },\n            a: {\n              textDecoration: 'none'\n            },\n            img: {\n              marginTop: 0,\n              marginBottom: 0,\n            }\n          }\n        }\n      })\n    },\n  },\n}\n\n","js",[22,1405,1406,1418,1432,1440,1445,1455,1460,1465,1484,1489,1494,1499,1504,1509,1519,1535,1548,1562,1575,1585,1594,1604,1611,1619,1624,1631,1637,1642,1647,1652,1668,1673,1679,1685,1691,1699,1714,1723,1728,1733,1739,1748,1753,1759,1770,1780,1786,1792,1798,1804,1809,1814],{"__ignoreMap":155},[159,1407,1408,1410,1413,1415],{"class":161,"line":162},[159,1409,516],{"class":287},[159,1411,1412],{"class":169}," typography ",[159,1414,479],{"class":287},[159,1416,1417],{"class":182}," '@tailwindcss/typography'\n",[159,1419,1420,1423,1426,1429],{"class":161,"line":173},[159,1421,1422],{"class":189},"/** ",[159,1424,1425],{"class":287},"@type",[159,1427,1428],{"class":165}," {import('tailwindcss').Config}",[159,1430,1431],{"class":189}," */\n",[159,1433,1434,1436,1438],{"class":161,"line":193},[159,1435,672],{"class":287},[159,1437,675],{"class":287},[159,1439,380],{"class":169},[159,1441,1442],{"class":161,"line":209},[159,1443,1444],{"class":169},"  content: [],\n",[159,1446,1447,1450,1452],{"class":161,"line":215},[159,1448,1449],{"class":169},"  plugins: [",[159,1451,1397],{"class":165},[159,1453,1454],{"class":169},"()],\n",[159,1456,1457],{"class":161,"line":221},[159,1458,1459],{"class":169},"  theme: {\n",[159,1461,1462],{"class":161,"line":227},[159,1463,1464],{"class":169},"    extend: {\n",[159,1466,1467,1470,1473,1477,1479,1481],{"class":161,"line":233},[159,1468,1469],{"class":165},"      typography",[159,1471,1472],{"class":169},": (",[159,1474,1476],{"class":1475},"sqxcx","theme",[159,1478,1333],{"class":169},[159,1480,377],{"class":287},[159,1482,1483],{"class":169}," ({\n",[159,1485,1486],{"class":161,"line":239},[159,1487,1488],{"class":169},"        DEFAULT: {\n",[159,1490,1491],{"class":161,"line":248},[159,1492,1493],{"class":169},"          css: {\n",[159,1495,1496],{"class":161,"line":254},[159,1497,1498],{"class":169},"            code: {\n",[159,1500,1501],{"class":161,"line":418},[159,1502,1503],{"class":189},"              // backgroundColor: theme('colors.gray.100'),\n",[159,1505,1506],{"class":161,"line":447},[159,1507,1508],{"class":189},"              // color: theme('colors.orange.400'),\n",[159,1510,1511,1514,1517],{"class":161,"line":612},[159,1512,1513],{"class":169},"              fontWeight: ",[159,1515,1516],{"class":182},"'normal'",[159,1518,784],{"class":169},[159,1520,1521,1524,1526,1529,1532],{"class":161,"line":618},[159,1522,1523],{"class":169},"              marginLeft: ",[159,1525,1476],{"class":165},[159,1527,1528],{"class":169},"(",[159,1530,1531],{"class":182},"'spacing.1'",[159,1533,1534],{"class":169},"),\n",[159,1536,1537,1540,1542,1544,1546],{"class":161,"line":623},[159,1538,1539],{"class":169},"              marginRight: ",[159,1541,1476],{"class":165},[159,1543,1528],{"class":169},[159,1545,1531],{"class":182},[159,1547,1534],{"class":169},[159,1549,1550,1553,1555,1557,1560],{"class":161,"line":629},[159,1551,1552],{"class":169},"              paddingLeft: ",[159,1554,1476],{"class":165},[159,1556,1528],{"class":169},[159,1558,1559],{"class":182},"'spacing.2'",[159,1561,1534],{"class":169},[159,1563,1564,1567,1569,1571,1573],{"class":161,"line":635},[159,1565,1566],{"class":169},"              paddingRight: ",[159,1568,1476],{"class":165},[159,1570,1528],{"class":169},[159,1572,1559],{"class":182},[159,1574,1534],{"class":169},[159,1576,1577,1580,1583],{"class":161,"line":641},[159,1578,1579],{"class":169},"              paddingTop: ",[159,1581,1582],{"class":182},"'1px'",[159,1584,784],{"class":169},[159,1586,1587,1590,1592],{"class":161,"line":647},[159,1588,1589],{"class":169},"              paddingBottom: ",[159,1591,1582],{"class":182},[159,1593,784],{"class":169},[159,1595,1596,1599,1602],{"class":161,"line":653},[159,1597,1598],{"class":169},"              borderRadius: ",[159,1600,1601],{"class":182},"'2px'",[159,1603,784],{"class":169},[159,1605,1606,1609],{"class":161,"line":659},[159,1607,1608],{"class":182},"              '&::before'",[159,1610,170],{"class":169},[159,1612,1613,1616],{"class":161,"line":664},[159,1614,1615],{"class":169},"                content: ",[159,1617,1618],{"class":182},"`''!important`\n",[159,1620,1621],{"class":161,"line":669},[159,1622,1623],{"class":169},"              },\n",[159,1625,1626,1629],{"class":161,"line":680},[159,1627,1628],{"class":182},"              '&::after'",[159,1630,170],{"class":169},[159,1632,1633,1635],{"class":161,"line":686},[159,1634,1615],{"class":169},[159,1636,1618],{"class":182},[159,1638,1639],{"class":161,"line":692},[159,1640,1641],{"class":169},"              }\n",[159,1643,1644],{"class":161,"line":701},[159,1645,1646],{"class":169},"            },\n",[159,1648,1649],{"class":161,"line":706},[159,1650,1651],{"class":169},"            p: {\n",[159,1653,1655,1658,1660,1662,1665],{"class":161,"line":1654},30,[159,1656,1657],{"class":169},"              lineHeight: ",[159,1659,1476],{"class":165},[159,1661,1528],{"class":169},[159,1663,1664],{"class":182},"'lineHeight.loose'",[159,1666,1667],{"class":169},")\n",[159,1669,1671],{"class":161,"line":1670},31,[159,1672,1646],{"class":169},[159,1674,1676],{"class":161,"line":1675},32,[159,1677,1678],{"class":169},"            pre: {\n",[159,1680,1682],{"class":161,"line":1681},33,[159,1683,1684],{"class":189},"              // paddingBottom: 0,\n",[159,1686,1688],{"class":161,"line":1687},34,[159,1689,1690],{"class":189},"              // paddingTop: 0,\n",[159,1692,1694,1697],{"class":161,"line":1693},35,[159,1695,1696],{"class":182},"              '& > code'",[159,1698,170],{"class":169},[159,1700,1702,1705,1707,1709,1712],{"class":161,"line":1701},36,[159,1703,1704],{"class":169},"                color: ",[159,1706,1476],{"class":165},[159,1708,1528],{"class":169},[159,1710,1711],{"class":182},"'colors.gray.900'",[159,1713,1534],{"class":169},[159,1715,1717,1720],{"class":161,"line":1716},37,[159,1718,1719],{"class":169},"                backgroundColor: ",[159,1721,1722],{"class":182},"'transparent'\n",[159,1724,1726],{"class":161,"line":1725},38,[159,1727,1641],{"class":169},[159,1729,1731],{"class":161,"line":1730},39,[159,1732,1646],{"class":169},[159,1734,1736],{"class":161,"line":1735},40,[159,1737,1738],{"class":169},"            a: {\n",[159,1740,1742,1745],{"class":161,"line":1741},41,[159,1743,1744],{"class":169},"              textDecoration: ",[159,1746,1747],{"class":182},"'none'\n",[159,1749,1751],{"class":161,"line":1750},42,[159,1752,1646],{"class":169},[159,1754,1756],{"class":161,"line":1755},43,[159,1757,1758],{"class":169},"            img: {\n",[159,1760,1762,1765,1768],{"class":161,"line":1761},44,[159,1763,1764],{"class":169},"              marginTop: ",[159,1766,1767],{"class":291},"0",[159,1769,784],{"class":169},[159,1771,1773,1776,1778],{"class":161,"line":1772},45,[159,1774,1775],{"class":169},"              marginBottom: ",[159,1777,1767],{"class":291},[159,1779,784],{"class":169},[159,1781,1783],{"class":161,"line":1782},46,[159,1784,1785],{"class":169},"            }\n",[159,1787,1789],{"class":161,"line":1788},47,[159,1790,1791],{"class":169},"          }\n",[159,1793,1795],{"class":161,"line":1794},48,[159,1796,1797],{"class":169},"        }\n",[159,1799,1801],{"class":161,"line":1800},49,[159,1802,1803],{"class":169},"      })\n",[159,1805,1807],{"class":161,"line":1806},50,[159,1808,764],{"class":169},[159,1810,1812],{"class":161,"line":1811},51,[159,1813,257],{"class":169},[159,1815,1817],{"class":161,"line":1816},52,[159,1818,450],{"class":169},[11,1820,1821,1822,1825,1826,1829],{},"这里我",[1024,1823,1824],{},"建议只改大小间距等属性","，颜色相关的我放在了其他地方管理，比如 ",[22,1827,1828],{},"assets/tailwind.css","：",[150,1831,1835],{"className":1832,"code":1833,"language":1834,"meta":155,"style":155},"language-css shiki shiki-themes github-light","/* 针对page的prose颜色配置 */\n.mdc-page-prose {\n  @apply prose prose-zinc prose-pre:bg-gray-100 dark:prose-pre:bg-zinc-400 dark:text-zinc-200 dark:prose-strong:text-zinc-200 prose-code:bg-zinc-200 dark:prose-code:bg-zinc-200 prose-code:text-zinc-800 dark:prose-blockquote:text-zinc-300\n}\n","css",[22,1836,1837,1842,1849,1894],{"__ignoreMap":155},[159,1838,1839],{"class":161,"line":162},[159,1840,1841],{"class":189},"/* 针对page的prose颜色配置 */\n",[159,1843,1844,1847],{"class":161,"line":173},[159,1845,1846],{"class":165},".mdc-page-prose",[159,1848,380],{"class":169},[159,1850,1851,1854,1857,1860,1863,1866,1869,1871,1874,1876,1879,1881,1884,1886,1889,1891],{"class":161,"line":193},[159,1852,1853],{"class":169},"  @",[159,1855,1856],{"class":291},"apply",[159,1858,1859],{"class":291}," prose",[159,1861,1862],{"class":291}," prose-zinc",[159,1864,1865],{"class":291}," prose-pre",[159,1867,1868],{"class":169},":bg-gray-100 ",[159,1870,267],{"class":291},[159,1872,1873],{"class":169},":prose-pre:bg-zinc-400 ",[159,1875,267],{"class":291},[159,1877,1878],{"class":169},":text-zinc-200 ",[159,1880,267],{"class":291},[159,1882,1883],{"class":169},":prose-strong:text-zinc-200 prose-code:bg-zinc-200 ",[159,1885,267],{"class":291},[159,1887,1888],{"class":169},":prose-code:bg-zinc-200 prose-code:text-zinc-800 ",[159,1890,267],{"class":291},[159,1892,1893],{"class":169},":prose-blockquote:text-zinc-300\n",[159,1895,1896],{"class":161,"line":209},[159,1897,450],{"class":169},[11,1899,1900,1901,1904],{},"因为后面还涉及到动态的展示，动态也是基于mdc渲染的，也共用一套样式，那我再定义一个 ",[22,1902,1903],{},".mac-memo-prose"," 可能会更灵活一些。",[15,1906,1908],{"id":1907},"解析markdown字符串","解析Markdown字符串",[11,1910,1911,1914,1915,1918],{},[22,1912,1913],{},"@nuxtjs/mdc"," 提供了 ",[22,1916,1917],{},"MDC"," 组件来渲染md字符串， 添加此模块后即可使用：",[150,1920,1924],{"className":1921,"code":1922,"language":1923,"meta":155,"style":155},"language-vue shiki shiki-themes github-light","\u003CMDC :value=\"content\" tag=\"section\" class=\"mdc-memo-prose prose\"/>\n","vue",[22,1925,1926],{"__ignoreMap":155},[159,1927,1928,1930,1933,1936,1939,1941,1944,1946,1948,1951,1953,1956,1959,1961,1964],{"class":161,"line":162},[159,1929,946],{"class":169},[159,1931,1917],{"class":1932},"shJU0",[159,1934,1935],{"class":169}," :",[159,1937,1938],{"class":165},"value",[159,1940,424],{"class":169},[159,1942,1943],{"class":182},"\"",[159,1945,740],{"class":169},[159,1947,1943],{"class":182},[159,1949,1950],{"class":165}," tag",[159,1952,424],{"class":169},[159,1954,1955],{"class":182},"\"section\"",[159,1957,1958],{"class":165}," class",[159,1960,424],{"class":169},[159,1962,1963],{"class":182},"\"mdc-memo-prose prose\"",[159,1965,1187],{"class":169},[11,1967,1968],{},"一开始我是没发现mdc可以直接使用。在搜github的issue时，早期的nuxt版本中，大家都是手动引入包内的解析函数😏 这就是用的晚的好处吧 ～",[11,1970,1971,1972,1975],{},"样式表现和文章解析出来一模一样，如果想自定义，就用 ",[22,1973,1974],{},"mdc-memo-prose"," 去添加。",[11,1977,1978,1979,1982],{},"如果要使用一个自定义组件（",[22,1980,1981],{},"Mtag.vue","）时：",[150,1984,1986],{"className":934,"code":1985,"language":936,"meta":155,"style":155},"::mtag\n是实打实\n::\n",[22,1987,1988,1996,2001],{"__ignoreMap":155},[159,1989,1990,1993],{"class":161,"line":162},[159,1991,1992],{"class":291},":",[159,1994,1995],{"class":182},":mtag\n",[159,1997,1998],{"class":161,"line":173},[159,1999,2000],{"class":165},"是实打实\n",[159,2002,2003],{"class":161,"line":193},[159,2004,2005],{"class":291},"::\n",[11,2007,2008,2009,2012,2013,1125],{},"在 ",[22,2010,2011],{},"components/global"," 目录下新建 ",[22,2014,1981],{},[150,2016,2018],{"className":1921,"code":2017,"language":1923,"meta":155,"style":155},"\u003Ctemplate>\n  \u003CTag class=\"h-6 mr-2\">\u003Cslot>\u003C/slot>\u003C/Tag>\n\u003C/template>\n",[22,2019,2020,2028,2059],{"__ignoreMap":155},[159,2021,2022,2024,2026],{"class":161,"line":162},[159,2023,946],{"class":169},[159,2025,1137],{"class":1932},[159,2027,1140],{"class":169},[159,2029,2030,2032,2035,2037,2039,2042,2045,2048,2051,2053,2055,2057],{"class":161,"line":173},[159,2031,1145],{"class":169},[159,2033,2034],{"class":1932},"Tag",[159,2036,1958],{"class":165},[159,2038,424],{"class":169},[159,2040,2041],{"class":182},"\"h-6 mr-2\"",[159,2043,2044],{"class":169},">\u003C",[159,2046,2047],{"class":1932},"slot",[159,2049,2050],{"class":169},">\u003C/",[159,2052,2047],{"class":1932},[159,2054,2050],{"class":169},[159,2056,2034],{"class":1932},[159,2058,1140],{"class":169},[159,2060,2061,2063,2065],{"class":161,"line":193},[159,2062,1201],{"class":169},[159,2064,1137],{"class":1932},[159,2066,1140],{"class":169},[11,2068,2069,2070,2073,2074,2076,2077,2079],{},"此 ",[22,2071,2072],{},"Mtag"," 中使用的是 ",[22,2075,456],{}," 中的 ",[22,2078,2034],{}," 组件，这也就意味着仅靠输入一些简单的语法，就实现了无限的组件呈现",[11,2081,2082],{},[1071,2083],{"alt":155,"src":2084},"https://img.zzao.club/1-img-20241119111158.png",[15,2086,2088],{"id":2087},"图片图标seo","图片、图标、SEO",[11,2090,2091],{},[1024,2092,2093,2094,2096],{},"图片使用 ",[22,2095,73],{}," 模块",[11,2098,2099,2100,2103],{},"如果仅使用 ",[22,2101,2102],{},"src"," 属性，NuxtImg 会输出原始的 img 标签。",[11,2105,2106],{},"它提供了 sizes、placeholder（占位符）、preset、format（指定格式）、quality（图片质量）、loading（懒加载）、preload（预加载） 等非常多的配置，非常省事、好用。",[11,2108,2109,2110,2115],{},"这里没有什么特殊用法，所以可以直接",[973,2111,2114],{"href":2112,"rel":2113},"https://image.nuxt.com/usage/nuxt-img#usage",[977],"查看所有配置"," 。",[11,2117,2118],{},[1024,2119,2093,2120,2096],{},[22,2121,79],{},[11,2123,2124,2125,2127],{},"搭配 icon 库(",[22,2126,87],{},")使用：",[150,2129,2131],{"className":934,"code":2130,"language":936,"meta":155,"style":155},"npm i -D @iconify-json/icon-park-outline\n",[22,2132,2133],{"__ignoreMap":155},[159,2134,2135,2138,2141,2144],{"class":161,"line":162},[159,2136,2137],{"class":165},"npm",[159,2139,2140],{"class":182}," i",[159,2142,2143],{"class":291}," -D",[159,2145,2146],{"class":182}," @iconify-json/icon-park-outline\n",[11,2148,2149],{},"直接使用：",[150,2151,2153],{"className":1921,"code":2152,"language":1923,"meta":155,"style":155},"\u003CIcon name=\"icon-park-outline:wechat\">\u003C/Icon>\n",[22,2154,2155],{"__ignoreMap":155},[159,2156,2157,2159,2162,2165,2167,2170,2172,2174],{"class":161,"line":162},[159,2158,946],{"class":169},[159,2160,2161],{"class":1932},"Icon",[159,2163,2164],{"class":165}," name",[159,2166,424],{"class":169},[159,2168,2169],{"class":182},"\"icon-park-outline:wechat\"",[159,2171,2050],{"class":169},[159,2173,2161],{"class":1932},[159,2175,1140],{"class":169},[11,2177,2178],{},"修改大小 ( 修改颜色直接改 style color: xxx )：",[150,2180,2182],{"className":1921,"code":2181,"language":1923,"meta":155,"style":155},"\u003CIcon name=\"icon-park-outline:wechat\" size=\"1.5em\">\u003C/Icon>\n",[22,2183,2184],{"__ignoreMap":155},[159,2185,2186,2188,2190,2192,2194,2196,2199,2201,2204,2206,2208],{"class":161,"line":162},[159,2187,946],{"class":169},[159,2189,2161],{"class":1932},[159,2191,2164],{"class":165},[159,2193,424],{"class":169},[159,2195,2169],{"class":182},[159,2197,2198],{"class":165}," size",[159,2200,424],{"class":169},[159,2202,2203],{"class":182},"\"1.5em\"",[159,2205,2050],{"class":169},[159,2207,2161],{"class":1932},[159,2209,1140],{"class":169},[11,2211,2212,2213,142,2215,1829],{},"如果要替换掉 ",[22,2214,456],{},[22,2216,2217],{},"icon",[150,2219,2221],{"className":1921,"code":2220,"language":1923,"meta":155,"style":155},"\u003CButton severity=\"parimary\" size=\"small\">\n    \u003CIcon name=\"icon-park-outline:wechat\" slot=\"icon\">\u003C/Icon>\n\u003C/Button>\n",[22,2222,2223,2247,2252],{"__ignoreMap":155},[159,2224,2225,2227,2230,2233,2235,2238,2240,2242,2245],{"class":161,"line":162},[159,2226,946],{"class":169},[159,2228,2229],{"class":1932},"Button",[159,2231,2232],{"class":165}," severity",[159,2234,424],{"class":169},[159,2236,2237],{"class":182},"\"parimary\"",[159,2239,2198],{"class":165},[159,2241,424],{"class":169},[159,2243,2244],{"class":182},"\"small\"",[159,2246,1140],{"class":169},[159,2248,2249],{"class":161,"line":173},[159,2250,2251],{"class":169},"    \u003CIcon name=\"icon-park-outline:wechat\" slot=\"icon\">\u003C/Icon>\n",[159,2253,2254,2256,2258],{"class":161,"line":193},[159,2255,1201],{"class":169},[159,2257,2229],{"class":1932},[159,2259,1140],{"class":169},[11,2261,2262],{},[1024,2263,2264],{},"SEO相关",[11,2266,2267,2268,2271],{},"最直接的办法就是，打开控制台，找到 ",[22,2269,2270],{},"Lighthouse"," , 开始分析即可",[11,2273,2274],{},[1071,2275],{"alt":155,"src":2276},"https://img.zzao.club/1-img-20241119111114.png",[11,2278,2279,2280,2283,2284,2286],{},"看看哪里加载慢，SEO里提示什么可以优化，比如没有 ",[22,2281,2282],{},"robots","，那就加入模块 ",[22,2285,92],{}," 就会自动帮你做好了。",[11,2288,2289,2290,142,2292,2295,2296,264,2299,2302],{},"其他的就是注意 ",[22,2291,1071],{},[22,2293,2294],{},"alt"," 有没有写，第三方 js/css 设置 ",[22,2297,2298],{},"sync",[22,2300,2301],{},"defer","，页面绘制时偏移等等",[11,2304,2305,2306,2309],{},"header 信息，可以用 ",[22,2307,2308],{},"useHead"," 轻松设置",[150,2311,2313],{"className":152,"code":2312,"language":154,"meta":155,"style":155},"useHead({\n  title: '早早集市｜博客站',\n  meta: [\n    {\n      name: 'description',\n      content: 'https://blog.zzao.club',\n    },\n    {\n      name: 'keywords',\n      content: '',\n    },\n  ],\n})\n",[22,2314,2315,2321,2331,2336,2341,2351,2361,2365,2369,2378,2387,2391,2396],{"__ignoreMap":155},[159,2316,2317,2319],{"class":161,"line":162},[159,2318,2308],{"class":165},[159,2320,1257],{"class":169},[159,2322,2323,2326,2329],{"class":161,"line":173},[159,2324,2325],{"class":169},"  title: ",[159,2327,2328],{"class":182},"'早早集市｜博客站'",[159,2330,784],{"class":169},[159,2332,2333],{"class":161,"line":193},[159,2334,2335],{"class":169},"  meta: [\n",[159,2337,2338],{"class":161,"line":209},[159,2339,2340],{"class":169},"    {\n",[159,2342,2343,2346,2349],{"class":161,"line":215},[159,2344,2345],{"class":169},"      name: ",[159,2347,2348],{"class":182},"'description'",[159,2350,784],{"class":169},[159,2352,2353,2356,2359],{"class":161,"line":221},[159,2354,2355],{"class":169},"      content: ",[159,2357,2358],{"class":182},"'https://blog.zzao.club'",[159,2360,784],{"class":169},[159,2362,2363],{"class":161,"line":227},[159,2364,764],{"class":169},[159,2366,2367],{"class":161,"line":233},[159,2368,2340],{"class":169},[159,2370,2371,2373,2376],{"class":161,"line":239},[159,2372,2345],{"class":169},[159,2374,2375],{"class":182},"'keywords'",[159,2377,784],{"class":169},[159,2379,2380,2382,2385],{"class":161,"line":248},[159,2381,2355],{"class":169},[159,2383,2384],{"class":182},"''",[159,2386,784],{"class":169},[159,2388,2389],{"class":161,"line":254},[159,2390,764],{"class":169},[159,2392,2393],{"class":161,"line":418},[159,2394,2395],{"class":169},"  ],\n",[159,2397,2398],{"class":161,"line":447},[159,2399,400],{"class":169},[15,2401,2403],{"id":2402},"prisma-和-gitea","prisma 和 gitea",[11,2405,2406],{},"这两篇太长了，我决定分出去两篇，下次再发！",[15,2408,2409],{"id":2409},"结语",[11,2411,2412,2413,2416],{},"作为一个展示为主的博客，前端使用这些模块、库已经够用了，但作为一个全栈框架，后端 ",[22,2414,2415],{},"Nitro"," 也是要玩一玩的，所以后续的开发计划偏向于后端。",[27,2418,2419,2422,2425,2428],{},[30,2420,2421],{},"登录、注册、用户分组",[30,2423,2424],{},"文章、动态支持评论",[30,2426,2427],{},"文章、动态支持分享（图片、短链接）",[30,2429,2430],{},"图片上传（cos）",[11,2432,2433],{},"其中涉及到大量对 Nitro 的探索，鉴权、中间件、数据库等等。这也是后面文章输出的重点方向，即 Nuxt3 的全栈开发。",[11,2435,2436],{},"👏👏欢迎关注 「早早集市」",[2438,2439,2440],"style",{},"html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}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 .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}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 .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html pre.shiki code .sqxcx, html code.shiki .sqxcx{--shiki-default:#E36209}html pre.shiki code .shJU0, html code.shiki .shJU0{--shiki-default:#22863A}",{"title":155,"searchDepth":173,"depth":173,"links":2442},[2443,2444,2445,2446,2447,2448,2449],{"id":17,"depth":173,"text":17},{"id":148,"depth":173,"text":148},{"id":729,"depth":173,"text":730},{"id":1907,"depth":173,"text":1908},{"id":2087,"depth":173,"text":2088},{"id":2402,"depth":173,"text":2403},{"id":2409,"depth":173,"text":2409},"2024-11-19T00:00:00.000Z","Nuxt3全栈开发个人博客，会用到哪些插件和包，以及它们的详细配置","md","2025-08-19T00:00:00.000Z",{},"/post/nuxt/nuxt3-full-stack-config","---\ntitle: Nuxt3全栈开发 · 配置篇\ndate: 2024-11-19\nlastmod: 2025-08-19\ntags: [\"博客\", \"Nuxt\"]\nversions: [\"nuxt@3.14.0\", \"@nuxt/content@2.13.4\", \"@prisma/client@5.22.0\"]\ndescription: Nuxt3全栈开发个人博客，会用到哪些插件和包，以及它们的详细配置\n---\n \n最近在用Nuxt3全栈开发个人博客，踩了不少小坑，这篇文章总结一下。\n\n## 依赖库及博客主要功能\n\n先来介绍一下我用到了哪些 `Nuxt3` 的相关生态及对应的功能。\n\n- `@nuxtjs/color-mode` 颜色模式：白天（light）、黑夜（dark）、系统（system）三者切换\n- `@nuxt/content` 展示文章，基于`mdc`，可以使用自定义组件渲染`markdown`，支持 `front matter` \n- `@nuxtjs/tailwindcss` 样式。以及配合 `@tailwindcss/typography` 自定义markdown主题\n- `@primevue/nuxt-module` 组件库。\n- `@nuxt/image` 图片\n- `@nuxt/icon` 图标。配合 `iconify` ，我目前用的图标主要是 `@iconify-json/icon-park-outline`\n- `@nuxt/robots` SEO\n- `@nuxt/mdc` 解析动态（类型Memos/朋友圈/X）展示。和文章有一致的表现，也可以通过`tailwindcss`自定义样式\n- `prisma` 管理数据库（sqlite3）\n- `gitea` 管理代码仓库（私有）。以及使用`workflows`自动部署\n\n基于这些库逐步使用和功能的逐渐实现，分享一下使用经验。\n\n如果没有刻意提到的安装方式，则默认都是用 `npx nuxi@latest module add xxxx` 进行安装。\n\n如果没有表明在何处配置，则默认是在 `nuxt.config.ts` 的顶级\n\n如果代码中变量明显没有引入，则是使用了 `Nuxt3` 的 `auto imports`\n## 颜色模式\n\n```typescript\ncolorMode: {\n    preference: 'system', // default value of $colorMode.preference\n    fallback: 'light', // fallback value if not system preference found\n    // hid: 'nuxt-color-mode-script',\n    // globalName: '__NUXT_COLOR_MODE__',\n    // componentName: 'ColorScheme',\n    // classPrefix: '',\n    // classSuffix: '-mode',\n    // storage: 'localStorage', // or 'sessionStorage' or 'cookie'\n    // storageKey: 'nuxt-color-mode'\n  },\n```\n\n有三种模式：`light` `dark` `system` ，默认为 `system` 根据系统模式来自动设置浅色或深色\n\n切换模式时：\n\n```typescript\nconst colorMode = useColorMode()\nconst index = ref(modes.indexOf(colorMode.preference))\n// 用来显示不同图标\nconst modes = ['system', 'light', 'dark']\n\nconst modeIcon = computed( () => {\n\tswitch ...\n\tcase ...\n})\n\nfunction toggleColorMode() {\n  colorMode.preference = modes[(++index.value) % modes.length]\n}\n```\n\n配合组件库 `primevue` 的配置\n\n```typescript\nprimevue: {\n    importTheme: { from: '~/primevue/theme.ts' },\n    // usePrimeVue: false\n  },\n```\n\n`theme.ts` 如下\n\n```typescript\n\nimport { definePreset } from '@primevue/themes';\nimport Aura from '@primevue/themes/aura';\n\n\nconst Noir = definePreset(Aura, {\n  semantic: {\n      primary: {\n\t      ...\n      },\n      colorScheme: {\n        light: { ... }\n        dark: { ... }\n      }\n  },\n  components: {\n    button: {\n      ...\n    }\n  }\n});\n\n\nexport default {\n    preset: Noir,\n    options: {\n        darkModeSelector:'.dark-mode'\n    }\n};\n\n```\n\n设置 `darkModeSelector` 为 `.dark-mode`。 使用colorMode切换时，会自动切换 `html` 的 `class`\n\n## 解析Markdown文件\n\n```typescript\ncontent: {\n    documentDriven: {\n      injectPage: false\n    },\n    highlight: {\n      theme: 'github-light',\n      langs: ['typescript', 'vue', 'javascript', 'go', 'shell', 'bash', 'yaml', 'markdown', 'json', 'html', 'ts', 'js']\n    },\n    sources: {\n      obsidian: {\n        prefix: '/obsidian', // All contents inside this source will be prefixed with `/fa`\n        driver: 'fs',\n        base: `/Users/your_name/code/notion/blog` // Path for source directory\n      },\n    }\n  },\n```\n\n`documentDriven` 的 `injectPage` 是为了解决一个警告信息\n\n```shell\n[@nuxt/content 09:52:13] Using \u003CNuxtLayout> inside app.vue will cause unwanted layout shifting in your application.\n```\n\n原因是，原代码从 `pages/[slug].vue` 改为 `pages/post/[slug].vue` 导致报错。\n\n以下是搜索时找到的相关issue\n\n[NuxtLayout warn vs documentation #15240](https://github.com/nuxt/nuxt/issues/15240) \n\n[nuxt-blog-starter](https://github.com/dan-bowen/nuxt-blog-starter/pull/9) \n\n`highlight` 是配置代码块高亮的，内部使用的是 [Shiki](https://github.com/shikijs/shiki)，同时和 `color-mode` 兼容，可以查看 [更多官方文档](https://content.nuxt.com/get-started/configuration#highlight)\n\n`sources` 是核心配置，\n\n官方的默认配置是 `base: resolve(__dirname, 'content')` , 即从当前项目下的content内读取md文件，我直接改成了自己本地的一个目录。\n\n启动项目时，会**读取并监听**该目录下的所有md文件，并有一个忽略规则（开头为 `.` 或 `-` 的 ），然后会解析并缓存到 `.nuxt` 内，`dev` 模式下就是从 `.nuxt` 中直接拿缓存数据，所以有一些奇怪的问题可以通过删除 `.nuxt` 并重新运行可以解决。\n\n当然这个配置也决定了必须带着 `.nuxt` 目录才能正常打包。\n\n只靠 `@nuxt/content` 解析出的文章还没眼看，需要借助 `@tailwindcss/typography`。\n\n使用前：\n\n![](https://img.zzao.club/1-img-20241106171191.png)\n\n使用（并自定义）后：\n\n![](https://img.zzao.club/1-img-20241119101171.png)\n\n`markdown` 被解析为 `p` 、`a` 、`code` 、`h1` 、`h2`、`img`、`strong` 等这些标签，而在 `@nuxt/content` 中，使用对应的 `ProseA`、`ProseH1` 组件进行渲染。\n\n并且支持自己编写然后覆盖这些组件预设，在 `components/content` 目录下新建一个同名的组件，如 `ProseA.vue` ：\n\n```typescript\n\u003Ctemplate>\n  \u003CNuxtLink :href=\"props.href\" :target=\"props.target\"\n    class=\"font-bold border-b-2 border-dashed border-zinc-600 hover:border-solid hover:border-zinc-900 dark:border-zinc-300 dark:hover:border-zinc-100\">\n    \u003Cslot />\n  \u003C/NuxtLink>\n\u003C/template>\n\n\u003Cscript setup lang=\"ts\">\nimport type { PropType } from 'vue'\n\nconst props = defineProps({\n  href: {\n    type: String,\n    default: ''\n  },\n  target: {\n    type: String as PropType\u003C'_blank' | '_parent' | '_self' | '_top' | (string & object) | null | undefined>,\n    default: '_blank',\n    required: false\n  }\n})\n\u003C/script>\n```\n\n这里我把他的默认打开方式改为了 `_blank` ，并自定义了功能和样式。其他组件同理，都是可以自定义的。 [查看NuxtContent中支持的组件](https://content.nuxt.com/components/prose#prosea)  \n\n同样的可以基于 `typography` 在顶层修改其样式。\n\n```js\nimport typography from '@tailwindcss/typography'\n/** @type {import('tailwindcss').Config} */\nexport default {\n  content: [],\n  plugins: [typography()],\n  theme: {\n    extend: {\n      typography: (theme) => ({\n        DEFAULT: {\n          css: {\n            code: {\n              // backgroundColor: theme('colors.gray.100'),\n              // color: theme('colors.orange.400'),\n              fontWeight: 'normal',\n              marginLeft: theme('spacing.1'),\n              marginRight: theme('spacing.1'),\n              paddingLeft: theme('spacing.2'),\n              paddingRight: theme('spacing.2'),\n              paddingTop: '1px',\n              paddingBottom: '1px',\n              borderRadius: '2px',\n              '&::before': {\n                content: `''!important`\n              },\n              '&::after': {\n                content: `''!important`\n              }\n            },\n            p: {\n              lineHeight: theme('lineHeight.loose')\n            },\n            pre: {\n              // paddingBottom: 0,\n              // paddingTop: 0,\n              '& > code': {\n                color: theme('colors.gray.900'),\n                backgroundColor: 'transparent'\n              }\n            },\n            a: {\n              textDecoration: 'none'\n            },\n            img: {\n              marginTop: 0,\n              marginBottom: 0,\n            }\n          }\n        }\n      })\n    },\n  },\n}\n\n```\n\n这里我**建议只改大小间距等属性**，颜色相关的我放在了其他地方管理，比如 `assets/tailwind.css`：\n\n```css\n/* 针对page的prose颜色配置 */\n.mdc-page-prose {\n  @apply prose prose-zinc prose-pre:bg-gray-100 dark:prose-pre:bg-zinc-400 dark:text-zinc-200 dark:prose-strong:text-zinc-200 prose-code:bg-zinc-200 dark:prose-code:bg-zinc-200 prose-code:text-zinc-800 dark:prose-blockquote:text-zinc-300\n}\n```\n\n因为后面还涉及到动态的展示，动态也是基于mdc渲染的，也共用一套样式，那我再定义一个 `.mac-memo-prose` 可能会更灵活一些。\n\n## 解析Markdown字符串\n\n`@nuxtjs/mdc` 提供了 `MDC` 组件来渲染md字符串， 添加此模块后即可使用：\n\n```vue\n\u003CMDC :value=\"content\" tag=\"section\" class=\"mdc-memo-prose prose\"/>\n```\n\n一开始我是没发现mdc可以直接使用。在搜github的issue时，早期的nuxt版本中，大家都是手动引入包内的解析函数😏 这就是用的晚的好处吧 ～\n\n样式表现和文章解析出来一模一样，如果想自定义，就用 `mdc-memo-prose` 去添加。\n\n如果要使用一个自定义组件（`Mtag.vue`）时：\n\n```shell\n::mtag\n是实打实\n::\n```\n\n在 `components/global` 目录下新建 `Mtag.vue` ：\n\n```vue\n\u003Ctemplate>\n  \u003CTag class=\"h-6 mr-2\">\u003Cslot>\u003C/slot>\u003C/Tag>\n\u003C/template>\n```\n\n此 `Mtag` 中使用的是 `primevue` 中的 `Tag` 组件，这也就意味着仅靠输入一些简单的语法，就实现了无限的组件呈现\n\n![](https://img.zzao.club/1-img-20241119111158.png)\n\n## 图片、图标、SEO\n\n**图片使用 `@nuxt/image` 模块**\n\n如果仅使用 `src` 属性，NuxtImg 会输出原始的 img 标签。\n\n它提供了 sizes、placeholder（占位符）、preset、format（指定格式）、quality（图片质量）、loading（懒加载）、preload（预加载） 等非常多的配置，非常省事、好用。\n\n这里没有什么特殊用法，所以可以直接[查看所有配置](https://image.nuxt.com/usage/nuxt-img#usage) 。\n\n**图片使用 `@nuxt/icon` 模块**\n\n搭配 icon 库(`@iconify-json/icon-park-outline`)使用：\n\n```shell\nnpm i -D @iconify-json/icon-park-outline\n```\n\n直接使用：\n\n```vue\n\u003CIcon name=\"icon-park-outline:wechat\">\u003C/Icon>\n```\n\n修改大小 ( 修改颜色直接改 style color: xxx )：\n\n```vue\n\u003CIcon name=\"icon-park-outline:wechat\" size=\"1.5em\">\u003C/Icon>\n```\n\n如果要替换掉 `primevue` 的 `icon`：\n\n```vue\n\u003CButton severity=\"parimary\" size=\"small\">\n\t\u003CIcon name=\"icon-park-outline:wechat\" slot=\"icon\">\u003C/Icon>\n\u003C/Button>\n```\n\n**SEO相关**\n\n最直接的办法就是，打开控制台，找到 `Lighthouse` , 开始分析即可\n\n![](https://img.zzao.club/1-img-20241119111114.png)\n\n看看哪里加载慢，SEO里提示什么可以优化，比如没有 `robots`，那就加入模块 `@nuxt/robots` 就会自动帮你做好了。\n\n其他的就是注意 `img` 的 `alt` 有没有写，第三方 js/css 设置 `sync` `defer`，页面绘制时偏移等等\n\nheader 信息，可以用 `useHead` 轻松设置\n\n```typescript\nuseHead({\n  title: '早早集市｜博客站',\n  meta: [\n    {\n      name: 'description',\n      content: 'https://blog.zzao.club',\n    },\n    {\n      name: 'keywords',\n      content: '',\n    },\n  ],\n})\n```\n\n## prisma 和 gitea\n\n这两篇太长了，我决定分出去两篇，下次再发！\n\n## 结语\n\n作为一个展示为主的博客，前端使用这些模块、库已经够用了，但作为一个全栈框架，后端 `Nitro` 也是要玩一玩的，所以后续的开发计划偏向于后端。\n\n- 登录、注册、用户分组\n- 文章、动态支持评论\n- 文章、动态支持分享（图片、短链接）\n- 图片上传（cos）\n\n其中涉及到大量对 Nitro 的探索，鉴权、中间件、数据库等等。这也是后面文章输出的重点方向，即 Nuxt3 的全栈开发。\n\n👏👏欢迎关注 「早早集市」\n",{"title":5,"description":2451},"post/nuxt/Nuxt3-full-stack-config",[2460,2461],"博客","Nuxt",[2463,2464,2465],"nuxt@3.14.0","@nuxt/content@2.13.4","@prisma/client@5.22.0","crkGHAweABmJXMi1d0eliclYmMVi7hh-JVPIgnNE6PA",[2468,2472],{"title":2469,"path":2470,"stem":2471},"OpenClaw 安装入门（Windows）","/post/zzao/openclaw/openclaw-install-windows","post/zzao/openclaw/openclaw-install-windows",{"title":2473,"path":2474,"stem":2475},"假设你是AI，你的Skill应该是什么样的","/post/zzao/ai-skill-structure","post/zzao/ai-skill-structure",1779005086581]