[{"content":"手动安装 官网：\nInstallation - shadcn/ui 安装tailwindcss 详情： Electron Vite 项目配置 tailwindcss | 知兀的博客 安装依赖 1 pnpm add shadcn class-variance-authority cn lucide-react tw-animate-css bash 不设置别名，用默认的 文档第三步是配置别名，我们这是electron-vite项目，不设置：\n普通纯前端 Vite 项目只有一套代码，@直接映射src很方便 但 Electron‑vite 是三进程架构：Main 主进程 + Preload 预加载 + Renderer 渲染进程，三套代码放在同一个仓库根目录，共享一份根tsconfig.json 配置style 和之前配置的tailwind css样式文件放在一起\n添加lib/utils.ts src/renderer/src/lib/utils.ts\n创建components.json文件 官方文档要求根目录下创建文件\n但因为使用electron-vite，渲染进程在src/renderer/src下，而且已经配置了别名@renderer，所以在根目录下创建后要统一修改\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 { \u0026#34;$schema\u0026#34;: \u0026#34;https://ui.shadcn.com/schema.json\u0026#34;, \u0026#34;style\u0026#34;: \u0026#34;base-nova\u0026#34;, \u0026#34;rsc\u0026#34;: false, \u0026#34;tsx\u0026#34;: true, \u0026#34;tailwind\u0026#34;: { \u0026#34;config\u0026#34;: \u0026#34;\u0026#34;, - \u0026#34;css\u0026#34;: \u0026#34;src/styles/globals.css\u0026#34;, + \u0026#34;css\u0026#34;: \u0026#34;@renderer/assets/globals.css\u0026#34;, \u0026#34;baseColor\u0026#34;: \u0026#34;neutral\u0026#34;, \u0026#34;cssVariables\u0026#34;: true, \u0026#34;prefix\u0026#34;: \u0026#34;\u0026#34; }, \u0026#34;aliases\u0026#34;: { - \u0026#34;components\u0026#34;: \u0026#34;@/components\u0026#34;, - \u0026#34;utils\u0026#34;: \u0026#34;@/lib/utils\u0026#34;, - \u0026#34;ui\u0026#34;: \u0026#34;@/components/ui\u0026#34;, - \u0026#34;lib\u0026#34;: \u0026#34;@/lib\u0026#34;, - \u0026#34;hooks\u0026#34;: \u0026#34;@/hooks\u0026#34; + \u0026#34;components\u0026#34;: \u0026#34;@renderer/components\u0026#34;, + \u0026#34;utils\u0026#34;: \u0026#34;@renderer/lib/utils\u0026#34;, + \u0026#34;ui\u0026#34;: \u0026#34;@renderer/components/ui\u0026#34;, + \u0026#34;lib\u0026#34;: \u0026#34;@renderer/lib\u0026#34;, + \u0026#34;hooks\u0026#34;: \u0026#34;@renderer/hooks\u0026#34; }, \u0026#34;iconLibrary\u0026#34;: \u0026#34;lucide\u0026#34; } diff 添加组件 官方文档下选择Manual，最后就能添加组件了\n","permalink":"https://zhiwu215.github.io/posts/electron-vite%E9%A1%B9%E7%9B%AE%E9%85%8D%E7%BD%AEshadcn-ui/","summary":"配置shadcn/ui","title":"Electron Vite项目配置shadcn/ui"},{"content":"界面 在问题面板，点击报错，会有复制和复制消息两个选项\n「复制」的内容 下面就是内容，我对字段加粗处理，方便查看，原本是没有的\n[{ \u0026ldquo;resource\u0026rdquo;: \u0026ldquo;/d:/Code/Demo/electron-vite-demo/electron.vite.config.ts\u0026rdquo;, \u0026ldquo;owner\u0026rdquo;: \u0026ldquo;typescript\u0026rdquo;, \u0026ldquo;code\u0026rdquo;: \u0026ldquo;2769\u0026rdquo;, \u0026ldquo;severity\u0026rdquo;: 8, \u0026ldquo;message\u0026rdquo;: \u0026ldquo;没有与此调用匹配的重载。\\n 最后一个重载给出了以下错误。\\n 不能将类型“Plugin[]”分配给类型“PluginOption”。\\n 不能将类型“Plugin[]”分配给类型“PluginOption[]”。\\n 不能将类型“Plugin”分配给类型“PluginOption”。\\n 不能将类型“import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.2_46041a9d4deccce9f9aaf674d7cdda12/node_modules/vite/dist/node/index\u0026quot;).Plugin”分配给类型“import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.20.1_jiti@2.7.0/node_modules/vite/dist/node/index\u0026quot;).Plugin”。\\n 属性“hotUpdate”的类型不兼容。\\n 类型“import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026quot;).ObjectHook\u0026lt;(this: import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026quot;).MinimalPluginContext \u0026amp; { \u0026hellip;; }, options: import(\u0026quot;D:/Code/Demo/electron-vite-demo/nod\u0026hellip;”无法分配给类型“import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026quot;).ObjectHook\u0026lt;(this: import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026quot;).MinimalPluginContext \u0026amp; { \u0026hellip;; }, options: import(\u0026quot;D:/Code/Demo/electron-vite-demo/nod\u0026hellip;”。存在具有此名称的两种不同类型，但它们是不相关的。\\n 不能将类型“(this: MinimalPluginContext \u0026amp; { environment: DevEnvironment; }, options: HotUpdateOptions) =\u0026gt; void | EnvironmentModuleNode[] | Promise\u0026lt;\u0026hellip;\u0026gt;”分配给类型“ObjectHook\u0026lt;(this: MinimalPluginContext \u0026amp; { environment: DevEnvironment; }, options: HotUpdateOptions) =\u0026gt; void | EnvironmentModuleNode[] | Promise\u0026lt;\u0026hellip;\u0026raquo; | undefined”。\\n 不能将类型“(this: import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026quot;).MinimalPluginContext \u0026amp; { \u0026hellip;; }, options: import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.2_46041a9d4deccce9f9aaf674d7cdda12/node_modules/vite/dist/node/index\u0026quot;).HotUp\u0026hellip;”分配给类型“(this: import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026quot;).MinimalPluginContext \u0026amp; { \u0026hellip;; }, options: import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.20.1_jiti@2.7.0/node_modules/vite/dist/node/index\u0026quot;).HotUpdateOptions) =\u0026gt; voi\u0026hellip;”。\\n 每个签名的 \u0026quot;this\u0026quot; 类型不兼容。\\n 不能将类型“import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026quot;).MinimalPluginContext \u0026amp; { environment: import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.20.1_jiti@2.7.0/node_modules/vite/dist/node/index\u0026quot;).DevEnvironment; }”分配给类型“import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026quot;).MinimalPluginContext \u0026amp; { environment: import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.2_46041a9d4deccce9f9aaf674d7cdda12/node_modules/vite/dist/node/index\u0026quot;).DevEnvironment; }”。\\n 不能将类型“MinimalPluginContext \u0026amp; { environment: DevEnvironment; }”分配给类型“{ environment: DevEnvironment; }”。\\n 在这些类型中，\u0026quot;environment.pluginContainer\u0026quot; 的类型不兼容。\\n 不能将类型“EnvironmentPluginContainer\u0026lt;import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.20.1_jiti@2.7.0/node_modules/vite/dist/node/index\u0026quot;).DevEnvironment\u0026gt;”分配给类型“EnvironmentPluginContainer\u0026lt;import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.2_46041a9d4deccce9f9aaf674d7cdda12/node_modules/vite/dist/node/index\u0026quot;).DevEnvironment\u0026gt;”。\\n 类型具有私有属性“_pluginContextMap”的单独声明。\u0026rdquo;, \u0026ldquo;source\u0026rdquo;: \u0026ldquo;ts\u0026rdquo;, \u0026ldquo;startLineNumber\u0026rdquo;: 17, \u0026ldquo;startColumn\u0026rdquo;: 7, \u0026ldquo;endLineNumber\u0026rdquo;: 17, \u0026ldquo;endColumn\u0026rdquo;: 20, \u0026ldquo;relatedInformation\u0026rdquo;: [ { \u0026ldquo;startLineNumber\u0026rdquo;: 118, \u0026ldquo;startColumn\u0026rdquo;: 18, \u0026ldquo;endLineNumber\u0026rdquo;: 118, \u0026ldquo;endColumn\u0026rdquo;: 30, \u0026ldquo;message\u0026rdquo;: \u0026ldquo;在此处声明最后一个重载。\u0026rdquo;, \u0026ldquo;resource\u0026rdquo;: \u0026ldquo;/D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/electron-vite@5.0.0_support_8fbe30fbe1a2ef11377a687dfa84a1af/node_modules/electron-vite/dist/index.d.ts\u0026rdquo; } ], \u0026ldquo;origin\u0026rdquo;: \u0026ldquo;extHost1\u0026rdquo; }]\n「复制消息」的内容 没有与此调用匹配的重载。 最后一个重载给出了以下错误。 不能将类型“Plugin[]”分配给类型“PluginOption”。 不能将类型“Plugin[]”分配给类型“PluginOption[]”。 不能将类型“Plugin”分配给类型“PluginOption”。 不能将类型“import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.2_46041a9d4deccce9f9aaf674d7cdda12/node_modules/vite/dist/node/index\u0026rdquo;).Plugin”分配给类型“import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.20.1_jiti@2.7.0/node_modules/vite/dist/node/index\u0026rdquo;).Plugin”。 属性“hotUpdate”的类型不兼容。 类型“import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026rdquo;).ObjectHook\u0026lt;(this: import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026rdquo;).MinimalPluginContext \u0026amp; { \u0026hellip;; }, options: import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/nod\u0026hellip;”无法分配给类型“import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026rdquo;).ObjectHook\u0026lt;(this: import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026rdquo;).MinimalPluginContext \u0026amp; { \u0026hellip;; }, options: import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/nod\u0026hellip;”。存在具有此名称的两种不同类型，但它们是不相关的。 不能将类型“(this: MinimalPluginContext \u0026amp; { environment: DevEnvironment; }, options: HotUpdateOptions) =\u0026gt; void | EnvironmentModuleNode[] | Promise\u0026lt;\u0026hellip;\u0026gt;”分配给类型“ObjectHook\u0026lt;(this: MinimalPluginContext \u0026amp; { environment: DevEnvironment; }, options: HotUpdateOptions) =\u0026gt; void | EnvironmentModuleNode[] | Promise\u0026lt;\u0026hellip;\u0026raquo; | undefined”。 不能将类型“(this: import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026rdquo;).MinimalPluginContext \u0026amp; { \u0026hellip;; }, options: import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.2_46041a9d4deccce9f9aaf674d7cdda12/node_modules/vite/dist/node/index\u0026rdquo;).HotUp\u0026hellip;”分配给类型“(this: import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026rdquo;).MinimalPluginContext \u0026amp; { \u0026hellip;; }, options: import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.20.1_jiti@2.7.0/node_modules/vite/dist/node/index\u0026rdquo;).HotUpdateOptions) =\u0026gt; voi\u0026hellip;”。 每个签名的 \u0026ldquo;this\u0026rdquo; 类型不兼容。 不能将类型“import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026rdquo;).MinimalPluginContext \u0026amp; { environment: import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.20.1_jiti@2.7.0/node_modules/vite/dist/node/index\u0026rdquo;).DevEnvironment; }”分配给类型“import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026rdquo;).MinimalPluginContext \u0026amp; { environment: import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.2_46041a9d4deccce9f9aaf674d7cdda12/node_modules/vite/dist/node/index\u0026rdquo;).DevEnvironment; }”。 不能将类型“MinimalPluginContext \u0026amp; { environment: DevEnvironment; }”分配给类型“{ environment: DevEnvironment; }”。 在这些类型中，\u0026ldquo;environment.pluginContainer\u0026rdquo; 的类型不兼容。 不能将类型“EnvironmentPluginContainer\u0026lt;import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.20.1_jiti@2.7.0/node_modules/vite/dist/node/index\u0026rdquo;).DevEnvironment\u0026gt;”分配给类型“EnvironmentPluginContainer\u0026lt;import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.2_46041a9d4deccce9f9aaf674d7cdda12/node_modules/vite/dist/node/index\u0026rdquo;).DevEnvironment\u0026gt;”。 类型具有私有属性“_pluginContextMap”的单独声明。\n总结 「复制」复制的是这个报错完整的内容，包括resource，owner，code等等各种字段\n这个json是完整的，表明了在vscode里这个报错的完整格式\n「复制消息」实际是复制完整的这个json的message里的内容，也就是报错的信息\n所以一般复制消息看报错就可以了\n第 1 行：结论 中间：推导过程 最后 1 行：真凶 ","permalink":"https://zhiwu215.github.io/posts/vscode%E9%97%AE%E9%A2%98%E9%9D%A2%E6%9D%BF%E7%9A%84%E6%8A%A5%E9%94%99%E6%9F%A5%E7%9C%8B/","summary":"\u003ch1 id=\"界面\"\u003e界面\u003c/h1\u003e\n\u003cp\u003e在问题面板，点击报错，会有\u003cstrong\u003e复制\u003c/strong\u003e和复制消息两个选项\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"image-20260905155710437\" loading=\"lazy\" src=\"https://raw.githubusercontent.com/zhiwu215/blog-img/main/image-20260905155710437.png\"\u003e\u003c/p\u003e\n\u003ch1 id=\"复制的内容\"\u003e「复制」的内容\u003c/h1\u003e\n\u003cblockquote\u003e\n\u003cp\u003e下面就是内容，我对字段加粗处理，方便查看，原本是没有的\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e[{\n\u003cstrong\u003e\u0026ldquo;resource\u0026rdquo;\u003c/strong\u003e: \u0026ldquo;/d:/Code/Demo/electron-vite-demo/electron.vite.config.ts\u0026rdquo;,\n\u003cstrong\u003e\u0026ldquo;owner\u0026rdquo;:\u003c/strong\u003e \u0026ldquo;typescript\u0026rdquo;,\n\u003cstrong\u003e\u0026ldquo;code\u0026rdquo;\u003c/strong\u003e: \u0026ldquo;2769\u0026rdquo;,\n\u003cstrong\u003e\u0026ldquo;severity\u0026rdquo;\u003c/strong\u003e: 8,\n\u003cstrong\u003e\u0026ldquo;message\u0026rdquo;:\u003c/strong\u003e \u0026ldquo;没有与此调用匹配的重载。\\n  最后一个重载给出了以下错误。\\n   不能将类型“Plugin\u003cany\u003e[]”分配给类型“PluginOption”。\\n    不能将类型“Plugin\u003cany\u003e[]”分配给类型“PluginOption[]”。\\n     不能将类型“Plugin\u003cany\u003e”分配给类型“PluginOption”。\\n      不能将类型“import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.2_46041a9d4deccce9f9aaf674d7cdda12/node_modules/vite/dist/node/index\u0026quot;).Plugin\u003cany\u003e”分配给类型“import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.20.1_jiti@2.7.0/node_modules/vite/dist/node/index\u0026quot;).Plugin\u003cany\u003e”。\\n       属性“hotUpdate”的类型不兼容。\\n        类型“import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026quot;).ObjectHook\u0026lt;(this: import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026quot;).MinimalPluginContext \u0026amp; { \u0026hellip;; }, options: import(\u0026quot;D:/Code/Demo/electron-vite-demo/nod\u0026hellip;”无法分配给类型“import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026quot;).ObjectHook\u0026lt;(this: import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026quot;).MinimalPluginContext \u0026amp; { \u0026hellip;; }, options: import(\u0026quot;D:/Code/Demo/electron-vite-demo/nod\u0026hellip;”。存在具有此名称的两种不同类型，但它们是不相关的。\\n         不能将类型“(this: MinimalPluginContext \u0026amp; { environment: DevEnvironment; }, options: HotUpdateOptions) =\u0026gt; void | EnvironmentModuleNode[] | Promise\u0026lt;\u0026hellip;\u0026gt;”分配给类型“ObjectHook\u0026lt;(this: MinimalPluginContext \u0026amp; { environment: DevEnvironment; }, options: HotUpdateOptions) =\u0026gt; void | EnvironmentModuleNode[] | Promise\u0026lt;\u0026hellip;\u0026raquo; | undefined”。\\n          不能将类型“(this: import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026quot;).MinimalPluginContext \u0026amp; { \u0026hellip;; }, options: import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.2_46041a9d4deccce9f9aaf674d7cdda12/node_modules/vite/dist/node/index\u0026quot;).HotUp\u0026hellip;”分配给类型“(this: import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026quot;).MinimalPluginContext \u0026amp; { \u0026hellip;; }, options: import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.20.1_jiti@2.7.0/node_modules/vite/dist/node/index\u0026quot;).HotUpdateOptions) =\u0026gt; voi\u0026hellip;”。\\n           每个签名的 \u0026quot;this\u0026quot; 类型不兼容。\\n            不能将类型“import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026quot;).MinimalPluginContext \u0026amp; { environment: import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.20.1_jiti@2.7.0/node_modules/vite/dist/node/index\u0026quot;).DevEnvironment; }”分配给类型“import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026quot;).MinimalPluginContext \u0026amp; { environment: import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.2_46041a9d4deccce9f9aaf674d7cdda12/node_modules/vite/dist/node/index\u0026quot;).DevEnvironment; }”。\\n             不能将类型“MinimalPluginContext \u0026amp; { environment: DevEnvironment; }”分配给类型“{ environment: DevEnvironment; }”。\\n              在这些类型中，\u0026quot;environment.pluginContainer\u0026quot; 的类型不兼容。\\n               不能将类型“EnvironmentPluginContainer\u0026lt;import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.20.1_jiti@2.7.0/node_modules/vite/dist/node/index\u0026quot;).DevEnvironment\u0026gt;”分配给类型“EnvironmentPluginContainer\u0026lt;import(\u0026quot;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.2_46041a9d4deccce9f9aaf674d7cdda12/node_modules/vite/dist/node/index\u0026quot;).DevEnvironment\u0026gt;”。\\n                类型具有私有属性“_pluginContextMap”的单独声明。\u0026rdquo;,\n\u003cstrong\u003e\u0026ldquo;source\u0026rdquo;\u003c/strong\u003e: \u0026ldquo;ts\u0026rdquo;,\n\u003cstrong\u003e\u0026ldquo;startLineNumber\u0026rdquo;\u003c/strong\u003e: 17,\n\u003cstrong\u003e\u0026ldquo;startColumn\u0026rdquo;\u003c/strong\u003e: 7,\n\u003cstrong\u003e\u0026ldquo;endLineNumber\u0026rdquo;\u003c/strong\u003e: 17,\n\u003cstrong\u003e\u0026ldquo;endColumn\u0026rdquo;\u003c/strong\u003e: 20,\n\u003cstrong\u003e\u0026ldquo;relatedInformation\u0026rdquo;\u003c/strong\u003e: [\n{\n\u0026ldquo;startLineNumber\u0026rdquo;: 118,\n\u0026ldquo;startColumn\u0026rdquo;: 18,\n\u0026ldquo;endLineNumber\u0026rdquo;: 118,\n\u0026ldquo;endColumn\u0026rdquo;: 30,\n\u0026ldquo;message\u0026rdquo;: \u0026ldquo;在此处声明最后一个重载。\u0026rdquo;,\n\u0026ldquo;resource\u0026rdquo;: \u0026ldquo;/D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/electron-vite@5.0.0_support_8fbe30fbe1a2ef11377a687dfa84a1af/node_modules/electron-vite/dist/index.d.ts\u0026rdquo;\n}\n],\n\u003cstrong\u003e\u0026ldquo;origin\u0026rdquo;\u003c/strong\u003e: \u0026ldquo;extHost1\u0026rdquo;\n}]\u003c/p\u003e","title":"Vscode问题面板的报错查看"},{"content":"报错 背景：使用electron-vite构建项目，然后安装tailwindcss，在electron.vite.config.ts中配置插件\n类似issue tailwindcss 具体issue 这个issue的作者使用SolidStart框架，然后安装@tailwindcss/vite：框架内部自带了一套 Vite 体系，而外部新安装的插件自带了另一套 Vite 体系\n但我的项目中\nFound 1 version of vite （全工程只找到了 1 个版本的 vite，那就是顶部的 7.3.6）\nFound 1 version of rollup （全工程也只有 1 个版本的 rollup，那就是 4.63.1）\n我的报错消息 把关键的地方已经加粗标记\n没有与此调用匹配的重载。 最后一个重载给出了以下错误。 不能将类型“Plugin[]”分配给类型“PluginOption”。 不能将类型“Plugin[]”分配给类型“PluginOption[]”。 不能将类型“Plugin”分配给类型“PluginOption”。 不能将类型“import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.2_46041a9d4deccce9f9aaf674d7cdda12/node_modules/vite/dist/node/index\u0026rdquo;).Plugin”分配给类型“import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.20.1_jiti@2.7.0/node_modules/vite/dist/node/index\u0026rdquo;).Plugin”。 属性“hotUpdate”的类型不兼容。 类型“import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026rdquo;).ObjectHook\u0026lt;(this: import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026rdquo;).MinimalPluginContext \u0026amp; { \u0026hellip;; }, options: import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/nod\u0026hellip;”无法分配给类型“import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026rdquo;).ObjectHook\u0026lt;(this: import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026rdquo;).MinimalPluginContext \u0026amp; { \u0026hellip;; }, options: import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/nod\u0026hellip;”。存在具有此名称的两种不同类型，但它们是不相关的。 不能将类型“(this: MinimalPluginContext \u0026amp; { environment: DevEnvironment; }, options: HotUpdateOptions) =\u0026gt; void | EnvironmentModuleNode[] | Promise\u0026lt;\u0026hellip;\u0026gt;”分配给类型“ObjectHook\u0026lt;(this: MinimalPluginContext \u0026amp; { environment: DevEnvironment; }, options: HotUpdateOptions) =\u0026gt; void | EnvironmentModuleNode[] | Promise\u0026lt;\u0026hellip;\u0026raquo; | undefined”。 不能将类型“(this: import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026rdquo;).MinimalPluginContext \u0026amp; { \u0026hellip;; }, options: import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.2_46041a9d4deccce9f9aaf674d7cdda12/node_modules/vite/dist/node/index\u0026rdquo;).HotUp\u0026hellip;”分配给类型“(this: import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026rdquo;).MinimalPluginContext \u0026amp; { \u0026hellip;; }, options: import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.20.1_jiti@2.7.0/node_modules/vite/dist/node/index\u0026rdquo;).HotUpdateOptions) =\u0026gt; voi\u0026hellip;”。 每个签名的 \u0026ldquo;this\u0026rdquo; 类型不兼容。 不能将类型“import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026rdquo;).MinimalPluginContext \u0026amp; { environment: import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.20.1_jiti@2.7.0/node_modules/vite/dist/node/index\u0026rdquo;).DevEnvironment; }”分配给类型“import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/rollup@4.63.1/node_modules/rollup/dist/rollup\u0026rdquo;).MinimalPluginContext \u0026amp; { environment: import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.2_46041a9d4deccce9f9aaf674d7cdda12/node_modules/vite/dist/node/index\u0026rdquo;).DevEnvironment; }”。 不能将类型“MinimalPluginContext \u0026amp; { environment: DevEnvironment; }”分配给类型“{ environment: DevEnvironment; }”。 在这些类型中，\u0026ldquo;environment.pluginContainer\u0026rdquo; 的类型不兼容。 不能将类型“EnvironmentPluginContainer\u0026lt;import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.20.1_jiti@2.7.0/node_modules/vite/dist/node/index\u0026rdquo;).DevEnvironment\u0026gt;”分配给类型“EnvironmentPluginContainer\u0026lt;import(\u0026ldquo;D:/Code/Demo/electron-vite-demo/node_modules/.pnpm/vite@7.3.6_@types+node@22.2_46041a9d4deccce9f9aaf674d7cdda12/node_modules/vite/dist/node/index\u0026rdquo;).DevEnvironment\u0026gt;”。 类型具有私有属性“_pluginContextMap”的单独声明。\n产生原因 pnpm官方文档：\nHow peers are resolved | pnpm 如果一个包所处的伴生依赖（peer）环境稍有差异，pnpm 就会在 .pnpm 下为它各建一个专属的隔离文件夹\n流程：\n刚创建项目\n刚初始化的 electron-vite 模板非常干净，环境里只有 ESLint 带来的 jiti 此时 Vite 环境里只有 node 和 jiti pnpm 便给它建了环境 A：vite@7.3.6_@types+node@22.20.1_jiti@2.7.0 安装tailwind css\n执行 pnpm add tailwindcss @tailwindcss/vite 时： Tailwind CSS v4 的底层核心编译引擎正是 lightningcss 它顺带把 lightningcss@1.32.0 安装进了项目 此时对Vite来说是个全新的环境，所以pnpm把三者算成了一串新哈希，为它创建了环境 B：vite@7.3.6_@types+node@22.2_46041a9d... 同一个包由于包管理器安装成了两份物理文件，导致私有属性跨文件比对失败，引发报错\n解决 在插件上方加上 // @ts-expect-error\n或直接断言 tailwindcss() as any\n1 2 3 4 5 plugins: [ react(), // @ts-expect-error tailwindcss() ] typescript 1 2 3 4 plugins: [ react(), tailwindcss() as any ] typescript 这属于纯构建期的类型假阳性报警，完全不影响项目运行与打包\n","permalink":"https://zhiwu215.github.io/posts/%E8%A7%A3%E5%86%B3tailwindcss%E6%8A%A5%E9%94%99%E6%B2%A1%E6%9C%89%E4%B8%8E%E6%AD%A4%E8%B0%83%E7%94%A8%E5%8C%B9%E9%85%8D%E7%9A%84%E9%87%8D%E8%BD%BD/","summary":"环境：electron-vite安装tailwindcss。报错：没有与此调用匹配的重载","title":"解决Tailwindcss报错：没有与此调用匹配的重载"},{"content":"官方文档 Installing Tailwind CSS with Vite - Tailwind CSS 安装依赖 1 pnpm add tailwindcss @tailwindcss/vite bash 配置vite插件 electron.vite.config.ts\n为什么要@ts-expect-error，查看 解决 Tailwindcss 报错：没有与此调用匹配的重载 | 知兀的博客 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import { resolve } from \u0026#39;path\u0026#39; import { defineConfig } from \u0026#39;electron-vite\u0026#39; import react from \u0026#39;@vitejs/plugin-react\u0026#39; +import tailwindcss from \u0026#39;@tailwindcss/vite\u0026#39; export default defineConfig({ main: {}, preload: {}, renderer: { resolve: { alias: { \u0026#39;@renderer\u0026#39;: resolve(\u0026#39;src/renderer/src\u0026#39;) } }, plugins: [ react(), + // @ts-expect-error + tailwindcss() ] } }) diff 导入Tailwind CSS src/renderer/src/assets/globals.css\n文件名随便\n1 @import \u0026#34;tailwindcss\u0026#34;; css ","permalink":"https://zhiwu215.github.io/posts/electron-vite%E9%A1%B9%E7%9B%AE%E9%85%8D%E7%BD%AEtailwindcss/","summary":"配置tailwindcss","title":"Electron Vite项目配置tailwindcss"},{"content":"为什么会报错 官方文档：\nBuild Settings | pnpm strictDepBuilds默认为true，一旦存在未审核的构建脚本构建脚本（build scripts），就是 npm 包生命周期脚本里的： preinstall、install、postinstall： 它不是你手动敲命令运行的脚本。 它是包被下载完之后，npm/pnpm 自动、悄悄执行的一段代码 ，安装直接报错终止\n这是一种安全措施，防止恶意包在安装时执行危险代码\n方法一：交互式审批 放行\n1 pnpm approve-builds bash 选中需要放行的包，这条命令会自动生成 pnpm‑workspace.yaml 文件，写入白名单\n运行\n1 pnpm dev bash pnpm 在执行 dev 脚本之前，检测 lock‑file 有缺失的构建任务，在启动脚本前自动补发执行了被拦截的构建脚本\n方法二：手动新建 pnpm‑workspace.yaml 官方文档：\nBuild Settings | pnpm 创建或修改pnpm-workspace.yaml\n在项目根目录创建或修改 pnpm-workspace.yaml 文件，添加 allowBuilds 配置：\n不带版本（全局放行）\n指定版本（精准放行）\n1 2 3 allowBuilds: \u0026lt;需要放行的包\u0026gt;: true \u0026lt;需要放行的包@版本\u0026gt;: true bash 运行\n1 pnpm dev bash ","permalink":"https://zhiwu215.github.io/posts/%E8%A7%A3%E5%86%B3err_pnpm_ignored_builds/","summary":"解决pnpm install时，ERR_PNPM_IGNORED_BUILDS的报错","title":"两种方法解决ERR_PNPM_IGNORED_BUILDS"},{"content":"搭建项目 官网：\n快速开始 | electron-vite 这里我使用react+ts，所以可以直接使用模板：\n1 pnpm create @quick-start/electron \u0026lt;项目名\u0026gt; --template react bash 解决ERR_PNPM_IGNORED_BUILDS 详情： 两种方法解决 ERR_PNPM_IGNORED_BUILDS | 知兀的博客 清理默认文件 保留\nsrc\\renderer\\src\\App.tsx\n1 2 3 4 5 function App(): React.JSX.Element { return \u0026lt;\u0026gt;\u0026lt;/\u0026gt; } export default App tsx 删除\nsrc/renderer/src/assets\nelectron.svg wavy-lines.svg main.css base.css src/renderer/src/components\nVersions.tsx src/main/src/index.ts\n1 2 // IPC test ipcMain.on(\u0026#39;ping\u0026#39;, () =\u0026gt; console.log(\u0026#39;pong\u0026#39;)) typescript src/renderer/src/main.tsx\n1 import \u0026#39;./assets/main.css\u0026#39; diff ","permalink":"https://zhiwu215.github.io/posts/electron-vite%E6%90%AD%E5%BB%BAreact+ts%E9%A1%B9%E7%9B%AE/","summary":"使用electron-vite搭建项目","title":"Electron Vite搭建react+ts项目"},{"content":"报错 原因 anthropic-sdk 首先看 anthropic-sdk 可以看到web_search和web_fetch这两个内置工具都是服务端工具，所以它们的执行都依赖 Anthropic 服务端\nWebFetch工具 在sdk中，web_search和web_fetch都是服务端工具\n但是，在泄露的claude code源码中：\nWebSearch，使用了 SDK 中定义的服务端 web_search WebFetch，自己用 axios 实现，属于客户端 既然如此，为什么WebFetch工具还会报错？\n参考这个 知乎文章 下的评论：\nClaude Code 的 WebFetch 在发起实际 HTTP 请求前，会先向 claude.ai/ 发送域名安全预检请求。claude.ai 前面部署了 Cloudflare Bot Protection，从 headless CLI 发出的请求会被 Cloudflare 返回 403 JavaScript challenge，而 CLI 环境无法执行 JS，所以预检永远失败。错误提示里说的 \u0026ldquo;network restrictions\u0026rdquo; 其实具有误导性——真正卡住的是 Anthropic 自己 CDN 上的 Cloudflare\n官方文档：\n数据使用 - Claude Code Docs 解决 官方文档：\nClaude Code 设置 - Claude Code Docs ~/.claude/settings.json\n这个配置可以跳过这一步预检\n{ \u0026#34;autoUpdatesChannel\u0026#34;: \u0026#34;latest\u0026#34;, \u0026#34;skipWebFetchPreflight\u0026#34;: true, \u0026#34;env\u0026#34;: { ... } } ","permalink":"https://zhiwu215.github.io/posts/%E8%A7%A3%E5%86%B3claudecode%E5%A4%96%E6%8E%A5%E5%A4%A7%E6%A8%A1%E5%9E%8B%E4%BD%BF%E7%94%A8webfetch%E7%9A%84%E9%97%AE%E9%A2%98/","summary":"解决Unable to verify if domain xxx is safe to fetch. This may be due to network restrictions or enterprise security policies blocking claude.ai.","title":"解决claude code外接大模型使用WebFetch的问题"},{"content":"功能分支合并主分支（保持线性历史） 1.保存当前修改\n在切换分支或变基前，必须确保工作区是干净的，否则 Git 会阻止你进行高风险操作\n1 2 git add . git commit -m \u0026#34;feat: xxx\u0026#34; bash 2.变基到主分支 (Rebase)\n如果你的 main 分支没有任何新的提交（没动过），你直接切换回 main 分支并执行merge，Git 默认就会直接触发 Fast-forward（快进）合并\n先 rebase 再 merge，是一种在团队协作里养成的“标准防御性习惯”\n1 2 # 当前处于功能分支 git rebase main bash 3.切换回主分支快进合并\n快进合并 (Fast-forward Merge)： 因为已经完成了变基对齐，此时合并就像快进录像带一样，直接把 main 指针移动到最新提交，绝对不会产生多余的 Merge Commit 节点。\n1 2 git switch main git merge \u0026lt;你的功能分支名\u0026gt; bash 4.清理开发分支\n1 git branch -d \u0026lt;你的功能分支名\u0026gt; bash ","permalink":"https://zhiwu215.github.io/posts/git%E6%93%8D%E4%BD%9C/","summary":"一系列场景下，git的操作，包含命令和vscode可视化界面","title":"Git操作"},{"content":" 提示：\n这里面给出的代码都是我第一次配置的使用的，有完全复制别人的代码，也有根据别人的代码改写的\n但随着配置的增多，我自己个人使用各种美化的配置和下文的会有所不同\n所以按照的 我的仓库中的配置 为准\n基础搭建 安装 Hugo 官网：\nQuick start 官方文档有有三种安装方式：\nPrebuilt binaries，预构建二进制文件 Package managers，包管理器 Build from source，从源代码开始构建 我这里使用winget安装\n创建项目与目录结构 官方文档进行说明，不要使用cmd，使用pwsh或者Linux终端\nDirectory structure 命令用于生成项目骨架\n1 2 # 比如 hugo new project blog hugo new project \u0026lt;项目名\u0026gt; bash 目录说明：\n文件名称 简要说明 archetypes 博客内容的模板，默认只有default.md，可以根据个人的主题配置添加自定义头部信息 assets 需要 Hugo Pipes 处理的全局资源，如 images, CSS, Sass, JavaScript, and TypeScript content 个人博客所有内容 data 生成站点时候所需要的配置文件 layouts 以为.html形式存储模板，将博客内容呈现为静态页面 resources 保存运行 hugo build 或 hugo server 命令时生成的缓存输出文件，用来加速站点生成 static 在构建项目时，这些文件会被复制到 public 目录中。例如： favicon.ico 、 robots.txt 等文件，还有一些用于验证网站所有权的文件 themes 使用的第三方主题，每个主题都有自己的layouts、static等。使用主题后，hugo先从这里主题加载，再加载自定义的覆盖文件 hugo.toml 个人博客主题样式配置文件 引入 PaperMod 主题 这里我使用PaperMod\n点进去跳转对应的github仓库\n然后查看安装指南\n这里有四种安装主题的方式：\nGit Clone和Download an unzip都是安装主题到本地themes目录下\n维度 Git Submodule Hugo Module 本质 Git 原生的子仓库机制，把另一个 Git 仓库嵌到当前仓库的子目录 Hugo 内置的模块系统，基于 Go Modules，是 Hugo 自己的依赖管理方案 版本管理 锁定到具体 commit hash，手动 git submodule update 升级 通过 go.mod / go.sum 管理，支持版本范围（如 v7.x），hugo mod get -u 一键升级 安装位置 物理文件在 themes/PaperMod/ 目录下，是真实的子目录 模块缓存在本地（$HUGO_CACHEDIR），项目目录里看不到主题文件，是虚拟挂载 Git 仓库体积 子模块文件不占主仓库体积，但 clone 时需 --recursive 项目仓库里完全没有主题文件，体积最小 协作成本 协作者必须知道 git submodule init \u0026amp;\u0026amp; git submodule update，容易忘 协作者只需装好 Hugo，hugo 命令自动拉取依赖，零心智负担 多主题 / 组件 每个主题一个 submodule，手动管理 支持声明多个模块，Hugo 自动合并 assets /layouts/static 适用场景 需要深度修改主题源码、团队熟悉 Git 子模块操作 纯使用主题、不想把主题文件塞进仓库、追求简洁的依赖管理，简单定制直接覆盖文件就行了 我使用hugo module\n初始化hugo mod\n如果使用Github Page部署博客，仓库一定是\u0026lt;你的用户名.github.io\u0026gt;\n1 2 # github仓库：github.com/你的github用户名/你的仓库名 hugo mod init \u0026lt;你的github仓库\u0026gt; bash 添加PaperMod到hugo.toml\n1 2 3 [module] [[module.imports]] path = \u0026#34;github.com/adityatelange/hugo-PaperMod\u0026#34; toml 更新\n1 hugo mod get -u bash 创建.gitignore\n排除不必要文件，让git管理和推送到仓库的文件更加清晰\n直接用官方主题的忽略文件\n创建文章与本地预览 Quick start 创建文章\nhugo new content content/posts/\u0026lt;标题名字\u0026gt;.md 首先content目录是存放所有的博客内容的\nposts只是我习惯放文章的地方，你甚至可以在根目录下创建md，只是用文件夹好分类\n下图是我看 别人的博客 的目录结构\n运行\n1 2 3 hugo server --buildDrafts 或 hugo server -D bash hugo server → 不构建草稿（draft: true 的文章会被跳过，网站上看不到）\nhugo server -D（即 --buildDrafts）→ 连草稿一起构建，本地预览时能看到\n样式太简陋\n可以看到，目前网站什么都没有，所以需要配置\n站点核心配置与页面 完整 hugo.toml 配置文件 hugo官方的配置，什么主题都通用\nAll settings 主题自定义参数\nVariables · adityatelange/hugo-PaperMod Wiki 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 # ========================================== # 站点基本信息配置 # ========================================== # 网站根域名 baseURL = \u0026#34;https://zhiwu.github.io/\u0026#34; # 网站标题（显示在浏览器标签页和首页 Header） title = \u0026#34;知兀的博客\u0026#34; # 站点区域语言设置（设置 HTML 的 \u0026lt;html lang=\u0026#34;zh-cn\u0026#34;\u0026gt; 属性） locale = \u0026#34;zh-cn\u0026#34; # 默认内容语言（Hugo 会自动加载 PaperMod 自带的中文语言包） defaultContentLanguage = \u0026#34;zh\u0026#34; # 开启中日韩（CJK）字符精准统计（解决中文文章字数与预计阅读时间统计偏少的问题） hasCJKLanguage = true # 首页及文章列表页每页显示的文章数量 paginate = 10 # 自动生成 robots.txt 文件（引导搜索引擎爬虫收录文章，有利于 SEO） enableRobotsTXT = true # 开启 Git 信息读取，用于自动获取最后修改时间 enableGitInfo = true # 配置 Frontmatter 获取时间的优先级（支持本地文件实时修改预览） [frontmatter] lastmod = [\u0026#34;:git\u0026#34;, \u0026#34;:fileModTime\u0026#34;, \u0026#34;lastmod\u0026#34;, \u0026#34;date\u0026#34;] # 主题导入 (Hugo Module) [module] [[module.imports]] path = \u0026#34;github.com/adityatelange/hugo-PaperMod\u0026#34; # 输出控制（JSON 用于站内搜索） [outputs] home = [\u0026#34;HTML\u0026#34;, \u0026#34;RSS\u0026#34;, \u0026#34;JSON\u0026#34;] # ========================================== # PaperMod 主题自定义参数 # ========================================== [params] env = \u0026#34;production\u0026#34; description = \u0026#34;知兀的个人博客\u0026#34; keywords = [\u0026#34;Blog\u0026#34;, \u0026#34;知兀\u0026#34;, \u0026#34;PaperMod\u0026#34;] author = \u0026#34;知兀\u0026#34; DateFormat = \u0026#34;2006年01月02日\u0026#34; defaultTheme = \u0026#34;auto\u0026#34; # 文章元信息与功能开关 ShowReadingTime = true ShowWordCount = true ShowPostNavLinks = true ShowBreadCrumbs = true ShowCodeCopyButtons = true comments = true # 全局开启评论功能 # 文章目录 (TOC) 设置 ShowToc = true TocOpen = true # 封面图片设置 (Cover) [params.cover] responsiveImages = false linkFullImages = true # Giscus 评论系统配置 [params.giscus] repo = \u0026#34;zhiwu215/zhiwu215.github.io\u0026#34; # 你的 GitHub 博客仓库（或专门放 Discussion 的仓库） repoId = \u0026#34;xxx\u0026#34; # 从 giscus.app 生成获取的 repoId category = \u0026#34;Announcements\u0026#34; # Discussion 的分类 categoryId = \u0026#34;xxx\u0026#34; # 从 giscus.app 生成获取的 categoryId mapping = \u0026#34;pathname\u0026#34; # 匹配方式：pathname strict = \u0026#34;0\u0026#34; reactionsEnabled = \u0026#34;1\u0026#34; emitMetadata = \u0026#34;0\u0026#34; inputPosition = \u0026#34;top\u0026#34; lightTheme = \u0026#34;light\u0026#34; # 浅色模式对应的 Giscus 主题 darkTheme = \u0026#34;dark\u0026#34; # 深色模式对应的 Giscus 主题 lang = \u0026#34;zh-CN\u0026#34; loading = \u0026#34;lazy\u0026#34; # 站点图标 (Favicon) [params.assets] favicon = \u0026#34;/favicon.jpg\u0026#34; favicon16x16 = \u0026#34;/favicon.jpg\u0026#34; favicon32x32 = \u0026#34;/favicon.jpg\u0026#34; apple_touch_icon = \u0026#34;/favicon.jpg\u0026#34; # 首页欢迎信息模式 (Home Info) [params.homeInfoParams] Title = \u0026#34;知兀\u0026#34; ImageUrl = \u0026#34;/avatar.jpg\u0026#34; Content = \u0026#34;print(\\\u0026#34;Hello, World\\\u0026#34;)\u0026#34; # 社交媒体链接 [[params.socialIcons]] name = \u0026#34;bilibili\u0026#34; url = \u0026#34;https://space.bilibili.com/3546704263514722\u0026#34; [[params.socialIcons]] name = \u0026#34;github\u0026#34; url = \u0026#34;https://github.com/zhiwu215\u0026#34; [[params.socialIcons]] name = \u0026#34;x\u0026#34; url = \u0026#34;https://x.com/zhiwu215\u0026#34; [[params.socialIcons]] name = \u0026#34;email\u0026#34; url = \u0026#34;mailto:zhiwu215@gmail.com\u0026#34; # ========================================== # 分类法 (Taxonomies) 配置 # ========================================== [taxonomies] tag = \u0026#34;tags\u0026#34; series = \u0026#34;series\u0026#34; # ========================================== # 顶部主导航菜单配置（纯文字，无 Emoji 图标） # ========================================== [[menu.main]] identifier = \u0026#34;search\u0026#34; name = \u0026#34;搜索\u0026#34; url = \u0026#34;/search/\u0026#34; weight = 1 [[menu.main]] identifier = \u0026#34;series\u0026#34; name = \u0026#34;合集\u0026#34; url = \u0026#34;/series/\u0026#34; weight = 2 [[menu.main]] identifier = \u0026#34;tags\u0026#34; name = \u0026#34;标签\u0026#34; url = \u0026#34;/tags/\u0026#34; weight = 3 [[menu.main]] identifier = \u0026#34;archives\u0026#34; name = \u0026#34;归档\u0026#34; url = \u0026#34;/archives/\u0026#34; weight = 4 [[menu.main]] identifier = \u0026#34;about\u0026#34; name = \u0026#34;关于\u0026#34; url = \u0026#34;/about/\u0026#34; weight = 5 # ========================================== # Markdown 与渲染设置 # ========================================== # 使用 CSS 类名控制代码高亮（配合 PaperMod 实现深/浅色模式代码颜色自动切换） pygmentsUseClasses = true [markup] # Goldmark Markdown 渲染器设置 [markup.goldmark.renderer] # 允许在 Markdown 中内嵌原生 HTML 代码（如 \u0026lt;br\u0026gt;、居中标签或视频/音频组件） unsafe = true # 代码高亮语法器设置 (Chroma) [markup.highlight] # 默认给所有代码块左侧加上 1, 2, 3... 行号 lineNos = true # 使用 CSS 类名控制代码高亮（避免硬编码内联样式 style=\u0026#34;background-color:...\u0026#34;） noClasses = false toml 首页欢迎模式 adityatelange/hugo-PaperMod: A fast, clean, responsive Hugo theme. PaperMod文档说有三种模式，我使用Home-Info\n支持图标：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 [params] # 首页欢迎信息模式 (Home Info) [params.homeInfoParams] Title = \u0026#34;你的标题\u0026#34; Content = \u0026#34;你的欢迎语\u0026#34; # 社交媒体链接 [[params.socialIcons]] name = \u0026#34;bilibili\u0026#34; url = \u0026#34;xxxx\u0026#34; [[params.socialIcons]] name = \u0026#34;github\u0026#34; url = \u0026#34;xxx\u0026#34; [[params.socialIcons]] name = \u0026#34;x\u0026#34; url = \u0026#34;xxx\u0026#34; [[params.socialIcons]] name = \u0026#34;email\u0026#34; url = \u0026#34;mailto:xxx\u0026#34; toml 导航栏配置 PaperMod官方github仓库的「Wiki」的「FAQs」\n归档页面 PaperMod官方github仓库的「Wiki」的「Feature」\nFeatures · adityatelange/hugo-PaperMod Wiki content/archives.md\n--- title: \u0026#34;归档\u0026#34; layout: \u0026#34;archives\u0026#34; --- 搜索页面 Features · adityatelange/hugo-PaperMod Wiki PaperMod官方github仓库的「Wiki」的「Feature」\ncontent/search.md\n1 2 3 4 5 --- title: \u0026#34;搜索\u0026#34; # 页面标题（显示在浏览器标签页与页面头部） layout: \u0026#34;search\u0026#34; # 核心配置：指定使用 PaperMod 内置的 search 搜索交互模板 placeholder: \u0026#34;支持搜索标题、文章、标签等\u0026#34; # 搜索输入框内的默认淡灰色提示文字 --- markdown 文章分类（自定义 Taxonomies） hugo自带的分类的标签是categories和tags\n我个人不习惯用categories分类，这个词就好像是要对所有的文章进行区分一样\n所以我选择自定义合集series，可以用来定义一系列的教程、文章之类的\nhugo.toml\n自定义配置了，就会覆盖默认配置，所以默认的tags会失效，所以要重新配置\n[taxonomies] tag = \u0026#34;tags\u0026#34; series = \u0026#34;series\u0026#34; 之后写文章的时候就能自带series了，比如：\n+++ date = \u0026#39;2026-08-13T09:03:47+08:00\u0026#39; title = \u0026#39;如何配置博客1\u0026#39; series = [\u0026#34;配置博客\u0026#34;] +++ ... 关于页面 在添加归档页面和搜索页面的时候，直接在 Front Matter 中写上 layout: \u0026quot;archives\u0026quot; 或 layout: \u0026quot;search\u0026quot; 就可以了，但是关于页面不行。\n因为 layout 的本质是告诉 Hugo：“去查找一个叫 xxx.html 的自定义模板来渲染当前页面”。但 PaperMod 主题并未内置专属于关于页的 about.html 模板。\n制作关于页面主要有两种方案：\n1.直接当普通文章写：\n直接在 content/ 下创建 about.md\n缺点：会默认带上左上角嵌套面包屑导航（如 首页 » 关于）、发布日期与阅读时长、上一篇/下一篇翻页按钮，本质上看起来依然像一篇普通博文\n2.自定义独立模板：在 layouts/_default/about.html 创建独立模板\n我直接创建独立模板\nlayouts/_default/about.html\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 {{- define \u0026#34;main\u0026#34; }} \u0026lt;article class=\u0026#34;post-single\u0026#34;\u0026gt; \u0026lt;header class=\u0026#34;post-header\u0026#34;\u0026gt; \u0026lt;h1 class=\u0026#34;post-title\u0026#34;\u0026gt; {{ .Title }} \u0026lt;/h1\u0026gt; {{- if .Description }} \u0026lt;div class=\u0026#34;post-description\u0026#34;\u0026gt; {{ .Description }} \u0026lt;/div\u0026gt; {{- end }} \u0026lt;/header\u0026gt; \u0026lt;!-- 必须包含 md-content 类名，否则 Markdown 样式（段落与标题间距）将失效 --\u0026gt; \u0026lt;div class=\u0026#34;post-content md-content\u0026#34;\u0026gt; {{- if not (.Param \u0026#34;disableAnchoredHeadings\u0026#34;) }} {{- partial \u0026#34;anchored_headings.html\u0026#34; .Content -}} {{- else }}{{ .Content }}{{ end }} \u0026lt;/div\u0026gt; \u0026lt;!-- 可选：支持评论区（如 Giscus） --\u0026gt; {{- if (.Param \u0026#34;comments\u0026#34;) }} {{- partial \u0026#34;comments.html\u0026#34; . }} {{- end }} \u0026lt;/article\u0026gt; {{- end }} html content/about.md\n1 2 3 4 5 6 --- title: \u0026#34;关于\u0026#34; layout: \u0026#34;about\u0026#34; --- 这里书写关于你的介绍信息 markdown 更好看（视觉美化） 字体 (霞鹜文楷 + JetBrains Mono) 中文使用霞鹜文楷\n官方仓库： lxgw/LxgwWenKai 因为官方仓库没有woff2字体。所以使用cdn引入，从ZSFT搜索ZeoSeven Fonts（ZSFT）是开源免费商用字体聚合站点 霞鹜文楷 | 霞鶩文楷 | LXGW WenKai | ZeoSeven Fonts (ZSFT) layouts/partials/extend_head.html\n1 2 \u0026lt;!-- 引入 霞鹜文楷 (LXGW WenKai) CDN 字体 --\u0026gt; \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;https://fontsapi.zeoseven.com/292/main/result.css\u0026#34;\u0026gt; html 英文字体使用JetBrains Mono\nHugo + PaperMod + Github Pages 搭建一个完善的个人博客(以 Windows11 为例) | SonnyCalcr\u0026rsquo;s Blog 这个博客也使用JetBrainsMono字体，但是对方是在Google Fonts搜索之后，通过CDN引入\n我选择下载文件\nJetBrains/JetBrainsMono: JetBrains Mono – the free and open-source typeface for developers 将 JetBrains Mono 的 .woff2 字体文件JetBrainsMono-Regular.woff2放入static/fonts\nassets/css/extended/blank.css\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 /* ========================================== 本地 JetBrains Mono 字体声明 ========================================== */ @font-face { font-family: \u0026#39;JetBrains Mono\u0026#39;; src: url(\u0026#39;/fonts/JetBrainsMono-Regular.woff2\u0026#39;) format(\u0026#39;woff2\u0026#39;); font-weight: 400; font-style: normal; font-display: swap; } /* ========================================== 全局应用：英文/数字用 JetBrains Mono，中文用 霞鹜文楷 ========================================== */ body { font-family: \u0026#39;JetBrains Mono\u0026#39;, \u0026#39;LXGW WenKai\u0026#39;, -apple-system, BlinkMacSystemFont, \u0026#34;Segoe UI\u0026#34;, Roboto, sans-serif; font-weight: normal; } /* ========================================== 代码块样式微调 ========================================== */ .post-content pre, .post-content code, .chroma, .chroma * { font-family: \u0026#39;JetBrains Mono\u0026#39;, \u0026#39;LXGW WenKai\u0026#39;, monospace !important; font-size: 1rem; line-height: 1.2; } css 盘古之白 参考： Hugo PaperMod 主题精装修 | Tai\u0026rsquo;s Blog 中文和英文以及数字之间有空格会更加便于阅读，使用盘古之白解决2026/8/14 尝试过 CSS text-autospace与中文排版的圣杯时刻 ，但目前效果不理想且编辑器有警告，故转而继续使用盘古之白。 。\n如果你没加空格，它会自动帮你加。如果你已经手动加了空格，就会直接跳过，什么都不做\nvinta/pangu.js: Opinionated paranoid text spacing in JavaScript 这是官方文档的使用说明，使用包管理工具，这是现代前端项目的使用，在代码演示中也使用import\n\u0026lt;scrpit\u0026gt;这是CDN 外部引用\n再下面就是展示各种高级功能\n我的做法是下载到本地使用，根据CDN文件的链接（就是演示里src后面的内容），直接把文件下载到assets/js/\n在layouts/partials/extend_head.html中添加以下代码：\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 \u0026lt;!-- 盘古之白：同步加载 + 隐藏页面直到格式化完成，彻底消除布局抖动 --\u0026gt; {{- $pangu := resources.Get \u0026#34;js/pangu.umd.js\u0026#34; -}} {{- if $pangu -}} \u0026lt;style\u0026gt;body { opacity: 0; }\u0026lt;/style\u0026gt; \u0026lt;script src=\u0026#34;{{ $pangu.RelPermalink }}\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; (function () { var revealed = false; function reveal() { if (revealed) return; revealed = true; document.body.style.transition = \u0026#34;opacity 0.15s ease\u0026#34;; document.body.style.opacity = \u0026#34;1\u0026#34;; } document.addEventListener(\u0026#34;DOMContentLoaded\u0026#34;, function () { pangu.spacingPage(); reveal(); }); // 兜底：即使 pangu 出错也确保页面可见 setTimeout(function () { if (document.body) reveal(); }, 300); })(); \u0026lt;/script\u0026gt; {{- end -}} html 站点图标 图片放在static/\n1 2 3 4 5 6 [params] [params.assets] favicon = \u0026#34;/favicon.jpg\u0026#34; favicon16x16 = \u0026#34;/favicon.jpg\u0026#34; favicon32x32 = \u0026#34;/favicon.jpg\u0026#34; apple_touch_icon = \u0026#34;/favicon.jpg\u0026#34; toml 优化主页个人信息展示 参考： 折腾 Hugo PaperMod 主题 - 她和她的猫 演示：\nlayouts/partials/home_info.html\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 {{- with site.Params.homeInfoParams }} \u0026lt;article class=\u0026#34;first-entry home-info\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;home-info-container home-info-main-container\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;home-info-content-wrapper\u0026#34;\u0026gt; {{- with site.Params.homeInfoParams }} \u0026lt;div class=\u0026#34;home-info-avatar home-info-avatar-container\u0026#34;\u0026gt; {{- if .ImageUrl -}} {{- $imgSrc := .ImageUrl | absURL }} {{- $img := resources.Get .ImageUrl }} {{- if $img }} {{- $size := printf \u0026#34;%dx%d\u0026#34; (.ImageWidth | default 100) (.ImageHeight | default 100) }} {{- $img = $img.Resize $size }} {{- $imgSrc = $img.Permalink }} {{- end }} \u0026lt;img id=\u0026#34;home-info-avatar\u0026#34; draggable=\u0026#34;false\u0026#34; src=\u0026#34;{{ $imgSrc }}\u0026#34; alt=\u0026#34;{{ .Title | default \u0026#34;profile image\u0026#34; }}\u0026#34; height=\u0026#34;{{ .ImageHeight | default 100 }}\u0026#34; width=\u0026#34;{{ .ImageWidth | default 100 }}\u0026#34; class=\u0026#34;home-info-avatar-img\u0026#34; /\u0026gt; {{- end }} \u0026lt;/div\u0026gt; {{- end }} \u0026lt;div class=\u0026#34;entry-main home-info-text-content\u0026#34;\u0026gt; \u0026lt;header class=\u0026#34;entry-header\u0026#34;\u0026gt; \u0026lt;h1\u0026gt;{{ .Title | markdownify }}\u0026lt;/h1\u0026gt; \u0026lt;/header\u0026gt; \u0026lt;div class=\u0026#34;entry-content\u0026#34;\u0026gt; {{ .Content | markdownify }} \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;footer class=\u0026#34;entry-footer\u0026#34;\u0026gt; {{ partial \u0026#34;social_icons.html\u0026#34; (dict \u0026#34;align\u0026#34; site.Params.homeInfoParams.AlignSocialIconsTo) }} \u0026lt;/footer\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/article\u0026gt; {{- end -}} html assets/extended/css\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 /* Home Info Layout Styles */ .home-info-main-container { display: flex; flex-direction: column; gap: 24px; max-width: 100%; } .home-info-content-wrapper { display: flex; align-items: center; gap: 32px; } .home-info-avatar-container { display: flex; align-items: center; justify-content: center; flex-shrink: 0; position: relative; } .home-info-avatar-container::after { content: \u0026#39;\u0026#39;; position: absolute; right: -16px; top: 50%; transform: translateY(-50%); width: 1px; height: 60px; background-color: #e5e5e5; } .home-info-text-content { flex: 1; display: flex; flex-direction: column; justify-content: center; margin-top: 8px; } .home-info-avatar-img { border-radius: 50% !important; border: 2px solid #f0f0f0; transition: transform 0.2s ease; } .home-info-avatar-img:hover { transform: scale(1.02); } /* 响应式设计 */ @media (max-width: 768px) { .home-info-content-wrapper { flex-direction: column; gap: 20px; text-align: center; } .home-info-text-content { margin-top: 0; } /* 移动端隐藏分隔线 */ .home-info-avatar-container::after { display: none; } /* 移动端社交图标居中 */ .home-info .entry-footer { display: flex; justify-content: center; align-items: center; } } /* 图标悬浮高亮 */ .social-icons svg:hover { transition: 0.15s; } .social-icons a[href*=\u0026#39;mailto\u0026#39;]:hover svg { color: #ea4335 !important; } .social-icons a[href*=\u0026#39;github\u0026#39;]:hover svg { color: #7c3aed !important; } .social-icons a[href*=\u0026#39;index.xml\u0026#39;]:hover svg { color: #ff6600 !important; } css 在 hugo.toml 中配置头像地址\n图片放在static/\n1 2 [params.homeInfoParams] ImageUrl = \u0026#34;/avatar.jpg\u0026#34; toml 消除html代码块误判 Hugo 自带的配色方案是 Chroma，PaperMod 用的 highlight.js，我继续用Chroma\nHugo 内置的 Chroma 高亮引擎在解析包含 HTML 模板标签（如 {{ if ... }}）或正则匹配式时，纯 HTML 解析器会将其误判为语法错误，如图：\nassets/css/extended/blank.css\n1 2 3 4 5 6 7 /* 消除 Hugo Chroma 代码块高亮误判的语法错误红底警告（兼顾内联 style 与 CSS Class） */ .post-content span[style*=\u0026#34;background-color:#1e0010\u0026#34;], .post-content span[style*=\u0026#34;background-color: #1e0010\u0026#34;], .chroma .err { background-color: transparent !important; color: inherit !important; } css hugo.toml\n1 2 3 [markup.highlight] # 使用 CSS 类名控制代码高亮（避免硬编码内联样式 style=\u0026#34;background-color:...\u0026#34;） noClasses = false toml 文章列表卡片增加独立 Tag 胶囊 演示：\nlayouts/_default/list.html\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 {{- define \u0026#34;main\u0026#34; }} {{- if (and site.Params.profileMode.enabled .IsHome) }} {{- partial \u0026#34;index_profile.html\u0026#34; . }} {{- else }} {{/* if not profileMode */}} {{- if not .IsHome | and .Title }} \u0026lt;header class=\u0026#34;page-header\u0026#34;\u0026gt; {{- partial \u0026#34;breadcrumbs.html\u0026#34; . }} \u0026lt;h1\u0026gt; {{ .Title }} {{- if and (or (eq .Kind `term`) (eq .Kind `section`)) (.Param \u0026#34;ShowRssButtonInSectionTermList\u0026#34;) }} {{- with .OutputFormats.Get \u0026#34;rss\u0026#34; }} \u0026lt;a href=\u0026#34;{{ .RelPermalink }}\u0026#34; title=\u0026#34;RSS\u0026#34; aria-label=\u0026#34;RSS\u0026#34;\u0026gt; \u0026lt;svg xmlns=\u0026#34;http://www.w3.org/2000/svg\u0026#34; viewBox=\u0026#34;0 0 24 24\u0026#34; fill=\u0026#34;none\u0026#34; stroke=\u0026#34;currentColor\u0026#34; stroke-width=\u0026#34;2\u0026#34; stroke-linecap=\u0026#34;round\u0026#34; stroke-linejoin=\u0026#34;round\u0026#34; height=\u0026#34;23\u0026#34;\u0026gt; \u0026lt;path d=\u0026#34;M4 11a9 9 0 0 1 9 9\u0026#34; /\u0026gt; \u0026lt;path d=\u0026#34;M4 4a16 16 0 0 1 16 16\u0026#34; /\u0026gt; \u0026lt;circle cx=\u0026#34;5\u0026#34; cy=\u0026#34;19\u0026#34; r=\u0026#34;1\u0026#34; /\u0026gt; \u0026lt;/svg\u0026gt; \u0026lt;/a\u0026gt; {{- end }} {{- end }} \u0026lt;/h1\u0026gt; {{- if .Description }} \u0026lt;div class=\u0026#34;post-description\u0026#34;\u0026gt; {{ .Description | markdownify }} \u0026lt;/div\u0026gt; {{- end }} \u0026lt;/header\u0026gt; {{- end }} {{- if .Content }} \u0026lt;div class=\u0026#34;post-content md-content\u0026#34;\u0026gt; {{- if not (.Param \u0026#34;disableAnchoredHeadings\u0026#34;) }} {{- partial \u0026#34;anchored_headings.html\u0026#34; .Content -}} {{- else }}{{ .Content }}{{ end }} \u0026lt;/div\u0026gt; {{- end }} {{- $pages := union .RegularPages .Sections }} {{- if .IsHome }} {{- $pages = where site.RegularPages \u0026#34;Type\u0026#34; \u0026#34;in\u0026#34; site.Params.mainSections }} {{- $pages = where $pages \u0026#34;Params.hiddenInHomeList\u0026#34; \u0026#34;!=\u0026#34; \u0026#34;true\u0026#34; }} {{- end }} {{- $paginator := .Paginate $pages }} {{- if and .IsHome site.Params.homeInfoParams (eq $paginator.PageNumber 1) }} {{- partial \u0026#34;home_info.html\u0026#34; . }} {{- end }} {{- $term := .Data.Term }} {{- range $index, $page := $paginator.Pages }} {{- $class := \u0026#34;post-entry\u0026#34; }} {{- $user_preferred := or site.Params.disableSpecial1stPost site.Params.homeInfoParams }} {{- if (and $.IsHome (eq $paginator.PageNumber 1) (eq $index 0) (not $user_preferred)) }} {{- $class = \u0026#34;first-entry\u0026#34; }} {{- else if $term }} {{- $class = \u0026#34;post-entry tag-entry\u0026#34; }} {{- end }} \u0026lt;article class=\u0026#34;{{ $class }}\u0026#34;\u0026gt; {{- $isHidden := (.Param \u0026#34;cover.hiddenInList\u0026#34;) | default (.Param \u0026#34;cover.hidden\u0026#34;) | default false }} {{- partial \u0026#34;cover.html\u0026#34; (dict \u0026#34;cxt\u0026#34; . \u0026#34;IsSingle\u0026#34; false \u0026#34;isHidden\u0026#34; $isHidden) }} \u0026lt;header class=\u0026#34;entry-header\u0026#34;\u0026gt; \u0026lt;h2 class=\u0026#34;entry-hint-parent\u0026#34;\u0026gt; {{- .Title }} {{- if .Draft }} \u0026lt;span class=\u0026#34;entry-hint\u0026#34; title=\u0026#34;Draft\u0026#34;\u0026gt; \u0026lt;svg xmlns=\u0026#34;http://www.w3.org/2000/svg\u0026#34; height=\u0026#34;20\u0026#34; viewBox=\u0026#34;0 -960 960 960\u0026#34; fill=\u0026#34;currentColor\u0026#34;\u0026gt; \u0026lt;path d=\u0026#34;M160-410v-60h300v60H160Zm0-165v-60h470v60H160Zm0-165v-60h470v60H160Zm360 580v-123l221-220q9-9 20-13t22-4q12 0 23 4.5t20 13.5l37 37q9 9 13 20t4 22q0 11-4.5 22.5T862.09-380L643-160H520Zm300-263-37-37 37 37ZM580-220h38l121-122-18-19-19-18-122 121v38Zm141-141-19-18 37 37-18-19Z\u0026#34; /\u0026gt; \u0026lt;/svg\u0026gt; \u0026lt;/span\u0026gt; {{- end }} \u0026lt;/h2\u0026gt; \u0026lt;/header\u0026gt; {{- if (ne (.Param \u0026#34;hideSummary\u0026#34;) true) }} \u0026lt;div class=\u0026#34;entry-content\u0026#34;\u0026gt; \u0026lt;p\u0026gt;{{ .Summary | plainify | htmlUnescape }}{{ if .Truncated }}...{{ end }}\u0026lt;/p\u0026gt; \u0026lt;/div\u0026gt; {{- end }} {{- if not (.Param \u0026#34;hideMeta\u0026#34;) }} \u0026lt;footer class=\u0026#34;entry-footer\u0026#34;\u0026gt; {{- partial \u0026#34;post_meta.html\u0026#34; . -}} \u0026lt;/footer\u0026gt; {{- end }} {{- if .Params.tags }} \u0026lt;div class=\u0026#34;entry-tags\u0026#34;\u0026gt; {{- range .Params.tags }} \u0026lt;a href=\u0026#34;{{ \u0026#34;tags/\u0026#34; | relLangURL }}{{ . | urlize }}/\u0026#34; class=\u0026#34;post-tag-badge\u0026#34;\u0026gt;#{{ . }}\u0026lt;/a\u0026gt; {{- end }} \u0026lt;/div\u0026gt; {{- end }} \u0026lt;a class=\u0026#34;entry-link\u0026#34; aria-label=\u0026#34;post link to {{ .Title | plainify }}\u0026#34; href=\u0026#34;{{ .Permalink }}\u0026#34;\u0026gt;\u0026lt;/a\u0026gt; \u0026lt;/article\u0026gt; {{- end }} {{- if gt $paginator.TotalPages 1 }} \u0026lt;footer class=\u0026#34;page-footer\u0026#34;\u0026gt; \u0026lt;nav class=\u0026#34;pagination\u0026#34;\u0026gt; {{- if $paginator.HasPrev }} \u0026lt;a class=\u0026#34;prev\u0026#34; href=\u0026#34;{{ $paginator.Prev.URL | absURL }}\u0026#34;\u0026gt; «\u0026amp;nbsp;{{ i18n \u0026#34;prev_page\u0026#34; }}\u0026amp;nbsp; {{- if (.Param \u0026#34;ShowPageNums\u0026#34;) }} {{- sub $paginator.PageNumber 1 }}/{{ $paginator.TotalPages }} {{- end }} \u0026lt;/a\u0026gt; {{- end }} {{- if $paginator.HasNext }} \u0026lt;a class=\u0026#34;next\u0026#34; href=\u0026#34;{{ $paginator.Next.URL | absURL }}\u0026#34;\u0026gt; {{- i18n \u0026#34;next_page\u0026#34; }}\u0026amp;nbsp; {{- if (.Param \u0026#34;ShowPageNums\u0026#34;) }} {{- add 1 $paginator.PageNumber }}/{{ $paginator.TotalPages }} {{- end }}\u0026amp;nbsp;» \u0026lt;/a\u0026gt; {{- end }} \u0026lt;/nav\u0026gt; \u0026lt;/footer\u0026gt; {{- end }} {{- end }}{{/* end profileMode */}} {{- end }}{{- /* end main */ -}} html assets/css/extended/blank.css\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 /* ========================================== 文章列表页标签胶囊 (Tag Badges) 样式 ========================================== */ .entry-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; position: relative; z-index: 2; } .post-tag-badge { display: inline-flex; align-items: center; padding: 2px 10px; font-size: 0.78rem; font-weight: 500; border-radius: 12px; background-color: var(--tertiary); color: var(--secondary) !important; text-decoration: none !important; transition: all 0.2s ease; } .post-tag-badge:hover { background-color: var(--primary); color: var(--theme) !important; transform: translateY(-1px); } css 代码块语言标签 展示：\nlayouts/_default/_markup/render-codeblock.html\n1 2 3 4 5 6 7 8 {{- $lang := .Type -}} {{- $attrs := .Attributes -}} \u0026lt;div class=\u0026#34;code-block-wrapper\u0026#34; {{ if $lang }}data-lang=\u0026#34;{{ $lang }}\u0026#34;{{ end }}\u0026gt; {{- highlight .Inner $lang (transform.Remarshal \u0026#34;TOML\u0026#34; $attrs) -}} {{- if $lang -}} \u0026lt;span class=\u0026#34;code-lang-badge\u0026#34;\u0026gt;{{ $lang }}\u0026lt;/span\u0026gt; {{- end -}} \u0026lt;/div\u0026gt; html assets/css/extended/blank.css\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 /* ========================================== 代码块语言标签 (Language Badge) ========================================== */ /* 代码块外层容器 */ .code-block-wrapper { position: relative; margin-bottom: var(--content-gap); } /* 标签样式绝对定位 */ .code-lang-badge { position: absolute; top: 8px; left: 12px; /* 放在左上角，避免与原生右侧复制按钮冲突 */ font-size: 12px; font-weight: bold; color: var(--secondary); background: var(--tertiary); padding: 2px 8px; border-radius: 4px; text-transform: uppercase; /* 转大写字母 */ user-select: none; pointer-events: none; opacity: 0.8; } /* 动态内边距：仅当容器存在 data-lang 属性时才下压空间，防止纯文本代码块顶部多出空白 */ .code-block-wrapper[data-lang] .highlight pre { padding-top: 34px !important; } css 更便于阅读 侧边悬浮目录 参考： 在PaperMod中引入侧边目录和阅读进度显示 | 周鑫的个人博客 原代码如果目录太长会出现滚动条，而且当页面滚动到某标题时，该目录项的字体瞬间放大 1.1 倍，导致布局抖动 演示：\nlayouts/partials/toc.html\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 {{- $headers := findRE \u0026#34;\u0026lt;h[1-6].*?\u0026gt;(.|\\n])+?\u0026lt;/h[1-6]\u0026gt;\u0026#34; .Content -}} {{- $has_headers := ge (len $headers) 1 -}} {{- if $has_headers -}} \u0026lt;aside id=\u0026#34;toc-container\u0026#34; class=\u0026#34;toc-container wide\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;toc\u0026#34;\u0026gt; \u0026lt;details {{if (.Param \u0026#34;TocOpen\u0026#34;) }} open{{ end }}\u0026gt; \u0026lt;summary accesskey=\u0026#34;c\u0026#34; title=\u0026#34;(Alt + C)\u0026#34;\u0026gt; \u0026lt;span class=\u0026#34;details\u0026#34;\u0026gt;{{- i18n \u0026#34;toc\u0026#34; | default \u0026#34;Table of Contents\u0026#34; }}\u0026lt;/span\u0026gt; \u0026lt;/summary\u0026gt; \u0026lt;div class=\u0026#34;inner\u0026#34;\u0026gt; {{- $largest := 6 -}} {{- range $headers -}} {{- $headerLevel := index (findRE \u0026#34;[1-6]\u0026#34; . 1) 0 -}} {{- $headerLevel := len (seq $headerLevel) -}} {{- if lt $headerLevel $largest -}} {{- $largest = $headerLevel -}} {{- end -}} {{- end -}} {{- $firstHeaderLevel := len (seq (index (findRE \u0026#34;[1-6]\u0026#34; (index $headers 0) 1) 0)) -}} {{- $.Scratch.Set \u0026#34;bareul\u0026#34; slice -}} \u0026lt;ul\u0026gt; {{- range seq (sub $firstHeaderLevel $largest) -}} \u0026lt;ul\u0026gt; {{- $.Scratch.Add \u0026#34;bareul\u0026#34; (sub (add $largest .) 1) -}} {{- end -}} {{- range $i, $header := $headers -}} {{- $headerLevel := index (findRE \u0026#34;[1-6]\u0026#34; . 1) 0 -}} {{- $headerLevel := len (seq $headerLevel) -}} {{/* get id=\u0026#34;xyz\u0026#34; */}} {{- $id := index (findRE \u0026#34;(id=\\\u0026#34;(.*?)\\\u0026#34;)\u0026#34; $header 9) 0 }} {{- /* strip id=\u0026#34;\u0026#34; to leave xyz, no way to get regex capturing groups in hugo */ -}} {{- $cleanedID := replace (replace $id \u0026#34;id=\\\u0026#34;\u0026#34; \u0026#34;\u0026#34;) \u0026#34;\\\u0026#34;\u0026#34; \u0026#34;\u0026#34; }} {{- $header := replaceRE \u0026#34;\u0026lt;h[1-6].*?\u0026gt;((.|\\n])+?)\u0026lt;/h[1-6]\u0026gt;\u0026#34; \u0026#34;$1\u0026#34; $header -}} {{- if ne $i 0 -}} {{- $prevHeaderLevel := index (findRE \u0026#34;[1-6]\u0026#34; (index $headers (sub $i 1)) 1) 0 -}} {{- $prevHeaderLevel := len (seq $prevHeaderLevel) -}} {{- if gt $headerLevel $prevHeaderLevel -}} {{- range seq $prevHeaderLevel (sub $headerLevel 1) -}} \u0026lt;ul\u0026gt; {{/* the first should not be recorded */}} {{- if ne $prevHeaderLevel . -}} {{- $.Scratch.Add \u0026#34;bareul\u0026#34; . -}} {{- end -}} {{- end -}} {{- else -}} \u0026lt;/li\u0026gt; {{- if lt $headerLevel $prevHeaderLevel -}} {{- range seq (sub $prevHeaderLevel 1) -1 $headerLevel -}} {{- if in ($.Scratch.Get \u0026#34;bareul\u0026#34;) . -}} \u0026lt;/ul\u0026gt; {{/* manually do pop item */}} {{- $tmp := $.Scratch.Get \u0026#34;bareul\u0026#34; -}} {{- $.Scratch.Delete \u0026#34;bareul\u0026#34; -}} {{- $.Scratch.Set \u0026#34;bareul\u0026#34; slice}} {{- range seq (sub (len $tmp) 1) -}} {{- $.Scratch.Add \u0026#34;bareul\u0026#34; (index $tmp (sub . 1)) -}} {{- end -}} {{- else -}} \u0026lt;/ul\u0026gt; \u0026lt;/li\u0026gt; {{- end -}} {{- end -}} {{- end -}} {{- end }} \u0026lt;li\u0026gt; \u0026lt;a href=\u0026#34;#{{- $cleanedID -}}\u0026#34; aria-label=\u0026#34;{{- $header | plainify -}}\u0026#34;\u0026gt;{{- $header | safeHTML -}}\u0026lt;/a\u0026gt; {{- else }} \u0026lt;li\u0026gt; \u0026lt;a href=\u0026#34;#{{- $cleanedID -}}\u0026#34; aria-label=\u0026#34;{{- $header | plainify -}}\u0026#34;\u0026gt;{{- $header | safeHTML -}}\u0026lt;/a\u0026gt; {{- end -}} {{- end -}} \u0026lt;!-- {{- $firstHeaderLevel := len (seq (index (findRE \u0026#34;[1-6]\u0026#34; (index $headers 0) 1) 0)) -}} --\u0026gt; {{- $firstHeaderLevel := $largest }} {{- $lastHeaderLevel := len (seq (index (findRE \u0026#34;[1-6]\u0026#34; (index $headers (sub (len $headers) 1)) 1) 0)) }} \u0026lt;/li\u0026gt; {{- range seq (sub $lastHeaderLevel $firstHeaderLevel) -}} {{- if in ($.Scratch.Get \u0026#34;bareul\u0026#34;) (add . $firstHeaderLevel) }} \u0026lt;/ul\u0026gt; {{- else }} \u0026lt;/ul\u0026gt; \u0026lt;/li\u0026gt; {{- end -}} {{- end }} \u0026lt;/ul\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/details\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/aside\u0026gt; \u0026lt;script\u0026gt; let activeElement; let elements; document.addEventListener(\u0026#39;DOMContentLoaded\u0026#39;, function (event) { checkTocPosition(); elements = document.querySelectorAll(\u0026#39;h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]\u0026#39;); if (elements.length \u0026gt; 0) { // Make the first header active activeElement = elements[0]; const id = encodeURI(activeElement.getAttribute(\u0026#39;id\u0026#39;)).toLowerCase(); document.querySelector(`.inner ul li a[href=\u0026#34;#${id}\u0026#34;]`).classList.add(\u0026#39;active\u0026#39;); } // Add event listener for the \u0026#34;back to top\u0026#34; link const topLink = document.getElementById(\u0026#39;top-link\u0026#39;); if (topLink) { topLink.addEventListener(\u0026#39;click\u0026#39;, (event) =\u0026gt; { // Prevent the default action event.preventDefault(); // Smooth scroll to the top window.scrollTo({ top: 0, behavior: \u0026#39;smooth\u0026#39; }); }); } }, false); window.addEventListener(\u0026#39;resize\u0026#39;, function(event) { checkTocPosition(); }, false); window.addEventListener(\u0026#39;scroll\u0026#39;, () =\u0026gt; { // Get the current scroll position const scrollPosition = window.pageYOffset || document.documentElement.scrollTop; // Check if the scroll position is at the top of the page if (scrollPosition === 0) { return; } // Ensure elements is a valid NodeList if (elements \u0026amp;\u0026amp; elements.length \u0026gt; 0) { // Check if there is an object in the top half of the screen or keep the last item active activeElement = Array.from(elements).find((element) =\u0026gt; { if ((getOffsetTop(element) - scrollPosition) \u0026gt; 0 \u0026amp;\u0026amp; (getOffsetTop(element) - scrollPosition) \u0026lt; window.innerHeight / 2) { return element; } }) || activeElement; elements.forEach(element =\u0026gt; { const id = encodeURI(element.getAttribute(\u0026#39;id\u0026#39;)).toLowerCase(); const tocLink = document.querySelector(`.inner ul li a[href=\u0026#34;#${id}\u0026#34;]`); if (element === activeElement){ tocLink.classList.add(\u0026#39;active\u0026#39;); // Ensure the active element is in view within the .inner container const tocContainer = document.querySelector(\u0026#39;.toc .inner\u0026#39;); const linkOffsetTop = tocLink.offsetTop; const containerHeight = tocContainer.clientHeight; const linkHeight = tocLink.clientHeight; // Calculate the scroll position to center the active link const scrollPosition = linkOffsetTop - (containerHeight / 2) + (linkHeight / 2); tocContainer.scrollTo({ top: scrollPosition, behavior: \u0026#39;smooth\u0026#39; }); } else { tocLink.classList.remove(\u0026#39;active\u0026#39;); } }); } }, false); const main = parseInt(getComputedStyle(document.body).getPropertyValue(\u0026#39;--article-width\u0026#39;), 10); const toc = parseInt(getComputedStyle(document.body).getPropertyValue(\u0026#39;--toc-width\u0026#39;), 10); const gap = parseInt(getComputedStyle(document.body).getPropertyValue(\u0026#39;--gap\u0026#39;), 10); function checkTocPosition() { const width = document.body.scrollWidth; if (width - main - (toc * 2) - (gap * 4) \u0026gt; 0) { document.getElementById(\u0026#34;toc-container\u0026#34;).classList.add(\u0026#34;wide\u0026#34;); } else { document.getElementById(\u0026#34;toc-container\u0026#34;).classList.remove(\u0026#34;wide\u0026#34;); } } function getOffsetTop(element) { if (!element.getClientRects().length) { return 0; } let rect = element.getBoundingClientRect(); let win = element.ownerDocument.defaultView; return rect.top + win.pageYOffset; } \u0026lt;/script\u0026gt; {{- end }} html /assets/css/extended/toc.css\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 :root { --nav-width: 1380px; --article-width: 650px; --toc-width: 300px; } .toc { margin: 0 2px 40px 2px; border: 1px solid var(--border); background: var(--entry); border-radius: var(--radius); padding: 0.4em; } .toc-container.wide { position: absolute; height: 100%; border-right: 1px solid var(--border); left: calc((var(--toc-width) + var(--gap)) * -1); top: calc(var(--gap) * 2); width: var(--toc-width); } .wide .toc { position: sticky; top: var(--gap); border: unset; background: unset; border-radius: unset; width: 100%; margin: 0 2px 40px 2px; } .toc details summary { cursor: default; margin-inline-start: 20px; padding: 12px 0; } .toc details[open] summary { font-weight: 500; } .toc-container.wide .toc .inner { margin: 0; } .active { font-size: 110%; font-weight: 600; } .toc ul { list-style-type: circle; } .toc .inner { margin: 0 0 0 20px; padding: 0px 15px 15px 20px; font-size: 16px; /*目录显示高度*/ max-height: 83vh; overflow-y: auto; } .toc .inner::-webkit-scrollbar-thumb { /*滚动条*/ background: var(--border); border: 7px solid var(--theme); border-radius: var(--radius); } .toc li ul { margin-inline-start: calc(var(--gap) * 0.5); list-style-type: none; } .toc li { list-style: none; font-size: 0.95rem; padding-bottom: 5px; } .toc li a:hover { color: var(--secondary); } css 图片点击放大 参考：在 Hugo+PaperMod搭建博客_哔哩哔哩_bilibili 这个视频的1:09:00看到的效果，但是up没有详细说明，所以我从 他的github仓库 抄的使用叫做 medium-zoom 的 JavaScript 库，—点击后在原地放大背景变白，再点一下就缩小，我比较喜欢这个精简的功能 我还看了 这个博客 ，通过引入Fancybox这个提供“放大、拖拽、左右滑动”等特效的 JavaScript 库 来实现图片放大和拖拽，不过是使用Hugo的Shortcode（短代码） 实现的，插入图片时不能用md原生的语法 blank.css\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 /* medium-zoom 图片放大的样式 (全平台通用层级) */ .medium-zoom-overlay { background: rgba(255, 255, 255, 0.7) !important; z-index: 99999 !important; } .dark .medium-zoom-overlay { background: rgba(0, 0, 0, 0.7) !important; } /* 放大后的图片层级必须高于遮罩层 (z-index: 100000)，确保手机与桌面端均不会被遮挡 */ .medium-zoom-image--opened { z-index: 100000 !important; position: relative; } /* Windows 系统专属自定义放大/缩小光标 */ .win11 .medium-zoom-image { cursor: url(/cursors/zoom-in.svg), default !important; } .win11 .medium-zoom--opened .medium-zoom-overlay { cursor: url(/cursors/zoom-out.svg), default !important; } .win11 .medium-zoom-image--opened { cursor: url(/cursors/zoom-out.svg), default !important; } css layouts/partials\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 \u0026lt;script src=\u0026#34;https://cdnjs.cloudflare.com/ajax/libs/medium-zoom/1.1.0/medium-zoom.min.js\u0026#34; integrity=\u0026#34;sha512-9ZKhgaFdKlsELap/dGw3Iaz5Bj+Las0XXZiRKYZaN9QArg6FtkD5rULNmNH4rTCTFxjPiBGr3MX8smRADRorDA==\u0026#34; crossorigin=\u0026#34;anonymous\u0026#34; referrerpolicy=\u0026#34;no-referrer\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; var OSName = \u0026#34;unknown\u0026#34;; var navApp = navigator.userAgent.toLowerCase(); switch (true) { case (navApp.indexOf(\u0026#34;win\u0026#34;) != -1): OSName = \u0026#34;windows\u0026#34;; break; case (navApp.indexOf(\u0026#34;mac\u0026#34;) != -1): OSName = \u0026#34;apple\u0026#34;; break; case (navApp.indexOf(\u0026#34;linux\u0026#34;) != -1): OSName = \u0026#34;linux\u0026#34;; break; case (navApp.indexOf(\u0026#34;x11\u0026#34;) != -1): OSName = \u0026#34;unix\u0026#34;; break; } const images = Array.from(document.querySelectorAll(\u0026#34;.post-content img\u0026#34;)); images.forEach(img =\u0026gt; { mediumZoom(img, { margin: 1, /* 1px 边距 */ container: null, template: null, }); }); if (OSName == \u0026#34;windows\u0026#34;) { document.body.className += \u0026#39; win11\u0026#39; } \u0026lt;/script\u0026gt; html static/cursors\n存放放大和缩小的svg图标\n官网：\nSVG Mac cursor downloads 我直接从作者的仓库复制粘贴的\n站外链接新窗口打开 参考： 魔改PaperMod主题和博客改动 | 梓言堂 - Yuk\u0026rsquo;s Blog 默认站外链接都是当前页打开，使用体验不好\nlayouts/default/_markup/render-link.html\n1 2 3 4 5 6 7 \u0026lt;!-- 让站外链接统统是新窗口打开 --\u0026gt; \u0026lt;a href=\u0026#34;{{ .Destination | safeURL }}\u0026#34; {{- with .Title }} title=\u0026#34;{{ . }}\u0026#34;{{ end -}} {{- if not (in .Destination \u0026#34;yuk7.com\u0026#34;) }} target=\u0026#34;_blank\u0026#34;{{ end -}} \u0026gt; {{- with .Text | safeHTML }}{{ . }}{{ end -}} \u0026lt;/a\u0026gt; html 添加修改时间 参考： Hugo PaperMod 主题精装修 | Tai\u0026rsquo;s Blog 但对方的代码存在一些问题，更新时间是需要自己手动设置的，不合理\n演示：\n参数说明：\nDocs-\u0026gt;Configuration-\u0026gt;All settings\n默认配置：Hugo 会从左向右依次检查，一旦在某一项找到了有效的时间，就立刻停下来，把这个时间作为文章的“最后修改时间\nhugo.toml\n手动设置了:fileModTime，方便在本地运行的时候查看\n1 2 3 4 5 6 # 开启 Git 信息读取 (用于自动获取文章最后更新时间) enableGitInfo = true # 配置 Frontmatter 获取时间的优先级（支持本地文件实时修改预览） [frontmatter] lastmod = [\u0026#34;:git\u0026#34;, \u0026#34;:fileModTime\u0026#34;, \u0026#34;lastmod\u0026#34;, \u0026#34;date\u0026#34;] toml layouts/partials/post_meta.html\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 {{- $scratch := newScratch }} {{- if not .Date.IsZero -}} {{- $scratch.Add \u0026#34;meta\u0026#34; (slice (printf \u0026#34;\u0026lt;span title=\u0026#39;%s\u0026#39;\u0026gt;%s\u0026lt;/span\u0026gt;\u0026#34; (.Date) (.Date.Format (default \u0026#34;January 2, 2006\u0026#34; .Site.Params.DateFormat)))) }} {{- end -}} {{- if (.Param \u0026#34;ShowReadingTime\u0026#34;) -}} {{- $scratch.Add \u0026#34;meta\u0026#34; (slice (i18n \u0026#34;read_time\u0026#34; .ReadingTime | default (printf \u0026#34;%d min\u0026#34; .ReadingTime))) }} {{- end -}} {{- if (.Param \u0026#34;ShowWordCount\u0026#34;) -}} {{- $scratch.Add \u0026#34;meta\u0026#34; (slice (i18n \u0026#34;words\u0026#34; .WordCount | default (printf \u0026#34;%d words\u0026#34; .WordCount))) }} {{- end -}} {{- /* 自动判断：如果最后修改时间(Lastmod) 不等于 发布时间(Date)，就显示“最后更新于” */ -}} {{- if and (not .Lastmod.IsZero) (not .Date.IsZero) -}} {{- if ne (.Lastmod.Format \u0026#34;2006-01-02\u0026#34;) (.Date.Format \u0026#34;2006-01-02\u0026#34;) -}} {{- $scratch.Add \u0026#34;meta\u0026#34; (slice (printf \u0026#34;更新于\u0026amp;nbsp;%s\u0026#34; (.Lastmod.Format (default \u0026#34;2006年01月02日\u0026#34; .Site.Params.DateFormat)))) }} {{- end -}} {{- end -}} {{- with ($scratch.Get \u0026#34;meta\u0026#34;) -}} {{- delimit . \u0026#34;\u0026amp;nbsp;·\u0026amp;nbsp;\u0026#34; | safeHTML -}} {{- end -}} html MarginNote旁注 参考： Hugo PaperMod 主题精装修 | Tai\u0026rsquo;s Blog 演示：\nlayouts/shortcodes/marginnote.html\n1 \u0026lt;span class=\u0026#34;sidenote-number\u0026#34;\u0026gt;\u0026lt;small class=\u0026#34;sidenote\u0026#34;\u0026gt;{{ .Inner | replaceRE \u0026#34;(?m)^\\\\s*\u0026gt;\\\\s?\u0026#34; \u0026#34;\u0026#34; | markdownify | replaceRE \u0026#34;(?s)\u0026lt;p\u0026gt;(.*?)\u0026lt;/p\u0026gt;\u0026#34; \u0026#34;\u0026lt;span class=\\\u0026#34;sidenote-block\\\u0026#34;\u0026gt;$1\u0026lt;/span\u0026gt;\u0026#34; | safeHTML }}\u0026lt;/small\u0026gt;\u0026lt;/span\u0026gt; html assets/css/extended/marginnote.css\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 /* ========================================== Sidenote / Marginnote 边注样式 ========================================== */ :root { --sidenote-bg: rgba(64, 157, 255, 0.08); --sidenote-color: var(--secondary); --sidenote-accent: #409dff; --sidenote-prefix: #e06c75; } .dark { --sidenote-bg: rgba(64, 157, 255, 0.15); --sidenote-color: #abb2bf; --sidenote-accent: #61afef; --sidenote-prefix: #e06c75; } /* 计数器初始化：在文章主体或 body 重置计数器 */ body, .post-single { counter-reset: sidenote-counter; } /* 正文中的上标编号 */ .sidenote-number { counter-increment: sidenote-counter; position: relative; cursor: pointer; user-select: none; } .sidenote-number::after { content: \u0026#34;#\u0026#34; counter(sidenote-counter); vertical-align: super; font-size: 0.8em; font-weight: 700; color: var(--sidenote-accent); padding: 0 2px; transition: all 0.2s ease; } .sidenote-number:hover::after { color: var(--sidenote-prefix); text-decoration: underline; } /* 侧边注本体（在大屏幕上浮动在右侧留白区域） */ .sidenote { float: right; clear: right; position: relative; margin-right: -18vw; width: 16vw; max-width: 220px; min-width: 140px; padding: 6px 10px; margin-top: 0.2em; margin-bottom: 0.8em; font-size: 0.82rem; line-height: 1.5; color: var(--sidenote-color); background-color: transparent; border-left: 2px solid rgba(64, 157, 255, 0.3); border-radius: 4px; transition: background-color 0.25s ease, border-color 0.25s ease, transform 0.2s ease; text-align: left; box-sizing: border-box; } .sidenote code { font-size: 0.85em !important; } .sidenote-block { display: block; margin-bottom: 0.5em; } .sidenote-block:last-child { margin-bottom: 0; } /* 侧边注前缀标记（自动带上序号） */ .sidenote::before { content: \u0026#34;#\u0026#34; counter(sidenote-counter) \u0026#34; \u0026#34;; position: relative; font-size: 0.9em; font-weight: 700; color: var(--sidenote-prefix); margin-right: 4px; } /* 鼠标悬停正文编号或悬停边注时高亮 */ .sidenote-number:hover .sidenote, .sidenote:hover { background-color: var(--sidenote-bg); border-left-color: var(--sidenote-accent); } /* ========================================== 移动端与窄屏自适应响应式处理 当屏幕宽度不足以在右侧展示边注时优雅内嵌 ========================================== */ @media (max-width: 1280px) { .sidenote { float: none; display: block; margin-right: 0; width: 100%; max-width: 100%; margin: 8px 0; padding: 8px 12px; background-color: var(--sidenote-bg); border-left: 3px solid var(--sidenote-accent); } } css 使用说明\n1 这里是正文内容{{\u0026lt; marginnote \u0026gt;}}这里是侧边边注说明，支持 **加粗** 等 Markdown 语法。{{\u0026lt; /marginnote \u0026gt;}}，接下来继续正常书写。 markdown 代码块折叠：底部渐变遮罩 + 一键展开/收起代码块 这个博客 展开按钮和限制代码块大小 比较符合我的偏好，但还是不够好，这个博客 用短代码 导致代码全部隐藏，体验不好\n所以我编写了底部渐变遮罩 + 一键展开/收起代码块\n演示：\nextend_footer.html\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 \u0026lt;!-- 超长代码块渐变遮罩与一键展开/收起 --\u0026gt; \u0026lt;script\u0026gt; document.addEventListener(\u0026#39;DOMContentLoaded\u0026#39;, () =\u0026gt; { const CODE_MAX_HEIGHT = 320; // 超过 320px 视为超长代码块 document.querySelectorAll(\u0026#39;.post-content .highlight\u0026#39;).forEach((container) =\u0026gt; { if (container.querySelector(\u0026#39;.code-mask-layer\u0026#39;)) return; // 此时尚未添加 code-collapsible 限高类，scrollHeight 即为真实内容高度 if (container.scrollHeight \u0026gt; CODE_MAX_HEIGHT + 20) { container.classList.add(\u0026#39;code-collapsible\u0026#39;); const maskLayer = document.createElement(\u0026#39;div\u0026#39;); maskLayer.className = \u0026#39;code-mask-layer\u0026#39;; const expandBtn = document.createElement(\u0026#39;button\u0026#39;); expandBtn.type = \u0026#39;button\u0026#39;; expandBtn.className = \u0026#39;code-expand-btn\u0026#39;; expandBtn.setAttribute(\u0026#39;aria-label\u0026#39;, \u0026#39;展开全部代码\u0026#39;); expandBtn.innerHTML = ` \u0026lt;span class=\u0026#34;code-btn-text\u0026#34;\u0026gt;展开全部代码\u0026lt;/span\u0026gt; \u0026lt;svg class=\u0026#34;code-btn-icon\u0026#34; width=\u0026#34;14\u0026#34; height=\u0026#34;14\u0026#34; viewBox=\u0026#34;0 0 24 24\u0026#34; fill=\u0026#34;none\u0026#34; stroke=\u0026#34;currentColor\u0026#34; stroke-width=\u0026#34;2\u0026#34; stroke-linecap=\u0026#34;round\u0026#34; stroke-linejoin=\u0026#34;round\u0026#34;\u0026gt; \u0026lt;polyline points=\u0026#34;6 9 12 15 18 9\u0026#34;\u0026gt;\u0026lt;/polyline\u0026gt; \u0026lt;/svg\u0026gt; `; const btnText = expandBtn.querySelector(\u0026#39;.code-btn-text\u0026#39;); const btnIcon = expandBtn.querySelector(\u0026#39;.code-btn-icon\u0026#39;); expandBtn.addEventListener(\u0026#39;click\u0026#39;, (e) =\u0026gt; { e.preventDefault(); const willCollapse = container.classList.contains(\u0026#39;is-expanded\u0026#39;); if (willCollapse) { container.classList.remove(\u0026#39;is-expanded\u0026#39;); btnText.textContent = \u0026#39;展开全部代码\u0026#39;; btnIcon.innerHTML = \u0026#39;\u0026lt;polyline points=\u0026#34;6 9 12 15 18 9\u0026#34;\u0026gt;\u0026lt;/polyline\u0026gt;\u0026#39;; expandBtn.setAttribute(\u0026#39;aria-label\u0026#39;, \u0026#39;展开全部代码\u0026#39;); // 收起后：如果代码块顶部已滚出视口上方，瞬间回到代码块位置 const rect = container.getBoundingClientRect(); if (rect.top \u0026lt; 0) { window.scrollTo({ top: window.scrollY + rect.top - 16, behavior: \u0026#39;instant\u0026#39; }); } } else { container.classList.add(\u0026#39;is-expanded\u0026#39;); btnText.textContent = \u0026#39;收起代码\u0026#39;; btnIcon.innerHTML = \u0026#39;\u0026lt;polyline points=\u0026#34;18 15 12 9 6 15\u0026#34;\u0026gt;\u0026lt;/polyline\u0026gt;\u0026#39;; expandBtn.setAttribute(\u0026#39;aria-label\u0026#39;, \u0026#39;收起代码\u0026#39;); } }); maskLayer.appendChild(expandBtn); container.appendChild(maskLayer); } }); }); \u0026lt;/script\u0026gt; html assets/css/extended/blank.css\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 /* ========================================== 长代码块限高 + 底部渐变遮罩 + 展开/收起按钮 ========================================== */ /* 处于可折叠状态的代码块容器（限高在容器本身，兼容 table 行号布局） */ .post-content .highlight.code-collapsible { position: relative; max-height: 320px; overflow: hidden; padding-bottom: 0; transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1); } /* 展开状态：移除高度限制 */ .post-content .highlight.code-collapsible.is-expanded { max-height: none; overflow: visible; } /* 底部渐变遮罩层 (未展开状态) */ .post-content .highlight.code-collapsible .code-mask-layer { position: absolute; bottom: 0; left: 0; right: 0; height: 90px; background: linear-gradient(to bottom, transparent 0%, var(--code-bg, #2e2e33) 85%); display: flex; align-items: flex-end; justify-content: center; padding-bottom: 12px; z-index: 10; pointer-events: none; border-bottom-left-radius: var(--radius); border-bottom-right-radius: var(--radius); } /* 展开状态下的遮罩层 (变为底部操作栏) */ .post-content .highlight.code-collapsible.is-expanded .code-mask-layer { position: relative; height: auto; background: transparent; padding: 8px 0 12px 0; } /* 展开/收起胶囊按钮样式 */ .code-expand-btn { pointer-events: auto; display: inline-flex; align-items: center; gap: 6px; padding: 4px 16px; font-size: 13px; font-weight: 500; color: var(--primary); background: var(--tertiary); border: 1px solid var(--border); border-radius: 20px; cursor: pointer; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); user-select: none; transition: all 0.2s ease; } .code-expand-btn:hover { background: var(--primary); color: var(--theme); border-color: var(--primary); transform: translateY(-1px); box-shadow: 0 6px 16px rgba(0, 0, 0, 0.25); } .code-expand-btn .code-btn-icon { transition: transform 0.2s ease; } .code-expand-btn:hover .code-btn-icon { transform: translateY(1px); } .is-expanded .code-expand-btn:hover .code-btn-icon { transform: translateY(-1px); } css Giscus 评论系统 参考： Hugo + PaperMod + Github Pages 搭建一个完善的个人博客(以 Windows11 为例) | SonnyCalcr\u0026rsquo;s Blog Hugo 博客引入 Giscus 评论系统 - 探索云原生 Giscus是由 GitHub Discussions 驱动的评论系统，因为它完全免费，而且部署方便，所以用这个\n仓库开启Discussions\n安装gitcus\nGitHub Apps - giscus 从官网获取配置信息\ngiscus 选好后往下滑会有配置文件\nrepoId 和 categoryId 本质是 GitHub 仓库和 Discussions 分类的公开标识符，通过 GitHub API 任何人都能查到公开仓库的这些 ID\ngiscus 配置本来就是写在前端 HTML 里的，网站访客右键查看源码就能看到，本来就是公开的\n虽然说暴露了你的仓库地址 + 讨论分类，别人知道了可以往你的 Discussions 里发评论，但这些本来就是公开的，我就不隐藏了\n配置到hugo.toml\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [params] # 全局开启文章评论功能 comments = true # ========================================== # Giscus 评论系统配置 # ========================================== [params.giscus] repo = \u0026#34;zhiwu215/zhiwu215.github.io\u0026#34; # GitHub 存储 Discussion 的仓库名 repoId = \u0026#34;\u0026lt;你的仓库id\u0026gt;\u0026#34; # 在 giscus.app 自动生成的仓库 ID category = \u0026#34;Announcements\u0026#34; # Discussion 的分类名称 categoryId = \u0026#34;\u0026lt;你的分类id\u0026gt;\u0026#34; # 在 giscus.app 自动生成的分类 ID mapping = \u0026#34;pathname\u0026#34; # 文章与 Discussion 的映射规则（推荐 pathname） strict = \u0026#34;0\u0026#34; reactionsEnabled = \u0026#34;1\u0026#34; # 是否开启文章/评论的 Emoji 表情回应 emitMetadata = \u0026#34;0\u0026#34; inputPosition = \u0026#34;top\u0026#34; lightTheme = \u0026#34;light\u0026#34; # 浅色模式对应的 Giscus 主题 darkTheme = \u0026#34;dark\u0026#34;\t# 深色模式对应的 Giscus 主题 lang = \u0026#34;zh-CN\u0026#34; # 评论组件界面语言 loading = \u0026#34;lazy\u0026#34; # 懒加载策略 toml layouts/partials/comments.html\n让评论能和主题一样明暗切换\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 \u0026lt;div id=\u0026#34;tw-comment\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;script\u0026gt; // 默认是暗色，根目录下的配置中的主题默认也是暗色 const getStoredTheme = () =\u0026gt; localStorage.getItem(\u0026#34;pref-theme\u0026#34;) === \u0026#34;light\u0026#34; ? \u0026#34;{{ .Site.Params.giscus.lightTheme }}\u0026#34; : \u0026#34;{{ .Site.Params.giscus.darkTheme }}\u0026#34;; const setGiscusTheme = () =\u0026gt; { const sendMessage = (message) =\u0026gt; { const iframe = document.querySelector(\u0026#39;iframe.giscus-frame\u0026#39;); if (iframe) { iframe.contentWindow.postMessage({giscus: message}, \u0026#39;https://giscus.app\u0026#39;); } } sendMessage({setConfig: {theme: getStoredTheme()}}) } document.addEventListener(\u0026#34;DOMContentLoaded\u0026#34;, () =\u0026gt; { const giscusAttributes = { \u0026#34;src\u0026#34;: \u0026#34;https://giscus.app/client.js\u0026#34;, \u0026#34;data-repo\u0026#34;: \u0026#34;{{ .Site.Params.giscus.repo }}\u0026#34;, \u0026#34;data-repo-id\u0026#34;: \u0026#34;{{ .Site.Params.giscus.repoId }}\u0026#34;, \u0026#34;data-category\u0026#34;: \u0026#34;{{ .Site.Params.giscus.category }}\u0026#34;, \u0026#34;data-category-id\u0026#34;: \u0026#34;{{ .Site.Params.giscus.categoryId }}\u0026#34;, \u0026#34;data-mapping\u0026#34;: \u0026#34;{{ .Site.Params.giscus.mapping }}\u0026#34;, \u0026#34;data-strict\u0026#34;: \u0026#34;{{ .Site.Params.giscus.strict }}\u0026#34;, \u0026#34;data-reactions-enabled\u0026#34;: \u0026#34;{{ .Site.Params.giscus.reactionsEnabled }}\u0026#34;, \u0026#34;data-emit-metadata\u0026#34;: \u0026#34;{{ .Site.Params.giscus.emitMetadata }}\u0026#34;, \u0026#34;data-input-position\u0026#34;: \u0026#34;{{ .Site.Params.giscus.inputPosition }}\u0026#34;, \u0026#34;data-theme\u0026#34;: getStoredTheme(), \u0026#34;data-lang\u0026#34;: \u0026#34;{{ .Site.Params.giscus.lang }}\u0026#34;, \u0026#34;data-loading\u0026#34;: \u0026#34;lazy\u0026#34;, \u0026#34;crossorigin\u0026#34;: \u0026#34;anonymous\u0026#34;, }; // 动态创建 giscus script const giscusScript = document.createElement(\u0026#34;script\u0026#34;); Object.entries(giscusAttributes).forEach( ([key, value]) =\u0026gt; giscusScript.setAttribute(key, value)); document.querySelector(\u0026#34;#tw-comment\u0026#34;).appendChild(giscusScript); // 页面主题变更后，变更 giscus 主题 const themeSwitcher = document.querySelector(\u0026#34;#theme-toggle\u0026#34;); if (themeSwitcher) { themeSwitcher.addEventListener(\u0026#34;click\u0026#34;, setGiscusTheme); } const themeFloatSwitcher = document.querySelector(\u0026#34;#theme-toggle-float\u0026#34;); if (themeFloatSwitcher) { themeFloatSwitcher.addEventListener(\u0026#34;click\u0026#34;, setGiscusTheme); } }); \u0026lt;/script\u0026gt; html 访问量统计 演示：\n看了很多别人的博客，很多人都用 不蒜子 我是从 魔改PaperMod主题和博客改动 | 梓言堂 - Yuk\u0026rsquo;s Blog 了解到的Umami，但这是一个网站分析工具，它可以分析出一个网站的详细访问数据，包括请求PV、UV、国家来源、来源于哪个网站、用户的操作系统、浏览器等等，不过对我没什么用\n然后我看到 Vercount: 一个比不蒜子更好的网站计数器 | EvanNotFound\u0026rsquo;s Blog ，Vercount比不蒜子更好，比如更稳定什么的\nlayouts/partials/extend_head.html\n1 2 \u0026lt;!-- Vercount 访问量统计 --\u0026gt; \u0026lt;script defer src=\u0026#34;https://vercount.one/js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; html layouts/partials/extend_footer.html\n1 2 3 4 5 6 \u0026lt;!-- Vercount 站点底部总访问量与访客数统计 --\u0026gt; \u0026lt;div class=\u0026#34;site-footer-stats\u0026#34; style=\u0026#34;text-align: center; padding: 4px 0; color: var(--secondary); font-size: 14px; margin-top: 2px;\u0026#34;\u0026gt; \u0026lt;span\u0026gt;本站总访问量 \u0026lt;span id=\u0026#34;busuanzi_value_site_pv\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; 次\u0026lt;/span\u0026gt; \u0026lt;span style=\u0026#34;margin: 0 4px;\u0026#34;\u0026gt;·\u0026lt;/span\u0026gt; \u0026lt;span\u0026gt;本站总访客数 \u0026lt;span id=\u0026#34;busuanzi_value_site_uv\u0026#34;\u0026gt;\u0026lt;/span\u0026gt; 人\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; html Github自动部署 部署在github page的教程：\nHost on GitHub Pages 我看 【大学生提高课】3 hexo与hugo博客搭建与github自动化推送和服务器推送_哔哩哔哩_bilibili 20:47说，创建privete仓库存放博客源码，创建public存放构建后的public文件\n我觉得博客的源码没有隐藏的必要，所以我就直接创建public仓库了\n完全可以看官方文档完成， Hugo+PaperMod搭建博客_哔哩哔哩_bilibili 这个视频最后的部署阶段也是创建public仓库，然后按照官方文档来，可以参考一下\n创建github仓库\ngithub仓库名必须是\u0026lt;你的用户名\u0026gt;.github.io\n步骤1\n步骤2\n在 .github/workflows 目录下创建一个名为 hugo.yaml 的文件\n从官网复制\n注意这三个对不对\n部署成功后，就可以访问网站：\u0026lt;你的用户名\u0026gt;.github.io\nPicGo+Github图床 PicGo是图片上传工具，Github充当图床\n创建公开图片仓库\n生成 GitHub Personal Access Token（访问密钥）\n配置PicGo\n图床使用jsdelivr作为CDN加速 我配置完之后试了一下，感觉是github更快，配置这个cdn加速反而更慢了，不知道是不是我的错觉\n参考： GitHub + jsDelivr + PicGo + Imagine 打造稳定快速、高效免费图床GitHub+jsDe - 掘金 设定自定义域名\n它的的作用是，在图片上传后，PicGo 会按照自定义域名+上传的图片名的方式生成访问链接，放到粘贴板上，因为我们要使用 jsDelivr 加速访问，所以可以设置为：\nhttps://cdn.jsdelivr.net/gh/用户名/图床仓库名\nTypora配置 手动上传图片，再粘贴链接太麻烦\n所以使用typora在里面配置\n我并没有配直接上传图片，因为一篇博客不是立刻完成的，图片不一定适合，可能会多次修改，如果直接上传，会导致一些图片用不到却依旧被存入github\n先选择保存在本地特定目录，再配置PicGo\n注意：\n编写文章的时候，明明可以在Typora里查看到图片的内容\n但是运行博客后，却发现显示不出来是正常的\nHugo 在执行构建时，会把 static/ 目录下的所有文件和子目录原样复制到 public/ 目录下。图片不在public/ 目录，浏览器在加载页面时找不到图片\n写完博客再一键上传图片\nGitHub Actions 清理孤儿图片 后续修改/删改文章依旧导致的“孤儿图片”，所以可以在GitHub Actions 中设置自动化清理\n在你的博客仓库，添加你的图床仓库的token\n添加脚本\n.github/scripts/clean_images.py\n需要手动填写\nIMAGE_REPO IMAGE_DIR 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 import os import re import requests # ==================== 配置区 ==================== # 1. 你的 GitHub 图床仓库 (格式: 用户名/图床仓库名) IMAGE_REPO = \u0026#34;zhiwu215/blog-img\u0026#34; # 2. 图片在图床仓库里的存储子目录 (例如 \u0026#34;posts\u0026#34; 或 \u0026#34;img\u0026#34;) # 如果在 PicGo 中未设置子目录，留空字符串 \u0026#34;\u0026#34; 即可 IMAGE_DIR = \u0026#34;\u0026#34; # 3. 博客文章所在目录 CONTENT_DIR = \u0026#34;content\u0026#34; # ================================================ GITHUB_TOKEN = os.getenv(\u0026#34;IMAGE_BED_TOKEN\u0026#34;) HEADERS = { \u0026#34;Authorization\u0026#34;: f\u0026#34;token {GITHUB_TOKEN}\u0026#34;, \u0026#34;Accept\u0026#34;: \u0026#34;application/vnd.github.v3+json\u0026#34; } def get_used_images(): \u0026#34;\u0026#34;\u0026#34;遍历 content 目录下所有 .md 文件，提取出文章中引用的所有图片文件名\u0026#34;\u0026#34;\u0026#34; used_images = set() # 正则匹配形如 filename.png / filename.jpg 等图片文件名 pattern = re.compile(r\u0026#39;\\/([^\\/\\s\\)\u0026#34;\\\u0026#39;]+\\.(?:png|jpg|jpeg|gif|webp|svg))\u0026#39;, re.IGNORECASE) for root, _, files in os.walk(CONTENT_DIR): for file in files: if file.endswith(\u0026#34;.md\u0026#34;): file_path = os.path.join(root, file) with open(file_path, \u0026#34;r\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;, errors=\u0026#34;ignore\u0026#34;) as f: content = f.read() matches = pattern.findall(content) for match in matches: used_images.add(match) print(f\u0026#34;✅ 在博客 Markdown 文章中共扫描到 {len(used_images)} 张在用图片。\u0026#34;) return used_images def get_remote_images(): \u0026#34;\u0026#34;\u0026#34;通过 GitHub API 获取图床仓库目录下的所有图片文件\u0026#34;\u0026#34;\u0026#34; path_suffix = f\u0026#34;/{IMAGE_DIR}\u0026#34; if IMAGE_DIR else \u0026#34;\u0026#34; url = f\u0026#34;https://api.github.com/repos/{IMAGE_REPO}/contents{path_suffix}\u0026#34; res = requests.get(url, headers=HEADERS) if res.status_code != 200: print(f\u0026#34;❌ 获取图床文件列表失败，HTTP 状态码: {res.status_code}\u0026#34;) print(res.json()) return [] files = res.json() images = [] for item in files: if item[\u0026#34;type\u0026#34;] == \u0026#34;file\u0026#34;: images.append({ \u0026#34;name\u0026#34;: item[\u0026#34;name\u0026#34;], \u0026#34;path\u0026#34;: item[\u0026#34;path\u0026#34;], \u0026#34;sha\u0026#34;: item[\u0026#34;sha\u0026#34;] }) print(f\u0026#34;📦 从 GitHub 图床仓库拉取到 {len(images)} 个图片文件。\u0026#34;) return images def delete_remote_image(file_info): \u0026#34;\u0026#34;\u0026#34;调用 API 删除图床仓库中的孤儿图片\u0026#34;\u0026#34;\u0026#34; url = f\u0026#34;https://api.github.com/repos/{IMAGE_REPO}/contents/{file_info[\u0026#39;path\u0026#39;]}\u0026#34; data = { \u0026#34;message\u0026#34;: f\u0026#34;chore: auto delete orphan image {file_info[\u0026#39;name\u0026#39;]}\u0026#34;, \u0026#34;sha\u0026#34;: file_info[\u0026#34;sha\u0026#34;] } res = requests.delete(url, headers=HEADERS, json=data) if res.status_code == 200: print(f\u0026#34;🗑️ 成功删除孤儿图片: {file_info[\u0026#39;name\u0026#39;]}\u0026#34;) else: print(f\u0026#34;❌ 删除失败: {file_info[\u0026#39;name\u0026#39;]}, 错误: {res.text}\u0026#34;) def main(): if not GITHUB_TOKEN: print(\u0026#34;❌ 未检测到 IMAGE_BED_TOKEN 环境变量，脚本退出。\u0026#34;) return used_images = get_used_images() remote_images = get_remote_images() orphan_count = 0 for img in remote_images: # 如果图床里的图片文件名没有在任何 Markdown 中引用过，即判定为孤儿图片 if img[\u0026#34;name\u0026#34;] not in used_images: print(f\u0026#34;🔍 发现孤儿图片: {img[\u0026#39;name\u0026#39;]}\u0026#34;) delete_remote_image(img) orphan_count += 1 print(f\u0026#34;🎉 清理完成！共删除 {orphan_count} 张孤儿图片。\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: main() python .github/workflows/clean-images.yaml\n每周一运行\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 name: 清理图床孤儿图片 on: # 定时任务：每周一 UTC 时间 0:00 (北京时间早上 8:00) 自动运行 schedule: - cron: \u0026#39;0 0 * * 1\u0026#39; # 支持在 GitHub 网页端的 Actions 页面手动点击按钮随时触发 workflow_dispatch: jobs: clean-orphan-images: runs-on: ubuntu-latest steps: - name: 检出博客源码 uses: actions/checkout@v4 - name: 配置 Python 环境 uses: actions/setup-python@v5 with: python-version: \u0026#39;3.x\u0026#39; - name: 安装依赖 run: | python -m pip install --upgrade pip pip install requests - name: 执行孤儿图片清理脚本 env: IMAGE_BED_TOKEN: ${{ secrets.IMAGE_BED_TOKEN }} run: | python .github/scripts/clean_images.py yaml 手动测试是否成功\n先往图床的仓库随便上传一张图片，然后运行：\n文章模板 archetypes/default.md\n1 2 3 4 5 6 7 +++ date = \u0026#39;{{ .Date }}\u0026#39; title = \u0026#39;{{ replace .File.ContentBaseName \u0026#34;-\u0026#34; \u0026#34; \u0026#34; | title }}\u0026#39; summary = \u0026#39;\u0026#39; tags = [] draft = true +++ markdown 文章封面图 PaperMod仓库-\u0026gt;Wiki-\u0026gt;Feature\n在文章的 Front Matter（文件头部配置区）中添加 [cover] 表格，即可为文章配置封面图。\nPaperMod仓库-\u0026gt;Wiki-\u0026gt;Variables\nrelative：是否使用相对路径。默认false，通常在采用 Hugo Page Bundles 结构普通文章结构content/posts/文章标题.md Page Bundles（文章包文件结构），就是每篇文章建一个文件夹文章放在：content/posts/文章标题当作文件夹名/index.md 图片放在同一目录下 时设置为 true\nhidden：默认文章封面图即显示在文章列表，也会在点进文章后挂在文章标题下方。设置为false，文章章内部不显示\nhugo.toml\nresponsiveImages设为false关闭响应式图片默认情况：如果你使用的是“文章包（Page Bundle）”的结构，Hugo默认会自动帮你处理图片。它会把你的一张封面图，自动裁剪生成好几种不同分辨率的小图和中图，并使用 HTML5 的 srcset 技术来让浏览器根据设备（如手机、电脑）自动加载最合适尺寸的图片 会增加 Hugo 每次生成博客的等待时间 1 2 3 [params.cover] responsiveImages = false linkFullImages = true toml 文章中\n1 2 3 4 5 [cover] image = \u0026#34;xxx\u0026#34; alt = \u0026#34;xxx\u0026#34; caption = \u0026#34;xxx\u0026#34; hidden = true markdown 我很少使用文章封面，所以没什么配置\n我看 这个博客 还专门优化了布局，因为PaperMod 的文章列表默认是图片在上、文字在下。这个博客选了文字在左，封面在右的左右布局\n文章同步助手 项目仓库：\nwechatsync/Wechatsync: 一键同步文章到多个内容平台，支持今日头条、WordPress、知乎、简书、掘金、CSDN、typecho各大平台，一次发布，多平台同步发布。解放个人生产力 这是浏览器的插件，可以帮你把文章同步到多个平台\n","permalink":"https://zhiwu215.github.io/posts/hugo+papermod%E6%90%AD%E5%BB%BA%E5%8D%9A%E5%AE%A2/","summary":"记录如何从零开始搭建个人博客，包含Hugo安装，PaperMod主题配置，giscus评论系统，图床等","title":"Hugo+PaperMod搭建博客"},{"content":"欢迎你 你好，我是 知兀（zhiwu）。一名热爱编程的开发者\n关于本站 这里是我的个人数字花园与思维自留地，主要记录：\n技术沉淀：开发踩坑、最佳实践与工具使用技巧 折腾记录：开源项目、效率神器与软硬件折腾心得 随笔思考：阅读、学习与阶段性个人感悟 建站初衷很简单：“写给人看的代码，写给自己读的文字”\n","permalink":"https://zhiwu215.github.io/about/","summary":"\u003ch3 id=\"欢迎你\"\u003e欢迎你\u003c/h3\u003e\n\u003cp\u003e你好，我是 \u003cstrong\u003e知兀\u003c/strong\u003e（zhiwu）。一名热爱编程的开发者\u003c/p\u003e\n\u003ch3 id=\"关于本站\"\u003e关于本站\u003c/h3\u003e\n\u003cp\u003e这里是我的个人数字花园与思维自留地，主要记录：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e技术沉淀\u003c/strong\u003e：开发踩坑、最佳实践与工具使用技巧\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e折腾记录\u003c/strong\u003e：开源项目、效率神器与软硬件折腾心得\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e随笔思考\u003c/strong\u003e：阅读、学习与阶段性个人感悟\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e建站初衷很简单：\u003cstrong\u003e“写给人看的代码，写给自己读的文字”\u003c/strong\u003e\u003c/p\u003e","title":"关于"}]