<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>加辣の秘密基地</title>
    <link>https://drda-x.github.io/drda-blog/</link>
    <description>——奶茶少糖，喜欢吃辣</description>
    <language>zh-CN</language>
    <copyright>All rights reserved 2026, Drda</copyright>
    <lastBuildDate>Fri, 26 Jun 2026 09:21:45 GMT</lastBuildDate>
    <generator>Hexo</generator>
    <atom:link href="https://drda-x.github.io/drda-blog/rss2.xml" rel="self" type="application/rss+xml"/>
    <atom:link href="https://pubsubhubbub.appspot.com/" rel="hub"/>
    <item>
      <title>利用GitHub+PicGo搭建个人图床</title>
      <link>https://drda-x.github.io/drda-blog/2026/06/26/%E5%88%A9%E7%94%A8GitHub-picGo%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%9B%BE%E5%BA%8A/</link>
      <description>
        <![CDATA[<h2 id="引言"><a class="markdownIt-Anchor" href="#引言"></a>]]>
      </description>
      <author>Drda</author>
      <category domain="https://drda-x.github.io/drda-blog/categories/%E7%AC%94%E8%AE%B0/">笔记</category>
      <category domain="https://drda-x.github.io/drda-blog/tags/GitHub/">GitHub</category>
      <category domain="https://drda-x.github.io/drda-blog/tags/picgo/">picgo</category>
      <category domain="https://drda-x.github.io/drda-blog/tags/Vue3/">Vue3</category>
      <category domain="https://drda-x.github.io/drda-blog/tags/ts/">ts</category>
      <pubDate>Fri, 26 Jun 2026 07:29:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h2 id="引言"><a class="markdownIt-Anchor" href="#引言"></a> 引言</h2><p>由于写博客的时候发现有时候需要上传一些图片，就想着自己搭建一个图床用来保存和使用自己上传的图片,于是我就搜到PicGo</p><p><strong>PicGo</strong> 正是为此而生的图片上传管理工具。官网中有PicGo.exe安装包，是可视化的上传工具，但是我不想下载软件，于是乎，在看了文档之后，发现它有完整的 <strong>Node.js API</strong> 和 <strong>CLI 接口</strong>，可以将图片上传能力无缝集成到任何 Node.js 工作流中。</p><p>我想了想了，自己写一个web网站作为我的图片上传工具—— <strong>ImgTools</strong></p><p>本文将基于一个完整的实战项目 —— <strong>ImgTools</strong>，深入讲解如何利用 PicGo 的 API 和 CLI 接口，将图片上传功能集成到构建脚本、编辑器以及各种自动化工作流中。</p><hr /><h2 id="一-picgo-简介"><a class="markdownIt-Anchor" href="#一-picgo-简介"></a> 一、PicGo 简介</h2><h3 id="11-什么是-picgo"><a class="markdownIt-Anchor" href="#11-什么是-picgo"></a> 1.1 什么是 PicGo</h3><p>PicGo 是一个用 Node.js 编写的使用简单即可看又可高图片上传管理工具，它支持将图片快速上传到各种图床服务，并返回可访问的图片 URL。</p><p>核心特性包括：</p><ul><li><strong>多平台支持</strong>：支持 SMMS、Imgur、GitHub、阿里云 OSS、腾讯云 COS、七牛云、又拍云等数十种图床</li><li><strong>插件化架构</strong>：通过插件系统可以轻松扩展新的上传目标</li><li><strong>CLI 接口</strong>：支持命令行调用，方便集成到自动化脚本</li><li><strong>API 接口</strong>：提供完整的 Node.js API，可在任何 Node.js 项目中调用</li><li><strong>剪贴板支持</strong>：直接上传系统剪贴板中的图片</li></ul><h3 id="12-picgo-的核心架构"><a class="markdownIt-Anchor" href="#12-picgo-的核心架构"></a> 1.2 PicGo 的核心架构</h3><p>PicGo 的核心架构由以下几个关键部分组成：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">┌─────────────────────────────────────────────┐</span><br><span class="line">│              PicGo 核心                       │</span><br><span class="line">├─────────────────────────────────────────────┤</span><br><span class="line">│  API 层      │  upload() / uploadByBuf()    │</span><br><span class="line">│  事件系统    │  on(&#x27;upload&#x27;) / on(&#x27;failed&#x27;) │</span><br><span class="line">│  插件管理    │  registerPlugin()            │</span><br><span class="line">├─────────────────────────────────────────────┤</span><br><span class="line">│  插件层      │  上传插件 / 转化插件          │</span><br><span class="line">│              │  上传插件 / 转化插件          │</span><br><span class="line">│              │  上传插件 / 转化插件          │</span><br><span class="line">├─────────────────────────────────────────────┤</span><br><span class="line">│  数据层      │  配置管理 / 上下文传递        │</span><br><span class="line">└─────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><p><strong>核心类 <code>PicGo</code></strong> 提供以下关键方法：</p><table><thead><tr><th>方法</th><th>描述</th></tr></thead><tbody><tr><td><code>upload(paths: string[])</code></td><td>上传指定路径的图片文件</td></tr><tr><td><code>uploadByBuf(buffer: Buffer[])</code></td><td>上传 Buffer 数组形式的图片</td></tr><tr><td><code>upload()</code></td><td>上传剪贴板中的第一张图片</td></tr><tr><td><code>on(event, callback)</code></td><td>监听事件（如 <code>upload</code>、<code>failed</code>、<code>error</code>）</td></tr><tr><td><code>publish(ctx)</code></td><td>发布处理后的上下文</td></tr></tbody></table><h3 id="13-为什么选择-picgo-api"><a class="markdownIt-Anchor" href="#13-为什么选择-picgo-api"></a> 1.3 为什么选择 PicGo API</h3><p>选择 PicGo API 而非直接调用图床 API 的原因：</p><ol><li><strong>统一接口</strong>：一个 API 对接多种图床，无需为每个平台编写不同的上传逻辑</li><li><strong>插件化处理</strong>：支持上传前处理（压缩、裁剪、格式转换）和上传后处理（链接替换）</li><li><strong>配置管理</strong>：内置配置文件管理，支持多平台切换</li><li><strong>社区生态</strong>：丰富的插件生态，持续更新维护</li></ol><hr /><h2 id="二-环境搭建与基础配置"><a class="markdownIt-Anchor" href="#二-环境搭建与基础配置"></a> 二、环境搭建与基础配置</h2><h3 id="21-安装-picgo"><a class="markdownIt-Anchor" href="#21-安装-picgo"></a> 2.1 安装 PicGo</h3><p>在你的 Node.js 项目中安装 PicGo：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm install picgo</span><br></pre></td></tr></table></figure><p>或者使用 yarn：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">yarn add picgo</span><br></pre></td></tr></table></figure><p><strong>版本要求</strong>：建议使用 <code>picgo@1.5.0</code> 或更高版本，这些版本提供了更稳定的 API 接口。</p><p>在我们的 ImgTools 项目中，<code>package.json</code> 中的依赖配置如下：</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;devDependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;picgo&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^2.0.3&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="22-初始化-picgo-实例"><a class="markdownIt-Anchor" href="#22-初始化-picgo-实例"></a> 2.2 初始化 PicGo 实例</h3><p>在 TypeScript/JavaScript 项目中，你需要如下初始化 PicGo：</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// @ts-ignore</span></span><br><span class="line"><span class="keyword">const</span> &#123; <span class="title class_">PicGo</span> &#125; = <span class="built_in">require</span>(<span class="string">&#x27;picgo&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> picgo = <span class="keyword">new</span> <span class="title class_">PicGo</span>()</span><br></pre></td></tr></table></figure><p><strong>为什么使用 <code>require</code> 而不是 <code>import</code>？</strong></p><p>因为 PicGo 使用 CommonJS 模块导出，而许多现代项目使用 ES Modules。使用 <code>require</code> 可以避免模块兼容性问题。<code>@ts-ignore</code> 注释用于抑制 TypeScript 的类型检查错误。</p><h3 id="23-配置上传插件"><a class="markdownIt-Anchor" href="#23-配置上传插件"></a> 2.3 配置上传插件</h3><p>在调用上传 API 之前，需要配置 PicGo 使用哪个上传插件。以 GitHub 为例：</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">picgo.<span class="property">gitHub</span> = &#123;</span><br><span class="line">  <span class="attr">token</span>: <span class="string">&#x27;ghp_xxxxxxxxxxxxxxxxxxxx&#x27;</span>,</span><br><span class="line">  <span class="attr">repo</span>: <span class="string">&#x27;username/repo&#x27;</span>,</span><br><span class="line">  <span class="attr">path</span>: <span class="string">&#x27;images&#x27;</span>,</span><br><span class="line">  <span class="attr">branch</span>: <span class="string">&#x27;main&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 或者使用 setConfig</span></span><br><span class="line">picgo.<span class="title function_">setConfig</span>(&#123;</span><br><span class="line">  picGo-<span class="attr">BED</span>: <span class="string">&#x27;github&#x27;</span>,  <span class="comment">// 设置默认上传平台</span></span><br><span class="line">  picGo-<span class="title class_">GitHub</span>-<span class="attr">token</span>: <span class="string">&#x27;ghp_xxxxxxxxxxxxxxxxxxxx&#x27;</span>,</span><br><span class="line">  picGo-<span class="title class_">GitHub</span>-<span class="attr">repo</span>: <span class="string">&#x27;username/repo&#x27;</span>,</span><br><span class="line">  picGo-<span class="title class_">GitHub</span>-<span class="attr">path</span>: <span class="string">&#x27;images&#x27;</span>,</span><br><span class="line">  picGo-<span class="title class_">GitHub</span>-<span class="attr">branch</span>: <span class="string">&#x27;main&#x27;</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><hr /><h2 id="三-通过-api-集成图片上传"><a class="markdownIt-Anchor" href="#三-通过-api-集成图片上传"></a> 三、通过 API 集成图片上传</h2><h3 id="31-基础上传流程"><a class="markdownIt-Anchor" href="#31-基础上传流程"></a> 3.1 基础上传流程</h3><p>PicGo API 的核心上传流程可以用以下流程图表示：</p><pre class="mermaid">sequenceDiagram    participant App as 应用程序    participant PicGo as PicGo 实例    participant Plugin as 上传插件    participant Bed as 图床服务    App->>PicGo: new PicGo()    App->>PicGo: 配置上传插件    App->>PicGo: 注册事件监听    PicGo->>PicGo: on('upload', callback)    PicGo->>PicGo: on('failed', callback)    PicGo->>PicGo: on('error', callback)        App->>PicGo: upload(files)    PicGo->>Plugin: 处理图片    Plugin->>Plugin: 上传前处理（可选）    Plugin->>Bed: 上传图片    Bed-->>Plugin: 返回 URL    Plugin->>Plugin: 上传后处理（可选）    Plugin-->>PicGo: 返回结果    PicGo-->>App: 触发 'upload' 事件</pre><h3 id="32-上传指定文件"><a class="markdownIt-Anchor" href="#32-上传指定文件"></a> 3.2 上传指定文件</h3><p>这是最常用的上传方式。你可以通过传入文件路径数组来指定要上传的图片：</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">PicGo</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;picgo&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> picgo = <span class="keyword">new</span> <span class="title class_">PicGo</span>()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 配置上传插件</span></span><br><span class="line">picgo.<span class="title function_">setConfig</span>(&#123;</span><br><span class="line">  <span class="string">&#x27;picGo-BED&#x27;</span>: <span class="string">&#x27;github&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;picGo-GitHub-token&#x27;</span>: <span class="string">&#x27;your-token&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;picGo-GitHub-repo&#x27;</span>: <span class="string">&#x27;username/repo&#x27;</span></span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 上传指定文件</span></span><br><span class="line"><span class="keyword">const</span> files = [<span class="string">&#x27;/path/to/image1.jpg&#x27;</span>, <span class="string">&#x27;/path/to/image2.png&#x27;</span>]</span><br><span class="line"><span class="keyword">const</span> result = <span class="keyword">await</span> picgo.<span class="title function_">upload</span>(files)</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(result.<span class="property">output</span>) <span class="comment">// 返回的图片 URL 数组</span></span><br></pre></td></tr></table></figure><p>在我们的 ImgTools 项目中，<code>picgo.ts</code> 工具函数的实现如下：</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * PicGo 工具封装（TypeScript）</span></span><br><span class="line"><span class="comment"> * 依赖：picgo (v1.5.0+)</span></span><br><span class="line"><span class="comment"> * 用法示例：</span></span><br><span class="line"><span class="comment"> *   import &#123; upload &#125; from &#x27;@/utils/picgo&#x27;</span></span><br><span class="line"><span class="comment"> *   await upload([&#x27;/path/to/img.jpg&#x27;]) // 上传指定文件</span></span><br><span class="line"><span class="comment"> *   await upload() // 上传剪贴板第一张图片</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用 require 以兼容 picgo 的 CommonJS 导出</span></span><br><span class="line"><span class="comment">// @ts-ignore</span></span><br><span class="line"><span class="keyword">const</span> &#123; <span class="title class_">PicGo</span> &#125; = <span class="built_in">require</span>(<span class="string">&#x27;picgo&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">UploadResult</span> = <span class="built_in">any</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 上传图片（支持传入文件路径数组，或不传则上传剪贴板第一张图片）</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> paths 可选的本地文件路径数组</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">upload</span>(<span class="params"><span class="attr">paths</span>?: <span class="built_in">string</span>[]</span>): <span class="title class_">Promise</span>&lt;<span class="title class_">UploadResult</span>&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> picgo = <span class="keyword">new</span> <span class="title class_">PicGo</span>()</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">onUpload</span> = (<span class="params"><span class="attr">ctx</span>: <span class="built_in">any</span></span>) =&gt; &#123;</span><br><span class="line">      <span class="comment">// PicGo 在上传完成会触发 &#x27;upload&#x27; 事件，回调中包含 ctx</span></span><br><span class="line">      <span class="title function_">cleanup</span>()</span><br><span class="line">      <span class="title function_">resolve</span>(ctx)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">onFailed</span> = (<span class="params"><span class="attr">ctx</span>: <span class="built_in">any</span></span>) =&gt; &#123;</span><br><span class="line">      <span class="title function_">cleanup</span>()</span><br><span class="line">      <span class="title function_">reject</span>(ctx)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">onError</span> = (<span class="params"><span class="attr">err</span>: <span class="built_in">any</span></span>) =&gt; &#123;</span><br><span class="line">      <span class="title function_">cleanup</span>()</span><br><span class="line">      <span class="title function_">reject</span>(err)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">cleanup</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">try</span> &#123;</span><br><span class="line">        picgo.<span class="property">removeListener</span> &amp;&amp; picgo.<span class="title function_">removeListener</span>(<span class="string">&#x27;upload&#x27;</span>, onUpload)</span><br><span class="line">        picgo.<span class="property">removeListener</span> &amp;&amp; picgo.<span class="title function_">removeListener</span>(<span class="string">&#x27;failed&#x27;</span>, onFailed)</span><br><span class="line">        picgo.<span class="property">removeListener</span> &amp;&amp; picgo.<span class="title function_">removeListener</span>(<span class="string">&#x27;error&#x27;</span>, onError)</span><br><span class="line">      &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">        <span class="comment">// ignore</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 绑定事件</span></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      picgo.<span class="property">on</span> &amp;&amp; picgo.<span class="title function_">on</span>(<span class="string">&#x27;upload&#x27;</span>, onUpload)</span><br><span class="line">      picgo.<span class="property">on</span> &amp;&amp; picgo.<span class="title function_">on</span>(<span class="string">&#x27;failed&#x27;</span>, onFailed)</span><br><span class="line">      picgo.<span class="property">on</span> &amp;&amp; picgo.<span class="title function_">on</span>(<span class="string">&#x27;error&#x27;</span>, onError)</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 触发上传：传入数组则按路径上传；不传则尝试上传剪贴板里的第一张图片</span></span><br><span class="line">      <span class="keyword">if</span> (<span class="title class_">Array</span>.<span class="title function_">isArray</span>(paths) &amp;&amp; paths.<span class="property">length</span> &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        picgo.<span class="title function_">upload</span>(paths)</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        picgo.<span class="title function_">upload</span>()</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">      <span class="title function_">cleanup</span>()</span><br><span class="line">      <span class="title function_">reject</span>(err)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123; upload &#125;</span><br></pre></td></tr></table></figure><p><strong>关键要点：</strong></p><ol><li><strong>Promise 封装</strong>：PicGo 的 API 基于事件系统，我们通过 Promise 将其封装为异步函数，方便与现代异步代码风格结合</li><li><strong>事件清理</strong>：在上传完成或失败后，及时移除事件监听器，避免内存泄漏</li><li><strong>灵活参数</strong>：支持传入文件路径数组，或不传参数（上传剪贴板图片）</li></ol><h3 id="33-上传剪贴板图片"><a class="markdownIt-Anchor" href="#33-上传剪贴板图片"></a> 3.3 上传剪贴板图片</h3><p>PicGo 的另一个强大功能是直接上传系统剪贴板中的图片。这在编辑器集成中特别有用：</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 不传参数，自动上传剪贴板中的第一张图片</span></span><br><span class="line"><span class="keyword">const</span> result = <span class="keyword">await</span> picgo.<span class="title function_">upload</span>()</span><br></pre></td></tr></table></figure><p>当不调用 <code>upload()</code> 方法传入文件路径时，PicGo 会自动检测系统剪贴板中的图片并进行上传。这在以下场景中非常有用：</p><ul><li><strong>截图后直接粘贴</strong>：用户截图后按 Ctrl+V，图片自动上传</li><li><strong>网页图片右键复制</strong>：复制网页图片后，自动上传并获取链接</li><li><strong>编辑器集成</strong>：在 Markdown 编辑器中粘贴图片，自动上传并插入 Markdown 链接</li></ul><h3 id="34-批量上传"><a class="markdownIt-Anchor" href="#34-批量上传"></a> 3.4 批量上传</h3><p>PicGo 支持一次上传多张图片：</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> files = [</span><br><span class="line">  <span class="string">&#x27;/path/to/image1.jpg&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;/path/to/image2.png&#x27;</span>,</span><br><span class="line">  <span class="string">&#x27;/path/to/image3.webp&#x27;</span></span><br><span class="line">]</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> result = <span class="keyword">await</span> picgo.<span class="title function_">upload</span>(files)</span><br><span class="line"><span class="comment">// result.output 包含所有上传成功的图片 URL</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(result.<span class="property">output</span>)</span><br><span class="line"><span class="comment">// [</span></span><br><span class="line"><span class="comment">//   &#x27;https://example.com/image1.jpg&#x27;,</span></span><br><span class="line"><span class="comment">//   &#x27;https://example.com/image2.png&#x27;,</span></span><br><span class="line"><span class="comment">//   &#x27;https://example.com/image3.webp&#x27;</span></span><br><span class="line"><span class="comment">// ]</span></span><br></pre></td></tr></table></figure><h3 id="35-错误处理与事件监听"><a class="markdownIt-Anchor" href="#35-错误处理与事件监听"></a> 3.5 错误处理与事件监听</h3><p>PicGo 提供了完善的事件系统来处理上传过程中的各种情况：</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line">picgo.<span class="title function_">on</span>(<span class="string">&#x27;upload&#x27;</span>, <span class="function">(<span class="params"><span class="attr">ctx</span>: <span class="built_in">any</span></span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;上传成功！&#x27;</span>, ctx.<span class="property">output</span>)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">picgo.<span class="title function_">on</span>(<span class="string">&#x27;failed&#x27;</span>, <span class="function">(<span class="params"><span class="attr">ctx</span>: <span class="built_in">any</span></span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;上传失败！&#x27;</span>, ctx.<span class="property">output</span>)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">picgo.<span class="title function_">on</span>(<span class="string">&#x27;error&#x27;</span>, <span class="function">(<span class="params"><span class="attr">err</span>: <span class="built_in">any</span></span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;发生错误！&#x27;</span>, err)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p><strong>常用事件：</strong></p><table><thead><tr><th>事件</th><th>触发时机</th><th>回调参数</th></tr></thead><tbody><tr><td><code>upload</code></td><td>上传成功完成</td><td><code>ctx</code>（上下文对象，包含 output）</td></tr><tr><td><code>failed</code></td><td>上传失败</td><td><code>ctx</code>（上下文对象，包含错误信息）</td></tr><tr><td><code>error</code></td><td>发生异常错误</td><td><code>err</code>（错误对象）</td></tr><tr><td><code>beforePlugin</code></td><td>插件处理前</td><td><code>(pluginName, ctx)</code></td></tr><tr><td><code>afterPlugin</code></td><td>插件处理后</td><td><code>(pluginName, ctx)</code></td></tr></tbody></table><hr /><h2 id="四-cli-命令行集成"><a class="markdownIt-Anchor" href="#四-cli-命令行集成"></a> 四、CLI 命令行集成</h2><h3 id="41-基本-cli-用法"><a class="markdownIt-Anchor" href="#41-基本-cli-用法"></a> 4.1 基本 CLI 用法</h3><p>PicGo 提供了完整的命令行接口，可以在终端中直接使用：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 上传指定文件</span></span><br><span class="line">picgo upload /path/to/image.jpg</span><br><span class="line"></span><br><span class="line"><span class="comment"># 批量上传</span></span><br><span class="line">picgo upload /path/to/image1.jpg /path/to/image2.png</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看当前配置</span></span><br><span class="line">picgo config</span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置上传平台</span></span><br><span class="line">picgo <span class="built_in">set</span> upload-bed bed</span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装插件</span></span><br><span class="line">picgo install plugin-name</span><br><span class="line"></span><br><span class="line"><span class="comment"># 更新插件</span></span><br><span class="line">picgo update plugin-name</span><br></pre></td></tr></table></figure><h3 id="42-在构建脚本中使用-cli"><a class="markdownIt-Anchor" href="#42-在构建脚本中使用-cli"></a> 4.2 在构建脚本中使用 CLI</h3><p>你可以在 npm scripts 或自定义构建脚本中调用 PicGo CLI：</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;upload:images&quot;</span><span class="punctuation">:</span> <span class="string">&quot;picgo upload ./assets/images/*.jpg&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;build:docs&quot;</span><span class="punctuation">:</span> <span class="string">&quot;vite build &amp;&amp; picgo upload docs/images/*&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>或者在 Node.js 脚本中使用 <code>child_process</code>：</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; execSync &#125; = <span class="built_in">require</span>(<span class="string">&#x27;child_process&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> result = <span class="title function_">execSync</span>(<span class="string">&#x27;picgo upload ./images/*.jpg&#x27;</span>, &#123;</span><br><span class="line">    <span class="attr">encoding</span>: <span class="string">&#x27;utf-8&#x27;</span></span><br><span class="line">  &#125;)</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;上传结果：&#x27;</span>, result)</span><br><span class="line">&#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;上传失败：&#x27;</span>, error.<span class="property">message</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="43-在-cicd-流水线中集成"><a class="markdownIt-Anchor" href="#43-在-cicd-流水线中集成"></a> 4.3 在 CI/CD 流水线中集成</h3><p>在 GitHub Actions 中集成 PicGo 自动上传图片：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">name:</span> <span class="string">Upload</span> <span class="string">Images</span></span><br><span class="line"></span><br><span class="line"><span class="attr">on:</span></span><br><span class="line">  <span class="attr">push:</span></span><br><span class="line">    <span class="attr">paths:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="string">&#x27;images/**&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">jobs:</span></span><br><span class="line">  <span class="attr">upload:</span></span><br><span class="line">    <span class="attr">runs-on:</span> <span class="string">ubuntu-latest</span></span><br><span class="line">    <span class="attr">steps:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">uses:</span> <span class="string">actions/checkout@v3</span></span><br><span class="line">      </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Setup</span> <span class="string">Node.js</span></span><br><span class="line">        <span class="attr">uses:</span> <span class="string">actions/setup-node@v3</span></span><br><span class="line">        <span class="attr">with:</span></span><br><span class="line">          <span class="attr">node-version:</span> <span class="string">&#x27;18&#x27;</span></span><br><span class="line">      </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Install</span> <span class="string">PicGo</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">npm</span> <span class="string">install</span> <span class="string">-g</span> <span class="string">picgo</span></span><br><span class="line">      </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Configure</span> <span class="string">PicGo</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">|</span></span><br><span class="line"><span class="string">          picgo set upload-bed github</span></span><br><span class="line"><span class="string">          picgo set picGo-GitHub-token $&#123;&#123; secrets.GITHUB_TOKEN &#125;&#125;</span></span><br><span class="line"><span class="string">          picgo set picGo-GitHub-repo username/repo</span></span><br><span class="line"><span class="string">          picgo set picGo-GitHub-path images</span></span><br><span class="line"><span class="string">          picgo set picGo-GitHub-branch main</span></span><br><span class="line"><span class="string"></span>      </span><br><span class="line">      <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Upload</span> <span class="string">Images</span></span><br><span class="line">        <span class="attr">run:</span> <span class="string">picgo</span> <span class="string">upload</span> <span class="string">images/**/*</span></span><br></pre></td></tr></table></figure><hr /><h2 id="五-编辑器与工作流集成"><a class="markdownIt-Anchor" href="#五-编辑器与工作流集成"></a> 五、编辑器与工作流集成</h2><h3 id="51-在-vs-code-扩展中使用-picgo"><a class="markdownIt-Anchor" href="#51-在-vs-code-扩展中使用-picgo"></a> 5.1 在 VS Code 扩展中使用 PicGo</h3><p>PicGo 的 API 设计使其非常适合集成到 VS Code 扩展中：</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> vscode <span class="keyword">from</span> <span class="string">&#x27;vscode&#x27;</span></span><br><span class="line"><span class="comment">// @ts-ignore</span></span><br><span class="line"><span class="keyword">const</span> &#123; <span class="title class_">PicGo</span> &#125; = <span class="built_in">require</span>(<span class="string">&#x27;picgo&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">activate</span>(<span class="params"><span class="attr">context</span>: vscode.<span class="title class_">ExtensionContext</span></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> picgo = <span class="keyword">new</span> <span class="title class_">PicGo</span>()</span><br><span class="line">  </span><br><span class="line">  context.<span class="property">subscriptions</span>.<span class="title function_">push</span>(vscode.<span class="property">commands</span>.<span class="title function_">registerCommand</span>(</span><br><span class="line">    <span class="string">&#x27;my-extension.uploadImage&#x27;</span>,</span><br><span class="line">    <span class="title function_">async</span> () =&gt; &#123;</span><br><span class="line">      <span class="keyword">const</span> editor = vscode.<span class="property">window</span>.<span class="property">activeTextEditor</span></span><br><span class="line">      <span class="keyword">if</span> (!editor) <span class="keyword">return</span></span><br><span class="line">      </span><br><span class="line">      <span class="keyword">const</span> selection = editor.<span class="property">selection</span></span><br><span class="line">      <span class="keyword">const</span> text = editor.<span class="property">document</span>.<span class="title function_">getText</span>(selection)</span><br><span class="line">      </span><br><span class="line">      <span class="comment">// 处理粘贴的图片</span></span><br><span class="line">      <span class="keyword">const</span> result = <span class="keyword">await</span> picgo.<span class="title function_">upload</span>()</span><br><span class="line">      <span class="keyword">const</span> urls = result.<span class="property">output</span> <span class="keyword">as</span> <span class="built_in">string</span>[]</span><br><span class="line">      </span><br><span class="line">      <span class="comment">// 插入 Markdown 链接</span></span><br><span class="line">      urls.<span class="title function_">forEach</span>(<span class="function"><span class="params">url</span> =&gt;</span> &#123;</span><br><span class="line">        editor.<span class="title function_">edit</span>(<span class="function"><span class="params">editBuilder</span> =&gt;</span> &#123;</span><br><span class="line">          editBuilder.<span class="title function_">insert</span>(selection.<span class="property">start</span>, <span class="string">`![image](<span class="subst">$&#123;url&#125;</span>)`</span>)</span><br><span class="line">        &#125;)</span><br><span class="line">      &#125;)</span><br><span class="line">    &#125;</span><br><span class="line">  ))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="52-在-markdown-编辑器中实时上传"><a class="markdownIt-Anchor" href="#52-在-markdown-编辑器中实时上传"></a> 5.2 在 Markdown 编辑器中实时上传</h3><p>结合剪贴板监听和 PicGo API，可以实现实时粘贴上传图片：</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">PasteUploader</span> &#123;</span><br><span class="line">  <span class="keyword">private</span> <span class="attr">picgo</span>: <span class="title class_">PicGo</span></span><br><span class="line">  </span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">picgo</span> = <span class="keyword">new</span> <span class="title class_">PicGo</span>()</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">configure</span>()</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">bindEvents</span>()</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">private</span> <span class="title function_">configure</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">picgo</span>.<span class="title function_">setConfig</span>(&#123;</span><br><span class="line">      <span class="string">&#x27;picGo-BED&#x27;</span>: <span class="string">&#x27;github&#x27;</span>,</span><br><span class="line">      <span class="string">&#x27;picGo-GitHub-token&#x27;</span>: process.<span class="property">env</span>.<span class="property">GITHUB_TOKEN</span>!,</span><br><span class="line">      <span class="string">&#x27;picGo-GitHub-repo&#x27;</span>: <span class="string">&#x27;username/repo&#x27;</span></span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">private</span> <span class="title function_">bindEvents</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">// 监听粘贴事件</span></span><br><span class="line">    <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;paste&#x27;</span>, <span class="title function_">async</span> (<span class="attr">event</span>: <span class="title class_">ClipboardEvent</span>) =&gt; &#123;</span><br><span class="line">      <span class="keyword">const</span> items = event.<span class="property">clipboardData</span>?.<span class="property">items</span></span><br><span class="line">      <span class="keyword">if</span> (!items) <span class="keyword">return</span></span><br><span class="line">      </span><br><span class="line">      <span class="keyword">for</span> (<span class="keyword">const</span> item <span class="keyword">of</span> items) &#123;</span><br><span class="line">        <span class="keyword">if</span> (item.<span class="property">type</span>.<span class="title function_">startsWith</span>(<span class="string">&#x27;image/&#x27;</span>)) &#123;</span><br><span class="line">          <span class="keyword">const</span> blob = item.<span class="title function_">getAsFile</span>()</span><br><span class="line">          <span class="keyword">if</span> (blob) &#123;</span><br><span class="line">            <span class="keyword">const</span> url = <span class="keyword">await</span> <span class="variable language_">this</span>.<span class="title function_">uploadBlob</span>(blob)</span><br><span class="line">            <span class="variable language_">this</span>.<span class="title function_">insertAtCursor</span>(<span class="string">`![image](<span class="subst">$&#123;url&#125;</span>)`</span>)</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">async</span> <span class="title function_">uploadBlob</span>(<span class="attr">blob</span>: <span class="title class_">Blob</span>): <span class="title class_">Promise</span>&lt;<span class="built_in">string</span>&gt; &#123;</span><br><span class="line">    <span class="comment">// 将 Blob 转为临时文件</span></span><br><span class="line">    <span class="keyword">const</span> tempPath = <span class="keyword">await</span> <span class="variable language_">this</span>.<span class="title function_">saveToTemp</span>(blob)</span><br><span class="line">    <span class="comment">// 调用 PicGo 上传</span></span><br><span class="line">    <span class="keyword">const</span> result = <span class="keyword">await</span> <span class="variable language_">this</span>.<span class="property">picgo</span>.<span class="title function_">upload</span>([tempPath])</span><br><span class="line">    <span class="keyword">return</span> (result.<span class="property">output</span> <span class="keyword">as</span> <span class="built_in">string</span>[])[<span class="number">0</span>]</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="53-git-hook-自动上传"><a class="markdownIt-Anchor" href="#53-git-hook-自动上传"></a> 5.3 Git Hook 自动上传</h3><p>通过 Git Hook，可以在提交代码时自动上传新增的图片资源：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="comment"># .git/hooks/post-commit</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 获取本次提交新增的图片文件</span></span><br><span class="line">NEW_IMAGES=$(git diff-tree --no-commit-id --name-only -r HEAD | \</span><br><span class="line">  grep -E <span class="string">&#x27;\.(jpg|jpeg|png|gif|webp|svg)$&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> [ -n <span class="string">&quot;<span class="variable">$NEW_IMAGES</span>&quot;</span> ]; <span class="keyword">then</span></span><br><span class="line">  <span class="built_in">echo</span> <span class="string">&quot;发现新增图片，开始上传...&quot;</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment"># 使用 PicGo 上传</span></span><br><span class="line">  <span class="keyword">for</span> img <span class="keyword">in</span> <span class="variable">$NEW_IMAGES</span>; <span class="keyword">do</span></span><br><span class="line">    picgo upload <span class="string">&quot;<span class="variable">$img</span>&quot;</span></span><br><span class="line">  <span class="keyword">done</span></span><br><span class="line">  </span><br><span class="line">  <span class="built_in">echo</span> <span class="string">&quot;图片上传完成！&quot;</span></span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure><hr /><h2 id="六-高级应用场景"><a class="markdownIt-Anchor" href="#六-高级应用场景"></a> 六、高级应用场景</h2><h3 id="61-自定义上传插件"><a class="markdownIt-Anchor" href="#61-自定义上传插件"></a> 6.1 自定义上传插件</h3><p>PicGo 的插件系统非常强大，你可以编写自定义上传插件：</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; <span class="title class_">PicGo</span> &#125; = <span class="built_in">require</span>(<span class="string">&#x27;picgo&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> picgo = <span class="keyword">new</span> <span class="title class_">PicGo</span>()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 注册自定义上传插件</span></span><br><span class="line">picgo.<span class="title function_">registerUploadPlugin</span>(<span class="string">&#x27;custom-bed&#x27;</span>, <span class="keyword">class</span> <span class="title class_">CustomUpload</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span> (<span class="params">ctx</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">ctx</span> = ctx</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">async</span> <span class="title function_">upload</span> (fileArray) &#123;</span><br><span class="line">    <span class="comment">// fileArray 格式：</span></span><br><span class="line">    <span class="comment">// [</span></span><br><span class="line">    <span class="comment">//   &#123;</span></span><br><span class="line">    <span class="comment">//     url: &#x27;file:///path/to/image.jpg&#x27;,</span></span><br><span class="line">    <span class="comment">//     base64Address: &#x27;data:image/jpeg;base64,...&#x27;,</span></span><br><span class="line">    <span class="comment">//     isImage: true</span></span><br><span class="line">    <span class="comment">//   &#125;</span></span><br><span class="line">    <span class="comment">// ]</span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">const</span> results = <span class="keyword">await</span> <span class="title class_">Promise</span>.<span class="title function_">all</span>(</span><br><span class="line">      fileArray.<span class="title function_">map</span>(<span class="title function_">async</span> (file) =&gt; &#123;</span><br><span class="line">        <span class="comment">// 自定义上传逻辑</span></span><br><span class="line">        <span class="keyword">const</span> url = <span class="keyword">await</span> <span class="variable language_">this</span>.<span class="title function_">uploadToCustomServer</span>(file)</span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">          url,</span><br><span class="line">          <span class="attr">base64Address</span>: file.<span class="property">base64Address</span>,</span><br><span class="line">          <span class="attr">isImage</span>: <span class="literal">true</span></span><br><span class="line">        &#125;</span><br><span class="line">      &#125;)</span><br><span class="line">    )</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> results</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">async</span> <span class="title function_">uploadToCustomServer</span> (file) &#123;</span><br><span class="line">    <span class="comment">// 你的自定义上传逻辑</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;https://your-server.com/uploaded-image.jpg&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用自定义平台</span></span><br><span class="line">picgo.<span class="title function_">setConfig</span>(<span class="string">&#x27;picGo-BED&#x27;</span>, <span class="string">&#x27;custom-bed&#x27;</span>)</span><br></pre></td></tr></table></figure><h3 id="62-与-github-api-结合的实现方案"><a class="markdownIt-Anchor" href="#62-与-github-api-结合的实现方案"></a> 6.2 与 GitHub API 结合的实现方案</h3><p>除了使用 PicGo 的 GitHub 插件，你也可以直接调用 GitHub API 实现图片上传。这种方式提供了更大的灵活性：</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&#x27;axios&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">GitHubConfig</span> &#123;</span><br><span class="line">  <span class="attr">token</span>: <span class="built_in">string</span></span><br><span class="line">  <span class="attr">owner</span>: <span class="built_in">string</span></span><br><span class="line">  <span class="attr">repo</span>: <span class="built_in">string</span></span><br><span class="line">  <span class="attr">branch</span>: <span class="built_in">string</span></span><br><span class="line">  <span class="attr">uploadPath</span>: <span class="built_in">string</span></span><br><span class="line">  <span class="attr">customDomain</span>?: <span class="built_in">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">ImageItem</span> &#123;</span><br><span class="line">  <span class="attr">id</span>: <span class="built_in">string</span></span><br><span class="line">  <span class="attr">url</span>: <span class="built_in">string</span></span><br><span class="line">  <span class="attr">name</span>: <span class="built_in">string</span></span><br><span class="line">  <span class="attr">size</span>: <span class="built_in">number</span></span><br><span class="line">  <span class="attr">uploadDate</span>: <span class="built_in">string</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 上传文件到 GitHub Repository</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">uploadToGitHub</span>(<span class="params"></span></span><br><span class="line"><span class="params">  <span class="attr">config</span>: <span class="title class_">GitHubConfig</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">file</span>: <span class="title class_">File</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">progress</span>?: (percent: <span class="built_in">number</span>) =&gt; <span class="built_in">void</span></span></span><br><span class="line"><span class="params"></span>): <span class="title class_">Promise</span>&lt;<span class="title class_">ImageItem</span>&gt; &#123;</span><br><span class="line">  <span class="comment">// 生成文件名：年月日时分秒 + 3 位随机数</span></span><br><span class="line">  <span class="keyword">const</span> now = <span class="keyword">new</span> <span class="title class_">Date</span>()</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">pad</span> = (<span class="params"><span class="attr">n</span>: <span class="built_in">number</span>, len = <span class="number">2</span></span>) =&gt; n.<span class="title function_">toString</span>().<span class="title function_">padStart</span>(len, <span class="string">&#x27;0&#x27;</span>)</span><br><span class="line">  <span class="keyword">const</span> fileName = <span class="string">`<span class="subst">$&#123;now.getFullYear()&#125;</span><span class="subst">$&#123;pad(now.getMonth() + <span class="number">1</span>)&#125;</span><span class="subst">$&#123;pad(now.getDate())&#125;</span><span class="subst">$&#123;pad(now.getHours())&#125;</span><span class="subst">$&#123;pad(now.getMinutes())&#125;</span><span class="subst">$&#123;pad(now.getSeconds())&#125;</span><span class="subst">$&#123;<span class="built_in">Math</span>.floor(<span class="built_in">Math</span>.random() * <span class="number">900</span>) + <span class="number">100</span>&#125;</span><span class="subst">$&#123;getFileExtension(file.name)&#125;</span>`</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> normalizedPath = (config.<span class="property">uploadPath</span> || <span class="string">&#x27;&#x27;</span>).<span class="title function_">replace</span>(<span class="regexp">/^\/+|\/+$/g</span>, <span class="string">&#x27;&#x27;</span>)</span><br><span class="line">  <span class="keyword">const</span> path = normalizedPath ? <span class="string">`<span class="subst">$&#123;normalizedPath&#125;</span>/<span class="subst">$&#123;fileName&#125;</span>`</span> : fileName</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> base64 = <span class="keyword">await</span> <span class="title function_">fileToBase64</span>(file)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">await</span> axios.<span class="title function_">put</span>(</span><br><span class="line">      <span class="string">`https://api.github.com/repos/<span class="subst">$&#123;config.owner&#125;</span>/<span class="subst">$&#123;config.repo&#125;</span>/contents/<span class="subst">$&#123;<span class="built_in">encodeURIComponent</span>(path)&#125;</span>`</span>,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">message</span>: <span class="string">`Upload image: <span class="subst">$&#123;file.name&#125;</span>`</span>,</span><br><span class="line">        <span class="attr">content</span>: base64,</span><br><span class="line">        <span class="attr">branch</span>: config.<span class="property">branch</span></span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">headers</span>: &#123;</span><br><span class="line">          <span class="title class_">Authorization</span>: <span class="string">`Bearer <span class="subst">$&#123;config.token&#125;</span>`</span>,</span><br><span class="line">          <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span></span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="attr">onUploadProgress</span>: <span class="function">(<span class="params">e</span>) =&gt;</span> &#123;</span><br><span class="line">          <span class="keyword">if</span> (progress &amp;&amp; e.<span class="property">total</span>) &#123;</span><br><span class="line">            <span class="title function_">progress</span>(<span class="title class_">Math</span>.<span class="title function_">round</span>((e.<span class="property">loaded</span> * <span class="number">100</span>) / e.<span class="property">total</span>))</span><br><span class="line">          &#125;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    )</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> imageUrl = config.<span class="property">customDomain</span></span><br><span class="line">      ? <span class="string">`<span class="subst">$&#123;config.customDomain&#125;</span>/<span class="subst">$&#123;path&#125;</span>`</span></span><br><span class="line">      : <span class="string">`https://raw.githubusercontent.com/<span class="subst">$&#123;config.owner&#125;</span>/<span class="subst">$&#123;config.repo&#125;</span>/<span class="subst">$&#123;config.branch&#125;</span>/<span class="subst">$&#123;path&#125;</span>`</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      <span class="attr">id</span>: <span class="title class_">Date</span>.<span class="title function_">now</span>().<span class="title function_">toString</span>(),</span><br><span class="line">      <span class="attr">url</span>: imageUrl,</span><br><span class="line">      <span class="attr">name</span>: file.<span class="property">name</span>,</span><br><span class="line">      <span class="attr">size</span>: file.<span class="property">size</span>,</span><br><span class="line">      <span class="attr">uploadDate</span>: <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">toISOString</span>()</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">catch</span> (<span class="attr">err</span>: <span class="built_in">any</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (err?.<span class="property">message</span> === <span class="string">&#x27;Network Error&#x27;</span> || err?.<span class="property">code</span> === <span class="string">&#x27;ERR_NETWORK&#x27;</span>) &#123;</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;Network Error: 无法连接到 GitHub API&#x27;</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (err?.<span class="property">response</span>) &#123;</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">`Upload failed: <span class="subst">$&#123;err.response.status&#125;</span> <span class="subst">$&#123;err.response.data?.message&#125;</span>`</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">throw</span> err</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>直接调用 GitHub API 的优势：</strong></p><ol><li><strong>浏览器端兼容</strong>：可以在前端浏览器中直接调用（需处理 CORS）</li><li><strong>进度反馈</strong>：原生支持上传进度回调</li><li><strong>错误处理</strong>：更细粒度的错误控制</li><li><strong>无需依赖</strong>：不需要安装 PicGo，减少依赖</li></ol><h3 id="63-多平台上传策略"><a class="markdownIt-Anchor" href="#63-多平台上传策略"></a> 6.3 多平台上传策略</h3><p>你可以实现多平台上传策略，将图片同时上传到多个平台以实现备份：</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">uploadToMultiplePlatforms</span>(<span class="params"><span class="attr">files</span>: <span class="built_in">string</span>[], <span class="attr">platforms</span>: <span class="built_in">string</span>[]</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> <span class="attr">results</span>: <span class="title class_">Record</span>&lt;<span class="built_in">string</span>, <span class="built_in">string</span>[]&gt; = &#123;&#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">for</span> (<span class="keyword">const</span> platform <span class="keyword">of</span> platforms) &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> picgo = <span class="keyword">new</span> <span class="title class_">PicGo</span>()</span><br><span class="line">      </span><br><span class="line">      <span class="comment">// 配置不同平台</span></span><br><span class="line">      <span class="keyword">switch</span> (platform) &#123;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">&#x27;github&#x27;</span>:</span><br><span class="line">          picgo.<span class="title function_">setConfig</span>(&#123;</span><br><span class="line">            <span class="string">&#x27;picGo-BED&#x27;</span>: <span class="string">&#x27;github&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;picGo-GitHub-token&#x27;</span>: process.<span class="property">env</span>.<span class="property">GITHUB_TOKEN</span>!,</span><br><span class="line">            <span class="string">&#x27;picGo-GitHub-repo&#x27;</span>: <span class="string">&#x27;username/repo&#x27;</span></span><br><span class="line">          &#125;)</span><br><span class="line">          <span class="keyword">break</span></span><br><span class="line">        <span class="keyword">case</span> <span class="string">&#x27;aliyun&#x27;</span>:</span><br><span class="line">          picgo.<span class="title function_">setConfig</span>(&#123;</span><br><span class="line">            <span class="string">&#x27;picGo-BED&#x27;</span>: <span class="string">&#x27;upstream-aliyun-oss&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;picGo-Aliyun-OSS-bucket&#x27;</span>: <span class="string">&#x27;my-bucket&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;picGo-Aliyun-OSS-region&#x27;</span>: <span class="string">&#x27;oss-cn-shanghai&#x27;</span></span><br><span class="line">          &#125;)</span><br><span class="line">          <span class="keyword">break</span></span><br><span class="line">      &#125;</span><br><span class="line">      </span><br><span class="line">      <span class="keyword">const</span> result = <span class="keyword">await</span> picgo.<span class="title function_">upload</span>(files)</span><br><span class="line">      results[platform] = result.<span class="property">output</span> <span class="keyword">as</span> <span class="built_in">string</span>[]</span><br><span class="line">    &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">`Platform <span class="subst">$&#123;platform&#125;</span> upload failed:`</span>, err)</span><br><span class="line">      results[platform] = []</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">return</span> results</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr /><h2 id="七-完整项目实战"><a class="markdownIt-Anchor" href="#七-完整项目实战"></a> 七、完整项目实战</h2><h3 id="71-项目概述"><a class="markdownIt-Anchor" href="#71-项目概述"></a> 7.1 项目概述</h3><p><strong>ImgTools</strong> 是一个基于 Vue 3 + TypeScript 的图片管理工具，它结合了 PicGo API 和 GitHub API，提供了完整的图片上传、管理和链接生成功能。</p><p><strong>技术栈：</strong></p><table><thead><tr><th>技术</th><th>版本</th><th>用途</th></tr></thead><tbody><tr><td>Vue 3</td><td>3.5.13</td><td>前端框架</td></tr><tr><td>TypeScript</td><td>5.7.3</td><td>类型安全</td></tr><tr><td>Vite</td><td>6.1.0</td><td>构建工具</td></tr><tr><td>Element Plus</td><td>2.9.1</td><td>UI 组件库</td></tr><tr><td>Pinia</td><td>2.3.0</td><td>状态管理</td></tr><tr><td>Axios</td><td>1.7.9</td><td>HTTP 客户端</td></tr><tr><td>PicGo</td><td>2.0.3</td><td>图片上传</td></tr><tr><td>Less</td><td>4.2.1</td><td>CSS 预处理器</td></tr></tbody></table><p><strong>核心功能：</strong></p><ul><li>📤 <strong>多图上传</strong>：支持批量上传、拖拽上传、粘贴上传</li><li>🖼️ <strong>图片管理</strong>：浏览、搜索、删除已上传图片</li><li>🔗 <strong>多格式链接</strong>：支持 Markdown、HTML、URL、UBB、自定义格式</li><li>⚙️ <strong>灵活配置</strong>：可配置 GitHub 仓库参数</li><li>📱 <strong>响应式设计</strong>：支持桌面端和移动端</li></ul><h3 id="72-核心代码解析"><a class="markdownIt-Anchor" href="#72-核心代码解析"></a> 7.2 核心代码解析</h3><h4 id="721-picgo-工具封装"><a class="markdownIt-Anchor" href="#721-picgo-工具封装"></a> 7.2.1 PicGo 工具封装</h4><p><code>src/utils/picgo.ts</code> 是 PicGo API 的核心封装：</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 使用 require 以兼容 picgo 的 CommonJS 导出</span></span><br><span class="line"><span class="comment">// @ts-ignore</span></span><br><span class="line"><span class="keyword">const</span> &#123; <span class="title class_">PicGo</span> &#125; = <span class="built_in">require</span>(<span class="string">&#x27;picgo&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">upload</span>(<span class="params"><span class="attr">paths</span>?: <span class="built_in">string</span>[]</span>): <span class="title class_">Promise</span>&lt;<span class="title class_">UploadResult</span>&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> picgo = <span class="keyword">new</span> <span class="title class_">PicGo</span>()</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">onUpload</span> = (<span class="params"><span class="attr">ctx</span>: <span class="built_in">any</span></span>) =&gt; &#123;</span><br><span class="line">      <span class="title function_">cleanup</span>()</span><br><span class="line">      <span class="title function_">resolve</span>(ctx)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">onFailed</span> = (<span class="params"><span class="attr">ctx</span>: <span class="built_in">any</span></span>) =&gt; &#123;</span><br><span class="line">      <span class="title function_">cleanup</span>()</span><br><span class="line">      <span class="title function_">reject</span>(ctx)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">onError</span> = (<span class="params"><span class="attr">err</span>: <span class="built_in">any</span></span>) =&gt; &#123;</span><br><span class="line">      <span class="title function_">cleanup</span>()</span><br><span class="line">      <span class="title function_">reject</span>(err)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">function</span> <span class="title function_">cleanup</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">try</span> &#123;</span><br><span class="line">        picgo.<span class="property">removeListener</span> &amp;&amp; picgo.<span class="title function_">removeListener</span>(<span class="string">&#x27;upload&#x27;</span>, onUpload)</span><br><span class="line">        picgo.<span class="property">removeListener</span> &amp;&amp; picgo.<span class="title function_">removeListener</span>(<span class="string">&#x27;failed&#x27;</span>, onFailed)</span><br><span class="line">        picgo.<span class="property">removeListener</span> &amp;&amp; picgo.<span class="title function_">removeListener</span>(<span class="string">&#x27;error&#x27;</span>, onError)</span><br><span class="line">      &#125; <span class="keyword">catch</span> (e) &#123;</span><br><span class="line">        <span class="comment">// ignore</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      picgo.<span class="property">on</span> &amp;&amp; picgo.<span class="title function_">on</span>(<span class="string">&#x27;upload&#x27;</span>, onUpload)</span><br><span class="line">      picgo.<span class="property">on</span> &amp;&amp; picgo.<span class="title function_">on</span>(<span class="string">&#x27;failed&#x27;</span>, onFailed)</span><br><span class="line">      picgo.<span class="property">on</span> &amp;&amp; picgo.<span class="title function_">on</span>(<span class="string">&#x27;error&#x27;</span>, onError)</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (<span class="title class_">Array</span>.<span class="title function_">isArray</span>(paths) &amp;&amp; paths.<span class="property">length</span> &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        picgo.<span class="title function_">upload</span>(paths)</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        picgo.<span class="title function_">upload</span>()</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">      <span class="title function_">cleanup</span>()</span><br><span class="line">      <span class="title function_">reject</span>(err)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>封装要点：</strong></p><ol><li><strong>Promise 化</strong>：将基于事件的 API 封装为 Promise，便于 async/await 使用</li><li><strong>事件清理</strong>：防止内存泄漏</li><li><strong>灵活参数</strong>：支持文件路径和剪贴板两种上传方式</li></ol><h4 id="722-github-api-直接调用"><a class="markdownIt-Anchor" href="#722-github-api-直接调用"></a> 7.2.2 GitHub API 直接调用</h4><p><code>src/utils/github.ts</code> 展示了如何直接调用 GitHub API 上传图片：</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">uploadToGitHub</span>(<span class="params"></span></span><br><span class="line"><span class="params">  <span class="attr">config</span>: <span class="title class_">GitHubConfig</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">file</span>: <span class="title class_">File</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">progress</span>?: (percent: <span class="built_in">number</span>) =&gt; <span class="built_in">void</span></span></span><br><span class="line"><span class="params"></span>): <span class="title class_">Promise</span>&lt;<span class="title class_">ImageItem</span>&gt; &#123;</span><br><span class="line">  <span class="comment">// 生成唯一文件名</span></span><br><span class="line">  <span class="keyword">const</span> fileName = <span class="title function_">generateUniqueFileName</span>(file.<span class="property">name</span>)</span><br><span class="line">  <span class="keyword">const</span> path = <span class="string">`<span class="subst">$&#123;config.uploadPath&#125;</span>/<span class="subst">$&#123;fileName&#125;</span>`</span></span><br><span class="line">  </span><br><span class="line">  <span class="keyword">const</span> base64 = <span class="keyword">await</span> <span class="title function_">fileToBase64</span>(file)</span><br><span class="line"></span><br><span class="line">  <span class="keyword">await</span> api.<span class="title function_">put</span>(</span><br><span class="line">    <span class="string">`/repos/<span class="subst">$&#123;config.owner&#125;</span>/<span class="subst">$&#123;config.repo&#125;</span>/contents/<span class="subst">$&#123;<span class="built_in">encodeURIComponent</span>(path)&#125;</span>`</span>,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">message</span>: <span class="string">`Upload image: <span class="subst">$&#123;file.name&#125;</span>`</span>,</span><br><span class="line">      <span class="attr">content</span>: base64,</span><br><span class="line">      <span class="attr">branch</span>: config.<span class="property">branch</span></span><br><span class="line">    &#125;,</span><br><span class="line">    &#123;</span><br><span class="line">      <span class="attr">headers</span>: &#123;</span><br><span class="line">        <span class="title class_">Authorization</span>: <span class="string">`Bearer <span class="subst">$&#123;config.token&#125;</span>`</span>,</span><br><span class="line">        <span class="string">&#x27;Content-Type&#x27;</span>: <span class="string">&#x27;application/json&#x27;</span></span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">onUploadProgress</span>: <span class="function">(<span class="params">e</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (progress &amp;&amp; e.<span class="property">total</span>) &#123;</span><br><span class="line">          <span class="title function_">progress</span>(<span class="title class_">Math</span>.<span class="title function_">round</span>((e.<span class="property">loaded</span> * <span class="number">100</span>) / e.<span class="property">total</span>))</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  )</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> imageUrl = config.<span class="property">customDomain</span></span><br><span class="line">    ? <span class="string">`<span class="subst">$&#123;config.customDomain&#125;</span>/<span class="subst">$&#123;path&#125;</span>`</span></span><br><span class="line">    : <span class="string">`https://raw.githubusercontent.com/<span class="subst">$&#123;config.owner&#125;</span>/<span class="subst">$&#123;config.repo&#125;</span>/<span class="subst">$&#123;config.branch&#125;</span>/<span class="subst">$&#123;path&#125;</span>`</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">id</span>: <span class="title class_">Date</span>.<span class="title function_">now</span>().<span class="title function_">toString</span>(),</span><br><span class="line">    <span class="attr">url</span>: imageUrl,</span><br><span class="line">    <span class="attr">name</span>: file.<span class="property">name</span>,</span><br><span class="line">    <span class="attr">size</span>: file.<span class="property">size</span>,</span><br><span class="line">    <span class="attr">uploadDate</span>: <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">toISOString</span>()</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>直接 API 调用的优势：</strong></p><ul><li>浏览器端可直接使用</li><li>支持上传进度回调</li><li>更细粒度的错误控制</li><li>无需安装额外依赖</li></ul><h4 id="723-链接生成工具"><a class="markdownIt-Anchor" href="#723-链接生成工具"></a> 7.2.3 链接生成工具</h4><p><code>src/utils/links.ts</code> 提供了多种格式的图片链接生成：</p><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">generateLink</span>(<span class="params"></span></span><br><span class="line"><span class="params">  <span class="attr">image</span>: <span class="title class_">ImageItem</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">format</span>: <span class="built_in">string</span>,</span></span><br><span class="line"><span class="params">  <span class="attr">customPattern</span>?: <span class="built_in">string</span></span></span><br><span class="line"><span class="params"></span>): <span class="built_in">string</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> url = image.<span class="property">url</span></span><br><span class="line">  <span class="keyword">switch</span> (format) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;markdown&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> <span class="string">`![<span class="subst">$&#123;image.name&#125;</span>](<span class="subst">$&#123;url&#125;</span>)`</span></span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;html&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> <span class="string">`&lt;img src=&quot;<span class="subst">$&#123;url&#125;</span>&quot; alt=&quot;<span class="subst">$&#123;image.name&#125;</span>&quot; /&gt;`</span></span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;url&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> url</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;ubb&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> <span class="string">`[img]<span class="subst">$&#123;url&#125;</span>[/img]`</span></span><br><span class="line">    <span class="keyword">case</span> <span class="string">&#x27;custom&#x27;</span>:</span><br><span class="line">      <span class="keyword">return</span> customPattern</span><br><span class="line">        ?.<span class="title function_">replace</span>(<span class="string">&#x27;&#123;url&#125;&#x27;</span>, url)</span><br><span class="line">        .<span class="title function_">replace</span>(<span class="string">&#x27;&#123;name&#125;&#x27;</span>, image.<span class="property">name</span>) || url</span><br><span class="line">    <span class="attr">default</span>:</span><br><span class="line">      <span class="keyword">return</span> <span class="string">`![<span class="subst">$&#123;image.name&#125;</span>](<span class="subst">$&#123;url&#125;</span>)`</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="73-项目结构"><a class="markdownIt-Anchor" href="#73-项目结构"></a> 7.3 项目结构</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">imgTools/</span><br><span class="line">├── src/</span><br><span class="line">│   ├── utils/</span><br><span class="line">│   │   ├── picgo.ts        # PicGo API 封装</span><br><span class="line">│   │   ├── github.ts       # GitHub API 直接调用</span><br><span class="line">│   │   └── links.ts        # 链接格式生成</span><br><span class="line">│   ├── stores/</span><br><span class="line">│   │   └── index.ts        # Pinia 状态管理</span><br><span class="line">│   ├── views/</span><br><span class="line">│   │   ├── Home.vue        # 上传页面</span><br><span class="line">│   │   ├── Album.vue       # 相册管理</span><br><span class="line">│   │   └── Settings.vue    # 设置页面</span><br><span class="line">│   ├── layouts/</span><br><span class="line">│   │   └── default.vue     # 默认布局</span><br><span class="line">│   ├── router/</span><br><span class="line">│   │   └── index.ts        # 路由配置</span><br><span class="line">│   ├── styles/</span><br><span class="line">│   │   ├── global.less     # 全局样式</span><br><span class="line">│   │   └── variables.less  # Less 变量</span><br><span class="line">│   ├── App.vue             # 根组件</span><br><span class="line">│   └── main.ts             # 入口文件</span><br><span class="line">├── package.json            # 依赖配置</span><br><span class="line">├── vite.config.ts          # Vite 配置</span><br><span class="line">└── tsconfig.json           # TypeScript 配置</span><br></pre></td></tr></table></figure><hr /><h2 id="八-最佳实践与性能优化"><a class="markdownIt-Anchor" href="#八-最佳实践与性能优化"></a> 八、最佳实践与性能优化</h2><h3 id="81-错误处理最佳实践"><a class="markdownIt-Anchor" href="#81-错误处理最佳实践"></a> 8.1 错误处理最佳实践</h3><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> result = <span class="keyword">await</span> picgo.<span class="title function_">upload</span>(files)</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;上传成功:&#x27;</span>, result.<span class="property">output</span>)</span><br><span class="line">&#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">  <span class="keyword">if</span> (err.<span class="property">code</span> === <span class="string">&#x27;PLUGIN_UPLOAD_ERROR&#x27;</span>) &#123;</span><br><span class="line">    <span class="comment">// 上传插件错误</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;上传失败:&#x27;</span>, err.<span class="property">message</span>)</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (err.<span class="property">code</span> === <span class="string">&#x27;CONFIG_NOT_FOUND&#x27;</span>) &#123;</span><br><span class="line">    <span class="comment">// 配置缺失</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;请检查 PicGo 配置&#x27;</span>)</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 其他错误</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&#x27;未知错误:&#x27;</span>, err)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="82-性能优化建议"><a class="markdownIt-Anchor" href="#82-性能优化建议"></a> 8.2 性能优化建议</h3><ol><li><strong>复用 PicGo 实例</strong>：避免频繁创建新实例</li></ol><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 推荐：复用实例</span></span><br><span class="line"><span class="keyword">const</span> picgo = <span class="keyword">new</span> <span class="title class_">PicGo</span>()</span><br><span class="line"><span class="comment">// 多次调用 upload</span></span><br><span class="line"><span class="keyword">await</span> picgo.<span class="title function_">upload</span>(files1)</span><br><span class="line"><span class="keyword">await</span> picgo.<span class="title function_">upload</span>(files2)</span><br></pre></td></tr></table></figure><ol start="2"><li><p><strong>批量上传</strong>：将多张图片一起上传，减少网络请求次数</p></li><li><p><strong>图片压缩</strong>：在上传前使用插件对图片进行压缩</p></li></ol><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line">picgo.<span class="title function_">registerTransformPlugin</span>(<span class="string">&#x27;compress&#x27;</span>, <span class="keyword">class</span> <span class="title class_">Compress</span> &#123;</span><br><span class="line">  <span class="keyword">async</span> <span class="title function_">transform</span> (file) &#123;</span><br><span class="line">    <span class="comment">// 使用 sharp 或其他库压缩图片</span></span><br><span class="line">    <span class="keyword">return</span> compressedBuffer</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><h3 id="83-安全注意事项"><a class="markdownIt-Anchor" href="#83-安全注意事项"></a> 8.3 安全注意事项</h3><ol><li><strong>Token 安全</strong>：不要将 GitHub Token 硬编码在代码中，使用环境变量</li></ol><figure class="highlight typescript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> token = process.<span class="property">env</span>.<span class="property">GITHUB_TOKEN</span></span><br><span class="line"><span class="keyword">if</span> (!token) &#123;</span><br><span class="line">  <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&#x27;GITHUB_TOKEN not set&#x27;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="2"><li><strong>环境变量管理</strong>：使用 <code>.env</code> 文件管理敏感信息</li></ol><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># .env</span></span><br><span class="line">GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx</span><br><span class="line">GITHUB_REPO=username/repo</span><br></pre></td></tr></table></figure><ol start="3"><li><strong>生产环境隔离</strong>：开发环境和生产环境使用不同的配置</li></ol><hr /><h2 id="结语"><a class="markdownIt-Anchor" href="#结语"></a> 结语</h2><p>通过本文的详细介绍，你应该已经掌握了：</p><ol><li><strong>PicGo API 的基础用法</strong>：初始化、配置、上传</li><li><strong>CLI 命令行集成</strong>：在脚本和 CI/CD 中使用 PicGo</li><li><strong>编辑器工作流集成</strong>：在 VS Code 扩展、Markdown 编辑器中实时上传</li><li><strong>高级应用场景</strong>：自定义插件、多平台上传、与 GitHub API 结合</li><li><strong>完整项目实战</strong>：基于 ImgTools 项目的代码解析</li></ol><p>PicGo 不仅仅是一个图片上传工具，更是一个强大的图片处理框架。通过其灵活的 API 设计和插件系统，你可以将图片上传能力无缝集成到任何 Node.js 工作流中。</p><p>无论是构建自动化脚本、开发编辑器扩展，还是搭建自己的图片管理工具，PicGo 都是你值得信赖的选择。</p><hr /><p><strong>参考资料：</strong></p><ul><li><a href="https://picgo.github.io/PicGo-Doc/">PicGo 官方文档</a></li><li><a href="https://github.com/Molunerfinn/PicGo">PicGo GitHub 仓库</a></li><li><a href="https://docs.github.com/en/rest">GitHub API 文档</a></li><li><a href="https://github.com/drda-x/imgTools">本项目 - ImgTools</a></li></ul><hr /><p><em>本文基于 ImgTools 项目实战编写，项目地址：<a href="https://drda-x.github.io/imgTools/">imgTools</a></em></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>VSCode Copilot 接入第三方 OpenAI 兼容接口</title>
      <link>https://drda-x.github.io/drda-blog/2026/06/18/AI%E6%8E%A5%E5%8F%A3/</link>
      <description>教你如何使用 OAI Compatible Provider 插件，让 VSCode Copilot 接入讯飞星辰等第三方 OpenAI 兼容接口，免费使用大模型。</description>
      <author>Drda</author>
      <category domain="https://drda-x.github.io/drda-blog/categories/%E7%AC%94%E8%AE%B0/">笔记</category>
      <category domain="https://drda-x.github.io/drda-blog/tags/AI/">AI</category>
      <category domain="https://drda-x.github.io/drda-blog/tags/VSCode/">VSCode</category>
      <category domain="https://drda-x.github.io/drda-blog/tags/Copilot/">Copilot</category>
      <pubDate>Thu, 18 Jun 2026 02:07:08 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>近日在关注 <strong>GitHubDaily</strong> 的公众号推送时，偶然看到一个标题吸引住了我 —— <strong>「无限量 Token 免费白嫖，科大讯飞大手笔啊！」</strong></p><p>如今 Claude Code、Codex 已成为开发者日常使用最多的 AI 生产力工具，但不少人都是时刻盯着 API 账单来使用，看着 Token 的大量消耗，心里都是一颤一颤的。所以有免费的当然值得试试，看看好不好用。</p><blockquote><p>💡 该活动来自 <a href="https://maas.xfyun.cn/modelSquare">讯飞星辰 MaaS 平台</a> 的模型集市，限时一个月免费，主打一个白嫖。</p></blockquote><p><img src="https://raw.githubusercontent.com/drda-x/images/main/20260618111434954.png" alt="Qwen 模型列表" /><br /><img src="https://raw.githubusercontent.com/drda-x/images/main/20260618111631737.png" alt="Qwen 模型详情" /></p><p>但是，<strong>如何使用这些 OpenAI 兼容接口呢？</strong></p><p>我使用的 VSCode Copilot 一直不支持通过 OpenAI 兼容接口来接入任意模型，默认的模型选择有限：</p><p><img src="https://raw.githubusercontent.com/drda-x/images/main/1.png" alt="Copilot 默认模型" /></p><p>经过一番搜索，发现有一个插件实现了让 VSCode Copilot 支持其他 OpenAI 兼容接口的功能，本文将详细记录使用方法。</p><h2 id="一-安装插件"><a class="markdownIt-Anchor" href="#一-安装插件"></a> 一、安装插件</h2><ol><li>打开 VSCode，点击左侧扩展市场（快捷键 <code>Ctrl+Shift+X</code>）</li><li>搜索 <strong><code>OAI Compatible Provider for Copilot</code></strong></li><li>点击 <strong>Install</strong> 安装该插件</li></ol><p><img src="https://raw.githubusercontent.com/drda-x/images/main/20260618113246819.png" alt="插件搜索与安装" /></p><h2 id="二-获取并配置-api-key"><a class="markdownIt-Anchor" href="#二-获取并配置-api-key"></a> 二、获取并配置 API Key</h2><p>本文以<strong>科大讯飞模型</strong>为例进行演示：</p><h3 id="21-创建应用"><a class="markdownIt-Anchor" href="#21-创建应用"></a> 2.1 创建应用</h3><ol><li>访问 <a href="https://console.xfyun.cn/app/myapp">讯飞开放平台</a></li><li>登录账号，点击 <strong>创建新应用</strong></li></ol><h3 id="22-配置模型服务"><a class="markdownIt-Anchor" href="#22-配置模型服务"></a> 2.2 配置模型服务</h3><ol><li>访问 <a href="https://maas.xfyun.cn/modelSquare">讯飞星辰 MaaS 平台</a></li><li>在模型集市中选择限时免费的模型</li><li>点击 <strong>API 调用</strong></li><li>配置以下信息：<ul><li><strong>模型服务 API 名称</strong>：自定义填写（随便写）</li><li><strong>授权应用</strong>：选择刚刚创建的新应用</li></ul></li><li>点击 <strong>确定</strong>，创建服务成功</li></ol><h3 id="23-获取-api-key"><a class="markdownIt-Anchor" href="#23-获取-api-key"></a> 2.3 获取 API Key</h3><p>创建成功后，在 MaaS 平台的 <strong>推理服务</strong> 中可以看到调用信息以及 <strong>API Key</strong>：</p><p><img src="https://raw.githubusercontent.com/drda-x/images/main/20260618133255488.png" alt="API Key 获取" /></p><blockquote><p>📌 <strong>提示</strong>：请妥善保管你的 API Key，不要泄露给他人。</p></blockquote><h2 id="三-配置插件"><a class="markdownIt-Anchor" href="#三-配置插件"></a> 三、配置插件</h2><h3 id="31-配置-base-url"><a class="markdownIt-Anchor" href="#31-配置-base-url"></a> 3.1 配置 Base URL</h3><ol><li>打开 VSCode 设置（快捷键 <code>Ctrl+,</code> 或点击左下角齿轮图标）</li><li>搜索 <strong><code>OAI</code></strong></li><li>选择 <strong>用户（User）</strong> 设置，拉到最下方的 <strong>扩展</strong> 部分</li><li>找到 <strong>OAI Compatible Provider for Copilot</strong> 插件设置</li><li>配置 <strong>Base URL</strong></li></ol><blockquote><p>✅ Base URL 只要是 OpenAI 兼容的接口都可以使用，例如讯飞星辰的接口地址。</p></blockquote><p><img src="https://raw.githubusercontent.com/drda-x/images/main/20260618113955666.png" alt="Base URL 配置" /></p><h3 id="32-添加模型"><a class="markdownIt-Anchor" href="#32-添加模型"></a> 3.2 添加模型</h3><ol><li>打开 Copilot 对话框</li><li>点击下方的 <strong>Auto</strong> 按钮，打开 Copilot 设置</li><li>在 <strong>语言模型列表</strong> 中添加你的模型信息</li><li>模型类型选择 <strong>OAI Compatible</strong><br /><img src="https://raw.githubusercontent.com/drda-x/images/main/20260618133930051.png" alt="添加模型" /><br /><img src="https://raw.githubusercontent.com/drda-x/images/main/20260618134007284.png" alt="模型配置完成" /></li></ol><h2 id="四-测试使用"><a class="markdownIt-Anchor" href="#四-测试使用"></a> 四、测试使用</h2><p>配置完成后，在对话框点击 <strong>Auto</strong>，选择你刚刚添加的模型，然后开始使用：</p><p><img src="https://raw.githubusercontent.com/drda-x/images/main/20260618135446750.png" alt="测试结果" /></p><blockquote><p>✨ 如果正常收到回复，说明配置成功！</p></blockquote><h2 id="五-总结"><a class="markdownIt-Anchor" href="#五-总结"></a> 五、总结</h2><p>通过 <strong>OAI Compatible Provider for Copilot</strong> 插件，我们可以突破 Copilot 默认模型的限制，灵活接入各种 OpenAI 兼容的第三方 API。这不仅能够节省 API 费用，还能体验到不同厂商的大模型能力。</p><p><strong>优点：</strong></p><ul><li>🆓 可以利用各平台的免费额度</li><li>🔧 灵活切换不同模型</li><li>🌐 支持所有 OpenAI 兼容接口</li></ul><p><strong>注意事项：</strong></p><ul><li>确保 Base URL 正确且可访问</li><li>API Key 要妥善保管</li><li>注意各平台的调用频率限制</li></ul><hr /><p><em>参考资料：</em></p><ul><li><a href="https://maas.xfyun.cn/modelSquare">讯飞星辰 MaaS 平台</a></li><li><a href="https://console.xfyun.cn/app/myapp">讯飞开放平台</a></li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>禁用浏览器debugger</title>
      <link>https://drda-x.github.io/drda-blog/2026/06/07/%E7%A6%81%E7%94%A8%E6%B5%8F%E8%A7%88%E5%99%A8debugger/</link>
      <description>
        <![CDATA[<h1 id="禁用浏览器-debugger-使用说明"><a class="markdownIt-Anchor" href="#禁用浏览器-debugger-使用说明"></a> 禁用浏览器 Debugger]]>
      </description>
      <author>Drda</author>
      <category domain="https://drda-x.github.io/drda-blog/categories/%E5%85%B6%E5%AE%83/">其它</category>
      <category domain="https://drda-x.github.io/drda-blog/tags/js/">js</category>
      <category domain="https://drda-x.github.io/drda-blog/tags/%E6%B5%8F%E8%A7%88%E5%99%A8/">浏览器</category>
      <pubDate>Sun, 07 Jun 2026 02:36:41 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h1 id="禁用浏览器-debugger-使用说明"><a class="markdownIt-Anchor" href="#禁用浏览器-debugger-使用说明"></a> 禁用浏览器 Debugger 使用说明</h1><blockquote><p>保护前端代码逻辑，防止通过浏览器 DevTools 调试器逆向分析业务流程。</p></blockquote><hr /><h2 id="目录"><a class="markdownIt-Anchor" href="#目录"></a> 目录</h2><ol><li><a href="#1-%E8%83%8C%E6%99%AF%E4%B8%8E%E7%9B%AE%E7%9A%84">背景与目的</a></li><li><a href="#2-%E6%A0%B8%E5%BF%83%E5%8E%9F%E7%90%86">核心原理</a></li><li><a href="#3-%E5%9F%BA%E7%A1%80%E6%96%B9%E6%A1%88">基础方案</a><ul><li>3.1 <code>debugger</code> 反调试陷阱</li><li>3.2 禁用 <code>console</code> 输出</li></ul></li><li><a href="#4-%E8%BF%9B%E9%98%B6%E6%96%B9%E6%A1%88">进阶方案</a><ul><li>4.1 定时检测 DevTools 状态</li><li>4.2 <code>debugger</code> 时间差检测</li><li>4.3 重写原生函数检测</li><li>4.4 <code>Worker</code> 后台检测线程</li></ul></li><li><a href="#5-%E5%AE%8C%E6%95%B4%E9%9B%86%E6%88%90%E7%A4%BA%E4%BE%8B">完整集成示例</a></li><li><a href="#6-%E5%B1%80%E9%99%90%E6%80%A7%E4%B8%8E%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9">局限性与注意事项</a></li></ol><hr /><h2 id="1-背景与目的"><a class="markdownIt-Anchor" href="#1-背景与目的"></a> 1. 背景与目的</h2><table><thead><tr><th>场景</th><th>说明</th></tr></thead><tbody><tr><td>接口加密逻辑保护</td><td>防止他人通过断点调试还原签名算法</td></tr><tr><td>防止业务流程绕过</td><td>避免通过控制台直接修改变量跳过校验</td></tr><tr><td>逆向成本提升</td><td>增加分析者的时间与精力成本</td></tr></tbody></table><blockquote><p><strong>重要提示：</strong> 前端代码在客户端运行，任何保护措施都无法做到 100% 安全。本方案的目标是<strong>提高逆向门槛</strong>，而非实现绝对防护。</p></blockquote><hr /><h2 id="2-核心原理"><a class="markdownIt-Anchor" href="#2-核心原理"></a> 2. 核心原理</h2><p>浏览器 DevTools 打开后，以下行为会发生变化，可被 JavaScript 感知：</p><ul><li><strong><code>debugger</code> 语句执行耗时</strong>：DevTools 打开时会触发断点暂停，执行时间远大于关闭时</li><li><strong><code>console</code> 对象属性变化</strong>：部分浏览器在打开 DevTools 后会改变 <code>console</code> 对象的尺寸属性</li><li><strong>窗口尺寸异常</strong>：DevTools 的停靠模式会改变 <code>window.outerWidth/Height</code> 与 <code>innerWidth/Height</code> 的差值</li><li><strong>调试器可被注入</strong>：通过高频插入 <code>debugger</code> 语句，让调试者无法正常单步执行</li></ul><hr /><h2 id="3-基础方案"><a class="markdownIt-Anchor" href="#3-基础方案"></a> 3. 基础方案</h2><h3 id="31-debugger-反调试陷阱"><a class="markdownIt-Anchor" href="#31-debugger-反调试陷阱"></a> 3.1 <code>debugger</code> 反调试陷阱</h3><p>在关键代码路径中插入 <code>debugger</code> 语句。当 DevTools 打开时，执行会在此处暂停，干扰调试者操作。</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 方式一：直接插入</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">encrypt</span>(<span class="params">data</span>) &#123;</span><br><span class="line">  <span class="keyword">debugger</span>; <span class="comment">// DevTools 打开时会在此暂停</span></span><br><span class="line">  <span class="comment">// ... 加密逻辑</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方式二：高频定时器注入，使调试器被持续中断</span></span><br><span class="line"><span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">debugger</span>;</span><br><span class="line">&#125;, <span class="number">100</span>);</span><br></pre></td></tr></table></figure><h3 id="32-禁用-console-输出"><a class="markdownIt-Anchor" href="#32-禁用-console-输出"></a> 3.2 禁用 console 输出</h3><p>清空或劫持 console 方法，防止敏感信息泄露到控制台。</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line">(<span class="keyword">function</span> <span class="title function_">disableConsole</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">noop</span> = (<span class="params"></span>) =&gt; &#123;&#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> methods = [</span><br><span class="line">    <span class="string">&#x27;log&#x27;</span>, <span class="string">&#x27;warn&#x27;</span>, <span class="string">&#x27;error&#x27;</span>, <span class="string">&#x27;info&#x27;</span>, <span class="string">&#x27;debug&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;table&#x27;</span>, <span class="string">&#x27;trace&#x27;</span>, <span class="string">&#x27;dir&#x27;</span>, <span class="string">&#x27;dirxml&#x27;</span>, <span class="string">&#x27;group&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;groupCollapsed&#x27;</span>, <span class="string">&#x27;groupEnd&#x27;</span>, <span class="string">&#x27;clear&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;count&#x27;</span>, <span class="string">&#x27;countReset&#x27;</span>, <span class="string">&#x27;assert&#x27;</span>, <span class="string">&#x27;profile&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;profileEnd&#x27;</span>, <span class="string">&#x27;time&#x27;</span>, <span class="string">&#x27;timeLog&#x27;</span>, <span class="string">&#x27;timeEnd&#x27;</span></span><br><span class="line">  ];</span><br><span class="line"></span><br><span class="line">  methods.<span class="title function_">forEach</span>(<span class="function"><span class="params">method</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>[method] = noop;</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 防止通过重赋值恢复：冻结 console 对象</span></span><br><span class="line">  <span class="title class_">Object</span>.<span class="title function_">freeze</span>(<span class="variable language_">console</span>);</span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure><h2 id="4-进阶方案"><a class="markdownIt-Anchor" href="#4-进阶方案"></a> 4. 进阶方案</h2><h3 id="41-定时检测-devtools-状态"><a class="markdownIt-Anchor" href="#41-定时检测-devtools-状态"></a> 4.1 定时检测 DevTools 状态</h3><p>通过定时检测窗口尺寸差异来判断 DevTools 是否打开。当 DevTools 以停靠模式打开时，<code>window.outerWidth</code> 和 <code>window.innerWidth</code> 的差值会明显增大。</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">detectDevTools</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> threshold = <span class="number">160</span>;</span><br><span class="line">  <span class="keyword">const</span> widthThreshold = <span class="variable language_">window</span>.<span class="property">outerWidth</span> - <span class="variable language_">window</span>.<span class="property">innerWidth</span> &gt; threshold;</span><br><span class="line">  <span class="keyword">const</span> heightThreshold = <span class="variable language_">window</span>.<span class="property">outerHeight</span> - <span class="variable language_">window</span>.<span class="property">innerHeight</span> &gt; threshold;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (widthThreshold || heightThreshold) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">clear</span>();</span><br><span class="line">    <span class="title function_">alert</span>(<span class="string">&#x27;检测到开发者工具，页面功能受限&#x27;</span>);</span><br><span class="line">    <span class="comment">// 可选：重定向或禁用核心功能</span></span><br><span class="line">    <span class="variable language_">window</span>.<span class="property">location</span>.<span class="property">href</span> = <span class="string">&#x27;about:blank&#x27;</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">setInterval</span>(detectDevTools, <span class="number">1000</span>);</span><br></pre></td></tr></table></figure><h3 id="42-debugger-时间差检测"><a class="markdownIt-Anchor" href="#42-debugger-时间差检测"></a> 4.2 <code>debugger</code> 时间差检测</h3><p>利用 <code>debugger</code> 语句在 DevTools 打开时会暂停执行的特性，通过测量执行时间差来检测调试状态。</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">detectByTiming</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> start = performance.<span class="title function_">now</span>();</span><br><span class="line">  <span class="keyword">debugger</span>; <span class="comment">// DevTools 打开时，这里会暂停</span></span><br><span class="line">  <span class="keyword">const</span> end = performance.<span class="title function_">now</span>();</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 如果时间差超过阈值，说明 debugger 被触发过</span></span><br><span class="line">  <span class="keyword">if</span> (end - start &gt; <span class="number">100</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;DevTools 检测：debugger 执行时间异常&#x27;</span>);</span><br><span class="line">    <span class="comment">// 执行防护逻辑</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 高频检测</span></span><br><span class="line"><span class="built_in">setInterval</span>(detectByTiming, <span class="number">500</span>);</span><br></pre></td></tr></table></figure><h3 id="43-重写原生函数检测"><a class="markdownIt-Anchor" href="#43-重写原生函数检测"></a> 4.3 重写原生函数检测</h3><p>调试者常常通过重写原生函数（如 <code>fetch</code>、<code>XMLHttpRequest</code>）来拦截请求。可以通过检测这些函数是否被篡改来判断。</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">checkNativeFunction</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> nativeFetch = <span class="variable language_">window</span>.<span class="property">fetch</span>;</span><br><span class="line">  <span class="keyword">const</span> nativeXHR = <span class="variable language_">window</span>.<span class="property">XMLHttpRequest</span>;</span><br><span class="line"></span><br><span class="line">  <span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">window</span>.<span class="property">fetch</span> !== nativeFetch) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">warn</span>(<span class="string">&#x27;fetch 被重写&#x27;</span>);</span><br><span class="line">      <span class="comment">// 恢复或告警</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">window</span>.<span class="property">XMLHttpRequest</span> !== nativeXHR) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">warn</span>(<span class="string">&#x27;XMLHttpRequest 被重写&#x27;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;, <span class="number">2000</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">checkNativeFunction</span>();</span><br></pre></td></tr></table></figure><h3 id="44-worker-后台检测线程"><a class="markdownIt-Anchor" href="#44-worker-后台检测线程"></a> 4.4 <code>Worker</code> 后台检测线程</h3><p>利用 Web Worker 在独立线程中运行检测逻辑，避免被主线程的调试操作干扰。</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// anti-debug.worker.js</span></span><br><span class="line">self.<span class="property">onmessage</span> = <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">let</span> count = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">const</span> start = <span class="title class_">Date</span>.<span class="title function_">now</span>();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">check</span>(<span class="params"></span>) &#123;</span><br><span class="line">    count++;</span><br><span class="line">    <span class="keyword">const</span> elapsed = <span class="title class_">Date</span>.<span class="title function_">now</span>() - start;</span><br><span class="line">    <span class="comment">// 正常情况下 count 和 elapsed 应该同步增长</span></span><br><span class="line">    <span class="comment">// 如果 DevTools 打断点，elapsed 会远大于 count</span></span><br><span class="line">    <span class="keyword">if</span> (elapsed &gt; count * <span class="number">100</span> + <span class="number">200</span>) &#123;</span><br><span class="line">      self.<span class="title function_">postMessage</span>(&#123; <span class="attr">detected</span>: <span class="literal">true</span> &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="built_in">setInterval</span>(check, <span class="number">100</span>);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 主线程中使用</span></span><br><span class="line"><span class="keyword">const</span> worker = <span class="keyword">new</span> <span class="title class_">Worker</span>(<span class="string">&#x27;anti-debug.worker.js&#x27;</span>);</span><br><span class="line">worker.<span class="property">onmessage</span> = <span class="function">(<span class="params">e</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (e.<span class="property">data</span>.<span class="property">detected</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;Worker 检测到调试行为&#x27;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><hr /><h2 id="5-完整集成示例"><a class="markdownIt-Anchor" href="#5-完整集成示例"></a> 5. 完整集成示例</h2><p>将上述方案整合为一个可复用的模块：</p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * AntiDebug - 前端反调试工具集</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">AntiDebug</span> = &#123;</span><br><span class="line">  <span class="attr">enabled</span>: <span class="literal">true</span>,</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 初始化所有检测</span></span><br><span class="line">  <span class="title function_">init</span>(<span class="params">options = &#123;&#125;</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="variable language_">this</span>.<span class="property">enabled</span>) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> config = &#123;</span><br><span class="line">      <span class="attr">detectDevTools</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">disableConsole</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">timingDetection</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">redirectOnDetect</span>: <span class="literal">false</span>,</span><br><span class="line">      <span class="attr">redirectUrl</span>: <span class="string">&#x27;about:blank&#x27;</span>,</span><br><span class="line">      ...options</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (config.<span class="property">disableConsole</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="title function_">disableConsole</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (config.<span class="property">detectDevTools</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="title function_">startDevToolsDetection</span>(config.<span class="property">redirectOnDetect</span>, config.<span class="property">redirectUrl</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (config.<span class="property">timingDetection</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="title function_">startTimingDetection</span>();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 禁用 console</span></span><br><span class="line">  <span class="title function_">disableConsole</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">noop</span> = (<span class="params"></span>) =&gt; &#123;&#125;;</span><br><span class="line">    [<span class="string">&#x27;log&#x27;</span>, <span class="string">&#x27;warn&#x27;</span>, <span class="string">&#x27;error&#x27;</span>, <span class="string">&#x27;info&#x27;</span>, <span class="string">&#x27;debug&#x27;</span>, <span class="string">&#x27;table&#x27;</span>, <span class="string">&#x27;trace&#x27;</span>]</span><br><span class="line">      .<span class="title function_">forEach</span>(<span class="function"><span class="params">method</span> =&gt;</span> &#123; <span class="variable language_">console</span>[method] = noop; &#125;);</span><br><span class="line">    <span class="title class_">Object</span>.<span class="title function_">freeze</span>(<span class="variable language_">console</span>);</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="comment">// DevTools 窗口检测</span></span><br><span class="line">  <span class="title function_">startDevToolsDetection</span>(<span class="params">redirect, url</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> threshold = <span class="number">160</span>;</span><br><span class="line">    <span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> widthDiff = <span class="variable language_">window</span>.<span class="property">outerWidth</span> - <span class="variable language_">window</span>.<span class="property">innerWidth</span>;</span><br><span class="line">      <span class="keyword">const</span> heightDiff = <span class="variable language_">window</span>.<span class="property">outerHeight</span> - <span class="variable language_">window</span>.<span class="property">innerHeight</span>;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (widthDiff &gt; threshold || heightDiff &gt; threshold) &#123;</span><br><span class="line">        <span class="keyword">if</span> (redirect) &#123;</span><br><span class="line">          <span class="variable language_">window</span>.<span class="property">location</span>.<span class="property">href</span> = url;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">          <span class="variable language_">document</span>.<span class="property">body</span>.<span class="property">innerHTML</span> = <span class="string">&#x27;&lt;h1&gt;页面不可用&lt;/h1&gt;&#x27;</span>;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;, <span class="number">1000</span>);</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 时间差检测</span></span><br><span class="line">  <span class="title function_">startTimingDetection</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">check</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">      <span class="keyword">const</span> start = performance.<span class="title function_">now</span>();</span><br><span class="line">      <span class="keyword">debugger</span>;</span><br><span class="line">      <span class="keyword">if</span> (performance.<span class="title function_">now</span>() - start &gt; <span class="number">100</span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">clear</span>();</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="built_in">setInterval</span>(check, <span class="number">500</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 页面加载完成后启动</span></span><br><span class="line"><span class="keyword">if</span> (<span class="variable language_">document</span>.<span class="property">readyState</span> === <span class="string">&#x27;loading&#x27;</span>) &#123;</span><br><span class="line">  <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;DOMContentLoaded&#x27;</span>, <span class="function">() =&gt;</span> <span class="title class_">AntiDebug</span>.<span class="title function_">init</span>());</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">  <span class="title class_">AntiDebug</span>.<span class="title function_">init</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>使用方式：</strong></p><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 基础防护</span></span><br><span class="line"><span class="title class_">AntiDebug</span>.<span class="title function_">init</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 自定义配置</span></span><br><span class="line"><span class="title class_">AntiDebug</span>.<span class="title function_">init</span>(&#123;</span><br><span class="line">  <span class="attr">disableConsole</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="attr">detectDevTools</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="attr">redirectOnDetect</span>: <span class="literal">true</span>,</span><br><span class="line">  <span class="attr">redirectUrl</span>: <span class="string">&#x27;/error.html&#x27;</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><hr /><h2 id="6-局限性与注意事项"><a class="markdownIt-Anchor" href="#6-局限性与注意事项"></a> 6. 局限性与注意事项</h2><h3 id="已知局限"><a class="markdownIt-Anchor" href="#已知局限"></a> 已知局限</h3><table><thead><tr><th>局限点</th><th>说明</th></tr></thead><tbody><tr><td>无法阻止抓包</td><td>浏览器 Network 面板的请求记录无法通过 JS 禁用</td></tr><tr><td>Source Map 暴露</td><td>如果发布了 Source Map，代码逻辑可被还原</td></tr><tr><td>禁用 JS 即可绕过</td><td>用户可以在 DevTools 的 Settings 中禁用 JavaScript</td></tr><tr><td>性能开销</td><td>高频检测会带来额外的 CPU 占用</td></tr><tr><td>用户体验</td><td>过度防护可能影响正常用户的调试需求</td></tr></tbody></table><h3 id="最佳实践建议"><a class="markdownIt-Anchor" href="#最佳实践建议"></a> 最佳实践建议</h3><ol><li><strong>适度使用</strong>：仅在核心加密、支付校验等敏感环节启用，不要全站铺开</li><li><strong>结合后端</strong>：前端防护只是第一层，关键逻辑校验必须在服务端完成</li><li><strong>代码混淆</strong>：配合 Webpack 混淆、变量名压缩等手段，增加阅读成本</li><li><strong>监控告警</strong>：记录触发反调试的行为日志，用于安全分析</li></ol><h3 id="浏览器兼容性"><a class="markdownIt-Anchor" href="#浏览器兼容性"></a> 浏览器兼容性</h3><p>上述方案在现代浏览器（Chrome、Edge、Firefox、Safari）中均可正常工作。部分方案（如 Worker 检测）在 IE11 中不支持。</p><hr /><blockquote><p><strong>免责声明</strong>：本文技术仅用于学习交流，请勿用于非法用途。任何逆向行为应遵守相关法律法规。</p></blockquote><pre><code></code></pre>]]>
      </content:encoded>
    </item>
    <item>
      <title>最后的据点</title>
      <link>https://drda-x.github.io/drda-blog/2026/03/05/%E6%9C%80%E5%90%8E%E7%9A%84%E6%8D%AE%E7%82%B9/</link>
      <description>
        <![CDATA[<p>他比我小两岁，零零年的。</p>
<p>初中没念完就出去打工了，这些年辗转广东广西，过年才回来。我们这帮发小，但凡他在家，总泡在他家——喝酒，聊天，打牌，手机开黑，麻将搓到半夜。他家就是我们的据点。</p>
<h2 id="过年"><a]]>
      </description>
      <author>Drda</author>
      <category domain="https://drda-x.github.io/drda-blog/categories/%E7%94%9F%E6%B4%BB/">生活</category>
      <category domain="https://drda-x.github.io/drda-blog/tags/%E6%9C%8B%E5%8F%8B/">朋友</category>
      <pubDate>Thu, 05 Mar 2026 13:10:04 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>他比我小两岁，零零年的。</p><p>初中没念完就出去打工了，这些年辗转广东广西，过年才回来。我们这帮发小，但凡他在家，总泡在他家——喝酒，聊天，打牌，手机开黑，麻将搓到半夜。他家就是我们的据点。</p><h2 id="过年"><a class="markdownIt-Anchor" href="#过年"></a> 过年</h2><p>今年过年，他爸妈在广东值班没回，他带着两个妹妹回来。</p><p>初二中午，他还开车带我们去镇上吃粉，回来后在他家支起麻将桌。下午三点多，他说头痛，以为是熬夜着了凉，没人太当回事。</p><p>五点多，他大伯家喊他们过去吃饭。听说喝了两杯土茅台——自家酿的那种，网上都叫它“广西土茅台”。我知道他的酒量，十杯的量。回来时他匆匆上楼，说不舒服，想睡一觉。</p><p>后来我们才知道，他躺下后头痛得厉害，实在扛不住，给村里另一个兄弟打了电话，想去卫生院拿点药。那兄弟到他床边时，看见他用头撞墙，二话不说直接拉去了县医院。</p><p>这些我们当时都不知道。我正在村委看篮球赛总决赛，人山人海，比分咬得紧。</p><h2 id="电话"><a class="markdownIt-Anchor" href="#电话"></a> 电话</h2><p>九点多，比赛刚结束，电话响了。那兄弟发视频过来，说人住院了，好像挺严重，让我们赶紧去。开车的人都喝了点酒，七八个人只能骑上几辆小电驴，往县医院赶。</p><p>到的时候，他已经躺在病床上，神志不清了。</p><p>我凑过去看检查单，<strong>脑出血38毫升</strong>。那几个字钉进眼睛里，我半天说不出话。</p><h2 id="转院"><a class="markdownIt-Anchor" href="#转院"></a> 转院</h2><p>县医院动不了这个手术，得转南宁医科大。可医院没有多余的救护车。我们所有人掏出手机，翻通讯录，托关系，到处问。十点多，终于打听到有一辆送病人回来的救护车正在路上。等它到，等人交接，等它再次出发。</p><p>十一点多，他上了车。我们站在医院门口，看着尾灯消失在夜色里。</p><p>回家后谁也没睡，聚在他家等消息。两点多，车到了南宁。三点多，消息传回来：那边医生看了一眼，说没救了，让拉回来。</p><p>来回颠簸，凌晨四点多，车回到村里。</p><h2 id="最后"><a class="markdownIt-Anchor" href="#最后"></a> 最后</h2><p>我看着他被抬下来，看着他躺在那里。四点四十多，他就这样没了。</p><p>我站在院子里，抽了两包烟。想不通。不是到了大医院吗？就没有一点抢救措施吗？就这样见死不救？</p><p>后来那兄弟说，医生讲，他瞳孔涣散，耽搁太久，就算开刀，存活率也极低——</p><blockquote><p>“你们也不想拉着‘破损’的‘尸体’回去吧。”</p></blockquote><p>他的家人沉默了，我们也沉默了。</p><h2 id="后来"><a class="markdownIt-Anchor" href="#后来"></a> 后来</h2><p>又想起另一件事：一个月前，他鼻子莫名其妙流了三天血，我们劝他去检查，他不乐意，说多了就烦。不知道他自己知不知道自己身体状况。也许知道，也许不知道。</p><p>最后他家里人商量，不办丧事了，吊唁三天就下葬。我们这帮人陪了他最后三天，守夜，烧纸，抽烟，偶尔说几句话，大多时候沉默。</p><p>出殡那天，我亲手挖土，也亲手埋土。</p><p><strong>一路走好。</strong></p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Hexo添加板娘</title>
      <link>https://drda-x.github.io/drda-blog/2025/12/17/Hexo%E6%B7%BB%E5%8A%A0%E6%9D%BF%E5%A8%98/</link>
      <description>
        <![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2>
<p>Live2D 看板娘可以为博客增添趣味性和互动感。本文以 Reimu]]>
      </description>
      <author>Drda</author>
      <category domain="https://drda-x.github.io/drda-blog/categories/%E7%AC%94%E8%AE%B0/">笔记</category>
      <category domain="https://drda-x.github.io/drda-blog/tags/hexo/">hexo</category>
      <pubDate>Wed, 17 Dec 2025 06:26:29 GMT</pubDate>
      <content:encoded>
        <![CDATA[<h2 id="前言"><a class="markdownIt-Anchor" href="#前言"></a> 前言</h2><p>Live2D 看板娘可以为博客增添趣味性和互动感。本文以 Reimu 主题为例，介绍两种添加看板娘的方式：<strong>主题内置</strong> 和 <strong>自定义配置</strong>。</p><hr /><h2 id="方式一使用主题内置的-live2d推荐新手"><a class="markdownIt-Anchor" href="#方式一使用主题内置的-live2d推荐新手"></a> 方式一：使用主题内置的 Live2D（推荐新手）</h2><p>很多 Hexo 主题（如 Reimu、Butterfly 等）已经集成了 Live2D 功能，只需简单配置即可启用。</p><h3 id="1-开启方法"><a class="markdownIt-Anchor" href="#1-开启方法"></a> 1. 开启方法</h3><p>打开主题配置文件 <code>themes/reimu/_config.yml</code>，找到 <code>live2d</code> 配置节：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Experimental</span></span><br><span class="line"><span class="attr">live2d:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>将 <code>enable</code> 改为 <code>true</code>，保存后重启 Hexo 即可看到看板娘。</p><p><img src="https://file.fishpi.cn/2026/06/kbn-38f3482a.png" alt="kbn.png" /></p><p>在页面中有多个板娘可以进行切换，还可以换装，拍照等许多互动体验。想了解更多功能实现可以访问<a href="https://github.com/LIlGG/plugin-live2d">https://github.com/LIlGG/plugin-live2d</a></p><h3 id="2-原理说明"><a class="markdownIt-Anchor" href="#2-原理说明"></a> 2. 原理说明</h3><p>Reimu 主题内置的 Live2D 使用 <a href="https://github.com/D-Sketon/plugin-live2d">D-Sketon/plugin-live2d</a> 库，脚本从 CDN 自动加载，无需额外安装任何依赖。</p><h2 id="方式二使用-hexo-helper-live2d-插件推荐进阶用户"><a class="markdownIt-Anchor" href="#方式二使用-hexo-helper-live2d-插件推荐进阶用户"></a> 方式二：使用 hexo-helper-live2d 插件（推荐进阶用户）</h2><p>如果需要更多模型选择或使用本地模型，可以使用 <code>hexo-helper-live2d</code> 插件。</p><h3 id="1-安装插件"><a class="markdownIt-Anchor" href="#1-安装插件"></a> 1. 安装插件</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm install hexo-helper-live2d --save</span><br></pre></td></tr></table></figure><h3 id="2-安装模型"><a class="markdownIt-Anchor" href="#2-安装模型"></a> 2. 安装模型</h3><p><strong>方式 A：使用 npm 模型包（推荐）</strong></p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 模型包</span></span><br><span class="line">npm install live2d-widget-model-shizuku --save   <span class="comment"># 诗库</span></span><br><span class="line">npm install live2d-widget-model-koharu --save    <span class="comment"># 小春</span></span><br><span class="line">npm install live2d-widget-model-hijiki --save    <span class="comment"># 羊驼</span></span><br><span class="line">npm install live2d-widget-model-tororo --save    <span class="comment"># 小白</span></span><br></pre></td></tr></table></figure><p><strong>方式 B：使用本地模型</strong></p><p>如果你有自定义的模型文件，可以放在项目根目录的 <code>live2d_modules/</code> 目录下，结构如下：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">live2d_modules/</span><br><span class="line">└── live2D/</span><br><span class="line">    ├── js/</span><br><span class="line">    │   ├── live2d.min.js</span><br><span class="line">    │   └── message.js</span><br><span class="line">    └── model/</span><br><span class="line">        └── xiaomai/</span><br><span class="line">            ├── xiaomai.model.json</span><br><span class="line">            ├── umaru.moc</span><br><span class="line">            └── ...</span><br></pre></td></tr></table></figure><h3 id="3-配置插件"><a class="markdownIt-Anchor" href="#3-配置插件"></a> 3. 配置插件</h3><p>在站点配置文件 <code>_config.yml</code>（不是主题的）中添加：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># live2d</span></span><br><span class="line"><span class="attr">live2d:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">scriptFrom:</span> <span class="string">local</span></span><br><span class="line">  <span class="attr">pluginRootPath:</span> <span class="string">live2dw/</span>      <span class="comment"># 静态资源根路径</span></span><br><span class="line">  <span class="attr">pluginJsPath:</span> <span class="string">lib/</span>            <span class="comment"># JS 文件路径</span></span><br><span class="line">  <span class="attr">pluginModelPath:</span> <span class="string">assets/</span>      <span class="comment"># 模型文件路径</span></span><br><span class="line">  <span class="attr">tagMode:</span> <span class="literal">false</span></span><br><span class="line">  <span class="attr">log:</span> <span class="literal">false</span></span><br><span class="line">  <span class="attr">model:</span></span><br><span class="line">    <span class="attr">use:</span> <span class="string">live2d-widget-model-hijiki</span>  <span class="comment"># npm 包名称</span></span><br><span class="line">  <span class="attr">display:</span></span><br><span class="line">    <span class="attr">position:</span> <span class="string">right</span>             <span class="comment"># 位置：left 或 right</span></span><br><span class="line">    <span class="attr">width:</span> <span class="number">150</span>                  <span class="comment"># 宽度</span></span><br><span class="line">    <span class="attr">height:</span> <span class="number">300</span>                 <span class="comment"># 高度</span></span><br><span class="line">  <span class="attr">mobile:</span></span><br><span class="line">    <span class="attr">show:</span> <span class="literal">true</span>                  <span class="comment"># 移动端是否显示</span></span><br><span class="line">  <span class="attr">react:</span></span><br><span class="line">    <span class="attr">opacity:</span> <span class="number">0.7</span>                <span class="comment"># 透明度</span></span><br></pre></td></tr></table></figure><p><strong>如果使用本地模型</strong>，将 <code>model.use</code> 改为本地目录路径：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">model:</span></span><br><span class="line">  <span class="attr">use:</span> <span class="string">live2d_modules/live2D/model/xiaomai</span> <span class="comment"># 这里我使用的是自定义的小埋模型</span></span><br></pre></td></tr></table></figure><blockquote><p><strong>注意</strong>：<code>model.use</code> 应该指向模型<strong>目录</strong>，而不是 <code>.model.json</code> 文件本身。</p></blockquote><h3 id="4-禁用主题内置的-live2d"><a class="markdownIt-Anchor" href="#4-禁用主题内置的-live2d"></a> 4. 禁用主题内置的 Live2D</h3><p>使用插件时，记得禁用主题内置的 Live2D，避免冲突：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># themes/reimu/_config.yml</span></span><br><span class="line"><span class="attr">live2d:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">false</span></span><br></pre></td></tr></table></figure><h3 id="5-测试"><a class="markdownIt-Anchor" href="#5-测试"></a> 5. 测试</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">hexo clean</span><br><span class="line">hexo server</span><br></pre></td></tr></table></figure><p>访问 <code>http://localhost:4000</code>，查看看板娘是否正常显示。</p><hr /><h3 id="如何更换模型"><a class="markdownIt-Anchor" href="#如何更换模型"></a> 如何更换模型</h3><p>只需修改 <code>model.use</code> 的值：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 使用 npm 包</span></span><br><span class="line"><span class="attr">model:</span></span><br><span class="line">  <span class="attr">use:</span> <span class="string">live2d-widget-model-shizuku</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 或使用本地模型</span></span><br><span class="line"><span class="attr">model:</span></span><br><span class="line">  <span class="attr">use:</span> <span class="string">live2d_modules/live2D/model/shizuku</span></span><br></pre></td></tr></table></figure><hr /><h2 id="总结"><a class="markdownIt-Anchor" href="#总结"></a> 总结</h2><p>如果只是想让博客多个看板娘，直接用主题内置的最简单；如果想深度定制或使用自己的模型，选择 <code>hexo-helper-live2d</code> 插件。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Hexo使用主题包</title>
      <link>https://drda-x.github.io/drda-blog/2025/07/10/%E4%BD%BF%E7%94%A8%E4%B8%BB%E9%A2%98%E5%8C%85/</link>
      <description>
        <![CDATA[<p>Hexo 安装完后，默认的 landscape 主题比较朴素，想要一个好看的博客界面，就需要换主题。Hexo 的主题生态非常丰富，从极简到华丽、从静态到动态交互，应有尽有。本文记录一下 Hexo 主题的安装与配置流程，并以我自己在用的]]>
      </description>
      <author>Drda</author>
      <category domain="https://drda-x.github.io/drda-blog/categories/%E7%AC%94%E8%AE%B0/">笔记</category>
      <category domain="https://drda-x.github.io/drda-blog/tags/hexo/">hexo</category>
      <pubDate>Thu, 10 Jul 2025 05:10:08 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>Hexo 安装完后，默认的 landscape 主题比较朴素，想要一个好看的博客界面，就需要换主题。Hexo 的主题生态非常丰富，从极简到华丽、从静态到动态交互，应有尽有。本文记录一下 Hexo 主题的安装与配置流程，并以我自己在用的 <strong>Reimu</strong> 主题为例做演示。</p><h2 id="1-查找主题"><a class="markdownIt-Anchor" href="#1-查找主题"></a> 1. 查找主题</h2><p>Hexo 官方维护了主题市场，地址是：</p><p>👉 <a href="https://hexo.io/themes/">https://hexo.io/themes/</a></p><p>上面有几百款主题，支持按标签筛选（比较推荐FlatPaper Nexmoe Mustom Kira Researcher ParticleX Materialis等，属于个人眼光哈）。挑选主题时建议关注以下几点：</p><table><thead><tr><th>维度</th><th>说明</th></tr></thead><tbody><tr><td><strong>Star 数</strong></td><td>GitHub Star 越多，说明用的人越多，维护相对积极</td></tr><tr><td><strong>最近更新</strong></td><td>看最后一次 commit 时间，太久没更新的主题容易有兼容性问题</td></tr><tr><td><strong>Hexo 版本要求</strong></td><td>主题 README 里一般会注明支持的 Hexo 版本</td></tr><tr><td><strong>功能需求</strong></td><td>是否需要评论系统、搜索、暗色模式、Pjax 等</td></tr><tr><td><strong>文档完整度</strong></td><td>配置项多不多、文档是否详细</td></tr></tbody></table><p>我自己选主题时，第一眼被 <a href="https://github.com/D-Sketon/hexo-theme-reimu">Reimu</a> 的东方 Project 风格吸引，再加上它内置了 Pjax、多种评论系统、APlayer 音乐播放器、暗色模式等，功能非常齐全，就果断换上了。</p><h2 id="2-安装主题以-reimu-为例"><a class="markdownIt-Anchor" href="#2-安装主题以-reimu-为例"></a> 2. 安装主题（以 Reimu 为例）</h2><h3 id="21-下载主题"><a class="markdownIt-Anchor" href="#21-下载主题"></a> 2.1 下载主题</h3><p>进入 Hexo 项目的 <code>themes</code> 目录，把主题克隆下来：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cd</span> 你的博客目录</span><br><span class="line">git <span class="built_in">clone</span> https://github.com/D-Sketon/hexo-theme-reimu.git themes/reimu</span><br></pre></td></tr></table></figure><p>如果是 pnpm 用户，也可以直接下载 release 压缩包解压到 <code>themes/reimu/</code> 目录。</p><h3 id="22-启用主题"><a class="markdownIt-Anchor" href="#22-启用主题"></a> 2.2 启用主题</h3><p>修改项目根目录的 <code>_config.yml</code>：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 找到 theme 字段，把 landscape 改成 reimu</span></span><br><span class="line"><span class="attr">theme:</span> <span class="string">reimu</span></span><br></pre></td></tr></table></figure><p>保存后运行 <code>hexo server</code> 预览，如果看到页面已经变成新主题的风格，说明切换成功。</p><h3 id="23-安装主题依赖"><a class="markdownIt-Anchor" href="#23-安装主题依赖"></a> 2.3 安装主题依赖</h3><p><strong>这一步非常关键，很多报错都出在这里。</strong></p><p>Reimu 主题依赖一些额外的 npm 包（如 <code>hexo-component-inferno</code>、<code>hexo-renderer-inferno</code>、<code>babel-plugin-inferno</code> 等）。直接运行：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pnpm install</span><br></pre></td></tr></table></figure><p>如果用的是 pnpm，可能会遇到 <strong>Babel 插件找不到</strong> 的报错：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Error: Cannot find module &#x27;babel-plugin-inferno&#x27;</span><br></pre></td></tr></table></figure><p>这是因为 pnpm 的依赖隔离机制导致的。在项目根目录创建 <code>.npmrc</code>：</p><figure class="highlight ini"><table><tr><td class="code"><pre><span class="line"><span class="attr">shamefully-hoist</span>=<span class="literal">true</span></span><br><span class="line"><span class="attr">auto-install-peers</span>=<span class="literal">true</span></span><br></pre></td></tr></table></figure><p>然后重新安装：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pnpm install</span><br></pre></td></tr></table></figure><h3 id="24-版本兼容"><a class="markdownIt-Anchor" href="#24-版本兼容"></a> 2.4 版本兼容</h3><p>Reimu 主题目前要求 Hexo <code>^7.0.0</code>，如果你的 Hexo 是 8.x 版本，启动时会报版本不匹配警告。解决方式是降级 Hexo：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pnpm install hexo@^7.0.0</span><br></pre></td></tr></table></figure><h2 id="3-配置主题"><a class="markdownIt-Anchor" href="#3-配置主题"></a> 3. 配置主题</h2><h3 id="31-复制主题配置"><a class="markdownIt-Anchor" href="#31-复制主题配置"></a> 3.1 复制主题配置</h3><p>每个主题都有自己的配置项，Reimu 的配置文件在 <code>themes/reimu/_config.yml</code>。但<strong>不要直接修改主题目录下的配置文件</strong>（否则主题升级时会被覆盖），而是把配置复制到项目根目录：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cp</span> themes/reimu/_config.yml _config.reimu.yml</span><br></pre></td></tr></table></figure><p>Hexo 会自动合并 <code>_config.reimu.yml</code> 和主题默认配置。</p><h3 id="32-常用配置项"><a class="markdownIt-Anchor" href="#32-常用配置项"></a> 3.2 常用配置项</h3><p>以下是 Reimu 主题中比较实用的几个配置：</p><h4 id="站点信息"><a class="markdownIt-Anchor" href="#站点信息"></a> 站点信息</h4><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 网站标题、副标题</span></span><br><span class="line"><span class="attr">title:</span> <span class="string">你的博客名称</span></span><br><span class="line"><span class="attr">subtitle:</span> <span class="string">副标题</span></span><br><span class="line"><span class="attr">author:</span> <span class="string">你的名字</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 语言</span></span><br><span class="line"><span class="attr">language:</span> <span class="string">zh-CN</span></span><br></pre></td></tr></table></figure><h4 id="导航菜单"><a class="markdownIt-Anchor" href="#导航菜单"></a> 导航菜单</h4><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">menu:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">home</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">/</span></span><br><span class="line">    <span class="attr">icon:</span> <span class="string">f015</span>  <span class="comment"># Font Awesome 图标代码</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">archives</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">/archives</span></span><br><span class="line">    <span class="attr">icon:</span> <span class="string">f187</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">about</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">/about</span></span><br><span class="line">    <span class="attr">icon:</span> <span class="string">f007</span></span><br></pre></td></tr></table></figure><h4 id="评论系统"><a class="markdownIt-Anchor" href="#评论系统"></a> 评论系统</h4><p>Reimu 支持多种评论系统（Waline、Giscus、Utterances、Beaudar 等），选一个启用即可：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">comment:</span></span><br><span class="line">  <span class="attr">default:</span> <span class="string">waline</span>  <span class="comment"># 默认评论系统</span></span><br><span class="line"></span><br><span class="line"><span class="attr">waline:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">serverURL:</span> <span class="string">https://你的-waline-地址</span></span><br></pre></td></tr></table></figure><h4 id="音乐播放器"><a class="markdownIt-Anchor" href="#音乐播放器"></a> 音乐播放器</h4><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">aplayer:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">meting:</span></span><br><span class="line">    <span class="attr">enable:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">options:</span></span><br><span class="line">      <span class="attr">id:</span> <span class="string">你的歌单ID</span></span><br><span class="line">      <span class="attr">server:</span> <span class="string">netease</span></span><br><span class="line">      <span class="attr">type:</span> <span class="string">playlist</span></span><br><span class="line">    <span class="attr">meting_api:</span> <span class="string">https://api.injahow.cn/meting/api?server=:server&amp;type=:type&amp;id=:id&amp;r=:r</span></span><br></pre></td></tr></table></figure><blockquote><p>注意：<code>meting_api</code> 的 URL 必须包含 <code>:server</code>、<code>:type</code>、<code>:id</code>、<code>:r</code> 占位符，否则 meting-js 无法正确替换参数。</p></blockquote><h4 id="搜索"><a class="markdownIt-Anchor" href="#搜索"></a> 搜索</h4><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">search:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">local</span>  <span class="comment"># local 或 algolia</span></span><br></pre></td></tr></table></figure><h3 id="33-创建独立页面"><a class="markdownIt-Anchor" href="#33-创建独立页面"></a> 3.3 创建独立页面</h3><p>Reimu 主题默认有 <code>about</code>（关于）和 <code>friend</code>（友链）页面，需要手动创建：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">hexo new page about</span><br><span class="line">hexo new page friend</span><br></pre></td></tr></table></figure><p>然后在 <code>source/about/index.md</code> 和 <code>source/friend/index.md</code> 中填写内容。</p><h2 id="4-切换主题的正确姿势"><a class="markdownIt-Anchor" href="#4-切换主题的正确姿势"></a> 4. 切换主题的正确姿势</h2><p>如果你想换一个主题，步骤如下：</p><ol><li><strong>下载新主题</strong>到 <code>themes/新主题名/</code></li><li><strong>修改 <code>_config.yml</code></strong> 中的 <code>theme</code> 字段</li><li><strong>安装新主题的依赖</strong></li><li><strong>复制新主题的配置文件</strong>到 <code>_config.新主题名.yml</code></li><li><strong><code>hexo clean</code></strong> 清理缓存（<strong>重要！</strong>）</li><li><strong><code>hexo server</code></strong> 预览效果</li></ol><h2 id="5-常见问题汇总"><a class="markdownIt-Anchor" href="#5-常见问题汇总"></a> 5. 常见问题汇总</h2><h3 id="q1-页面白屏样式没加载"><a class="markdownIt-Anchor" href="#q1-页面白屏样式没加载"></a> Q1: 页面白屏，样式没加载</h3><p>检查 <code>_config.yml</code> 中的 <code>url</code> 和 <code>root</code> 是否填写正确。如果是项目站点（如 <code>drda-blog</code>）：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">url:</span> <span class="string">https://你的用户名.github.io/drda-blog</span></span><br><span class="line"><span class="attr">root:</span> <span class="string">/drda-blog/</span></span><br></pre></td></tr></table></figure><h3 id="q2-图标显示不出来"><a class="markdownIt-Anchor" href="#q2-图标显示不出来"></a> Q2: 图标显示不出来</h3><p>Reimu 主题默认使用 iconfont，要使用 Font Awesome 需要：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">icon_font:</span> <span class="literal">false</span>  <span class="comment"># 关闭 iconfont，启用 fontawesome</span></span><br></pre></td></tr></table></figure><p>菜单图标用十六进制代码（如 <code>f015</code>、<code>f187</code>），不要带反斜杠。</p><h3 id="q3-部署后页面还是旧的"><a class="markdownIt-Anchor" href="#q3-部署后页面还是旧的"></a> Q3: 部署后页面还是旧的</h3><p>每次换主题或修改主题配置后，务必执行：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">hexo clean; hexo generate; hexo deploy</span><br></pre></td></tr></table></figure><p><code>hexo clean</code> 会清除数据库和已生成的文件，避免缓存导致旧页面残留。</p><h3 id="q4-主题升级会覆盖配置吗"><a class="markdownIt-Anchor" href="#q4-主题升级会覆盖配置吗"></a> Q4: 主题升级会覆盖配置吗？</h3><p>如果把配置写在 <code>_config.reimu.yml</code>（项目根目录），升级主题时不会被覆盖。如果直接改 <code>themes/reimu/_config.yml</code>，升级后配置会丢失。</p><h2 id="6-写在最后"><a class="markdownIt-Anchor" href="#6-写在最后"></a> 6. 写在最后</h2><p>Hexo 的主题生态非常丰富，选主题时建议先本地预览，确认功能符合需求后再正式启用。Reimu 主题功能很全，但也意味着配置项比较多，初次上手可能需要花点时间熟悉。</p><p>如果你也打算用 Reimu，推荐先通读一遍 <a href="https://github.com/D-Sketon/hexo-theme-reimu">官方 README</a>，里面每个配置项都有详细说明。遇到问题优先看文档，其次搜 GitHub Issues，一般都能找到解决方案。</p><p><strong>相关阅读</strong>：</p><ul><li><a href="https://hexo.io/docs/themes.html">Hexo 官方文档 - 主题</a></li><li><a href="https://github.com/D-Sketon/hexo-theme-reimu">Reimu 主题 GitHub 仓库</a></li><li><a href="https://fontawesome.com/search">Font Awesome 图标代码查询</a></li></ul>]]>
      </content:encoded>
    </item>
    <item>
      <title>el-table 双击单元格修改</title>
      <link>https://drda-x.github.io/drda-blog/2025/05/01/el-table%E5%8F%8C%E5%87%BB%E5%8D%95%E5%85%83%E6%A0%BC%E4%BF%AE%E6%94%B9/</link>
      <description>
        <![CDATA[<p>在使用 Element UI 的 <code>el-table</code>]]>
      </description>
      <author>Drda</author>
      <category domain="https://drda-x.github.io/drda-blog/categories/%E7%AC%94%E8%AE%B0/">笔记</category>
      <category domain="https://drda-x.github.io/drda-blog/tags/Vue/">Vue</category>
      <category domain="https://drda-x.github.io/drda-blog/tags/Element/">Element</category>
      <pubDate>Wed, 30 Apr 2025 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>在使用 Element UI 的 <code>el-table</code> 时，我们经常会遇到这样的需求：<strong>在表格中直接双击某个单元格就能进入编辑状态，输入完成后按回车或失去焦点自动保存</strong>。这个功能在后台管理系统中非常实用，省去了跳转到编辑页面的繁琐操作。本文就来详细记录一下这个交互的实现方式。</p><h2 id="核心思路"><a class="markdownIt-Anchor" href="#核心思路"></a> 核心思路</h2><p>实现这个效果主要分为三步：</p><ol><li>给 <code>el-table</code> 绑定 <code>cell-dblclick</code> 事件，监听单元格双击</li><li>双击时记录当前行和列信息，显示输入框（<code>el-input</code>）</li><li>输入完成后，触发保存逻辑并更新数据</li></ol><h2 id="基础表格结构"><a class="markdownIt-Anchor" href="#基础表格结构"></a> 基础表格结构</h2><p>先准备一个基础的 <code>el-table</code>：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&lt;template&gt;</span><br><span class="line">  &lt;el-table</span><br><span class="line">    :data=&quot;tableData&quot;</span><br><span class="line">    border</span><br><span class="line">    @cell-dblclick=&quot;handleCellDblClick&quot;</span><br><span class="line">  &gt;</span><br><span class="line">    &lt;el-table-column prop=&quot;name&quot; label=&quot;姓名&quot; /&gt;</span><br><span class="line">    &lt;el-table-column prop=&quot;age&quot; label=&quot;年龄&quot; /&gt;</span><br><span class="line">    &lt;el-table-column prop=&quot;address&quot; label=&quot;地址&quot; /&gt;</span><br><span class="line">  &lt;/el-table&gt;</span><br><span class="line">&lt;/template&gt;</span><br><span class="line"></span><br><span class="line">&lt;script&gt;</span><br><span class="line">export default &#123;</span><br><span class="line">  data() &#123;</span><br><span class="line">    return &#123;</span><br><span class="line">      tableData: [</span><br><span class="line">        &#123; name: &#x27;张三&#x27;, age: 24, address: &#x27;上海市&#x27; &#125;,</span><br><span class="line">        &#123; name: &#x27;李四&#x27;, age: 28, address: &#x27;北京市&#x27; &#125;,</span><br><span class="line">        &#123; name: &#x27;王五&#x27;, age: 32, address: &#x27;广州市&#x27; &#125;</span><br><span class="line">      ],</span><br><span class="line">      editRow: null,</span><br><span class="line">      editColumn: null,</span><br><span class="line">      editValue: &#x27;&#x27;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  methods: &#123;</span><br><span class="line">    handleCellDblClick(row, column, cell, event) &#123;</span><br><span class="line">      // 双击逻辑</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">&lt;/script&gt;</span><br></pre></td></tr></table></figure><h2 id="实现双击编辑"><a class="markdownIt-Anchor" href="#实现双击编辑"></a> 实现双击编辑</h2><p>关键点是利用 Vue 的 <strong>作用域插槽</strong>（<code>scoped-slot</code>）来控制单元格的渲染状态。平时显示普通文本，双击时切换成 <code>el-input</code>。</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">&lt;template&gt;</span><br><span class="line">  &lt;el-table</span><br><span class="line">    :data=&quot;tableData&quot;</span><br><span class="line">    border</span><br><span class="line">    @cell-dblclick=&quot;handleCellDblClick&quot;</span><br><span class="line">  &gt;</span><br><span class="line">    &lt;el-table-column prop=&quot;name&quot; label=&quot;姓名&quot;&gt;</span><br><span class="line">      &lt;template #default=&quot;&#123; row &#125;&quot;&gt;</span><br><span class="line">        &lt;el-input</span><br><span class="line">          v-if=&quot;editRow === row &amp;&amp; editColumn === &#x27;name&#x27;&quot;</span><br><span class="line">          v-model=&quot;editValue&quot;</span><br><span class="line">          size=&quot;small&quot;</span><br><span class="line">          @blur=&quot;handleSave&quot;</span><br><span class="line">          @keyup.enter=&quot;handleSave&quot;</span><br><span class="line">          ref=&quot;inputRef&quot;</span><br><span class="line">        /&gt;</span><br><span class="line">        &lt;span v-else&gt;&#123;&#123; row.name &#125;&#125;&lt;/span&gt;</span><br><span class="line">      &lt;/template&gt;</span><br><span class="line">    &lt;/el-table-column&gt;</span><br><span class="line"></span><br><span class="line">    &lt;el-table-column prop=&quot;age&quot; label=&quot;年龄&quot;&gt;</span><br><span class="line">      &lt;template #default=&quot;&#123; row &#125;&quot;&gt;</span><br><span class="line">        &lt;el-input</span><br><span class="line">          v-if=&quot;editRow === row &amp;&amp; editColumn === &#x27;age&#x27;&quot;</span><br><span class="line">          v-model=&quot;editValue&quot;</span><br><span class="line">          size=&quot;small&quot;</span><br><span class="line">          @blur=&quot;handleSave&quot;</span><br><span class="line">          @keyup.enter=&quot;handleSave&quot;</span><br><span class="line">          ref=&quot;inputRef&quot;</span><br><span class="line">        /&gt;</span><br><span class="line">        &lt;span v-else&gt;&#123;&#123; row.age &#125;&#125;&lt;/span&gt;</span><br><span class="line">      &lt;/template&gt;</span><br><span class="line">    &lt;/el-table-column&gt;</span><br><span class="line"></span><br><span class="line">    &lt;el-table-column prop=&quot;address&quot; label=&quot;地址&quot;&gt;</span><br><span class="line">      &lt;template #default=&quot;&#123; row &#125;&quot;&gt;</span><br><span class="line">        &lt;el-input</span><br><span class="line">          v-if=&quot;editRow === row &amp;&amp; editColumn === &#x27;address&#x27;&quot;</span><br><span class="line">          v-model=&quot;editValue&quot;</span><br><span class="line">          size=&quot;small&quot;</span><br><span class="line">          @blur=&quot;handleSave&quot;</span><br><span class="line">          @keyup.enter=&quot;handleSave&quot;</span><br><span class="line">          ref=&quot;inputRef&quot;</span><br><span class="line">        /&gt;</span><br><span class="line">        &lt;span v-else&gt;&#123;&#123; row.address &#125;&#125;&lt;/span&gt;</span><br><span class="line">      &lt;/template&gt;</span><br><span class="line">    &lt;/el-table-column&gt;</span><br><span class="line">  &lt;/el-table&gt;</span><br><span class="line">&lt;/template&gt;</span><br></pre></td></tr></table></figure><h2 id="事件处理逻辑"><a class="markdownIt-Anchor" href="#事件处理逻辑"></a> 事件处理逻辑</h2><figure class="highlight javascript"><table><tr><td class="code"><pre><span class="line"><span class="attr">methods</span>: &#123;</span><br><span class="line">  <span class="title function_">handleCellDblClick</span>(<span class="params">row, column</span>) &#123;</span><br><span class="line">    <span class="comment">// 如果已经在编辑另一个单元格，先保存</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">editRow</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="title function_">handleSave</span>()</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">editRow</span> = row</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">editColumn</span> = column.<span class="property">property</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">editValue</span> = row[column.<span class="property">property</span>]</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 自动聚焦输入框（需要等 DOM 更新）</span></span><br><span class="line">    <span class="variable language_">this</span>.$nextTick(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">$refs</span>.<span class="property">inputRef</span>?.<span class="title function_">focus</span>()</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="title function_">handleSave</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="variable language_">this</span>.<span class="property">editRow</span> || !<span class="variable language_">this</span>.<span class="property">editColumn</span>) <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 可以在这里做数据校验</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">editColumn</span> === <span class="string">&#x27;age&#x27;</span> &amp;&amp; !<span class="regexp">/^\d+$/</span>.<span class="title function_">test</span>(<span class="variable language_">this</span>.<span class="property">editValue</span>)) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">$message</span>.<span class="title function_">warning</span>(<span class="string">&#x27;年龄必须为数字&#x27;</span>)</span><br><span class="line">      <span class="keyword">return</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 更新数据</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">editRow</span>[<span class="variable language_">this</span>.<span class="property">editColumn</span>] = <span class="variable language_">this</span>.<span class="property">editValue</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 清空编辑状态</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">editRow</span> = <span class="literal">null</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">editColumn</span> = <span class="literal">null</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">editValue</span> = <span class="string">&#x27;&#x27;</span></span><br><span class="line"></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">$message</span>.<span class="title function_">success</span>(<span class="string">&#x27;保存成功&#x27;</span>)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="关键要点"><a class="markdownIt-Anchor" href="#关键要点"></a> 关键要点</h2><h3 id="1-为什么用-cell-dblclick-而不是-row-dblclick"><a class="markdownIt-Anchor" href="#1-为什么用-cell-dblclick-而不是-row-dblclick"></a> 1. 为什么用 <code>cell-dblclick</code> 而不是 <code>row-dblclick</code>？</h3><p><code>cell-dblclick</code> 能直接拿到 <code>column</code> 参数，包含了 <code>property</code>（对应列的 <code>prop</code>），而 <code>row-dblclick</code> 只能拿到行数据，无法知道用户点了哪一列。</p><h3 id="2-输入框自动聚焦"><a class="markdownIt-Anchor" href="#2-输入框自动聚焦"></a> 2. 输入框自动聚焦</h3><p>用 <code>this.$nextTick()</code> 包裹 <code>focus()</code> 调用，因为 <code>v-if</code> 切换后输入框的 DOM 还未插入，要等 Vue 完成一次更新周期。</p><h3 id="3-如何避免保存错乱"><a class="markdownIt-Anchor" href="#3-如何避免保存错乱"></a> 3. 如何避免保存错乱</h3><p>同时只能编辑一个单元格，所以在开启新编辑前，先判断 <code>this.editRow</code> 是否存在，存在则先执行 <code>handleSave()</code>。</p><h3 id="4-数据校验"><a class="markdownIt-Anchor" href="#4-数据校验"></a> 4. 数据校验</h3><p><code>handleSave</code> 里可以对特定字段做校验，比如年龄必须是数字、邮箱格式等。校验不通过时阻止保存并给出提示。</p><h2 id="效果预览"><a class="markdownIt-Anchor" href="#效果预览"></a> 效果预览</h2><p>实际效果就是：<strong>双击任意单元格</strong> → 该单元格变成输入框并自动聚焦 → <strong>回车或点击外部</strong> → 数据更新并恢复为普通文本。</p><h2 id="写在最后"><a class="markdownIt-Anchor" href="#写在最后"></a> 写在最后</h2><p>这种方式适合字段较少、编辑频率不高的场景。如果列数很多，可以考虑封装一个 <strong>可编辑表格组件</strong>，通过 <code>render</code> 函数动态生成插槽，避免重复写大量模板代码。如果有更多行内操作需求，也可以结合 <code>el-popover</code> 做成浮层编辑面板，体验会更好。</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Git 首次提交代码</title>
      <link>https://drda-x.github.io/drda-blog/2023/04/12/git%E9%A6%96%E6%AC%A1%E6%8F%90%E4%BA%A4%E4%BB%A3%E7%A0%81/</link>
      <description>
        <![CDATA[<p>记录一下本地项目第一次提交到 Git 仓库的完整流程。</p>
<h2 id="1-初始化仓库"><a class="markdownIt-Anchor" href="#1-初始化仓库"></a> 1.]]>
      </description>
      <author>Drda</author>
      <category domain="https://drda-x.github.io/drda-blog/categories/%E7%AC%94%E8%AE%B0/">笔记</category>
      <category domain="https://drda-x.github.io/drda-blog/tags/git/">git</category>
      <pubDate>Wed, 12 Apr 2023 05:19:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>记录一下本地项目第一次提交到 Git 仓库的完整流程。</p><h2 id="1-初始化仓库"><a class="markdownIt-Anchor" href="#1-初始化仓库"></a> 1. 初始化仓库</h2><p>进入项目根目录，执行：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git init</span><br></pre></td></tr></table></figure><p>该命令会在当前目录生成一个 <code>.git</code> 隐藏目录，Git 会从这里开始追踪所有文件的变更。</p><h2 id="2-配置用户信息"><a class="markdownIt-Anchor" href="#2-配置用户信息"></a> 2. 配置用户信息</h2><p>首次使用 Git 必须要配置用户名和邮箱，否则提交会失败：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git config --global user.name <span class="string">&quot;你的用户名&quot;</span></span><br><span class="line">git config --global user.email <span class="string">&quot;your_email@example.com&quot;</span></span><br></pre></td></tr></table></figure><p><code>--global</code> 表示对当前用户下所有仓库生效。如果只想对当前仓库配置，去掉 <code>--global</code> 即可。</p><h2 id="3-添加文件到暂存区"><a class="markdownIt-Anchor" href="#3-添加文件到暂存区"></a> 3. 添加文件到暂存区</h2><p>把项目所有文件加入 Git 追踪：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git add .          <span class="comment"># 添加全部文件</span></span><br><span class="line">git add &lt;file&gt;     <span class="comment"># 添加指定文件</span></span><br></pre></td></tr></table></figure><p><code>.</code> 表示当前目录下的所有文件。</p><h2 id="4-提交到本地仓库"><a class="markdownIt-Anchor" href="#4-提交到本地仓库"></a> 4. 提交到本地仓库</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git commit -m <span class="string">&quot;首次提交：项目初始化&quot;</span></span><br></pre></td></tr></table></figure><p><code>-m</code> 后面是本次提交的说明信息，<strong>建议用简短的中文或英文描述本次提交的内容</strong>。</p><h2 id="5-关联远程仓库"><a class="markdownIt-Anchor" href="#5-关联远程仓库"></a> 5. 关联远程仓库</h2><p>以 GitHub 为例，先在 GitHub 上新建一个空仓库，复制仓库地址（HTTP 或 SSH 形式），然后执行：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git remote add origin https://github.com/your_name/your_repo.git</span><br></pre></td></tr></table></figure><p><code>origin</code> 是远程仓库的默认别名，可以自定义，但一般都用 <code>origin</code>。</p><h2 id="6-推送到远程"><a class="markdownIt-Anchor" href="#6-推送到远程"></a> 6. 推送到远程</h2><p>第一次推送需要加上 <code>-u</code> 参数，建立本地分支与远程分支的关联：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">git push -u origin main</span><br></pre></td></tr></table></figure><p>如果是旧版 Git，默认分支可能是 <code>master</code>，根据实际情况替换即可。</p><h2 id="常见问题"><a class="markdownIt-Anchor" href="#常见问题"></a> 常见问题</h2><ul><li><strong>推送时提示输入用户名密码</strong>：说明使用的是 HTTPS 协议，可以改用 SSH 密钥方式免密推送。</li><li><strong><code>! [rejected] (non-fast-forward)</code></strong>：远程有本地没有的提交，需要先 <code>git pull --rebase</code> 再推送。</li><li><strong>大文件推送失败</strong>：GitHub 不允许单个文件超过 100MB，可以考虑使用 Git LFS。</li></ul><p>完成以上步骤后，代码就已经成功推送到远程仓库了 🎉</p>]]>
      </content:encoded>
    </item>
    <item>
      <title>Hexo 初体验</title>
      <link>https://drda-x.github.io/drda-blog/2023/04/08/Hexo%E5%88%9D%E4%BD%93%E9%AA%8C/</link>
      <description>
        <![CDATA[<p>一直想搭建一个属于自己的个人博客，记录学习和生活。试过多种方案后，最终选择了 <strong>Hexo + GitHub Pages</strong>，原因是：免费、静态、速度快、主题丰富。下面把完整的搭建过程记录下来，希望能帮到有同样需求的朋友。</p>
<h2]]>
      </description>
      <author>Drda</author>
      <category domain="https://drda-x.github.io/drda-blog/categories/%E7%AC%94%E8%AE%B0/">笔记</category>
      <category domain="https://drda-x.github.io/drda-blog/tags/hexo/">hexo</category>
      <category domain="https://drda-x.github.io/drda-blog/tags/github/">github</category>
      <pubDate>Fri, 07 Apr 2023 16:00:00 GMT</pubDate>
      <content:encoded>
        <![CDATA[<p>一直想搭建一个属于自己的个人博客，记录学习和生活。试过多种方案后，最终选择了 <strong>Hexo + GitHub Pages</strong>，原因是：免费、静态、速度快、主题丰富。下面把完整的搭建过程记录下来，希望能帮到有同样需求的朋友。</p><h2 id="前置准备"><a class="markdownIt-Anchor" href="#前置准备"></a> 前置准备</h2><p>在开始之前，确保你的环境已经准备好：</p><ol><li><strong>Node.js 和 npm</strong>（建议使用 nvm 管理版本）</li><li><strong>Git</strong> 和 <strong>GitHub 账号</strong></li><li><strong>VS Code</strong> 或任意代码编辑器</li></ol><h2 id="第一步安装-hexo-cli"><a class="markdownIt-Anchor" href="#第一步安装-hexo-cli"></a> 第一步：安装 Hexo CLI</h2><p>打开终端，全局安装 Hexo 命令行工具：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm install -g hexo-cli</span><br></pre></td></tr></table></figure><p>安装完成后，验证是否成功：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">hexo -v</span><br></pre></td></tr></table></figure><h2 id="第二步初始化项目"><a class="markdownIt-Anchor" href="#第二步初始化项目"></a> 第二步：初始化项目</h2><p>创建一个文件夹，名字随便取，例如 <code>myHexo</code>，然后进入该目录执行初始化：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> myHexo</span><br><span class="line"><span class="built_in">cd</span> myHexo</span><br><span class="line">hexo init</span><br><span class="line">npm install</span><br></pre></td></tr></table></figure><p><code>hexo init</code> 会自动下载依赖并生成目录结构，看到提示 <strong>“start blogging with hexo!”</strong> 就成功了。目录结构如下：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">myHexo/</span><br><span class="line">├── _config.yml          # 站点配置文件</span><br><span class="line">├── package.json</span><br><span class="line">├── scaffolds/             # 文章模板</span><br><span class="line">├── source/                # 源文件（文章、页面）</span><br><span class="line">├── themes/                # 主题目录</span><br><span class="line">└── node_modules/</span><br></pre></td></tr></table></figure><h2 id="第三步本地预览"><a class="markdownIt-Anchor" href="#第三步本地预览"></a> 第三步：本地预览</h2><p>启动本地服务器，默认端口 4000：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">hexo server</span><br><span class="line"><span class="comment"># 或简写</span></span><br><span class="line">hexo s</span><br></pre></td></tr></table></figure><p>浏览器打开 <code>http://localhost:4000</code>，就能看到默认的 Hello World 页面了。按 <code>Ctrl + C</code> 停止服务。</p><h2 id="第四步创建-github-仓库"><a class="markdownIt-Anchor" href="#第四步创建-github-仓库"></a> 第四步：创建 GitHub 仓库</h2><ol><li>登录 GitHub，新建一个仓库</li><li>仓库名必须是 <strong><code>你的用户名.github.io</code></strong>（这是 GitHub Pages 的规则）</li><li>保持仓库为空（不要勾选 README），记录仓库地址：</li></ol><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">https://github.com/你的用户名/你的用户名.github.io.git</span><br></pre></td></tr></table></figure><h2 id="第五步配置部署"><a class="markdownIt-Anchor" href="#第五步配置部署"></a> 第五步：配置部署</h2><p>打开项目根目录的 <code>_config.yml</code>，翻到最底部，找到 <code>deploy</code> 字段并修改为：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">deploy:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">git</span></span><br><span class="line">  <span class="attr">repo:</span> <span class="string">https://github.com/你的用户名/你的用户名.github.io.git</span></span><br><span class="line">  <span class="attr">branch:</span> <span class="string">main</span></span><br></pre></td></tr></table></figure><blockquote><p>注意：<code>branch</code> 的值根据你的仓库默认分支填写，旧版 GitHub 可能是 <code>master</code>。</p></blockquote><p>同时把 <code>_config.yml</code> 顶部的 <code>url</code> 字段改成你的站点地址：</p><figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line"><span class="attr">url:</span> <span class="string">https://你的用户名.github.io</span></span><br></pre></td></tr></table></figure><h2 id="第六步安装部署插件"><a class="markdownIt-Anchor" href="#第六步安装部署插件"></a> 第六步：安装部署插件</h2><p>Hexo 默认不自带 Git 部署功能，需要额外安装插件：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">npm install hexo-deployer-git --save</span><br></pre></td></tr></table></figure><h2 id="第七步生成并推送"><a class="markdownIt-Anchor" href="#第七步生成并推送"></a> 第七步：生成并推送</h2><p>首次部署前，建议先执行生成命令：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">hexo generate</span><br><span class="line"><span class="comment"># 或简写</span></span><br><span class="line">hexo g</span><br></pre></td></tr></table></figure><p>然后一键部署到 GitHub：</p><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">hexo deploy</span><br><span class="line"><span class="comment"># 或简写</span></span><br><span class="line">hexo d</span><br></pre></td></tr></table></figure><p>等待命令执行完毕，浏览器访问 <code>https://你的用户名.github.io</code>，博客就上线了！</p><h2 id="常用命令总结"><a class="markdownIt-Anchor" href="#常用命令总结"></a> 常用命令总结</h2><table><thead><tr><th>命令</th><th>作用</th></tr></thead><tbody><tr><td><code>hexo new &quot;文章标题&quot;</code></td><td>新建文章</td></tr><tr><td><code>hexo server</code></td><td>启动本地预览</td></tr><tr><td><code>hexo generate</code></td><td>生成静态文件</td></tr><tr><td><code>hexo deploy</code></td><td>部署到远程</td></tr><tr><td><code>hexo clean</code></td><td>清理缓存和已生成文件</td></tr><tr><td><code>hexo clean &amp;&amp; hexo generate &amp;&amp; hexo deploy</code></td><td>完整发布流程</td></tr></tbody></table><h2 id="常见问题"><a class="markdownIt-Anchor" href="#常见问题"></a> 常见问题</h2><ul><li><strong>部署失败提示 <code>Authentication failed</code></strong>：说明你用的是 HTTPS 协议，需要输入用户名密码，或者提前配置 SSH key 免密推送。</li><li><strong>页面样式丢失</strong>：检查 <code>_config.yml</code> 里的 <code>url</code> 和 <code>root</code> 是否配置正确。</li><li><strong>修改后不生效</strong>：先 <code>hexo clean</code> 再重新生成。</li></ul><h2 id="写在最后"><a class="markdownIt-Anchor" href="#写在最后"></a> 写在最后</h2><p>Hexo 的社区非常活跃，主题和插件都很丰富。后续可以尝试换主题、加评论系统、接入 CDN 加速等。搭建博客只是第一步，持续输出内容才是重点。加油！</p>]]>
      </content:encoded>
    </item>
  </channel>
</rss>
