<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="https://map1ebr1dge.github.io/atom.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-CN">
  <id>https://map1ebr1dge.github.io/</id>
  <title>枫桥居</title>
  <subtitle>Wandering Zephyr&amp;apos;s Yesteryear 「流浪的清风与旧梦」</subtitle>
  <icon>https://map1ebr1dge.github.io/favicon.ico</icon>
  <logo>https://map1ebr1dge.github.io/avatar.jpg</logo>
  <updated>2026-07-26T18:46:39.249Z</updated>
  <generator>@vuepress/plugin-feed</generator>
  <link rel="self" href="https://map1ebr1dge.github.io/atom.xml"/>
  <link rel="alternate" href="https://map1ebr1dge.github.io/"/>
  <category term="开源项目"/>
  <category term="AI 创作"/>
  <category term="博客搭建"/>
  <category term="前端工程"/>
  <category term="漏洞挖掘"/>
  <category term="PDF"/>
  <entry>
    <title type="text">BlueTidy 0.2.0：先看清空间，再安全处理</title>
    <id>https://map1ebr1dge.github.io/posts/bluetidy-0.2.0.html</id>
    <link href="https://map1ebr1dge.github.io/posts/bluetidy-0.2.0.html"/>
    <updated>2026-07-26T18:46:39.251Z</updated>
    <summary type="html"><![CDATA[<div class="hint-container info">
<p class="hint-container-title">项目状态</p>
<p>本文对应 <a href="https://github.com/by-fengqiao/BlueTidy/releases/tag/v0.2.0" target="_blank" rel="noopener noreferrer">BlueTidy 0.2.0 Preview</a>。它已经可以用于实际体验，但仍属于 Preview：迁移前请备份重要数据，不要把唯一一份数据交给测试版工具。</p>
</div>
<p>C 盘又红了。想腾出空间时，真正麻烦的往往不是“删什么”，而是三件事：最大的目录到底是什么、删掉会不会出问题、软件能不能挪到别的盘后继续运行。</p>]]></summary>
    <content type="html"><![CDATA[<div class="hint-container info">
<p class="hint-container-title">项目状态</p>
<p>本文对应 <a href="https://github.com/by-fengqiao/BlueTidy/releases/tag/v0.2.0" target="_blank" rel="noopener noreferrer">BlueTidy 0.2.0 Preview</a>。它已经可以用于实际体验，但仍属于 Preview：迁移前请备份重要数据，不要把唯一一份数据交给测试版工具。</p>
</div>
<p>C 盘又红了。想腾出空间时，真正麻烦的往往不是“删什么”，而是三件事：最大的目录到底是什么、删掉会不会出问题、软件能不能挪到别的盘后继续运行。</p>
<p>磁盘分析工具能画出一堆色块，软件管理器能卸载程序，但中间那一步通常仍要自己判断。BlueTidy 想补上的就是这里：先把占用和风险说明白，再决定迁移、清理还是先不动。</p>
<h2>两种模式，各管一件事</h2>
<p>最初我试图把文件夹分析和应用迁移塞进同一套界面，结果两边都不够顺手。看磁盘时需要的是层级、占用和变化；处理应用时则需要安装来源、运行状态、迁移条件和回滚记录。</p>

<p>现在首页只回答一个问题：怎样释放 C 盘空间。你可以先看磁盘，也可以直接去治理软件资产。</p>
<h2>文件夹模式：先把账算清楚</h2>
<p>BlueTidy 可以扫描整个磁盘，也可以只看一个指定目录。结果同时显示逻辑大小与实际磁盘占用，因为这两个数字并不总是相同。</p>
<p>读不到的目录不会再被当成 <code>0 B</code>。程序会记录无法访问的路径和数量，并把扫描结果标为不完整。这样至少能知道，眼前的结果不是整块磁盘的全部。</p>
<p>首次扫描会把目录树放进内存缓存。之后展开子目录不需要重复访问磁盘；同一路径再次扫描还会留下快照，可用于查看哪些目录变大、哪些内容消失，并支持导出 CSV。</p>
<div class="hint-container warning">
<p class="hint-container-title">迁移范围</p>
<p>分析可以从磁盘根目录开始，迁移不可以。文件夹迁移只接受当前用户目录下的具体子目录；整个用户目录、<code>AppData</code> 根目录和已有链接都会被拒绝。</p>
</div>
<p>这是刻意保留的边界。把整个用户目录直接搬走，出错时受影响的不会只是一个应用，而可能是整个账户环境。</p>
<h2>应用迁移：重点在“搬完以后”</h2>
<p>应用模式会从卸载注册表、开始菜单、Winget、Scoop 和常见游戏平台收集软件信息。传统 MSI / EXE 软件通过预检查后，BlueTidy 会把安装目录移到目标盘，并在原路径创建 Windows 目录连接，让软件继续访问原来的路径。</p>
<p>跨盘迁移不能直接重命名，只能先复制。复制完成后，BlueTidy 会核对文件数、目录数和总字节；校验通过才删除源目录，并尽量保留时间戳与权限。源目录中已有连接、符号链接或其他重解析点时，计划会被阻断。</p>
<p>回滚也不是简单地把文件复制回来。程序会确认原路径仍是事务记录中的连接，并且确实指向当时的目标目录。用户后来手动改过路径时，BlueTidy 会停下来，而不是擅自“修好”一个已经变化的目录。</p>
<details class="hint-container details"><summary>为什么不让 AI 决定是否迁移？</summary>
<p>TidyPilot 可以解释迁移支持状态、清理风险和回滚条件，也可以接入兼容 OpenAI <code>chat/completions</code> 的接口。但保护路径、运行进程和预检查拦截都在本地代码里，AI 没有放行权限。</p>
<p>如果接入外部模型，发送的是软件名称、版本、安装路径、容量和本地风险结论，不包含文件内容。0.2.0 的 API Key 仍以明文保存在本地设置文件中，建议只使用额度有限的专用 Key。</p>
</details>
<h2>清理功能保持克制</h2>
<p>目前可以清理的主要是安装目录中的缓存、日志和临时文件。文档、示例和离线帮助默认不选；即便确认处理，也会先进 Windows 回收站。</p>
<p>语言包更难判断：单靠文件名无法可靠识别当前语言和回退语言。因此 0.2.0 只统计它们占了多少空间，不提供删除按钮。</p>
<h2>这版能做什么，还不能做什么</h2>

<p>安装包尚未签名，Windows SmartScreen 可能提示风险；自动更新也还没有接入。磁盘扫描采用目录遍历，不是 NTFS MFT，遇到百万级文件时会比 WizTree 一类工具慢得多。</p>
<p>驱动、服务、系统组件，以及安装路径不可信的软件会被拦住。目录连接对许多传统软件有效，但不保证每一个应用都能正常工作。</p>
<h2>技术栈与下载</h2>
<p>桌面界面使用 Tauri 2、React 和 TypeScript；磁盘扫描、软件采集、迁移、清理和事务回滚由 Rust 完成。</p>

<p><a href="https://github.com/by-fengqiao/BlueTidy/releases/tag/v0.2.0" target="_blank" rel="noopener noreferrer">下载 BlueTidy 0.2.0 Preview</a></p>
<p>如果你试过 BlueTidy，欢迎把兼容性结果或 Bug 提交到 <a href="https://github.com/by-fengqiao/BlueTidy/issues/new/choose" target="_blank" rel="noopener noreferrer">Issues</a>。涉及路径越界、错误删除或凭据泄露，请使用 <a href="https://github.com/by-fengqiao/BlueTidy/security/advisories/new" target="_blank" rel="noopener noreferrer">私密安全报告</a>，不要把含有私人路径的日志贴到公开页面。</p>
]]></content>
    <category term="开源项目"/>
    <published>2026-07-26T00:00:00.000Z</published>
  </entry>
  <entry>
    <title type="text">LanNook：在局域网里，把手机和电脑真正连起来</title>
    <id>https://map1ebr1dge.github.io/posts/lannook-introduction.html</id>
    <link href="https://map1ebr1dge.github.io/posts/lannook-introduction.html"/>
    <updated>2026-07-26T18:46:39.253Z</updated>
    <summary type="html"><![CDATA[<p>手机和电脑之间传文件，常见做法无非是接数据线、走网盘，或再装一个第三方工具。它们都能用，只是有时显得绕：两台设备明明在同一个房间，文件却要先传到云端，再从另一端下载回来。</p>
<p>LanNook 想把这件事收回到局域网里解决。电脑端启动服务，手机用浏览器连接；在同一个可信网络中，文件直接在设备之间流动，不需要注册云端账号，也不必把内容交给公共存储服务。</p>
<div class="hint-container info">
<p class="hint-container-title">项目概览</p>
<ul>
<li>项目地址：<a href="https://github.com/by-fengqiao/lannook" target="_blank" rel="noopener noreferrer">by-fengqiao/lannook</a></li>
<li>适用场景：家庭、工作室、办公室等可信局域网内的临时文件交换</li>
<li>核心边界：它是局域网传输工具，不是云盘，也不负责远程备份</li>
</ul>
</div>]]></summary>
    <content type="html"><![CDATA[<p>手机和电脑之间传文件，常见做法无非是接数据线、走网盘，或再装一个第三方工具。它们都能用，只是有时显得绕：两台设备明明在同一个房间，文件却要先传到云端，再从另一端下载回来。</p>
<p>LanNook 想把这件事收回到局域网里解决。电脑端启动服务，手机用浏览器连接；在同一个可信网络中，文件直接在设备之间流动，不需要注册云端账号，也不必把内容交给公共存储服务。</p>
<div class="hint-container info">
<p class="hint-container-title">项目概览</p>
<ul>
<li>项目地址：<a href="https://github.com/by-fengqiao/lannook" target="_blank" rel="noopener noreferrer">by-fengqiao/lannook</a></li>
<li>适用场景：家庭、工作室、办公室等可信局域网内的临时文件交换</li>
<li>核心边界：它是局域网传输工具，不是云盘，也不负责远程备份</li>
</ul>
</div>
<h2>为什么做它</h2>
<p>很多需求其实很小：把手机里一组照片拷到电脑，或者把电脑上的压缩包临时发给手机。可在常见流程里，常常要经历登录、上传、等待同步，再下载一次。</p>
<p>如果设备本来就在一起，这段绕路没有太多必要。LanNook 不试图覆盖远程访问或云端同步，而是把事情限定为两件：让同一局域网中的设备建立连接，然后完成文件传输。</p>
<p>这也意味着它有明确的使用条件：手机和电脑需要处在能够互相访问的网络中。公共 Wi-Fi、访客网络，或开启客户端隔离的网络，可能无法正常连接。</p>
<h2>从扫码到传输</h2>
<p>启动 LanNook 后，电脑端会给出局域网地址和连接二维码。手机可以扫码，也可以在浏览器输入完整地址。</p>
<div class="hint-container warning">
<p class="hint-container-title">不要填 <code>localhost</code></p>
<p>手机浏览器中的 <code>localhost</code> 和 <code>127.0.0.1</code> 指向的是手机自己，而不是电脑。连接时应使用电脑显示的局域网地址。</p>
</div>
<p>手机发起连接后，电脑端会展示设备授权请求。可以查看设备名称、平台和连接状态，再决定只允许这一次，还是加入信任列表。局域网并不天然等于安全；电脑端主动确认设备，至少避免了“被发现就开放”的情况。</p>

<p>传输任务会集中显示在“传输中心”，可查看等待、进行中、完成、失败或取消的记录。发送大文件时，页面还会给出进度、已传输大小、实时速度和预计剩余时间，便于判断任务是在正常推进，还是已经停住。</p>
<h2>文件校验解决什么问题</h2>
<p>传输完成不代表文件一定完好。网络抖动、连接中断或写入异常，都可能让接收的文件不完整。</p>
<p>LanNook 会在传输结束后生成 SHA-256 指纹，用户可以展开查看并手工比对。它只回答“文件有没有变化”，并不判断文件是否安全，也不能代替杀毒软件或备份。</p>
<details class="hint-container details"><summary>数据和安全边界</summary>
<p>设备记录、授权状态和传输历史保存在运行 LanNook 的电脑本地，文件传输以局域网连接为主。项目不要求注册账号，也不把公共云端当作默认中转站。</p>
<p>这减少了对第三方云服务的依赖，但不替用户判断网络是否可信。连接前仍应确认网络环境，不要随意授权陌生设备。</p>
</details>
<h2>桌面端与手机端各自做什么</h2>
<p>LanNook 的桌面端使用 Tauri 2，负责启动局域网服务、管理设备授权、保存本地记录和处理文件操作。手机端通过浏览器访问移动页面，因此无需单独维护 Android 或 iOS 客户端。</p>
<p>桌面应用面向 Windows、macOS 与 Linux，并可放到系统托盘中运行。设置中可以决定关闭窗口后直接退出，还是继续在后台提供服务。界面支持中英文切换，首次启动会说明启动服务、扫码连接、电脑端授权和传输文件的基本步骤。</p>
<h2>技术实现</h2>
<p>前端使用 Vue 3、TypeScript、Vite 与 Pinia；桌面能力由 Rust 和 Tauri 2 提供。局域网服务基于 Axum 与 Tokio，设备发现使用 mDNS，实时状态通过 WebSocket 推送，本地数据保存到 SQLite。</p>
<p>文件传输包含分片处理、断点续传和完整性校验。Vue 负责界面与交互，Rust 处理本地服务、文件系统和跨平台桌面能力，两部分各自做自己擅长的事。</p>
<h2>安装与运行</h2>

<h2>它适合什么，不适合什么</h2>
<ul class="task-list-container">
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" id="task-item-0" checked="checked" disabled="disabled"><label class="task-list-item-label" for="task-item-0"> 在可信局域网内临时传照片、视频、文档或压缩包</label></li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" id="task-item-1" checked="checked" disabled="disabled"><label class="task-list-item-label" for="task-item-1"> 不想注册新账号，也不想把文件默认交给云端中转</label></li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" id="task-item-2" checked="checked" disabled="disabled"><label class="task-list-item-label" for="task-item-2"> 希望在电脑端明确控制哪些设备可以接入</label></li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" id="task-item-3" disabled="disabled"><label class="task-list-item-label" for="task-item-3"> 不能把它当作云盘、异地访问工具或长期备份方案</label></li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" id="task-item-4" disabled="disabled"><label class="task-list-item-label" for="task-item-4"> 电脑离线、关机，或设备不在同一可访问网络时，无法继续连接</label></li>
</ul>
<p>我更愿意把 LanNook 看成“附近设备传文件”的工具。它把局域网内的连接和传输做得更直接，剩下的选择仍由使用者自己决定。</p>
<p>项目按 GPL-3.0-only 许可证开源。遇到问题或有改进想法，可以前往 <a href="https://github.com/by-fengqiao/lannook/issues" target="_blank" rel="noopener noreferrer">GitHub Issues</a> 反馈。</p>
]]></content>
    <category term="开源项目"/>
    <published>2026-07-24T00:00:00.000Z</published>
  </entry>
  <entry>
    <title type="text">用 hatch-pet 制作 Codex 桌宠：Hoodie Tide</title>
    <id>https://map1ebr1dge.github.io/posts/Codex%E6%A1%8C%E5%AE%A0%E5%88%B6%E4%BD%9C%E8%AE%B0%E5%BD%95HoodieTide.html</id>
    <link href="https://map1ebr1dge.github.io/posts/Codex%E6%A1%8C%E5%AE%A0%E5%88%B6%E4%BD%9C%E8%AE%B0%E5%BD%95HoodieTide.html"/>
    <updated>2026-07-01T23:27:37.000Z</updated>
    <summary type="html"><![CDATA[
<p>Hoodie Tide 是我根据一张参考图制作的 Codex 自定义桌宠。成品是一张透明 WebP spritesheet，共 8 列 9 行，单格 <code>192x208</code>，包含待机、移动、等待和审阅等状态。</p>
]]></summary>
    <content type="html"><![CDATA[
<p>Hoodie Tide 是我根据一张参考图制作的 Codex 自定义桌宠。成品是一张透明 WebP spritesheet，共 8 列 9 行，单格 <code>192x208</code>，包含待机、移动、等待和审阅等状态。</p>
<!-- more -->
<p>这项工作主要卡在一致性：几十帧里的脸、发型、衣服和比例要保持稳定，动作方向、透明背景和帧尺寸也必须逐项校验。下面按实际制作顺序记录。</p>
<h2>成品规格</h2>
<p>本次产物包含两个文件，spritesheet 规格一并列在后面：</p>
<div class="language-text line-numbers-mode" data-highlighter="shiki" data-ext="text" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-text"><span class="line"><span>Hoodie Tide</span></span>
<span class="line"><span>├─ pet.json</span></span>
<span class="line"><span>└─ spritesheet.webp</span></span>
<span class="line"><span></span></span>
<span class="line"><span>atlas: 1536x1872</span></span>
<span class="line"><span>grid: 8 x 9</span></span>
<span class="line"><span>frame: 192x208</span></span>
<span class="line"><span>format: WebP / RGBA</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>9 行动画分别对应这些状态：</p>
<p>| Row | State | Frames | 用途 |<br>
|</p>
]]></content>
    <category term="AI 创作"/>
    <published>2026-06-07T00:00:00.000Z</published>
  </entry>
  <entry>
    <title type="text">GitHub Pages 绑定 feng-qiao.top 并接入 Cloudflare</title>
    <id>https://map1ebr1dge.github.io/posts/GitHub-Pages%E8%87%AA%E5%AE%9A%E4%B9%89%E5%9F%9F%E5%90%8D%E4%B8%8ECloudflare%E5%8A%A0%E9%80%9F.html</id>
    <link href="https://map1ebr1dge.github.io/posts/GitHub-Pages%E8%87%AA%E5%AE%9A%E4%B9%89%E5%9F%9F%E5%90%8D%E4%B8%8ECloudflare%E5%8A%A0%E9%80%9F.html"/>
    <updated>2026-07-01T23:27:37.000Z</updated>
    <summary type="html"><![CDATA[
<p>这篇记录 <code>feng-qiao.top</code> 当时的部署过程。站点由 GitHub Pages 托管，域名在腾讯云注册，Cloudflare 负责 DNS 托管，并可代理网站流量、处理 HTTPS 和缓存。</p>
<div class="hint-container note">
<p class="hint-container-title">配置背景</p>
<p>文中的分支、控制台截图和 DNS 记录按当时的操作过程保留，不用于描述域名现在的解析状态。</p>
</div>
]]></summary>
    <content type="html"><![CDATA[
<p>这篇记录 <code>feng-qiao.top</code> 当时的部署过程。站点由 GitHub Pages 托管，域名在腾讯云注册，Cloudflare 负责 DNS 托管，并可代理网站流量、处理 HTTPS 和缓存。</p>
<div class="hint-container note">
<p class="hint-container-title">配置背景</p>
<p>文中的分支、控制台截图和 DNS 记录按当时的操作过程保留，不用于描述域名现在的解析状态。</p>
</div>
<!-- more -->
<p>配置顺序如下：</p>
<ol>
<li>将 VuePress 构建产物发布到 GitHub Pages。</li>
<li>在 GitHub Pages 中绑定 <code>feng-qiao.top</code>。</li>
<li>在 Cloudflare 添加 DNS 记录。</li>
<li>把腾讯云中的权威 DNS 服务器改为 Cloudflare 提供的地址。</li>
<li>检查 HTTPS、根域名和 <code>www</code> 的访问结果。</li>
</ol>
<h2>发布到 GitHub Pages</h2>
<p>GitHub Pages 可以直接托管静态文件，适合 VuePress 这类构建后只包含 HTML、CSS 和 JavaScript 的站点。</p>
<h3>用户站点和项目站点</h3>
<p>新建仓库时，先确定使用哪种 Pages 形式：</p>
<p>| 类型 | 仓库名 | 默认访问地址 | VuePress <code>base</code> |<br>
|</p>
]]></content>
    <category term="博客搭建"/>
    <published>2026-06-08T00:00:00.000Z</published>
  </entry>
  <entry>
    <title type="text">VuePress 2 添加 HTML 首屏 Loading</title>
    <id>https://map1ebr1dge.github.io/posts/VuePress%E5%AE%9E%E7%8E%B0HTML%E7%BA%A7%E9%A6%96%E5%B1%8FLoading.html</id>
    <link href="https://map1ebr1dge.github.io/posts/VuePress%E5%AE%9E%E7%8E%B0HTML%E7%BA%A7%E9%A6%96%E5%B1%8FLoading.html"/>
    <updated>2026-07-01T23:27:37.000Z</updated>
    <summary type="html"><![CDATA[
<p>如果 Loading 本身是 Vue 组件，它必须等入口脚本下载、执行并完成应用挂载后才能出现。页面最初那段空白恰好发生在这些步骤之前，因此组件级 Loading 接不住。</p>
]]></summary>
    <content type="html"><![CDATA[
<p>如果 Loading 本身是 Vue 组件，它必须等入口脚本下载、执行并完成应用挂载后才能出现。页面最初那段空白恰好发生在这些步骤之前，因此组件级 Loading 接不住。</p>
<!-- more -->
<p>我的处理方式是把 Loading 直接写进 VuePress 的 HTML 模板，放在 <code>#app</code> 外面。浏览器拿到 HTML 后就能渲染它，页面资源加载完成时再淡出并移除节点。</p>
<div class="hint-container note">
<p class="hint-container-title">作用边界</p>
<p>这只是首屏反馈，不会缩短脚本、样式或图片的下载时间。</p>
</div>
<h2>适用范围</h2>
<p>本文只处理首次打开页面或手动刷新。</p>
<p>VuePress 的站内跳转由客户端路由完成，不会重新请求完整 HTML。若要给站内切页增加 Loading，需要监听路由状态，和本文的模板方案是两套逻辑。</p>
<h2>在配置中指定 HTML 模板</h2>
<p>VuePress 2 支持分别设置开发和构建模板。在 <code>config.ts</code> 中加入：</p>
<div class="language-ts line-numbers-mode" data-highlighter="shiki" data-ext="ts" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-ts"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">templateDev</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">fileURLToPath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> URL</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"./templates/dev.html"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">meta</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">url</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)),</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">templateBuild</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">: </span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">fileURLToPath</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">new</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> URL</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"./templates/build.html"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">import</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">meta</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">url</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">)),</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><p>对应文件放在：</p>
<div class="language-text line-numbers-mode" data-highlighter="shiki" data-ext="text" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-text"><span class="line"><span>src/.vuepress/templates/dev.html</span></span>
<span class="line"><span>src/.vuepress/templates/build.html</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><p><code>dev.html</code> 至少要保留 <code>#app</code>。<code>build.html</code> 还包含 VuePress 构建时使用的占位符：</p>
<div class="language-html line-numbers-mode" data-highlighter="shiki" data-ext="html" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-html"><span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">&#x3C;!--vuepress-ssr-head--></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">&#x3C;!--vuepress-ssr-styles--></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">&#x3C;!--vuepress-ssr-preload--></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">&#x3C;!--vuepress-ssr-prefetch--></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>body 中还需要：</p>
<div class="language-html line-numbers-mode" data-highlighter="shiki" data-ext="html" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-html"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"app"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">&#x3C;!--vuepress-ssr-content--></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">&#x3C;!--vuepress-ssr-scripts--></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div></div></div><div class="hint-container warning">
<p class="hint-container-title">不要删除模板占位符</p>
<p>这些注释是 VuePress 的构建占位符。VuePress 会把页面内容、样式和脚本注入对应位置，删除后可能得到结构不完整的页面。</p>
</div>
<h2>把 Loading 放在 #app 前面</h2>
<p>先看最小结构关系：</p>
<div class="language-html line-numbers-mode" data-highlighter="shiki" data-ext="html" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-html"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">body</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"page-loader"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>...&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"app"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">&#x3C;!-- VuePress app --></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">body</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>实际模板中的 body 结构如下：</p>
<div class="language-html line-numbers-mode" data-highlighter="shiki" data-ext="html" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-html"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">body</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"page-loader"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> role</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"status"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> aria-live</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"polite"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"page-loader__stack"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"page-loader__animation-wrap"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">        &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"loader"</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> aria-hidden</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"true"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">      &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">p</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> class</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"page-loader__text"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">>欢迎来到枫桥居，资源太大正在加载中...&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">p</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">    &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  &#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  &#x3C;</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> id</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">=</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"app"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">&#x3C;!--vuepress-ssr-content--></span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">div</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  &#x3C;!-- hide loader script --></span></span>
<span class="line"><span style="--shiki-light:#A0A1A7;--shiki-light-font-style:italic;--shiki-dark:#7F848E;--shiki-dark-font-style:italic">  &#x3C;!--vuepress-ssr-scripts--></span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">&#x3C;/</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">body</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">></span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>#page-loader</code> 与 <code>#app</code> 是同级节点。这样做有两个好处：</p>
<ul>
<li>VuePress 水合 <code>#app</code> 时不会处理 Loading 节点。</li>
<li>即使客户端 JavaScript 尚未执行，浏览器也能显示 Loading。</li>
</ul>
<p>动画和文字使用两个容器。动画中的小球会跳出 <code>.loader</code> 的初始高度，单独设置 <code>.page-loader__animation-wrap</code> 可以给它留出空间，避免文字被顶动。</p>
<h2>Loading 动画</h2>
<p>动画来自 Uiverse 上 alexruix 的 <a href="https://uiverse.io/alexruix/tame-fly-42" target="_blank" rel="noopener noreferrer">tame-fly-42</a>。下面保留原始 HTML/CSS 和来源注释。</p>

<p>放进模板时，我给选择器加上 <code>#page-loader</code> 前缀，防止 <code>.loader</code> 与站内其他组件重名。遮罩层使用固定定位并覆盖视口：</p>
<div class="language-css line-numbers-mode" data-highlighter="shiki" data-ext="css" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-css"><span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">#page-loader</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  position: </span><span style="--shiki-light:#383A42;--shiki-dark:#D19A66">fixed</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  inset: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">0</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  z-index: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">2147483647</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  display: </span><span style="--shiki-light:#383A42;--shiki-dark:#D19A66">grid</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  place-items: </span><span style="--shiki-light:#383A42;--shiki-dark:#D19A66">center</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">#page-loader</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">.page-loader--hidden</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  opacity: </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">0</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  visibility: </span><span style="--shiki-light:#383A42;--shiki-dark:#D19A66">hidden</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  pointer-events: </span><span style="--shiki-light:#383A42;--shiki-dark:#D19A66">none</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><h2>字体不要阻塞 Loading</h2>
<p>Loading 文案是：</p>
<div class="language-text line-numbers-mode" data-highlighter="shiki" data-ext="text" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-text"><span class="line"><span>欢迎来到枫桥居，资源太大正在加载中...</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>字体栈先写站点使用的 LXGW WenKai，再接系统中文字体：</p>
<div class="language-css line-numbers-mode" data-highlighter="shiki" data-ext="css" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-css"><span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">.page-loader__text</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  font-family:</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">    "LXGW WenKai"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">    "LXGW WenKai Screen"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">    "Microsoft YaHei"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">    "PingFang SC"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">    "Hiragino Sans GB"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">,</span></span>
<span class="line"><span style="--shiki-light:#986801;--shiki-dark:#D19A66">    sans-serif</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>这里不等待字体文件。LXGW WenKai 尚未下载时，浏览器直接使用 fallback 字体显示文案。Loading 若先等待自定义字体，就失去了首屏提示的作用。</p>
<h2>在 window.load 后移除</h2>
<p><code>DOMContentLoaded</code> 只表示 HTML 已完成解析，图片、字体和其他资源可能仍在下载。本文记录的方案在 <code>window.load</code> 后隐藏 Loading：</p>
<div class="language-js line-numbers-mode" data-highlighter="shiki" data-ext="js" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-js"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">document</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">readyState</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> ===</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379"> "complete"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) {</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">  window</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">setTimeout</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">hideLoader</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">0</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">} </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">else</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> {</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">  window</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">addEventListener</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"load"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">hideLoader</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, { </span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">once</span><span style="--shiki-light:#0184BC;--shiki-dark:#ABB2BF">:</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> });</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">}</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">window</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">setTimeout</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">hideLoader</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">, </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">12000</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>第二个计时器是 12 秒兜底。某个资源如果长时间没有结束，遮罩也不会永久挡住页面。</p>
<p><code>hideLoader</code> 负责添加隐藏 class，并在过渡完成后删除节点：</p>
<div class="language-js line-numbers-mode" data-highlighter="shiki" data-ext="js" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-js"><span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">var</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> done</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> false</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">var</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF"> hideLoader</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD"> function</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> () {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">done</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">  done</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#986801;--shiki-dark:#D19A66"> true</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  var</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75"> loader</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2"> =</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B"> document</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">getElementById</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"page-loader"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">  if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#0184BC;--shiki-dark:#56B6C2">!</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">loader</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) </span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">return</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">  loader</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">classList</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">add</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#50A14F;--shiki-dark:#98C379">"page-loader--hidden"</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">  window</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">setTimeout</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">function</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> () {</span></span>
<span class="line"><span style="--shiki-light:#A626A4;--shiki-dark:#C678DD">    if</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF"> (</span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">loader</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E06C75">parentNode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">) </span><span style="--shiki-light:#383A42;--shiki-dark:#E5C07B">loader</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#E45649;--shiki-dark:#E5C07B">parentNode</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">.</span><span style="--shiki-light:#4078F2;--shiki-dark:#61AFEF">removeChild</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">(</span><span style="--shiki-light:#383A42;--shiki-dark:#E06C75">loader</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">  }, </span><span style="--shiki-light:#986801;--shiki-dark:#D19A66">420</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">);</span></span>
<span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">};</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p><code>done</code> 用来防止 <code>load</code> 事件和兜底计时器重复执行。脚本不调用 <code>document.write</code>，也不改写 <code>#app</code> 的内容。</p>
<h2>构建和浏览器验证</h2>
<p>先执行生产构建：</p>
<div class="language-powershell line-numbers-mode" data-highlighter="shiki" data-ext="powershell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-powershell"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">npm run docs:build</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>构建通过后，还需要用静态服务器打开输出目录。检查这些场景：</p>
<ul>
<li>页面刚打开时 Loading 立即出现。</li>
<li><code>window.load</code> 触发后遮罩淡出并被移除。</li>
<li>页面内容可点击，没有透明遮罩残留。</li>
<li>控制台没有水合错误。</li>
<li>刷新文章页和首页都能正常进入。</li>
<li>站内路由跳转不会重复显示首屏 Loading。</li>
<li>资源加载失败时，12 秒兜底仍能放行页面。</li>
</ul>
<h2>方案边界</h2>
<p>HTML 模板方案解决的是“应用挂载前没有反馈”。若页面加载慢，仍应继续检查资源体积、字体、图片、预加载和构建产物。</p>
<p>站内路由 Loading、骨架屏和真实性能优化也应分别处理。把这些功能都塞进同一个全屏遮罩，反而更难判断页面到底卡在哪里。</p>
]]></content>
    <category term="博客搭建"/>
    <category term="前端工程"/>
    <published>2026-06-09T00:00:00.000Z</published>
  </entry>
  <entry>
    <title type="text">xxCMS v1.6 本地漏洞挖掘复盘</title>
    <id>https://map1ebr1dge.github.io/posts/%E8%AE%B0%E5%BD%95%E7%AC%AC%E4%B8%80%E6%AC%A1%E5%B0%9D%E8%AF%95CMS%E6%BC%8F%E6%B4%9E%E6%8C%96%E6%8E%98.html</id>
    <link href="https://map1ebr1dge.github.io/posts/%E8%AE%B0%E5%BD%95%E7%AC%AC%E4%B8%80%E6%AC%A1%E5%B0%9D%E8%AF%95CMS%E6%BC%8F%E6%B4%9E%E6%8C%96%E6%8E%98.html"/>
    <updated>2026-07-01T23:27:37.000Z</updated>
    <summary type="html"><![CDATA[
<p>这次测试针对本地搭建的 <code>xxCMS v1.6</code>。检查范围覆盖安装入口、前台注册与登录、内容发布、后台管理和数据库备份，共整理出 12 个风险点。</p>
<div class="hint-container caution">
<p class="hint-container-title">授权范围</p>
<p>本文中的请求、payload、命令和截图均来自自有靶场，只用于复盘验证方法。不要把这些步骤用于未获授权的系统。</p>
</div>
]]></summary>
    <content type="html"><![CDATA[
<p>这次测试针对本地搭建的 <code>xxCMS v1.6</code>。检查范围覆盖安装入口、前台注册与登录、内容发布、后台管理和数据库备份，共整理出 12 个风险点。</p>
<div class="hint-container caution">
<p class="hint-container-title">授权范围</p>
<p>本文中的请求、payload、命令和截图均来自自有靶场，只用于复盘验证方法。不要把这些步骤用于未获授权的系统。</p>
</div>
<!-- more -->
<h2>测试环境</h2>
<p>测试目标：</p>
<div class="language-text line-numbers-mode" data-highlighter="shiki" data-ext="text" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-text"><span class="line"><span>http://localhost/xxcms/</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><p>基础环境：</p>
<div class="language-text line-numbers-mode" data-highlighter="shiki" data-ext="text" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-text"><span class="line"><span>Apache/2.4.23 (Win32)</span></span>
<span class="line"><span>PHP/5.2.17</span></span>
<span class="line"><span>MySQL 5.5.53</span></span>
<span class="line"><span>xxCMS v1.6</span></span>
<span class="line"><span>字符集：gb2312 / gbk</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><figure><img src="/images/articles/cms-first-hunt/image-20260605082942665.png" alt="基础环境" tabindex="0" loading="lazy"><figcaption>基础环境</figcaption></figure>
<p>这是一套较旧的 PHP 与 CMS 环境，并使用 GBK 字符集。后续发现的宽字节注入和路径处理问题，都需要结合这些基础条件判断。</p>
<h2>攻击面梳理</h2>
<p>先用目录扫描定位公开入口，再结合 <code>robots.txt</code> 和手工访问确认功能。</p>
<div class="language-powershell line-numbers-mode" data-highlighter="shiki" data-ext="powershell" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-powershell"><span class="line"><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">python dirsearch.py </span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">-</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">u http:</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">//</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">localhost</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">/</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">xxcms</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">/</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2"> -</span><span style="--shiki-light:#383A42;--shiki-dark:#ABB2BF">e</span><span style="--shiki-light:#383A42;--shiki-dark:#56B6C2">*</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><figure><img src="/images/articles/cms-first-hunt/image-20260604154948984.png" alt="目录扫描" tabindex="0" loading="lazy"><figcaption>目录扫描</figcaption></figure>
<p>重点路径如下：</p>
<div class="language-text line-numbers-mode" data-highlighter="shiki" data-ext="text" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-text"><span class="line"><span>/admin/                       后台入口</span></span>
<span class="line"><span>/install/                     安装向导残留</span></span>
<span class="line"><span>/data/                        数据与备份目录</span></span>
<span class="line"><span>/templates/                   模板目录</span></span>
<span class="line"><span>/include/fckeditor/           富文本编辑器组件</span></span>
<span class="line"><span>/user.php                     前台登录、注册、支付和个人中心入口</span></span>
<span class="line"><span>/comment.php                  评论入口</span></span>
<span class="line"><span>/api/                         API 相关入口</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><figure><img src="/images/articles/cms-first-hunt/image-20260605084140732.png" alt="高价值路径" tabindex="0" loading="lazy"><figcaption>高价值路径</figcaption></figure>
<p>版本信息能够确认目标为 <code>xxCMS v1.6</code>，站点还保留了旧版 FCKeditor 的文件管理相关接口。</p>
<figure><img src="/images/articles/cms-first-hunt/image-20260605084645279.png" alt="xxCMS 版本" tabindex="0" loading="lazy"><figcaption>xxCMS 版本</figcaption></figure>
<figure><img src="/images/articles/cms-first-hunt/image-20260604164030973.png" alt="FCKeditor 接口" tabindex="0" loading="lazy"><figcaption>FCKeditor 接口</figcaption></figure>
<div class="language-text line-numbers-mode" data-highlighter="shiki" data-ext="text" style="--shiki-light:#383A42;--shiki-dark:#abb2bf;--shiki-light-bg:#FAFAFA;--shiki-dark-bg:#282c34"><pre class="shiki shiki-themes one-light one-dark-pro vp-code"><code class="language-text"><span class="line"><span>http://127.0.0.1/xxcms/include/fckeditor/editor/filemanager/connectors/test.html</span></span></code></pre>
<div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0"><div class="line-number"></div></div></div><figure><img src="/images/articles/cms-first-hunt/image-20260605084551982.png" alt="FCKeditor test 页面" tabindex="0" loading="lazy"><figcaption>FCKeditor test 页面</figcaption></figure>
<p>前台暴露注册、登录、新闻发布和支付流程；后台包含登录、模板管理、数据库管理和用户管理。</p>
<figure><img src="/images/articles/cms-first-hunt/image-20260605084850836.png" alt="前台功能入口" tabindex="0" loading="lazy"><figcaption>前台功能入口</figcaption></figure>
<h2>风险概览</h2>
<p>| 模块 | 问题 | 主要影响 |<br>
|</p>
]]></content>
    <category term="漏洞挖掘"/>
    <published>2026-06-06T00:00:00.000Z</published>
  </entry>
  <entry>
    <title type="text">0day安全：软件漏洞分析技术（第2版）</title>
    <id>https://map1ebr1dge.github.io/posts/PDF/0day%E5%AE%89%E5%85%A8-%E8%BD%AF%E4%BB%B6%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E6%8A%80%E6%9C%AF%E7%AC%AC2%E7%89%88.html</id>
    <link href="https://map1ebr1dge.github.io/posts/PDF/0day%E5%AE%89%E5%85%A8-%E8%BD%AF%E4%BB%B6%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E6%8A%80%E6%9C%AF%E7%AC%AC2%E7%89%88.html"/>
    <updated>2026-07-01T23:27:37.000Z</updated>
    <summary type="html"><![CDATA[

<p><a href="/pdfs/0day-security-vulnerability-analysis-2nd.pdf">下载 PDF</a></p>
]]></summary>
    <content type="html"><![CDATA[

<p><a href="/pdfs/0day-security-vulnerability-analysis-2nd.pdf">下载 PDF</a></p>
]]></content>
    <category term="PDF"/>
    <published>2026-06-07T00:00:00.000Z</published>
  </entry>
  <entry>
    <title type="text">Kali Linux 渗透测试技术详解</title>
    <id>https://map1ebr1dge.github.io/posts/PDF/Kali-Linux%E6%B8%97%E9%80%8F%E6%B5%8B%E8%AF%95%E6%8A%80%E6%9C%AF%E8%AF%A6%E8%A7%A3.html</id>
    <link href="https://map1ebr1dge.github.io/posts/PDF/Kali-Linux%E6%B8%97%E9%80%8F%E6%B5%8B%E8%AF%95%E6%8A%80%E6%9C%AF%E8%AF%A6%E8%A7%A3.html"/>
    <updated>2026-07-01T23:27:37.000Z</updated>
    <summary type="html"><![CDATA[

<p><a href="/pdfs/kali-linux-pentest-techniques.pdf">下载 PDF</a></p>
]]></summary>
    <content type="html"><![CDATA[

<p><a href="/pdfs/kali-linux-pentest-techniques.pdf">下载 PDF</a></p>
]]></content>
    <category term="PDF"/>
    <published>2026-06-07T00:00:00.000Z</published>
  </entry>
  <entry>
    <title type="text">现代密码学简介</title>
    <id>https://map1ebr1dge.github.io/posts/PDF/%E7%8E%B0%E4%BB%A3%E5%AF%86%E7%A0%81%E5%AD%A6%E7%AE%80%E4%BB%8B.html</id>
    <link href="https://map1ebr1dge.github.io/posts/PDF/%E7%8E%B0%E4%BB%A3%E5%AF%86%E7%A0%81%E5%AD%A6%E7%AE%80%E4%BB%8B.html"/>
    <updated>2026-07-01T23:27:37.000Z</updated>
    <summary type="html"><![CDATA[

<p><a href="/pdfs/modern-cryptography-introduction.pdf">下载 PDF</a></p>
]]></summary>
    <content type="html"><![CDATA[

<p><a href="/pdfs/modern-cryptography-introduction.pdf">下载 PDF</a></p>
]]></content>
    <category term="PDF"/>
    <published>2026-06-07T00:00:00.000Z</published>
  </entry>
</feed>