<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://www.5al.top/</id>
  <title type="text">星觅海</title>
  <subtitle type="text">记录生活与技术的小站</subtitle>
  <updated>2026-06-21T10:36:00.000Z</updated>
  <author><name>星觅海</name></author>
  <link rel="alternate" href="https://www.5al.top/"/>
  <link rel="self" href="https://www.5al.top/atom.xml"/>
  <generator uri="https://github.com/CuteLeaf/Firefly">Firefly v6.16.8</generator>
    <entry>
      <id>https://www.5al.top/posts/202606211836/</id>
      <title type="text">陪伴我三年的手机出现粉线了</title>
      <published>2026-06-21T10:36:00.000Z</published>
      <updated>2026-06-21T10:36:00.000Z</updated>
      <author><name>星觅海</name></author>
      <link rel="alternate" href="https://www.5al.top/posts/202606211836/"/>
      <summary type="text">用了三年的手机屏幕出现粉线，记录一下。</summary>
      <content type="html"><![CDATA[<p></p>]]></content>
    </entry>
    <entry>
      <id>https://www.5al.top/posts/ten-year-pact/</id>
      <title type="text">十年之约</title>
      <published>2026-06-13T16:00:00.000Z</published>
      <updated>2026-06-13T16:00:00.000Z</updated>
      <author><name>星觅海</name></author>
      <link rel="alternate" href="https://www.5al.top/posts/ten-year-pact/"/>
      <summary type="text">从 2026 年 6 月 14 日到 2036 年 6 月 14 日，一张会随时间自动走的十年进度卡片。</summary>
      <content type="html"><![CDATA[<section><h2>十年之约<a href="#十年之约"><span>#</span></a></h2><section><h3>效果<a href="#效果"><span>#</span></a></h3><p></p><hr /></section><section><h3>关于<a href="#关于"><span>#</span></a></h3><p>这是一个记录十年时光流逝的动态进度卡片。从 <strong>2026年6月14日</strong> 开始，到 <strong>2036年6月14日</strong> 结束，整整十年的约定。</p><hr /></section><section><h3>技术说明<a href="#技术说明"><span>#</span></a></h3><ul>
<li><strong>格式</strong>：SVG（可缩放矢量图形）</li>
<li><strong>尺寸</strong>：250 × 250（正方形）</li>
<li><strong>动态更新</strong>：使用 JavaScript 读取系统时间，每秒自动刷新进度</li>
<li><strong>进度显示</strong>：圆形进度环，红橙渐变</li>
</ul><hr /></section><section><h3>使用方法<a href="#使用方法"><span>#</span></a></h3><section><h4>1. 直接嵌入 HTML<a href="#1-直接嵌入-html"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>object</span><span> </span><span>data</span><span>=</span><span>"ten_year_promise.svg"</span><span> </span><span>type</span><span>=</span><span>"image/svg+xml"</span><span> </span><span>width</span><span>=</span><span>"300"</span><span> </span><span>height</span><span>=</span><span>"300"</span><span>&gt;&lt;/</span><span>object</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>或</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>iframe</span><span> </span><span>src</span><span>=</span><span>"ten_year_promise.svg"</span><span> </span><span>width</span><span>=</span><span>"250"</span><span> </span><span>height</span><span>=</span><span>"250"</span><span> </span><span>frameborder</span><span>=</span><span>"0"</span><span>&gt;&lt;/</span><span>iframe</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h4>2. 作为图片引用（静态显示）<a href="#2-作为图片引用静态显示"><span>#</span></a></h4><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>img</span><span> </span><span>src</span><span>=</span><span>"ten_year_promise.svg"</span><span> </span><span>alt</span><span>=</span><span>"十年之约"</span><span> </span><span>width</span><span>=</span><span>"250&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div></div><div>Note</div></div><div><p>注意：使用 <code>&lt;img&gt;</code> 标签时，SVG 内的 JavaScript 不会执行，进度不会动态更新。</p></div></div></section><section><h4>3. 内联 SVG<a href="#3-内联-svg"><span>#</span></a></h4><p>直接将 SVG 代码复制到 HTML 文件中，JavaScript 会自动运行。</p><hr /></section></section><section><h3>设计特点<a href="#设计特点"><span>#</span></a></h3>

<table><thead><tr><th>项目</th><th>说明</th></tr></thead><tbody><tr><td>背景</td><td>半透灰 <code>rgba(200, 200, 210, 0.45)</code>，带阴影</td></tr><tr><td>边框</td><td>白色半透明边框，双层设计</td></tr><tr><td>进度环</td><td>红橙渐变，圆角线帽</td></tr><tr><td>统计</td><td>已过去天数 / 剩余天数</td></tr><tr><td>时间戳</td><td>底部实时显示当前系统时间</td></tr></tbody></table><hr /></section><section><h3>当前进度<a href="#当前进度"><span>#</span></a></h3><div><div><div></div><div>Note</div></div><div><p>进度会随系统时间自动更新，每秒刷新一次。</p></div></div><hr /></section><section><h3>自定义修改<a href="#自定义修改"><span>#</span></a></h3><p>如需修改时间范围，编辑 SVG 文件中的以下部分：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>var</span><span><span> </span><span>startDate</span><span> </span></span><span>=</span><span> </span><span>new</span><span> </span><span>Date</span><span>(</span><span>"2026-06-14T00:00:00+08:00"</span><span>);</span></div></div><div><div><div>2</div></div><div><span>var</span><span><span> </span><span>endDate</span><span> </span></span><span>=</span><span> </span><span>new</span><span> </span><span>Date</span><span>(</span><span>"2036-06-14T00:00:00+08:00"</span><span>);</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>修改日期即可适配其他时间跨度。</p><hr /><p><em>愿十年之后，回首无悔。</em></p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://www.5al.top/posts/202606132250/</id>
      <title type="text">没错，我的博客的评论没了</title>
      <published>2026-06-13T14:50:00.000Z</published>
      <updated>2026-06-13T14:50:00.000Z</updated>
      <author><name>星觅海</name></author>
      <link rel="alternate" href="https://www.5al.top/posts/202606132250/"/>
      <summary type="text">把 Waline 从 Netlify 迁到 Vercel + neon，顺手修好邮件通知，加载速度也快了，代价是历史评论没了。</summary>
      <content type="html"><![CDATA[<section><h2>前言<a href="#前言"><span>#</span></a></h2><p>我用的是 <a href="https://github.com/walinejs/waline" target="_blank">Waline</a> 评论系统，之前部署在 <a href="https://app.netlify.com/" target="_blank">Netlify</a> 上，存储好像用的是 <a href="https://mongodb.com/" target="_blank">MongoDB</a> —— 忘了🤪</p><p>一段时间没管，也不知道干了什么：</p><ul>
<li>邮件通知挂了</li>
<li>评论加载特别慢</li>
</ul><p>干脆重新在 <a href="https://vercel.com/" target="_blank">Vercel</a> 部署了一个。</p><hr /></section>
<section><h2>重新部署 Waline 到 Vercel<a href="#重新部署-waline-到-vercel"><span>#</span></a></h2><section><h3>1. 一键部署<a href="#1-一键部署"><span>#</span></a></h3><p>Waline 官方支持 Vercel 一键部署，非常省心：</p><ol>
<li>打开 <a href="https://waline.js.org/guide/get-started.html" target="_blank">Waline 官方文档</a></li>
<li>点击 <strong>“Deploy with Vercel”</strong> 按钮</li>
<li>登录 GitHub 账号授权</li>
<li>填写必要的环境变量（见下文）</li>
<li>等待部署完成</li>
</ol></section><section><h3>2. 环境变量配置<a href="#2-环境变量配置"><span>#</span></a></h3><p>在 Vercel 项目的 <strong>Settings → Environment Variables</strong> 中配置：</p>

<table><thead><tr><th>变量名</th><th>说明</th><th>示例</th></tr></thead><tbody><tr><td><code>LEAN_ID</code></td><td>LeanCloud App ID</td><td>你的 LeanCloud App ID</td></tr><tr><td><code>LEAN_KEY</code></td><td>LeanCloud App Key</td><td>你的 LeanCloud App Key</td></tr><tr><td><code>LEAN_MASTER_KEY</code></td><td>LeanCloud Master Key</td><td>你的 LeanCloud Master Key</td></tr><tr><td><code>SITE_NAME</code></td><td>站点名称</td><td>我的博客</td></tr><tr><td><code>SITE_URL</code></td><td>站点地址</td><td><a href="https://yourblog.com" target="_blank">https://yourblog.com</a></td></tr><tr><td><code>SMTP_SERVICE</code></td><td>SMTP 服务商</td><td>例如 QQ / 163 / Gmail</td></tr><tr><td><code>SMTP_USER</code></td><td>发件邮箱</td><td><a href="#">your-email@example.com</a></td></tr><tr><td><code>SMTP_PASS</code></td><td>邮箱授权码</td><td>你的授权码</td></tr><tr><td><code>AUTHOR_EMAIL</code></td><td>博主邮箱</td><td>用于接收通知</td></tr></tbody></table><blockquote><p>💡 <strong>关于存储</strong>：Waline 支持多种数据库，包括 MongoDB、PostgreSQL、MySQL、SQLite、LeanCloud 等。我这次用的是 neon，免费额度够用，而且不用自己维护 MongoDB。</p></blockquote></section><section><h3>3. 绑定自定义域名（可选）<a href="#3-绑定自定义域名可选"><span>#</span></a></h3><ol>
<li>进入 Vercel 项目 → <strong>Settings → Domains</strong></li>
<li>输入你的域名，例如 <code>waline.yourblog.com</code></li>
<li>在域名服务商处添加 CNAME 记录：</li>
</ol>

<table><thead><tr><th>Type</th><th>Name</th><th>Value</th></tr></thead><tbody><tr><td>CNAME</td><td>waline</td><td>cname.vercel-dns.com</td></tr></tbody></table><ol>
<li>等待 DNS 生效，访问 <code>https://waline.yourblog.com/ui/register</code> 注册管理员账号</li>
</ol><div><div><div></div><div>Note</div></div><div><p>⚠️ <strong>注意</strong>：首个注册的用户自动成为管理员，务必第一时间注册！</p></div></div><hr /></section></section>
<section><h2>修复邮件通知<a href="#修复邮件通知"><span>#</span></a></h2><p>之前邮件通知挂了，这次重新配置了一下，以下是常见问题的排查思路：</p><section><h3>检查环境变量<a href="#检查环境变量"><span>#</span></a></h3><p>确保以下变量都已正确配置：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>SMTP_SERVICE</span><span>=</span><span>QQ</span><span>          </span><span># 或其他服务商</span></div></div><div><div><div>2</div></div><div><span>SMTP_USER</span><span>=</span><span>your@qq.com</span></div></div><div><div><div>3</div></div><div><span>SMTP_PASS</span><span>=</span><span>你的授权码</span><span>       </span><span># 不是登录密码！是 SMTP 授权码</span></div></div><div><div><div>4</div></div><div><span>AUTHOR_EMAIL</span><span>=</span><span>your@qq.com</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>常见 SMTP 服务商配置<a href="#常见-smtp-服务商配置"><span>#</span></a></h3>

<table><thead><tr><th>服务商</th><th><code>SMTP_SERVICE</code></th><th>端口</th></tr></thead><tbody><tr><td>QQ 邮箱</td><td><code>QQ</code></td><td>465</td></tr><tr><td>163 邮箱</td><td><code>163</code></td><td>465</td></tr><tr><td>Gmail</td><td><code>Gmail</code></td><td>465</td></tr><tr><td>Outlook</td><td><code>Outlook365</code></td><td>587</td></tr></tbody></table></section><section><h3>测试邮件发送<a href="#测试邮件发送"><span>#</span></a></h3><p>部署完成后，可以在 Waline 管理后台发一条测试评论，看看能否收到邮件通知。如果收不到：</p><ol>
<li>检查 Vercel 的 <strong>Runtime Logs</strong> 是否有报错</li>
<li>确认邮箱授权码是否正确（不是登录密码）</li>
<li>检查垃圾邮件箱</li>
</ol><hr /></section></section>
<section><h2>客户端接入<a href="#客户端接入"><span>#</span></a></h2><p>在你的博客页面中引入 Waline 客户端：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;!-- 引入 Waline CSS --&gt;</span></div></div><div><div><div>2</div></div><div><span>&lt;</span><span>link</span><span> </span><span>rel</span><span>=</span><span>"stylesheet"</span><span> </span><span>href</span><span>=</span><span>"https://unpkg.com/@waline/client@v3/dist/waline.css"</span><span> /&gt;</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>&lt;!-- 引入 Waline JS --&gt;</span></div></div><div><div><div>5</div></div><div><span>&lt;</span><span>script</span><span> </span><span>src</span><span>=</span><span>"https://unpkg.com/@waline/client@v3/dist/waline.js"</span><span>&gt;&lt;/</span><span>script</span><span>&gt;</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>&lt;</span><span>script</span><span>&gt;</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>Waline</span><span>.</span><span>init</span><span>({</span></div></div><div><div><div>9</div></div><div><span>    </span><span>el</span><span>:</span><span> </span><span>'#waline'</span><span>,</span></div></div><div><div><div>10</div></div><div><span>    </span><span>serverURL</span><span>:</span><span> </span><span>'https://waline.yourblog.com'</span><span>,  </span><span>// 你的 Waline 服务地址</span></div></div><div><div><div>11</div></div><div><span>    </span><span>path</span><span>:</span><span><span> </span><span>window</span><span>.</span></span><span>location</span><span>.</span><span>pathname</span><span>,</span></div></div><div><div><div>12</div></div><div><span>    </span><span>// 可选配置</span></div></div><div><div><div>13</div></div><div><span>    </span><span>dark</span><span>:</span><span> </span><span>'html[data-theme="dark"]'</span><span>,</span></div></div><div><div><div>14</div></div><div><span>    </span><span>comment</span><span>:</span><span> </span><span>true</span><span>,</span></div></div><div><div><div>15</div></div><div><span>    </span><span>pageview</span><span>:</span><span> </span><span>true</span><span>,</span></div></div><div><div><div>16</div></div><div><span><span>  </span></span><span>});</span></div></div><div><div><div>17</div></div><div><span>&lt;/</span><span>script</span><span>&gt;</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>&lt;!-- 评论容器 --&gt;</span></div></div><div><div><div>20</div></div><div><span>&lt;</span><span>div</span><span> </span><span>id</span><span>=</span><span>"waline"</span><span>&gt;&lt;/</span><span>div</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><div><div><div></div><div>Note</div></div><div><p>📌 <strong>注意</strong>：V3 版本不需要手动引入 CSS，但为了兼容性建议保留。如果使用的是旧版 Waline，请根据文档调整。</p></div></div><hr /></section>
<section><h2>数据迁移（可选）<a href="#数据迁移可选"><span>#</span></a></h2><p>如果你之前的数据还在 MongoDB 里，想迁移到新部署的 Waline，可以：</p><ol>
<li><strong>导出旧数据</strong>：从 MongoDB 导出 JSON/CSV</li>
<li><strong>导入新数据库</strong>：根据新部署使用的数据库类型，使用对应工具导入</li>
<li><strong>或者直接放弃</strong>：评论不多就重新来过，反正博客也没几个人看（不是</li>
</ol><div><div><div></div><div>Note</div></div><div><p>😅 我这次就是直接放弃了旧数据，反正之前的评论也没几条，重新来过更清爽。</p></div></div><hr /></section>
<section><h2>总结<a href="#总结"><span>#</span></a></h2>

<table><thead><tr><th>项目</th><th>之前</th><th>现在</th></tr></thead><tbody><tr><td>部署平台</td><td>Netlify</td><td>Vercel</td></tr><tr><td>数据库</td><td>MongoDB（忘了）</td><td>neon</td></tr><tr><td>邮件通知</td><td>❌ 挂了</td><td>✅ 正常</td></tr><tr><td>加载速度</td><td>🐢 特别慢</td><td>🚀 飞快</td></tr><tr><td>历史评论</td><td>😭 没了</td><td>🆕 重新开始</td></tr></tbody></table><p>虽然评论没了有点心疼，但重新部署后一切正常，邮件通知也恢复了，加载速度肉眼可见地提升。Vercel + neon 的组合对于个人博客来说完全够用，而且免费。</p><p>下次一定要记得备份数据！</p><hr /><div><div><div></div><div>Note</div></div><div><p>📝 本文写于 2026-06-13，Waline 版本为 v3.15.2，如有变动请以 <a href="https://waline.js.org/" target="_blank">官方文档</a> 为准。</p></div></div></section>]]></content>
    </entry>
    <entry>
      <id>https://www.5al.top/posts/202509301932/</id>
      <title type="text">蓝奏云直链解析工具</title>
      <published>2025-09-30T11:32:00.000Z</published>
      <updated>2025-09-30T11:32:00.000Z</updated>
      <author><name>星觅海</name></author>
      <link rel="alternate" href="https://www.5al.top/posts/202509301932/"/>
      <summary type="text">基于 Cloudflare Worker 的蓝奏云直链解析工具，一键获取真实下载地址，跳过中间页与广告，支持调试模式。</summary>
      <content type="html"><![CDATA[<div><div><div></div><div>体验地址</div></div><div><p><a href="https://lanzou.jasmiam.top" target="_blank">lanzou.jasmiam.top</a></p></div></div>
<a href="https://github.com/xingmihai/Lanzou_straight_chain_analysis" target="_blank"><div><div><div><div></div><div>xingmihai</div></div><div>/</div><div>Lanzou_straight_chain_analysis</div></div><div></div></div><div>蓝奏云直链解析</div><div><div>—</div><div>—</div><div>MIT</div><span>JavaScript</span></div></a>
<section><h3>或最下面直接复制<a href="#或最下面直接复制"><span>#</span></a></h3></section>
<section><h1>🌟 蓝奏云直链解析工具<a href="#-蓝奏云直链解析工具"><span>#</span></a></h1><p>一个基于 Cloudflare Worker 的蓝奏云直链解析工具，可以一键获取蓝奏云分享链接的真实下载地址，跳过所有中间页面和广告。</p><section><h2>✨ 功能特点<a href="#-功能特点"><span>#</span></a></h2><ul>
<li>🚀 <strong>一键解析</strong>：自动解析蓝奏云分享链接</li>
<li>⚡ <strong>直接下载</strong>：重定向到真实文件下载地址</li>
<li>🔍 <strong>调试模式</strong>：支持详细解析过程查看</li>
<li>🌐 <strong>跨平台</strong>：在任何设备浏览器中均可使用</li>
<li>🆓 <strong>完全免费</strong>：基于 Cloudflare Worker 免费额度</li>
<li>🔒 <strong>隐私安全</strong>：不存储任何用户数据</li>
</ul></section><section><h2>🚀 快速开始<a href="#-快速开始"><span>#</span></a></h2><section><h3>基本用法<a href="#基本用法"><span>#</span></a></h3><ol>
<li>
<p><strong>获取蓝奏云分享链接</strong>
例如：<a href="https://hjgg.lanzoue.com/iN4ig37czcte" target="_blank">https://hjgg.lanzoue.com/iN4ig37czcte</a></p>
</li>
<li>
<p><strong>访问解析工具</strong>
<a href="https://%E4%BD%A0%E7%9A%84worker.workers.dev/?url=%E4%BD%A0%E7%9A%84%E8%93%9D%E5%A5%8F%E4%BA%91%E9%93%BE%E6%8E%A5" target="_blank">https://你的worker.workers.dev/?url=你的蓝奏云链接</a></p>
</li>
<li>
<p><strong>自动开始下载</strong></p>
</li>
</ol><ul>
<li>浏览器会自动重定向到真实下载地址</li>
</ul></section><section><h3>示例<a href="#示例"><span>#</span></a></h3><p>直接下载模式</p><p><a href="https://%E4%BD%A0%E7%9A%84worker.workers.dev/?url=https://wwi.lanzoup.com/i12345678" target="_blank">https://你的worker.workers.dev/?url=https://wwi.lanzoup.com/i12345678</a></p><p>调试查看模式</p><p><a href="https://%E4%BD%A0%E7%9A%84worker.workers.dev/?url=https://wwi.lanzoup.com/i12345678&amp;debug=1" target="_blank">https://你的worker.workers.dev/?url=https://wwi.lanzoup.com/i12345678&amp;debug=1</a></p></section></section><section><h2>🛠️ 部署到 Cloudflare Worker<a href="#️-部署到-cloudflare-worker"><span>#</span></a></h2><section><h3>前置要求<a href="#前置要求"><span>#</span></a></h3><ul>
<li><a href="https://dash.cloudflare.com/sign-up" target="_blank">Cloudflare 账号</a></li>
<li>自定义域名（可选）</li>
<li>基本的计算机操作能力</li>
</ul></section><section><h3>部署步骤<a href="#部署步骤"><span>#</span></a></h3><ol>
<li>
<p><strong>注册 Cloudflare 账号</strong></p>
<ul>
<li>访问 <a href="https://dash.cloudflare.com/sign-up" target="_blank">Cloudflare 注册页面</a></li>
<li>完成邮箱验证</li>
</ul>
</li>
<li>
<p><strong>创建 Worker 服务</strong></p>
<ul>
<li>登录 <a href="https://dash.cloudflare.com/" target="_blank">Cloudflare 仪表板</a></li>
<li>左侧菜单选择 “Workers”</li>
<li>点击 “Create a Service”</li>
</ul>
</li>
<li>
<p><strong>配置服务设置</strong></p>
<ul>
<li>服务名称：<code>lanzou-parser</code>（或其他名称）</li>
<li>选择免费计划</li>
</ul>
</li>
<li>
<p><strong>上传代码</strong></p>
<ul>
<li>复制本项目的完整代码</li>
<li>在 Worker 编辑器中粘贴代码</li>
<li>点击 “Save and Deploy”</li>
</ul>
</li>
<li>
<p><strong>测试部署</strong></p>
<ul>
<li>访问你的 Worker 地址进行测试</li>
<li>格式：<code>https://你的服务名称.你的账号子域.workers.dev</code></li>
</ul>
</li>
</ol></section></section><section><h2>🔒 隐私与安全<a href="#-隐私与安全"><span>#</span></a></h2><ul>
<li>❌ 不存储用户文件内容</li>
<li>❌ 不记录下载历史</li>
<li>❌ 不收集个人信息</li>
<li>✅ 所有处理在内存中完成</li>
<li>✅ HTTPS加密传输</li>
<li>✅ Cloudflare安全防护</li>
</ul></section><section><h2>❓ 常见问题<a href="#-常见问题"><span>#</span></a></h2><section><h3>Q1: 为什么需要这个工具？<a href="#q1-为什么需要这个工具"><span>#</span></a></h3><p>A: 蓝奏云下载需要多次跳转和等待，这个工具可以跳过广告和等待时间，直接获取真实下载链接。</p></section><section><h3>Q2: 是否支持所有蓝奏云链接？<a href="#q2-是否支持所有蓝奏云链接"><span>#</span></a></h3><p>A: 支持大部分链接，但需要提取码的私密链接或已失效的链接可能无法解析。</p></section><section><h3>Q3: 有没有使用限制？<a href="#q3-有没有使用限制"><span>#</span></a></h3><p>A: Cloudflare Worker免费版有每日10万次请求限制，个人使用完全足够。</p></section><section><h3>Q4: 如何更新解析逻辑？<a href="#q4-如何更新解析逻辑"><span>#</span></a></h3><p>A: 直接在Cloudflare Worker编辑器中更新代码并重新部署。</p></section></section><section><h2>📞 技术支持<a href="#-技术支持"><span>#</span></a></h2><p>如果遇到问题：</p><ol>
<li>首先使用 <code>&amp;debug=1</code> 参数查看详细错误信息</li>
<li>检查蓝奏云链接是否有效</li>
<li>在GitHub Issues中提交问题</li>
</ol></section><section><h2>📄 许可证<a href="#-许可证"><span>#</span></a></h2><p>本项目采用 MIT 许可证 - 详见 <a href="LICENSE">LICENSE</a> 文件。</p></section><section><h2>🤝 贡献<a href="#-贡献"><span>#</span></a></h2><p>欢迎提交 Issue 和 Pull Request！</p><hr /><p><strong>⭐ 如果这个项目对你有帮助，请给个Star！</strong></p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>addEventListener</span><span>(</span><span>"fetch"</span><span><span>, </span><span>event</span><span> </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>event</span><span>.</span><span>respondWith</span><span>(</span><span>handleRequest</span><span><span>(</span><span>event</span><span>.</span></span><span>request</span><span>));</span></div></div><div><div><div>3</div></div><div><span>});</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>async</span><span> </span><span>function</span><span> </span><span>handleRequest</span><span><span>(</span><span>request</span><span>) {</span></span></div></div><div><div><div>6</div></div><div><span>    </span><span>const</span><span> </span><span>url</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>URL</span><span><span>(</span><span>request</span><span>.</span></span><span>url</span><span>);</span></div></div><div><div><div>7</div></div><div><span>    </span><span>const</span><span> </span><span>targetUrl</span><span> </span><span>=</span><span><span> </span><span>url</span><span>.</span></span><span>searchParams</span><span>.</span><span>get</span><span>(</span><span>"url"</span><span>);</span></div></div><div><div><div>8</div></div><div><span>    </span><span>const</span><span> </span><span>debug</span><span> </span><span>=</span><span><span> </span><span>url</span><span>.</span></span><span>searchParams</span><span>.</span><span>get</span><span>(</span><span>"debug"</span><span>);</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>    </span><span>// 如果没有提供 URL 参数，返回使用说明</span></div></div><div><div><div>11</div></div><div><span>    </span><span>if</span><span> (</span><span>!</span><span><span>targetUrl</span><span>) {</span></span></div></div><div><div><div>12</div></div><div><span>        </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>`</span></div></div><div><div><div>13</div></div><div><span><span>            </span></span><span>&lt;!DOCTYPE html&gt;</span></div></div><div><div><div>14</div></div><div><span><span>            </span></span><span>&lt;html&gt;</span></div></div><div><div><div>15</div></div><div><span><span>            </span></span><span>&lt;head&gt;</span></div></div><div><div><div>16</div></div><div><span><span>                </span></span><span>&lt;title&gt;蓝奏云直链下载工具&lt;/title&gt;</span></div></div><div><div><div>17</div></div><div><span><span>                </span></span><span>&lt;style&gt;</span></div></div><div><div><div>18</div></div><div><span><span>                    </span></span><span>body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }</span></div></div><div><div><div>19</div></div><div><span><span>                    </span></span><span>.container { background: #f8f9fa; padding: 30px; border-radius: 10px; }</span></div></div><div><div><div>20</div></div><div><span><span>                    </span></span><span>code { background: #eee; padding: 2px 5px; border-radius: 3px; }</span></div></div><div><div><div>21</div></div><div><span><span>                    </span></span><span>.example { background: #e8f4f8; padding: 15px; border-left: 4px solid #007bff; }</span></div></div><div><div><div>22</div></div><div><span><span>                    </span></span><span>.debug { background: #fff3cd; padding: 15px; border-left: 4px solid #ffc107; }</span></div></div><div><div><div>23</div></div><div><span><span>                </span></span><span>&lt;/style&gt;</span></div></div><div><div><div>24</div></div><div><span><span>            </span></span><span>&lt;/head&gt;</span></div></div><div><div><div>25</div></div><div><span><span>            </span></span><span>&lt;body&gt;</span></div></div><div><div><div>26</div></div><div><span><span>                </span></span><span>&lt;div class="container"&gt;</span></div></div><div><div><div>27</div></div><div><span><span>                    </span></span><span>&lt;h1&gt;🔗 蓝奏云直链下载工具&lt;/h1&gt;</span></div></div><div><div><div>28</div></div><div><span><span>                    </span></span><span>&lt;p&gt;使用方式：在URL后添加 &lt;code&gt;?url=你的蓝奏云链接&lt;/code&gt;&lt;/p&gt;</span></div></div><div><div><div>29</div></div><div>
</div></div><div><div><div>30</div></div><div><span><span>                    </span></span><span>&lt;div class="example"&gt;</span></div></div><div><div><div>31</div></div><div><span><span>                        </span></span><span>&lt;strong&gt;示例：&lt;/strong&gt;&lt;br&gt;</span></div></div><div><div><div>32</div></div><div><span><span>                        </span></span><span>&lt;code&gt;https://你的worker.workers.dev/?url=https://wwi.lanzoup.com/xxxxxxxx&lt;/code&gt;</span></div></div><div><div><div>33</div></div><div><span><span>                    </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>34</div></div><div>
</div></div><div><div><div>35</div></div><div><span><span>                    </span></span><span>&lt;p&gt;📝 功能：&lt;/p&gt;</span></div></div><div><div><div>36</div></div><div><span><span>                    </span></span><span>&lt;ul&gt;</span></div></div><div><div><div>37</div></div><div><span><span>                        </span></span><span>&lt;li&gt;自动解析蓝奏云分享链接&lt;/li&gt;</span></div></div><div><div><div>38</div></div><div><span><span>                        </span></span><span>&lt;li&gt;直接重定向到文件下载&lt;/li&gt;</span></div></div><div><div><div>39</div></div><div><span><span>                    </span></span><span>&lt;/ul&gt;</span></div></div><div><div><div>40</div></div><div>
</div></div><div><div><div>41</div></div><div><span><span>                    </span></span><span>&lt;div class="debug"&gt;</span></div></div><div><div><div>42</div></div><div><span><span>                        </span></span><span>&lt;p&gt;🔍 &lt;strong&gt;调试模式&lt;/strong&gt;：添加 &lt;code&gt;&amp;debug=1&lt;/code&gt; 参数查看解析过程&lt;/p&gt;</span></div></div><div><div><div>43</div></div><div><span><span>                        </span></span><span>&lt;code&gt;https://你的worker.workers.dev/?url=蓝奏云链接&amp;debug=1&lt;/code&gt;</span></div></div><div><div><div>44</div></div><div><span><span>                    </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>45</div></div><div><span><span>                </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>46</div></div><div><span><span>            </span></span><span>&lt;/body&gt;</span></div></div><div><div><div>47</div></div><div><span><span>            </span></span><span>&lt;/html&gt;</span></div></div><div><div><div>48</div></div><div><span><span>        </span></span><span>`</span><span>, {</span></div></div><div><div><div>49</div></div><div><span>            </span><span>headers</span><span>:</span><span> {</span></div></div><div><div><div>50</div></div><div><span>                </span><span>"Content-Type"</span><span>:</span><span> </span><span>"text/html; charset=utf-8"</span></div></div><div><div><div>51</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>52</div></div><div><span><span>        </span></span><span>});</span></div></div><div><div><div>53</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>54</div></div><div>
</div></div><div><div><div>55</div></div><div><span>    </span><span>try</span><span> {</span></div></div><div><div><div>56</div></div><div><span>        </span><span>// 解析蓝奏云链接</span></div></div><div><div><div>57</div></div><div><span>        </span><span>const</span><span> { </span><span>finalUrl</span><span>, </span><span>logs</span><span> } </span><span>=</span><span> </span><span>await</span><span> </span><span>parseLanzouUrl</span><span><span>(</span><span>targetUrl</span><span>);</span></span></div></div><div><div><div>58</div></div><div>
</div></div><div><div><div>59</div></div><div><span>        </span><span>// 调试模式：显示解析过程</span></div></div><div><div><div>60</div></div><div><span>        </span><span>if</span><span><span> (</span><span>debug</span><span>) {</span></span></div></div><div><div><div>61</div></div><div><span>            </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>`</span></div></div><div><div><div>62</div></div><div><span><span>                </span></span><span>&lt;!DOCTYPE html&gt;</span></div></div><div><div><div>63</div></div><div><span><span>                </span></span><span>&lt;html&gt;</span></div></div><div><div><div>64</div></div><div><span><span>                </span></span><span>&lt;head&gt;</span></div></div><div><div><div>65</div></div><div><span><span>                    </span></span><span>&lt;title&gt;蓝奏云解析调试模式&lt;/title&gt;</span></div></div><div><div><div>66</div></div><div><span><span>                    </span></span><span>&lt;style&gt;</span></div></div><div><div><div>67</div></div><div><span><span>                        </span></span><span>body { font-family: 'Courier New', monospace; background: #000; color: #00ff00; padding: 20px; }</span></div></div><div><div><div>68</div></div><div><span><span>                        </span></span><span>.log-entry { margin: 10px 0; padding: 5px; border-left: 3px solid transparent; }</span></div></div><div><div><div>69</div></div><div><span><span>                        </span></span><span>.info { border-left-color: #17a2b8; color: #17a2b8; }</span></div></div><div><div><div>70</div></div><div><span><span>                        </span></span><span>.success { border-left-color: #28a745; color: #28a745; }</span></div></div><div><div><div>71</div></div><div><span><span>                        </span></span><span>.warning { border-left-color: #ffc107; color: #ffc107; }</span></div></div><div><div><div>72</div></div><div><span><span>                        </span></span><span>.error { border-left-color: #dc3545; color: #dc3545; }</span></div></div><div><div><div>73</div></div><div><span><span>                        </span></span><span>.timestamp { color: #6c757d; font-size: 12px; margin-right: 10px; }</span></div></div><div><div><div>74</div></div><div><span><span>                        </span></span><span>.result { background: #155724; padding: 15px; border-radius: 5px; margin: 20px 0; }</span></div></div><div><div><div>75</div></div><div><span><span>                        </span></span><span>.final-url { color: #00ffff; word-break: break-all; }</span></div></div><div><div><div>76</div></div><div><span><span>                        </span></span><span>.action-buttons { margin: 20px 0; }</span></div></div><div><div><div>77</div></div><div><span><span>                        </span></span><span>button { background: #007bff; color: white; border: none; padding: 10px 15px; border-radius: 5px; cursor: pointer; margin-right: 10px; }</span></div></div><div><div><div>78</div></div><div><span><span>                        </span></span><span>button:hover { background: #0056b3; }</span></div></div><div><div><div>79</div></div><div><span><span>                    </span></span><span>&lt;/style&gt;</span></div></div><div><div><div>80</div></div><div><span><span>                </span></span><span>&lt;/head&gt;</span></div></div><div><div><div>81</div></div><div><span><span>                </span></span><span>&lt;body&gt;</span></div></div><div><div><div>82</div></div><div><span><span>                    </span></span><span>&lt;h1&gt;🔍 蓝奏云解析调试模式&lt;/h1&gt;</span></div></div><div><div><div>83</div></div><div>
</div></div><div><div><div>84</div></div><div><span><span>                    </span></span><span>&lt;div id="logs"&gt;</span></div></div><div><div><div>85</div></div><div><span>                        </span><span>${</span><span>logs</span><span>.</span><span>map</span><span><span>(</span><span>log</span></span><span> </span><span>=&gt;</span><span> </span><span>`</span></div></div><div><div><div>86</div></div><div><span><span>                            </span></span><span>&lt;div class="log-entry </span><span>${</span><span>log</span><span>.</span><span>type</span><span>}</span><span>"&gt;</span></div></div><div><div><div>87</div></div><div><span><span>                                </span></span><span>&lt;span class="timestamp"&gt;</span><span>${</span><span>log</span><span>.</span><span>time</span><span>}</span><span>&lt;/span&gt; </span><span>${</span><span>log</span><span>.</span><span>message</span><span>}</span></div></div><div><div><div>88</div></div><div><span><span>                            </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>89</div></div><div><span><span>                        </span></span><span>`</span><span>)</span><span>.</span><span>join</span><span>(</span><span>''</span><span>)</span><span>}</span></div></div><div><div><div>90</div></div><div><span><span>                    </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>91</div></div><div>
</div></div><div><div><div>92</div></div><div><span><span>                    </span></span><span>&lt;div class="result"&gt;</span></div></div><div><div><div>93</div></div><div><span><span>                        </span></span><span>&lt;h3&gt;✅ 解析成功！&lt;/h3&gt;</span></div></div><div><div><div>94</div></div><div><span><span>                        </span></span><span>&lt;p&gt;&lt;strong&gt;最终直链：&lt;/strong&gt;&lt;/p&gt;</span></div></div><div><div><div>95</div></div><div><span><span>                        </span></span><span>&lt;p class="final-url"&gt;</span><span>${</span><span>finalUrl</span><span>}</span><span>&lt;/p&gt;</span></div></div><div><div><div>96</div></div><div><span><span>                    </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>97</div></div><div>
</div></div><div><div><div>98</div></div><div><span><span>                    </span></span><span>&lt;div class="action-buttons"&gt;</span></div></div><div><div><div>99</div></div><div><span><span>                        </span></span><span>&lt;button onclick="window.open('</span><span>${</span><span>finalUrl</span><span>}</span><span>')"&gt;⬇️ 直接下载&lt;/button&gt;</span></div></div><div><div><div>100</div></div><div><span><span>                        </span></span><span>&lt;button onclick="window.location.href='/?url=</span><span>${</span><span>encodeURIComponent</span><span><span>(</span><span>targetUrl</span><span>)</span></span><span>}</span><span>'"&gt;🚀 直接访问（非调试模式）&lt;/button&gt;</span></div></div><div><div><div>101</div></div><div><span><span>                        </span></span><span>&lt;button onclick="copyToClipboard('</span><span>${</span><span>finalUrl</span><span>}</span><span>')"&gt;📋 复制直链&lt;/button&gt;</span></div></div><div><div><div>102</div></div><div><span><span>                    </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>103</div></div><div>
</div></div><div><div><div>104</div></div><div><span><span>                    </span></span><span>&lt;script&gt;</span></div></div><div><div><div>105</div></div><div><span><span>                        </span></span><span>function copyToClipboard(text) {</span></div></div><div><div><div>106</div></div><div><span><span>                            </span></span><span>navigator.clipboard.writeText(text).then(() =&gt; {</span></div></div><div><div><div>107</div></div><div><span><span>                                </span></span><span>alert('已复制到剪贴板！');</span></div></div><div><div><div>108</div></div><div><span><span>                            </span></span><span>});</span></div></div><div><div><div>109</div></div><div><span><span>                        </span></span><span>}</span></div></div><div><div><div>110</div></div><div><span><span>                    </span></span><span>&lt;/script&gt;</span></div></div><div><div><div>111</div></div><div><span><span>                </span></span><span>&lt;/body&gt;</span></div></div><div><div><div>112</div></div><div><span><span>                </span></span><span>&lt;/html&gt;</span></div></div><div><div><div>113</div></div><div><span><span>            </span></span><span>`</span><span>, {</span></div></div><div><div><div>114</div></div><div><span>                </span><span>headers</span><span>:</span><span> {</span></div></div><div><div><div>115</div></div><div><span>                    </span><span>"Content-Type"</span><span>:</span><span> </span><span>"text/html; charset=utf-8"</span></div></div><div><div><div>116</div></div><div><span><span>                </span></span><span>}</span></div></div><div><div><div>117</div></div><div><span><span>            </span></span><span>});</span></div></div><div><div><div>118</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>119</div></div><div>
</div></div><div><div><div>120</div></div><div><span>        </span><span>// 正常模式：直接重定向到下载链接</span></div></div><div><div><div>121</div></div><div><span>        </span><span>return</span><span><span> </span><span>Response</span><span>.</span></span><span>redirect</span><span><span>(</span><span>finalUrl</span><span>, </span></span><span>302</span><span>);</span></div></div><div><div><div>122</div></div><div>
</div></div><div><div><div>123</div></div><div><span><span>    </span></span><span>} </span><span>catch</span><span><span> (</span><span>error</span><span>) {</span></span></div></div><div><div><div>124</div></div><div><span>        </span><span>// 返回错误页面</span></div></div><div><div><div>125</div></div><div><span>        </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>`</span></div></div><div><div><div>126</div></div><div><span><span>            </span></span><span>&lt;!DOCTYPE html&gt;</span></div></div><div><div><div>127</div></div><div><span><span>            </span></span><span>&lt;html&gt;</span></div></div><div><div><div>128</div></div><div><span><span>            </span></span><span>&lt;head&gt;</span></div></div><div><div><div>129</div></div><div><span><span>                </span></span><span>&lt;title&gt;解析失败&lt;/title&gt;</span></div></div><div><div><div>130</div></div><div><span><span>                </span></span><span>&lt;style&gt;</span></div></div><div><div><div>131</div></div><div><span><span>                    </span></span><span>body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }</span></div></div><div><div><div>132</div></div><div><span><span>                    </span></span><span>.error { color: #dc3545; background: #f8d7da; padding: 20px; border-radius: 5px; }</span></div></div><div><div><div>133</div></div><div><span><span>                    </span></span><span>.debug-link { color: #007bff; text-decoration: underline; cursor: pointer; }</span></div></div><div><div><div>134</div></div><div><span><span>                </span></span><span>&lt;/style&gt;</span></div></div><div><div><div>135</div></div><div><span><span>            </span></span><span>&lt;/head&gt;</span></div></div><div><div><div>136</div></div><div><span><span>            </span></span><span>&lt;body&gt;</span></div></div><div><div><div>137</div></div><div><span><span>                </span></span><span>&lt;h1&gt;❌ 解析失败&lt;/h1&gt;</span></div></div><div><div><div>138</div></div><div><span><span>                </span></span><span>&lt;div class="error"&gt;</span></div></div><div><div><div>139</div></div><div><span><span>                    </span></span><span>&lt;p&gt;&lt;strong&gt;错误信息:&lt;/strong&gt; </span><span>${</span><span>error</span><span>.</span><span>message</span><span>}</span><span>&lt;/p&gt;</span></div></div><div><div><div>140</div></div><div><span><span>                    </span></span><span>&lt;p&gt;&lt;strong&gt;目标链接:&lt;/strong&gt; </span><span>${</span><span>targetUrl</span><span>}</span><span>&lt;/p&gt;</span></div></div><div><div><div>141</div></div><div><span><span>                </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>142</div></div><div><span><span>                </span></span><span>&lt;p&gt;💡 请检查链接是否正确&lt;/p&gt;</span></div></div><div><div><div>143</div></div><div><span><span>                </span></span><span>&lt;p&gt;</span></div></div><div><div><div>144</div></div><div><span><span>                    </span></span><span>&lt;a href="/"&gt;返回首页&lt;/a&gt; |</span></div></div><div><div><div>145</div></div><div><span><span>                    </span></span><span>&lt;span class="debug-link" onclick="location.href=location.href + '&amp;debug=1'"&gt;查看详细错误信息&lt;/span&gt;</span></div></div><div><div><div>146</div></div><div><span><span>                </span></span><span>&lt;/p&gt;</span></div></div><div><div><div>147</div></div><div><span><span>            </span></span><span>&lt;/body&gt;</span></div></div><div><div><div>148</div></div><div><span><span>            </span></span><span>&lt;/html&gt;</span></div></div><div><div><div>149</div></div><div><span><span>        </span></span><span>`</span><span>, {</span></div></div><div><div><div>150</div></div><div><span>            </span><span>status</span><span>:</span><span> </span><span>500</span><span>,</span></div></div><div><div><div>151</div></div><div><span>            </span><span>headers</span><span>:</span><span> {</span></div></div><div><div><div>152</div></div><div><span>                </span><span>"Content-Type"</span><span>:</span><span> </span><span>"text/html; charset=utf-8"</span></div></div><div><div><div>153</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>154</div></div><div><span><span>        </span></span><span>});</span></div></div><div><div><div>155</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>156</div></div><div><span>}</span></div></div><div><div><div>157</div></div><div>
</div></div><div><div><div>158</div></div><div><span>// 解析蓝奏云链接的核心函数</span></div></div><div><div><div>159</div></div><div><span>async</span><span> </span><span>function</span><span> </span><span>parseLanzouUrl</span><span><span>(</span><span>targetUrl</span><span>) {</span></span></div></div><div><div><div>160</div></div><div><span>    </span><span>const</span><span> </span><span>logs</span><span> </span><span>=</span><span> [];</span></div></div><div><div><div>161</div></div><div>
</div></div><div><div><div>162</div></div><div><span>    </span><span>function</span><span> </span><span>addLog</span><span><span>(</span><span>message</span><span>, </span><span>type</span><span> </span></span><span>=</span><span> </span><span>'info'</span><span>) {</span></div></div><div><div><div>163</div></div><div><span><span>        </span></span><span>logs</span><span>.</span><span>push</span><span>({</span></div></div><div><div><div>164</div></div><div><span>            </span><span>time</span><span>:</span><span> </span><span>new</span><span> </span><span>Date</span><span>().</span><span>toLocaleTimeString</span><span>(),</span></div></div><div><div><div>165</div></div><div><span><span>            </span></span><span>message</span><span>,</span></div></div><div><div><div>166</div></div><div><span><span>            </span></span><span>type</span></div></div><div><div><div>167</div></div><div><span><span>        </span></span><span>});</span></div></div><div><div><div>168</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>169</div></div><div>
</div></div><div><div><div>170</div></div><div><span>    </span><span>addLog</span><span>(</span><span>'开始解析蓝奏云链接'</span><span>, </span><span>'info'</span><span>);</span></div></div><div><div><div>171</div></div><div><span>    </span><span>addLog</span><span>(</span><span>`目标URL: </span><span>${</span><span>targetUrl</span><span>}</span><span>`</span><span>, </span><span>'info'</span><span>);</span></div></div><div><div><div>172</div></div><div>
</div></div><div><div><div>173</div></div><div><span>    </span><span>try</span><span> {</span></div></div><div><div><div>174</div></div><div><span>        </span><span>// 1. 获取初始页面内容</span></div></div><div><div><div>175</div></div><div><span>        </span><span>addLog</span><span>(</span><span>'步骤1: 获取初始页面'</span><span>, </span><span>'info'</span><span>);</span></div></div><div><div><div>176</div></div><div><span>        </span><span>const</span><span> </span><span>page1Response</span><span> </span><span>=</span><span> </span><span>await</span><span> </span><span>fetch</span><span><span>(</span><span>targetUrl</span><span>, {</span></span></div></div><div><div><div>177</div></div><div><span>            </span><span>headers</span><span>:</span><span> {</span></div></div><div><div><div>178</div></div><div><span>                </span><span>"User-Agent"</span><span>:</span><span> </span><span>"Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.120 Mobile Safari/537.36"</span><span>,</span></div></div><div><div><div>179</div></div><div><span>                </span><span>"Referer"</span><span>:</span><span> </span><span>"https://www.lanzou.com/"</span></div></div><div><div><div>180</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>181</div></div><div><span><span>        </span></span><span>});</span></div></div><div><div><div>182</div></div><div>
</div></div><div><div><div>183</div></div><div><span>        </span><span>if</span><span> (</span><span>!</span><span><span>page1Response</span><span>.</span></span><span>ok</span><span>) {</span></div></div><div><div><div>184</div></div><div><span>            </span><span>throw</span><span> </span><span>new</span><span> </span><span>Error</span><span>(</span><span>`初始页面请求失败: </span><span>${</span><span>page1Response</span><span>.</span><span>status</span><span>}</span><span>`</span><span>);</span></div></div><div><div><div>185</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>186</div></div><div>
</div></div><div><div><div>187</div></div><div><span>        </span><span>const</span><span> </span><span>page1Html</span><span> </span><span>=</span><span> </span><span>await</span><span><span> </span><span>page1Response</span><span>.</span></span><span>text</span><span>();</span></div></div><div><div><div>188</div></div><div><span>        </span><span>addLog</span><span>(</span><span>'初始页面获取成功'</span><span>, </span><span>'success'</span><span>);</span></div></div><div><div><div>189</div></div><div>
</div></div><div><div><div>190</div></div><div><span>        </span><span>// 提取下载链接</span></div></div><div><div><div>191</div></div><div><span>        </span><span>const</span><span> </span><span>downUrlMatch</span><span> </span><span>=</span><span><span> </span><span>page1Html</span><span>.</span></span><span>match</span><span>(</span><span>/&lt;a href="(</span><span>[</span><span>^</span><span>"]</span><span><span>+</span><span>)"</span></span><span>[</span><span>^</span><span>&gt;]</span><span><span>*</span><span>id="downurl"/</span></span><span>i</span><span>);</span></div></div><div><div><div>192</div></div><div><span>        </span><span>if</span><span> (</span><span>!</span><span><span>downUrlMatch</span><span>) {</span></span></div></div><div><div><div>193</div></div><div><span>            </span><span>throw</span><span> </span><span>new</span><span> </span><span>Error</span><span>(</span><span>"无法从页面提取下载链接"</span><span>);</span></div></div><div><div><div>194</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>195</div></div><div>
</div></div><div><div><div>196</div></div><div><span>        </span><span>const</span><span> </span><span>downUrl</span><span> </span><span>=</span><span> </span><span>"https://wwi.lanzoup.com"</span><span> </span><span>+</span><span><span> </span><span>downUrlMatch</span><span>[</span></span><span>1</span><span>];</span></div></div><div><div><div>197</div></div><div><span>        </span><span>addLog</span><span>(</span><span>`提取到下载链接: </span><span>${</span><span>downUrl</span><span>}</span><span>`</span><span>, </span><span>'success'</span><span>);</span></div></div><div><div><div>198</div></div><div>
</div></div><div><div><div>199</div></div><div><span>        </span><span>// 2. 获取第二页内容</span></div></div><div><div><div>200</div></div><div><span>        </span><span>addLog</span><span>(</span><span>'步骤2: 获取第二页内容'</span><span>, </span><span>'info'</span><span>);</span></div></div><div><div><div>201</div></div><div><span>        </span><span>const</span><span> </span><span>page2Response</span><span> </span><span>=</span><span> </span><span>await</span><span> </span><span>fetch</span><span><span>(</span><span>downUrl</span><span>, {</span></span></div></div><div><div><div>202</div></div><div><span>            </span><span>headers</span><span>:</span><span> {</span></div></div><div><div><div>203</div></div><div><span>                </span><span>"User-Agent"</span><span>:</span><span> </span><span>"Mozilla/5.0 (Linux; Android 10; Pixel 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.120 Mobile Safari/537.36"</span><span>,</span></div></div><div><div><div>204</div></div><div><span>                </span><span>"Referer"</span><span>:</span><span><span> </span><span>targetUrl</span></span></div></div><div><div><div>205</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>206</div></div><div><span><span>        </span></span><span>});</span></div></div><div><div><div>207</div></div><div>
</div></div><div><div><div>208</div></div><div><span>        </span><span>if</span><span> (</span><span>!</span><span><span>page2Response</span><span>.</span></span><span>ok</span><span>) {</span></div></div><div><div><div>209</div></div><div><span>            </span><span>throw</span><span> </span><span>new</span><span> </span><span>Error</span><span>(</span><span>`第二页请求失败: </span><span>${</span><span>page2Response</span><span>.</span><span>status</span><span>}</span><span>`</span><span>);</span></div></div><div><div><div>210</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>211</div></div><div>
</div></div><div><div><div>212</div></div><div><span>        </span><span>const</span><span> </span><span>page2Html</span><span> </span><span>=</span><span> </span><span>await</span><span><span> </span><span>page2Response</span><span>.</span></span><span>text</span><span>();</span></div></div><div><div><div>213</div></div><div><span>        </span><span>addLog</span><span>(</span><span>'第二页获取成功'</span><span>, </span><span>'success'</span><span>);</span></div></div><div><div><div>214</div></div><div>
</div></div><div><div><div>215</div></div><div><span>        </span><span>// 提取变量</span></div></div><div><div><div>216</div></div><div><span>        </span><span>addLog</span><span>(</span><span>'步骤3: 提取URL参数'</span><span>, </span><span>'info'</span><span>);</span></div></div><div><div><div>217</div></div><div><span>        </span><span>const</span><span> </span><span>part1Match</span><span> </span><span>=</span><span><span> </span><span>page2Html</span><span>.</span></span><span>match</span><span>(</span><span>/(?:var</span><span>\s</span><span><span>+</span><span>)</span><span>?</span><span>vkjxld</span></span><span>\s</span><span><span>*</span><span>=</span></span><span>\s</span><span>*</span><span>['"]</span><span>(</span><span>[</span><span>^</span><span>'"]</span><span><span>+</span><span>)/</span></span><span>i</span><span>);</span></div></div><div><div><div>218</div></div><div><span>        </span><span>const</span><span> </span><span>part2Match</span><span> </span><span>=</span><span><span> </span><span>page2Html</span><span>.</span></span><span>match</span><span>(</span><span>/(?:var</span><span>\s</span><span><span>+</span><span>)</span><span>?</span><span>hyggid</span></span><span>\s</span><span><span>*</span><span>=</span></span><span>\s</span><span>*</span><span>['"]</span><span>(</span><span>[</span><span>^</span><span>'"]</span><span><span>+</span><span>)/</span></span><span>i</span><span>);</span></div></div><div><div><div>219</div></div><div>
</div></div><div><div><div>220</div></div><div><span>        </span><span>if</span><span> (</span><span>!</span><span><span>part1Match</span><span> </span></span><span>||</span><span> </span><span>!</span><span><span>part2Match</span><span>) {</span></span></div></div><div><div><div>221</div></div><div><span>            </span><span>throw</span><span> </span><span>new</span><span> </span><span>Error</span><span>(</span><span>"无法提取URL参数"</span><span>);</span></div></div><div><div><div>222</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>223</div></div><div>
</div></div><div><div><div>224</div></div><div><span>        </span><span>const</span><span> </span><span>part1</span><span> </span><span>=</span><span><span> </span><span>part1Match</span><span>[</span></span><span>1</span><span>];</span></div></div><div><div><div>225</div></div><div><span>        </span><span>const</span><span> </span><span>part2</span><span> </span><span>=</span><span><span> </span><span>part2Match</span><span>[</span></span><span>1</span><span>];</span></div></div><div><div><div>226</div></div><div><span>        </span><span>addLog</span><span>(</span><span>`提取参数: vkjxld=</span><span>${</span><span>part1</span><span>.</span><span>substring</span><span>(</span><span>0</span><span>,</span><span> </span><span>20</span><span>)</span><span>}</span><span>..., hyggid=</span><span>${</span><span>part2</span><span>.</span><span>substring</span><span>(</span><span>0</span><span>,</span><span> </span><span>20</span><span>)</span><span>}</span><span>...`</span><span>, </span><span>'success'</span><span>);</span></div></div><div><div><div>227</div></div><div>
</div></div><div><div><div>228</div></div><div><span>        </span><span>const</span><span> </span><span>finalUrl</span><span> </span><span>=</span><span><span> </span><span>part1</span><span> </span></span><span>+</span><span><span> </span><span>part2</span><span>;</span></span></div></div><div><div><div>229</div></div><div><span>        </span><span>addLog</span><span>(</span><span>`拼接最终URL: </span><span>${</span><span>finalUrl</span><span>}</span><span>`</span><span>, </span><span>'success'</span><span>);</span></div></div><div><div><div>230</div></div><div><span>        </span><span>addLog</span><span>(</span><span>'✅ 解析完成！'</span><span>, </span><span>'success'</span><span>);</span></div></div><div><div><div>231</div></div><div>
</div></div><div><div><div>232</div></div><div><span>        </span><span>return</span><span><span> { </span><span>finalUrl</span><span>, </span><span>logs</span><span> };</span></span></div></div><div><div><div>233</div></div><div>
</div></div><div><div><div>234</div></div><div><span><span>    </span></span><span>} </span><span>catch</span><span><span> (</span><span>error</span><span>) {</span></span></div></div><div><div><div>235</div></div><div><span>        </span><span>addLog</span><span>(</span><span>`解析失败: </span><span>${</span><span>error</span><span>.</span><span>message</span><span>}</span><span>`</span><span>, </span><span>'error'</span><span>);</span></div></div><div><div><div>236</div></div><div><span>        </span><span>throw</span><span><span> </span><span>error</span><span>;</span></span></div></div><div><div><div>237</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>238</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section>]]></content>
    </entry>
    <entry>
      <id>https://www.5al.top/posts/202509140747/</id>
      <title type="text">用 Cloudflare Workers + R2 搭建免费的 Serverless 云存储系统</title>
      <published>2025-09-13T23:47:00.000Z</published>
      <updated>2025-09-13T23:47:00.000Z</updated>
      <author><name>星觅海</name></author>
      <link rel="alternate" href="https://www.5al.top/posts/202509140747/"/>
      <summary type="text">基于 Cloudflare Workers + R2 对象存储的轻量无服务器文件管理器，支持密码登录、多文件上传、搜索排序与明暗主题。</summary>
      <content type="html"><![CDATA[<div><div><div></div><div>源码在最后</div></div><div><p>完整源码在文章 <code>最下面</code>，也可在 GitHub 仓库直接取用。</p></div></div>
<section><h2>☁️ Cloudflare Workers 云存储文件管理器<a href="#️-cloudflare-workers-云存储文件管理器"><span>#</span></a></h2><p>一个基于 <strong>Cloudflare Workers + R2 对象存储</strong> 的轻量级、无服务器、支持登录验证的在线文件管理工具。</p><p>无需购买服务器，无需数据库，全球 CDN 加速，即开即用！</p><hr /><section><h3>🏗️ 系统架构<a href="#️-系统架构"><span>#</span></a></h3><p>整套系统只有三个角色：浏览器、Worker、R2 存储桶。Worker 既是后端也是前端——页面 HTML 由它直接拼接返回。</p><div><div><div><div><span><p>HTTPS 请求</p></span></div><div><span><p>转发</p></span></div><div><span><p>R2 Bindings 读写</p></span></div><div><span><p>读取环境变量</p></span></div><div><span><p>返回页面或文件流</p></span></div><div><span><p>👤 用户浏览器</p></span></div><div><span><p>🌍 Cloudflare CDN<br /><br />全球边缘节点</p></span></div><div><span><p>⚙️ Cloudflare Worker<br /><br />鉴权 · 路由 · 渲染 HTML</p></span></div><div><span><p>🗄️ R2 存储桶<br /><br />MY_BUCKET</p></span></div><div><span><p>🔐 ACCESS_PASSWORD</p></span></div>
</div><div><div><span><p>HTTPS 请求</p></span></div><div><span><p>转发</p></span></div><div><span><p>R2 Bindings 读写</p></span></div><div><span><p>读取环境变量</p></span></div><div><span><p>返回页面或文件流</p></span></div><div><span><p>👤 用户浏览器</p></span></div><div><span><p>🌍 Cloudflare CDN<br /><br />全球边缘节点</p></span></div><div><span><p>⚙️ Cloudflare Worker<br /><br />鉴权 · 路由 · 渲染 HTML</p></span></div><div><span><p>🗄️ R2 存储桶<br /><br />MY_BUCKET</p></span></div><div><span><p>🔐 ACCESS_PASSWORD</p></span></div>
</div></div></div><hr /></section><section><h3>🔄 请求处理时序<a href="#-请求处理时序"><span>#</span></a></h3><p>一次「登录 → 列文件 → 上传」的完整交互：</p><div><div><div>R2 存储桶Cloudflare Worker用户R2 存储桶Cloudflare Worker用户alt[未携带有效 Cookie]访问站点返回登录页POST /login 提交密码Set-Cookie 并跳转首页GET / 获取文件列表bucket.list()对象列表渲染 HTML 列表页POST /upload 上传文件bucket.put(key, stream)写入成功返回上传结果
</div><div>R2 存储桶Cloudflare Worker用户R2 存储桶Cloudflare Worker用户alt[未携带有效 Cookie]访问站点返回登录页POST /login 提交密码Set-Cookie 并跳转首页GET / 获取文件列表bucket.list()对象列表渲染 HTML 列表页POST /upload 上传文件bucket.put(key, stream)写入成功返回上传结果
</div></div></div><hr /></section><section><h3>🚀 部署流程<a href="#-部署流程"><span>#</span></a></h3><div><div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>① 注册 Cloudflare 账号</p></span></div><div><span><p>② 创建 R2 存储桶<br /><br />例如 my-files</p></span></div><div><span><p>③ 创建 Worker 应用<br /><br />Create Application &gt; Worker</p></span></div><div><span><p>④ 粘贴 worker.js 源码</p></span></div><div><span><p>⑤ 配置环境变量<br /><br />ACCESS_PASSWORD</p></span></div><div><span><p>⑥ 绑定 R2 Bucket<br /><br />变量名 MY_BUCKET</p></span></div><div><span><p>⑦ Save and Deploy</p></span></div><div><span><p>⑧ 访问 *.workers.dev<br /><br />输入密码即可使用</p></span></div>
</div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>① 注册 Cloudflare 账号</p></span></div><div><span><p>② 创建 R2 存储桶<br /><br />例如 my-files</p></span></div><div><span><p>③ 创建 Worker 应用<br /><br />Create Application &gt; Worker</p></span></div><div><span><p>④ 粘贴 worker.js 源码</p></span></div><div><span><p>⑤ 配置环境变量<br /><br />ACCESS_PASSWORD</p></span></div><div><span><p>⑥ 绑定 R2 Bucket<br /><br />变量名 MY_BUCKET</p></span></div><div><span><p>⑦ Save and Deploy</p></span></div><div><span><p>⑧ 访问 *.workers.dev<br /><br />输入密码即可使用</p></span></div>
</div></div></div><hr /></section><section><h3>✨ 功能特性<a href="#-功能特性"><span>#</span></a></h3><div><div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>文件管理器</p></span></div><div><span><p>🔐 安全</p></span></div><div><span><p>密码登录保护</p></span></div><div><span><p>Cookie 鉴权</p></span></div><div><span><p>可放行匿名下载</p></span></div><div><span><p>📁 文件操作</p></span></div><div><span><p>多文件批量上传</p></span></div><div><span><p>单文件下载</p></span></div><div><span><p>单个或批量删除</p></span></div><div><span><p>🔍 检索</p></span></div><div><span><p>文件名搜索</p></span></div><div><span><p>按名称排序</p></span></div><div><span><p>按大小排序</p></span></div><div><span><p>按时间排序</p></span></div><div><span><p>🎨 体验</p></span></div><div><span><p>响应式布局</p></span></div><div><span><p>亮色暗色主题</p></span></div><div><span><p>图片缩略图</p></span></div><div><span><p>🌍 性能</p></span></div><div><span><p>CDN 全球加速</p></span></div><div><span><p>免服务器免运维</p></span></div>
</div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>文件管理器</p></span></div><div><span><p>🔐 安全</p></span></div><div><span><p>密码登录保护</p></span></div><div><span><p>Cookie 鉴权</p></span></div><div><span><p>可放行匿名下载</p></span></div><div><span><p>📁 文件操作</p></span></div><div><span><p>多文件批量上传</p></span></div><div><span><p>单文件下载</p></span></div><div><span><p>单个或批量删除</p></span></div><div><span><p>🔍 检索</p></span></div><div><span><p>文件名搜索</p></span></div><div><span><p>按名称排序</p></span></div><div><span><p>按大小排序</p></span></div><div><span><p>按时间排序</p></span></div><div><span><p>🎨 体验</p></span></div><div><span><p>响应式布局</p></span></div><div><span><p>亮色暗色主题</p></span></div><div><span><p>图片缩略图</p></span></div><div><span><p>🌍 性能</p></span></div><div><span><p>CDN 全球加速</p></span></div><div><span><p>免服务器免运维</p></span></div>
</div></div></div><hr /></section><section><h3>🛠️ 技术栈<a href="#️-技术栈"><span>#</span></a></h3>

<table><thead><tr><th>技术</th><th>说明</th></tr></thead><tbody><tr><td><strong>Cloudflare Workers</strong></td><td>无服务器运行时，处理文件上传、下载、删除等逻辑</td></tr><tr><td><strong>Cloudflare R2</strong></td><td>对象存储服务，用于存储用户上传的文件（类似 AWS S3）</td></tr><tr><td><strong>前端</strong></td><td>原生 HTML + CSS + JavaScript，单页应用，无框架依赖</td></tr><tr><td><strong>环境变量</strong></td><td><code>ACCESS_PASSWORD</code>：用于登录验证；<code>MY_BUCKET</code>：绑定的 R2 存储桶</td></tr></tbody></table><hr /></section><section><h3>📁 使用指南<a href="#-使用指南"><span>#</span></a></h3><p>登录后所有操作都围绕文件列表页展开：</p><div><div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>打开站点</p></span></div><div><span><p>输入密码登录</p></span></div><div><span><p>📋 文件列表页</p></span></div><div><span><p>📤 选择多个文件 → 上传</p></span></div><div><span><p>🔍 搜索文件名</p></span></div><div><span><p>⬆️⬇️ 按名称 / 大小 / 时间排序</p></span></div><div><span><p>📥 点击文件名或下载按钮</p></span></div><div><span><p>🗑️ 单个删除（带确认）</p></span></div><div><span><p>✅ 勾选多个文件</p></span></div><div><span><p>🗑️ 批量删除</p></span></div>
</div><div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span></span></div><div><span><p>打开站点</p></span></div><div><span><p>输入密码登录</p></span></div><div><span><p>📋 文件列表页</p></span></div><div><span><p>📤 选择多个文件 → 上传</p></span></div><div><span><p>🔍 搜索文件名</p></span></div><div><span><p>⬆️⬇️ 按名称 / 大小 / 时间排序</p></span></div><div><span><p>📥 点击文件名或下载按钮</p></span></div><div><span><p>🗑️ 单个删除（带确认）</p></span></div><div><span><p>✅ 勾选多个文件</p></span></div><div><span><p>🗑️ 批量删除</p></span></div>
</div></div></div><hr /></section><section><h3>🛡️ 安全建议<a href="#️-安全建议"><span>#</span></a></h3><ul>
<li>一定要设置 <code>ACCESS_PASSWORD</code>，否则可能造成未授权访问！</li>
<li>不要将密码硬编码在前端，也不要泄露环境变量</li>
<li>可根据需要进一步扩展权限控制、日志记录等功能</li>
</ul><hr /></section><section><h3>🧩 可扩展功能（TODO / Ideas）<a href="#-可扩展功能todo--ideas"><span>#</span></a></h3><ul>
<li> 文件预览（图片、PDF、视频等）</li>
<li> 支持文件夹 / 目录结构</li>
<li> 上传进度条</li>
<li> 多用户 / 权限管理</li>
<li> 文件分享链接（带密码或过期时间）</li>
<li> 操作日志记录</li>
<li> 更多主题 / UI 定制</li>
</ul><hr /></section><section><h3>📄 项目结构<a href="#-项目结构"><span>#</span></a></h3><ul>
<li><code>worker.js</code>：主入口，包含路由、身份验证、文件操作逻辑</li>
<li>前端界面：内置 HTML / CSS / JS，单页应用，无外部依赖</li>
<li>托管于 Cloudflare Worker，无需额外服务器或前端部署</li>
</ul><hr /></section><section><h3>🤝 贡献<a href="#-贡献"><span>#</span></a></h3><p>欢迎提交 Issue、PR，一起改进这个项目！</p><p>如果你觉得这个工具对你有帮助，欢迎：</p><ul>
<li>✨ Star 本项目</li>
<li>📤 分享给朋友或团队</li>
<li>🛠️ 提出改进建议或新功能需求</li>
</ul><hr /></section><section><h3>🔗 相关技术<a href="#-相关技术"><span>#</span></a></h3><ul>
<li><a href="https://developers.cloudflare.com/workers/" target="_blank">Cloudflare Workers</a></li>
<li><a href="https://developers.cloudflare.com/r2/" target="_blank">Cloudflare R2</a></li>
<li><a href="https://en.wikipedia.org/wiki/Serverless_computing" target="_blank">Serverless 架构</a></li>
<li><a href="https://developers.cloudflare.com/workers/runtime-apis/r2/" target="_blank">R2 SDK (Cloudflare Workers Bindings)</a></li>
</ul><hr /></section><section><h3>✨ License<a href="#-license"><span>#</span></a></h3><p>MIT License — 可自由使用、修改和分发</p><hr /><p>📸 <strong>界面预览：</strong></p><div><figure><img src="https://img.xmhai.cn/v2/CnJtItN.jpeg" alt="界面预览" /><figcaption>界面预览</figcaption></figure><figure><img src="https://img.xmhai.cn/v2/uP1am9E.jpeg" alt="界面预览" /><figcaption>界面预览</figcaption></figure><figure><img src="https://img.xmhai.cn/v2/zJbJWxM.jpeg" alt="界面预览 3" /><figcaption>界面预览 3</figcaption></figure></div></section></section>
<section><h2>如果要不输入密码也能下载将以下代码<a href="#如果要不输入密码也能下载将以下代码"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>if</span><span> (</span><span>!</span><span>ACCESS_PASSWORD</span><span>) {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>return</span><span> </span><span>handleRequest</span><span><span>(</span><span>request</span><span>, </span><span>env</span><span>, </span><span>bucket</span><span>, </span><span>url</span><span>);</span></span></div></div><div><div><div>3</div></div><div><span>}</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>if</span><span><span> (</span><span>url</span><span>.</span></span><span>pathname</span><span> </span><span>===</span><span> </span><span>"/login"</span><span> </span><span>&amp;&amp;</span><span><span> </span><span>request</span><span>.</span></span><span>method</span><span> </span><span>===</span><span> </span><span>"POST"</span><span>) {</span></div></div><div><div><div>6</div></div><div><span>  </span><span>return</span><span> </span><span>await</span><span> </span><span>handleLogin</span><span><span>(</span><span>request</span><span>, </span></span><span>ACCESS_PASSWORD</span><span>);</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>if</span><span> (</span><span>!</span><span>isAuthenticated</span><span><span>(</span><span>request</span><span>, </span></span><span>ACCESS_PASSWORD</span><span>) </span><span>&amp;&amp;</span><span><span> </span><span>url</span><span>.</span></span><span>pathname</span><span> </span><span>!==</span><span> </span><span>"/login"</span><span>) {</span></div></div><div><div><div>10</div></div><div><span>  </span><span>return</span><span> </span><span>showLoginPage</span><span><span>(</span><span>request</span><span>, </span></span><span>"请先登录"</span><span>, </span><span>false</span><span>);</span></div></div><div><div><div>11</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>替换为<a href="#替换为"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>const</span><span> </span><span>isFileDownload</span><span> </span><span>=</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>request</span><span>.</span><span>method</span><span> </span><span>===</span><span> </span><span>"GET"</span><span> </span><span>&amp;&amp;</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>url</span><span>.</span><span>pathname</span><span> </span><span>!==</span><span> </span><span>"/"</span><span> </span><span>&amp;&amp;</span></div></div><div><div><div>4</div></div><div><span>  </span><span>!</span><span><span>url</span><span>.</span></span><span>pathname</span><span>.</span><span>startsWith</span><span>(</span><span>"/upload"</span><span>) </span><span>&amp;&amp;</span></div></div><div><div><div>5</div></div><div><span>  </span><span>!</span><span><span>url</span><span>.</span></span><span>pathname</span><span>.</span><span>startsWith</span><span>(</span><span>"/delete"</span><span>) </span><span>&amp;&amp;</span></div></div><div><div><div>6</div></div><div><span>  </span><span>!</span><span><span>url</span><span>.</span></span><span>pathname</span><span>.</span><span>startsWith</span><span>(</span><span>"/login"</span><span>);</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>const</span><span> </span><span>isLoggedIn</span><span> </span><span>=</span><span> </span><span>isAuthenticated</span><span><span>(</span><span>request</span><span>, </span></span><span>ACCESS_PASSWORD</span><span>);</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span>if</span><span> (</span><span>!</span><span>ACCESS_PASSWORD</span><span>) {</span></div></div><div><div><div>11</div></div><div><span>  </span><span>// 如果没有设置密码，直接允许访问（可选，也可只放行下载）</span></div></div><div><div><div>12</div></div><div><span>  </span><span>return</span><span> </span><span>handleRequest</span><span><span>(</span><span>request</span><span>, </span><span>env</span><span>, </span><span>bucket</span><span>, </span><span>url</span><span>);</span></span></div></div><div><div><div>13</div></div><div><span>}</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>if</span><span><span> (</span><span>url</span><span>.</span></span><span>pathname</span><span> </span><span>===</span><span> </span><span>"/login"</span><span> </span><span>&amp;&amp;</span><span><span> </span><span>request</span><span>.</span></span><span>method</span><span> </span><span>===</span><span> </span><span>"POST"</span><span>) {</span></div></div><div><div><div>16</div></div><div><span>  </span><span>// 登录请求，照常处理</span></div></div><div><div><div>17</div></div><div><span>  </span><span>return</span><span> </span><span>await</span><span> </span><span>handleLogin</span><span><span>(</span><span>request</span><span>, </span></span><span>ACCESS_PASSWORD</span><span>);</span></div></div><div><div><div>18</div></div><div><span>}</span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span>if</span><span> (</span><span>!</span><span><span>isLoggedIn</span><span>) {</span></span></div></div><div><div><div>21</div></div><div><span>  </span><span>if</span><span><span> (</span><span>isFileDownload</span><span>) {</span></span></div></div><div><div><div>22</div></div><div><span>    </span><span>// 🟢 文件下载：允许匿名访问，不检查登录</span></div></div><div><div><div>23</div></div><div><span>    </span><span>return</span><span> </span><span>handleRequest</span><span><span>(</span><span>request</span><span>, </span><span>env</span><span>, </span><span>bucket</span><span>, </span><span>url</span><span>);</span></span></div></div><div><div><div>24</div></div><div><span><span>  </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>25</div></div><div><span>    </span><span>// 🔴 其他操作：需要登录</span></div></div><div><div><div>26</div></div><div><span>    </span><span>return</span><span> </span><span>showLoginPage</span><span><span>(</span><span>request</span><span>, </span></span><span>"请先登录"</span><span>, </span><span>false</span><span>);</span></div></div><div><div><div>27</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>28</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>
<section><h2>即可实现<a href="#即可实现"><span>#</span></a></h2><p>鉴权判断逻辑变成这样：</p><div><div><div><div><span></span></div><div><span><p>否</p></span></div><div><span><p>是</p></span></div><div><span><p>是</p></span></div><div><span><p>否</p></span></div><div><span><p>是</p></span></div><div><span><p>否</p></span></div><div><span><p>是</p></span></div><div><span><p>否</p></span></div><div><span><p>收到请求</p></span></div><div><span><p>是否设置了<br /><br />ACCESS_PASSWORD?</p></span></div><div><span><p>直接放行<br /><br />handleRequest</p></span></div><div><span><p>是 POST /login?</p></span></div><div><span><p>校验密码<br /><br />下发 Cookie</p></span></div><div><span><p>Cookie 校验通过?</p></span></div><div><span><p>是文件下载请求?<br /><br />GET 且非 upload / delete / login</p></span></div><div><span><p>返回登录页 401</p></span></div>
</div><div><div><span></span></div><div><span><p>否</p></span></div><div><span><p>是</p></span></div><div><span><p>是</p></span></div><div><span><p>否</p></span></div><div><span><p>是</p></span></div><div><span><p>否</p></span></div><div><span><p>是</p></span></div><div><span><p>否</p></span></div><div><span><p>收到请求</p></span></div><div><span><p>是否设置了<br /><br />ACCESS_PASSWORD?</p></span></div><div><span><p>直接放行<br /><br />handleRequest</p></span></div><div><span><p>是 POST /login?</p></span></div><div><span><p>校验密码<br /><br />下发 Cookie</p></span></div><div><span><p>Cookie 校验通过?</p></span></div><div><span><p>是文件下载请求?<br /><br />GET 且非 upload / delete / login</p></span></div><div><span><p>返回登录页 401</p></span></div>
</div></div></div><p>对应到具体行为：</p>

<table><thead><tr><th>行为</th><th>是否需要密码</th><th>结果</th></tr></thead><tbody><tr><td>访问首页 <code>/</code>（文件列表页）</td><td>✅ 需要</td><td>未登录跳转登录页</td></tr><tr><td>点击文件下载链接（如 <code>/myfile.zip</code>）</td><td>❌ 不需要</td><td>✅ 直接下载</td></tr><tr><td>上传文件 <code>/upload</code></td><td>✅ 需要</td><td>未登录被拦截</td></tr><tr><td>删除文件 <code>/delete</code></td><td>✅ 需要</td><td>未登录被拦截</td></tr><tr><td>登录页 <code>/login</code></td><td>✅（提交密码时）</td><td>正常显示与提交</td></tr></tbody></table><a href="https://github.com/xingmihai/cloudflare-file-manager" target="_blank"><div><div><div><div></div><div>xingmihai</div></div><div>/</div><div>cloudflare-file-manager</div></div><div></div></div><div>Description not set</div><div><div>—</div><div>—</div><div>MIT</div><span>JavaScript</span></div></a><section><h3>或直接复制<a href="#或直接复制"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>export</span><span> </span><span>default</span><span> {</span></div></div><div><div><div>2</div></div><div><span>  </span><span>async</span><span> </span><span>fetch</span><span><span>(</span><span>request</span><span>, </span><span>env</span><span>) {</span></span></div></div><div><div><div>3</div></div><div><span>    </span><span>const</span><span> </span><span>bucket</span><span> </span><span>=</span><span><span> </span><span>env</span><span>.</span></span><span>MY_BUCKET</span><span>;</span></div></div><div><div><div>4</div></div><div><span>    </span><span>const</span><span> </span><span>url</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>URL</span><span><span>(</span><span>request</span><span>.</span></span><span>url</span><span>);</span></div></div><div><div><div>5</div></div><div><span>    </span><span>const</span><span> </span><span>ACCESS_PASSWORD</span><span> </span><span>=</span><span><span> </span><span>env</span><span>.</span></span><span>ACCESS_PASSWORD</span><span>;</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>    </span><span>if</span><span> (</span><span>!</span><span>ACCESS_PASSWORD</span><span>) {</span></div></div><div><div><div>8</div></div><div><span>      </span><span>return</span><span> </span><span>handleRequest</span><span>(</span><span>request</span><span>, </span><span>env</span><span>, </span><span>bucket</span><span>, </span><span>url</span><span>);</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>    </span><span>if</span><span><span> (</span><span>url</span><span>.</span></span><span>pathname</span><span> </span><span>===</span><span> </span><span>"/login"</span><span> </span><span>&amp;&amp;</span><span><span> </span><span>request</span><span>.</span></span><span>method</span><span> </span><span>===</span><span> </span><span>"POST"</span><span>) {</span></div></div><div><div><div>12</div></div><div><span>      </span><span>return</span><span> </span><span>await</span><span> </span><span>handleLogin</span><span>(</span><span>request</span><span>, </span><span>ACCESS_PASSWORD</span><span>);</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>    </span><span>if</span><span> (</span><span>!</span><span>isAuthenticated</span><span>(</span><span>request</span><span>, </span><span>ACCESS_PASSWORD</span><span>) </span><span>&amp;&amp;</span><span><span> </span><span>url</span><span>.</span></span><span>pathname</span><span> </span><span>!==</span><span> </span><span>"/login"</span><span>) {</span></div></div><div><div><div>16</div></div><div><span>      </span><span>return</span><span> </span><span>showLoginPage</span><span>(</span><span>request</span><span>, </span><span>"请先登录"</span><span>, </span><span>false</span><span>);</span></div></div><div><div><div>17</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>    </span><span>return</span><span> </span><span>handleRequest</span><span>(</span><span>request</span><span>, </span><span>env</span><span>, </span><span>bucket</span><span>, </span><span>url</span><span>);</span></div></div><div><div><div>20</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>21</div></div><div><span>};</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span>function</span><span> </span><span>isAuthenticated</span><span><span>(</span><span>request</span><span>, </span><span>password</span><span>) {</span></span></div></div><div><div><div>24</div></div><div><span>  </span><span>const</span><span> </span><span>cookies</span><span> </span><span>=</span><span><span> </span><span>request</span><span>.</span></span><span>headers</span><span>.</span><span>get</span><span>(</span><span>"Cookie"</span><span>) </span><span>||</span><span> </span><span>""</span><span>;</span></div></div><div><div><div>25</div></div><div><span>  </span><span>return</span><span><span> </span><span>cookies</span><span>.</span></span><span>includes</span><span>(</span><span>`access_token=</span><span>${</span><span>password</span><span>}</span><span>`</span><span>);</span></div></div><div><div><div>26</div></div><div><span>}</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span>function</span><span> </span><span>showLoginPage</span><span><span>(</span><span>request</span><span>, </span><span>errorMsg</span><span> </span></span><span>=</span><span> </span><span>""</span><span><span>, </span><span>failed</span><span> </span></span><span>=</span><span> </span><span>false</span><span>) {</span></div></div><div><div><div>29</div></div><div><span>  </span><span>const</span><span> </span><span>html</span><span> </span><span>=</span><span> </span><span>buildLoginPage</span><span><span>(</span><span>errorMsg</span><span>);</span></span></div></div><div><div><div>30</div></div><div><span>  </span><span>const</span><span> </span><span>headers</span><span> </span><span>=</span><span> { </span><span>"Content-Type"</span><span>:</span><span> </span><span>"text/html"</span><span> };</span></div></div><div><div><div>31</div></div><div><span>  </span><span>if</span><span><span> (</span><span>failed</span><span>) </span><span>headers</span><span>[</span></span><span>"Cache-Control"</span><span>] </span><span>=</span><span> </span><span>"no-store"</span><span>;</span></div></div><div><div><div>32</div></div><div><span>  </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span><span>(</span><span>html</span><span>, { </span></span><span>status</span><span>:</span><span><span> </span><span>failed</span><span> </span></span><span>?</span><span> </span><span>401</span><span> </span><span>:</span><span> </span><span>200</span><span><span>, </span><span>headers</span><span> });</span></span></div></div><div><div><div>33</div></div><div><span>}</span></div></div><div><div><div>34</div></div><div>
</div></div><div><div><div>35</div></div><div><span>async</span><span> </span><span>function</span><span> </span><span>handleLogin</span><span><span>(</span><span>request</span><span>, </span><span>correctPassword</span><span>) {</span></span></div></div><div><div><div>36</div></div><div><span>  </span><span>const</span><span> </span><span>formData</span><span> </span><span>=</span><span> </span><span>await</span><span><span> </span><span>request</span><span>.</span></span><span>formData</span><span>();</span></div></div><div><div><div>37</div></div><div><span>  </span><span>const</span><span> </span><span>password</span><span> </span><span>=</span><span><span> </span><span>formData</span><span>.</span></span><span>get</span><span>(</span><span>"password"</span><span>);</span></div></div><div><div><div>38</div></div><div>
</div></div><div><div><div>39</div></div><div><span>  </span><span>if</span><span><span> (</span><span>password</span><span> </span></span><span>===</span><span><span> </span><span>correctPassword</span><span>) {</span></span></div></div><div><div><div>40</div></div><div><span>    </span><span>const</span><span> </span><span>headers</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>Headers</span><span>();</span></div></div><div><div><div>41</div></div><div><span><span>    </span></span><span>headers</span><span>.</span><span>set</span><span>(</span><span>"Location"</span><span>, </span><span>"/"</span><span>);</span></div></div><div><div><div>42</div></div><div><span><span>    </span></span><span>headers</span><span>.</span><span>set</span><span>(</span></div></div><div><div><div>43</div></div><div><span>      </span><span>"Set-Cookie"</span><span>,</span></div></div><div><div><div>44</div></div><div><span>      </span><span>`access_token=</span><span>${</span><span>correctPassword</span><span>}</span><span>; Path=/; HttpOnly; Max-Age=86400; SameSite=Lax`</span></div></div><div><div><div>45</div></div><div><span><span>    </span></span><span>);</span></div></div><div><div><div>46</div></div><div><span>    </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>null</span><span>, { </span><span>status</span><span>:</span><span> </span><span>302</span><span><span>, </span><span>headers</span><span> });</span></span></div></div><div><div><div>47</div></div><div><span><span>  </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>48</div></div><div><span>    </span><span>return</span><span> </span><span>showLoginPage</span><span><span>(</span><span>request</span><span>, </span></span><span>"密码错误"</span><span>, </span><span>true</span><span>);</span></div></div><div><div><div>49</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>50</div></div><div><span>}</span></div></div><div><div><div>51</div></div><div>
</div></div><div><div><div>52</div></div><div><span>async</span><span> </span><span>function</span><span> </span><span>handleRequest</span><span><span>(</span><span>request</span><span>, </span><span>env</span><span>, </span><span>bucket</span><span>, </span><span>url</span><span>) {</span></span></div></div><div><div><div>53</div></div><div><span>  </span><span>const</span><span> </span><span>pathname</span><span> </span><span>=</span><span><span> </span><span>url</span><span>.</span></span><span>pathname</span><span>;</span></div></div><div><div><div>54</div></div><div>
</div></div><div><div><div>55</div></div><div><span>  </span><span>try</span><span> {</span></div></div><div><div><div>56</div></div><div><span>    </span><span>if</span><span><span> (</span><span>pathname</span><span> </span></span><span>===</span><span> </span><span>"/"</span><span> </span><span>&amp;&amp;</span><span><span> </span><span>request</span><span>.</span></span><span>method</span><span> </span><span>===</span><span> </span><span>"GET"</span><span>) {</span></div></div><div><div><div>57</div></div><div><span>      </span><span>return</span><span> </span><span>await</span><span> </span><span>handleFileList</span><span><span>(</span><span>bucket</span><span>, </span><span>url</span><span>);</span></span></div></div><div><div><div>58</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> </span><span>if</span><span><span> (</span><span>request</span><span>.</span></span><span>method</span><span> </span><span>===</span><span> </span><span>"GET"</span><span> </span><span>&amp;&amp;</span><span><span> </span><span>pathname</span><span> </span></span><span>!==</span><span> </span><span>"/"</span><span>) {</span></div></div><div><div><div>59</div></div><div><span>      </span><span>return</span><span> </span><span>await</span><span> </span><span>handleFileDownload</span><span><span>(</span><span>bucket</span><span>, </span><span>url</span><span>);</span></span></div></div><div><div><div>60</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> </span><span>if</span><span><span> (</span><span>pathname</span><span> </span></span><span>===</span><span> </span><span>"/upload"</span><span> </span><span>&amp;&amp;</span><span><span> </span><span>request</span><span>.</span></span><span>method</span><span> </span><span>===</span><span> </span><span>"POST"</span><span>) {</span></div></div><div><div><div>61</div></div><div><span>      </span><span>return</span><span> </span><span>await</span><span> </span><span>handleFileUploadWithProgress</span><span><span>(</span><span>request</span><span>, </span><span>bucket</span><span>);</span></span></div></div><div><div><div>62</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> </span><span>if</span><span><span> (</span><span>pathname</span><span> </span></span><span>===</span><span> </span><span>"/delete"</span><span> </span><span>&amp;&amp;</span><span><span> </span><span>request</span><span>.</span></span><span>method</span><span> </span><span>===</span><span> </span><span>"POST"</span><span>) {</span></div></div><div><div><div>63</div></div><div><span>      </span><span>return</span><span> </span><span>await</span><span> </span><span>handleFileDelete</span><span><span>(</span><span>bucket</span><span>, </span><span>request</span><span>);</span></span></div></div><div><div><div>64</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>65</div></div><div><span><span>  </span></span><span>} </span><span>catch</span><span><span> (</span><span>error</span><span>) {</span></span></div></div><div><div><div>66</div></div><div><span>    </span><span>return</span><span> </span><span>buildErrorResponse</span><span><span>(</span><span>error</span><span>);</span></span></div></div><div><div><div>67</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>68</div></div><div>
</div></div><div><div><div>69</div></div><div><span>  </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>"Not Found"</span><span>, { </span><span>status</span><span>:</span><span> </span><span>404</span><span> });</span></div></div><div><div><div>70</div></div><div><span>}</span></div></div><div><div><div>71</div></div><div>
</div></div><div><div><div>72</div></div><div><span>async</span><span> </span><span>function</span><span> </span><span>handleFileList</span><span><span>(</span><span>bucket</span><span>, </span><span>url</span><span>) {</span></span></div></div><div><div><div>73</div></div><div><span>  </span><span>try</span><span> {</span></div></div><div><div><div>74</div></div><div><span>    </span><span>const</span><span> </span><span>listResult</span><span> </span><span>=</span><span> </span><span>await</span><span><span> </span><span>bucket</span><span>.</span></span><span>list</span><span>();</span></div></div><div><div><div>75</div></div><div><span>    </span><span>let</span><span><span> </span><span>objects</span><span> </span></span><span>=</span><span><span> (</span><span>listResult</span><span>.</span></span><span>objects</span><span> </span><span>||</span><span> [])</span></div></div><div><div><div>76</div></div><div><span><span>      </span></span><span>.</span><span>filter</span><span><span>((</span><span>o</span><span>) </span></span><span>=&gt;</span><span><span> </span><span>o</span><span>.</span></span><span>key</span><span>)</span></div></div><div><div><div>77</div></div><div><span><span>      </span></span><span>.</span><span>map</span><span><span>(</span><span>normalizeFileObject</span><span>);</span></span></div></div><div><div><div>78</div></div><div>
</div></div><div><div><div>79</div></div><div><span>    </span><span>const</span><span> </span><span>search</span><span> </span><span>=</span><span><span> </span><span>url</span><span>.</span></span><span>searchParams</span><span>.</span><span>get</span><span>(</span><span>"search"</span><span>) </span><span>||</span><span> </span><span>""</span><span>;</span></div></div><div><div><div>80</div></div><div><span>    </span><span>if</span><span><span> (</span><span>search</span><span>) {</span></span></div></div><div><div><div>81</div></div><div><span><span>      </span></span><span>objects</span><span> </span><span>=</span><span><span> </span><span>objects</span><span>.</span></span><span>filter</span><span><span>((</span><span>obj</span><span>) </span></span><span>=&gt;</span></div></div><div><div><div>82</div></div><div><span><span>        </span></span><span>obj</span><span>.</span><span>key</span><span>.</span><span>toLowerCase</span><span>().</span><span>includes</span><span><span>(</span><span>search</span><span>.</span></span><span>toLowerCase</span><span>())</span></div></div><div><div><div>83</div></div><div><span><span>      </span></span><span>);</span></div></div><div><div><div>84</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>85</div></div><div>
</div></div><div><div><div>86</div></div><div><span>    </span><span>const</span><span> </span><span>sortBy</span><span> </span><span>=</span><span><span> </span><span>url</span><span>.</span></span><span>searchParams</span><span>.</span><span>get</span><span>(</span><span>"sort"</span><span>) </span><span>||</span><span> </span><span>"name"</span><span>;</span></div></div><div><div><div>87</div></div><div><span>    </span><span>const</span><span> </span><span>order</span><span> </span><span>=</span><span><span> </span><span>url</span><span>.</span></span><span>searchParams</span><span>.</span><span>get</span><span>(</span><span>"order"</span><span>) </span><span>||</span><span> </span><span>"asc"</span><span>;</span></div></div><div><div><div>88</div></div><div><span><span>    </span></span><span>objects</span><span>.</span><span>sort</span><span><span>((</span><span>a</span><span>, </span><span>b</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>89</div></div><div><span>      </span><span>let</span><span><span> </span><span>x</span><span>, </span><span>y</span><span>;</span></span></div></div><div><div><div>90</div></div><div><span>      </span><span>if</span><span><span> (</span><span>sortBy</span><span> </span></span><span>===</span><span> </span><span>"name"</span><span>) {</span></div></div><div><div><div>91</div></div><div><span><span>        </span></span><span>x</span><span> </span><span>=</span><span><span> </span><span>a</span><span>.</span></span><span>key</span><span>.</span><span>toLowerCase</span><span>();</span></div></div><div><div><div>92</div></div><div><span><span>        </span></span><span>y</span><span> </span><span>=</span><span><span> </span><span>b</span><span>.</span></span><span>key</span><span>.</span><span>toLowerCase</span><span>();</span></div></div><div><div><div>93</div></div><div><span><span>      </span></span><span>} </span><span>else</span><span> </span><span>if</span><span><span> (</span><span>sortBy</span><span> </span></span><span>===</span><span> </span><span>"size"</span><span>) {</span></div></div><div><div><div>94</div></div><div><span><span>        </span></span><span>x</span><span> </span><span>=</span><span><span> </span><span>a</span><span>.</span></span><span>size</span><span>;</span></div></div><div><div><div>95</div></div><div><span><span>        </span></span><span>y</span><span> </span><span>=</span><span><span> </span><span>b</span><span>.</span></span><span>size</span><span>;</span></div></div><div><div><div>96</div></div><div><span><span>      </span></span><span>} </span><span>else</span><span> </span><span>if</span><span><span> (</span><span>sortBy</span><span> </span></span><span>===</span><span> </span><span>"date"</span><span>) {</span></div></div><div><div><div>97</div></div><div><span><span>        </span></span><span>x</span><span> </span><span>=</span><span><span> (</span><span>a</span><span>.</span></span><span>uploaded</span><span> </span><span>||</span><span> </span><span>0</span><span>);</span></div></div><div><div><div>98</div></div><div><span><span>        </span></span><span>y</span><span> </span><span>=</span><span><span> (</span><span>b</span><span>.</span></span><span>uploaded</span><span> </span><span>||</span><span> </span><span>0</span><span>);</span></div></div><div><div><div>99</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>100</div></div><div><span>      </span><span>return</span><span><span> </span><span>order</span><span> </span></span><span>===</span><span> </span><span>"asc"</span><span> </span><span>?</span><span><span> (</span><span>x</span><span> </span></span><span>&gt;</span><span><span> </span><span>y</span><span> </span></span><span>?</span><span> </span><span>1</span><span> </span><span>:</span><span> </span><span>-</span><span>1</span><span>) </span><span>:</span><span><span> (</span><span>x</span><span> </span></span><span>&lt;</span><span><span> </span><span>y</span><span> </span></span><span>?</span><span> </span><span>1</span><span> </span><span>:</span><span> </span><span>-</span><span>1</span><span>);</span></div></div><div><div><div>101</div></div><div><span><span>    </span></span><span>});</span></div></div><div><div><div>102</div></div><div>
</div></div><div><div><div>103</div></div><div><span>    </span><span>const</span><span> </span><span>html</span><span> </span><span>=</span><span> </span><span>renderEnhancedFileListPage</span><span><span>(</span><span>objects</span><span>, </span><span>url</span><span>);</span></span></div></div><div><div><div>104</div></div><div><span>    </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span><span>(</span><span>html</span><span>, {</span></span></div></div><div><div><div>105</div></div><div><span>      </span><span>headers</span><span>:</span><span> { </span><span>"Content-Type"</span><span>:</span><span> </span><span>"text/html; charset=UTF-8"</span><span> },</span></div></div><div><div><div>106</div></div><div><span><span>    </span></span><span>});</span></div></div><div><div><div>107</div></div><div><span><span>  </span></span><span>} </span><span>catch</span><span><span> (</span><span>error</span><span>) {</span></span></div></div><div><div><div>108</div></div><div><span>    </span><span>return</span><span> </span><span>buildErrorResponse</span><span><span>(</span><span>error</span><span>);</span></span></div></div><div><div><div>109</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>110</div></div><div><span>}</span></div></div><div><div><div>111</div></div><div>
</div></div><div><div><div>112</div></div><div><span>function</span><span> </span><span>normalizeFileObject</span><span><span>(</span><span>obj</span><span>) {</span></span></div></div><div><div><div>113</div></div><div><span>  </span><span>return</span><span> {</span></div></div><div><div><div>114</div></div><div><span>    </span><span>key</span><span>:</span><span><span> </span><span>obj</span><span>.</span></span><span>key</span><span> </span><span>||</span><span> </span><span>""</span><span>,</span></div></div><div><div><div>115</div></div><div><span>    </span><span>size</span><span>:</span><span><span> </span><span>obj</span><span>.</span></span><span>size</span><span> </span><span>||</span><span> </span><span>0</span><span>,</span></div></div><div><div><div>116</div></div><div><span>    </span><span>uploaded</span><span>:</span><span><span> </span><span>obj</span><span>.</span></span><span>uploaded</span><span> </span><span>||</span><span> </span><span>new</span><span> </span><span>Date</span><span>().</span><span>toISOString</span><span>(),</span></div></div><div><div><div>117</div></div><div><span><span>  </span></span><span>};</span></div></div><div><div><div>118</div></div><div><span>}</span></div></div><div><div><div>119</div></div><div>
</div></div><div><div><div>120</div></div><div><span>async</span><span> </span><span>function</span><span> </span><span>handleFileDownload</span><span><span>(</span><span>bucket</span><span>, </span><span>url</span><span>) {</span></span></div></div><div><div><div>121</div></div><div><span>  </span><span>const</span><span> </span><span>key</span><span> </span><span>=</span><span> </span><span>decodeURIComponent</span><span><span>(</span><span>url</span><span>.</span></span><span>pathname</span><span>.</span><span>slice</span><span>(</span><span>1</span><span>));</span></div></div><div><div><div>122</div></div><div><span>  </span><span>const</span><span> </span><span>object</span><span> </span><span>=</span><span> </span><span>await</span><span><span> </span><span>bucket</span><span>.</span></span><span>get</span><span><span>(</span><span>key</span><span>);</span></span></div></div><div><div><div>123</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span><span>object</span><span>) </span></span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>"文件不存在"</span><span>, { </span><span>status</span><span>:</span><span> </span><span>404</span><span> });</span></div></div><div><div><div>124</div></div><div>
</div></div><div><div><div>125</div></div><div><span>  </span><span>const</span><span> </span><span>headers</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>Headers</span><span>();</span></div></div><div><div><div>126</div></div><div><span>  </span><span>if</span><span><span> (</span><span>object</span><span>.</span></span><span>writeHttpMetadata</span><span><span>) </span><span>object</span><span>.</span></span><span>writeHttpMetadata</span><span><span>(</span><span>headers</span><span>);</span></span></div></div><div><div><div>127</div></div><div><span><span>  </span></span><span>headers</span><span>.</span><span>set</span><span>(</span><span>"Content-Length"</span><span><span>, (</span><span>object</span><span>.</span></span><span>size</span><span> </span><span>||</span><span> </span><span>0</span><span>).</span><span>toString</span><span>());</span></div></div><div><div><div>128</div></div><div><span><span>  </span></span><span>headers</span><span>.</span><span>set</span><span>(</span><span>"Content-Disposition"</span><span>, </span><span>`attachment; filename*=UTF-8''</span><span>${</span><span>encodeURIComponent</span><span><span>(</span><span>key</span><span>)</span></span><span>}</span><span>`</span><span>);</span></div></div><div><div><div>129</div></div><div><span>  </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span><span>(</span><span>object</span><span>.</span></span><span>body</span><span><span>, { </span><span>headers</span><span> });</span></span></div></div><div><div><div>130</div></div><div><span>}</span></div></div><div><div><div>131</div></div><div>
</div></div><div><div><div>132</div></div><div><span>async</span><span> </span><span>function</span><span> </span><span>handleFileUploadWithProgress</span><span><span>(</span><span>request</span><span>, </span><span>bucket</span><span>) {</span></span></div></div><div><div><div>133</div></div><div><span>  </span><span>const</span><span> </span><span>formData</span><span> </span><span>=</span><span> </span><span>await</span><span><span> </span><span>request</span><span>.</span></span><span>formData</span><span>();</span></div></div><div><div><div>134</div></div><div><span>  </span><span>const</span><span> </span><span>files</span><span> </span><span>=</span><span><span> </span><span>formData</span><span>.</span></span><span>getAll</span><span>(</span><span>"file"</span><span>);</span></div></div><div><div><div>135</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span><span>files</span><span>.</span></span><span>length</span><span>) </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>JSON</span><span>.</span><span>stringify</span><span>({ </span><span>error</span><span>:</span><span> </span><span>"未选择文件"</span><span> }), { </span><span>status</span><span>:</span><span> </span><span>400</span><span> });</span></div></div><div><div><div>136</div></div><div>
</div></div><div><div><div>137</div></div><div><span>  </span><span>const</span><span> </span><span>results</span><span> </span><span>=</span><span> [];</span></div></div><div><div><div>138</div></div><div><span>  </span><span>for</span><span> (</span><span>const</span><span> </span><span>file</span><span> </span><span>of</span><span><span> </span><span>files</span><span>) {</span></span></div></div><div><div><div>139</div></div><div><span>    </span><span>try</span><span> {</span></div></div><div><div><div>140</div></div><div><span>      </span><span>await</span><span><span> </span><span>bucket</span><span>.</span></span><span>put</span><span><span>(</span><span>file</span><span>.</span></span><span>name</span><span><span>, </span><span>file</span><span>.</span></span><span>stream</span><span>());</span></div></div><div><div><div>141</div></div><div><span><span>      </span></span><span>results</span><span>.</span><span>push</span><span>({ </span><span>name</span><span>:</span><span><span> </span><span>file</span><span>.</span></span><span>name</span><span>, </span><span>success</span><span>:</span><span> </span><span>true</span><span> });</span></div></div><div><div><div>142</div></div><div><span><span>    </span></span><span>} </span><span>catch</span><span><span> (</span><span>err</span><span>) {</span></span></div></div><div><div><div>143</div></div><div><span><span>      </span></span><span>results</span><span>.</span><span>push</span><span>({ </span><span>name</span><span>:</span><span><span> </span><span>file</span><span>.</span></span><span>name</span><span>, </span><span>success</span><span>:</span><span> </span><span>false</span><span>, </span><span>error</span><span>:</span><span><span> </span><span>err</span><span>.</span></span><span>message</span><span> });</span></div></div><div><div><div>144</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>145</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>146</div></div><div><span>  </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>JSON</span><span>.</span><span>stringify</span><span><span>({ </span><span>results</span><span> }), {</span></span></div></div><div><div><div>147</div></div><div><span>    </span><span>headers</span><span>:</span><span> { </span><span>"Content-Type"</span><span>:</span><span> </span><span>"application/json"</span><span> },</span></div></div><div><div><div>148</div></div><div><span><span>  </span></span><span>});</span></div></div><div><div><div>149</div></div><div><span>}</span></div></div><div><div><div>150</div></div><div>
</div></div><div><div><div>151</div></div><div><span>async</span><span> </span><span>function</span><span> </span><span>handleFileDelete</span><span><span>(</span><span>bucket</span><span>, </span><span>request</span><span>) {</span></span></div></div><div><div><div>152</div></div><div><span>  </span><span>const</span><span> </span><span>formData</span><span> </span><span>=</span><span> </span><span>await</span><span><span> </span><span>request</span><span>.</span></span><span>formData</span><span>();</span></div></div><div><div><div>153</div></div><div><span>  </span><span>const</span><span> </span><span>fileKeys</span><span> </span><span>=</span><span><span> </span><span>formData</span><span>.</span></span><span>getAll</span><span>(</span><span>"fileKey"</span><span>);</span></div></div><div><div><div>154</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span><span>fileKeys</span><span>.</span></span><span>length</span><span>) {</span></div></div><div><div><div>155</div></div><div><span>    </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>JSON</span><span>.</span><span>stringify</span><span>({ </span><span>error</span><span>:</span><span> </span><span>"未选择文件"</span><span> }), { </span><span>status</span><span>:</span><span> </span><span>400</span><span> });</span></div></div><div><div><div>156</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>157</div></div><div>
</div></div><div><div><div>158</div></div><div><span>  </span><span>const</span><span> </span><span>results</span><span> </span><span>=</span><span> [];</span></div></div><div><div><div>159</div></div><div><span>  </span><span>for</span><span> (</span><span>const</span><span> </span><span>key</span><span> </span><span>of</span><span><span> </span><span>fileKeys</span><span>) {</span></span></div></div><div><div><div>160</div></div><div><span>    </span><span>try</span><span> {</span></div></div><div><div><div>161</div></div><div><span>      </span><span>await</span><span><span> </span><span>bucket</span><span>.</span></span><span>delete</span><span><span>(</span><span>key</span><span>);</span></span></div></div><div><div><div>162</div></div><div><span><span>      </span></span><span>results</span><span>.</span><span>push</span><span><span>({ </span><span>key</span><span>, </span></span><span>success</span><span>:</span><span> </span><span>true</span><span> });</span></div></div><div><div><div>163</div></div><div><span><span>    </span></span><span>} </span><span>catch</span><span><span> (</span><span>err</span><span>) {</span></span></div></div><div><div><div>164</div></div><div><span><span>      </span></span><span>results</span><span>.</span><span>push</span><span><span>({ </span><span>key</span><span>, </span></span><span>success</span><span>:</span><span> </span><span>false</span><span>, </span><span>error</span><span>:</span><span><span> </span><span>err</span><span>.</span></span><span>message</span><span> });</span></div></div><div><div><div>165</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>166</div></div><div><span><span>  </span></span><span>}</span></div></div><div><div><div>167</div></div><div><span>  </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>JSON</span><span>.</span><span>stringify</span><span><span>({ </span><span>results</span><span> }), {</span></span></div></div><div><div><div>168</div></div><div><span>    </span><span>headers</span><span>:</span><span> { </span><span>"Content-Type"</span><span>:</span><span> </span><span>"application/json"</span><span> },</span></div></div><div><div><div>169</div></div><div><span><span>  </span></span><span>});</span></div></div><div><div><div>170</div></div><div><span>}</span></div></div><div><div><div>171</div></div><div>
</div></div><div><div><div>172</div></div><div><span>function</span><span> </span><span>renderEnhancedFileListPage</span><span><span>(</span><span>objects</span><span>, </span><span>url</span><span>) {</span></span></div></div><div><div><div>173</div></div><div><span>  </span><span>const</span><span> </span><span>stats</span><span> </span><span>=</span><span> {</span></div></div><div><div><div>174</div></div><div><span>    </span><span>count</span><span>:</span><span><span> </span><span>objects</span><span>.</span></span><span>length</span><span>,</span></div></div><div><div><div>175</div></div><div><span>    </span><span>size</span><span>:</span><span><span> </span><span>objects</span><span>.</span></span><span>reduce</span><span><span>((</span><span>sum</span><span>, </span><span>o</span><span>) </span></span><span>=&gt;</span><span><span> </span><span>sum</span><span> </span></span><span>+</span><span><span> </span><span>o</span><span>.</span></span><span>size</span><span>, </span><span>0</span><span>),</span></div></div><div><div><div>176</div></div><div><span><span>  </span></span><span>};</span></div></div><div><div><div>177</div></div><div>
</div></div><div><div><div>178</div></div><div><span>  </span><span>const</span><span> </span><span>searchParams</span><span> </span><span>=</span><span> </span><span>new</span><span> </span><span>URLSearchParams</span><span><span>(</span><span>url</span><span>.</span></span><span>searchParams</span><span>);</span></div></div><div><div><div>179</div></div><div><span>  </span><span>const</span><span> </span><span>currentSearch</span><span> </span><span>=</span><span><span> </span><span>searchParams</span><span>.</span></span><span>get</span><span>(</span><span>"search"</span><span>) </span><span>||</span><span> </span><span>""</span><span>;</span></div></div><div><div><div>180</div></div><div><span>  </span><span>const</span><span> </span><span>currentSort</span><span> </span><span>=</span><span><span> </span><span>searchParams</span><span>.</span></span><span>get</span><span>(</span><span>"sort"</span><span>) </span><span>||</span><span> </span><span>"name"</span><span>;</span></div></div><div><div><div>181</div></div><div><span>  </span><span>const</span><span> </span><span>currentOrder</span><span> </span><span>=</span><span><span> </span><span>searchParams</span><span>.</span></span><span>get</span><span>(</span><span>"order"</span><span>) </span><span>||</span><span> </span><span>"asc"</span><span>;</span></div></div><div><div><div>182</div></div><div>
</div></div><div><div><div>183</div></div><div><span>  </span><span>const</span><span> </span><span>fileItems</span><span> </span><span>=</span><span><span> </span><span>objects</span><span>.</span></span><span>length</span><span> </span><span>&gt;</span><span> </span><span>0</span></div></div><div><div><div>184</div></div><div><span>    </span><span>?</span><span><span> </span><span>objects</span><span>.</span></span><span>map</span><span><span>((</span><span>file</span><span>, </span><span>index</span><span>) </span></span><span>=&gt;</span><span> {</span></div></div><div><div><div>185</div></div><div><span>        </span><span>const</span><span> </span><span>encodedKey</span><span> </span><span>=</span><span> </span><span>encodeURIComponent</span><span><span>(</span><span>file</span><span>.</span></span><span>key</span><span>);</span></div></div><div><div><div>186</div></div><div><span>        </span><span>const</span><span> </span><span>isImage</span><span> </span><span>=</span><span><span> /</span><span>\.</span><span>(jpg</span><span>|</span><span>jpeg</span><span>|</span><span>png</span><span>|</span><span>gif</span><span>|</span><span>webp</span><span>|</span><span>bmp)</span></span><span>$</span><span>/</span><span>i</span><span>.</span><span>test</span><span><span>(</span><span>file</span><span>.</span></span><span>key</span><span>);</span></div></div><div><div><div>187</div></div><div><span>        </span><span>const</span><span> </span><span>isFolder</span><span> </span><span>=</span><span><span> </span><span>file</span><span>.</span></span><span>key</span><span>.</span><span>endsWith</span><span>(</span><span>"/"</span><span>);</span></div></div><div><div><div>188</div></div><div><span>        </span><span>const</span><span> </span><span>icon</span><span> </span><span>=</span><span><span> </span><span>isFolder</span><span> </span></span><span>?</span><span> </span><span>"fa-folder"</span><span> </span><span>:</span><span><span> (</span><span>isImage</span><span> </span></span><span>?</span><span> </span><span>"fa-image"</span><span> </span><span>:</span><span> </span><span>"fa-file"</span><span>);</span></div></div><div><div><div>189</div></div><div><span>        </span><span>const</span><span> </span><span>display</span><span> </span><span>=</span><span><span> </span><span>isImage</span></span></div></div><div><div><div>190</div></div><div><span>          </span><span>?</span><span> </span><span>`&lt;img src="/</span><span>${</span><span>encodedKey</span><span>}</span><span>" style="max-width: 60px; max-height: 60px; object-fit: cover; border-radius: 4px;" alt="preview" /&gt;`</span></div></div><div><div><div>191</div></div><div><span>          </span><span>:</span><span> </span><span>`&lt;i class="fas </span><span>${</span><span>icon</span><span>}</span><span>" style="font-size: 1.5rem; color: var(--primary);"&gt;&lt;/i&gt;`</span><span>;</span></div></div><div><div><div>192</div></div><div>
</div></div><div><div><div>193</div></div><div><span>        </span><span>return</span><span> </span><span>`</span></div></div><div><div><div>194</div></div><div><span><span>          </span></span><span>&lt;div class="file-item"&gt;</span></div></div><div><div><div>195</div></div><div><span><span>            </span></span><span>&lt;div class="file-checkbox"&gt;</span></div></div><div><div><div>196</div></div><div><span><span>              </span></span><span>&lt;input type="checkbox" name="fileKey" value="</span><span>${</span><span>encodeURIComponent</span><span><span>(</span><span>file</span></span><span>.</span><span>key</span><span>)</span><span>}</span><span>" id="check-</span><span>${</span><span>index</span><span>}</span><span>" /&gt;</span></div></div><div><div><div>197</div></div><div><span><span>            </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>198</div></div><div><span><span>            </span></span><span>&lt;div class="file-icon"&gt;</span><span>${</span><span>display</span><span>}</span><span>&lt;/div&gt;</span></div></div><div><div><div>199</div></div><div><span><span>            </span></span><span>&lt;div class="file-name-cell"&gt;</span></div></div><div><div><div>200</div></div><div><span><span>              </span></span><span>&lt;a href="/</span><span>${</span><span>encodedKey</span><span>}</span><span>" class="file-name link"&gt;</span><span>${</span><span>escapeHtml</span><span><span>(</span><span>file</span></span><span>.</span><span>key</span><span>)</span><span>}</span><span>&lt;/a&gt;</span></div></div><div><div><div>201</div></div><div><span><span>            </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>202</div></div><div><span><span>            </span></span><span>&lt;div class="file-size"&gt;</span><span>${</span><span>formatFileSize</span><span><span>(</span><span>file</span></span><span>.</span><span>size</span><span>)</span><span>}</span><span>&lt;/div&gt;</span></div></div><div><div><div>203</div></div><div><span><span>            </span></span><span>&lt;div class="file-date"&gt;</span><span>${</span><span>new</span><span> </span><span>Date</span><span><span>(</span><span>file</span></span><span>.</span><span>uploaded</span><span>)</span><span>.</span><span>toLocaleString</span><span>(</span><span>'</span><span>zh-CN</span><span>'</span><span>)</span><span>}</span><span>&lt;/div&gt;</span></div></div><div><div><div>204</div></div><div><span><span>            </span></span><span>&lt;div class="file-actions"&gt;</span></div></div><div><div><div>205</div></div><div><span><span>              </span></span><span>&lt;div class="actions-wrapper"&gt;</span></div></div><div><div><div>206</div></div><div><span><span>                </span></span><span>&lt;a href="/</span><span>${</span><span>encodedKey</span><span>}</span><span>" class="btn btn-primary btn-sm"&gt;下载&lt;/a&gt;</span></div></div><div><div><div>207</div></div><div><span><span>                </span></span><span>&lt;form style="display: inline;" onsubmit="return deleteSingleFile('</span><span>${</span><span>escapeHtml</span><span><span>(</span><span>file</span></span><span>.</span><span>key</span><span>)</span><span>}</span><span>')"&gt;</span></div></div><div><div><div>208</div></div><div><span><span>                  </span></span><span>&lt;input type="hidden" name="fileKey" value="</span><span>${</span><span>escapeHtml</span><span><span>(</span><span>file</span></span><span>.</span><span>key</span><span>)</span><span>}</span><span>"&gt;</span></div></div><div><div><div>209</div></div><div><span><span>                  </span></span><span>&lt;button type="submit" class="btn btn-danger btn-sm"&gt;删除&lt;/button&gt;</span></div></div><div><div><div>210</div></div><div><span><span>                </span></span><span>&lt;/form&gt;</span></div></div><div><div><div>211</div></div><div><span><span>              </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>212</div></div><div><span><span>            </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>213</div></div><div><span><span>          </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>214</div></div><div><span><span>        </span></span><span>`</span><span>;</span></div></div><div><div><div>215</div></div><div><span><span>      </span></span><span>}).</span><span>join</span><span>(</span><span>""</span><span>)</span></div></div><div><div><div>216</div></div><div><span>    </span><span>:</span><span> </span><span>`</span></div></div><div><div><div>217</div></div><div><span><span>      </span></span><span>&lt;div class="empty-state"&gt;</span></div></div><div><div><div>218</div></div><div><span><span>        </span></span><span>&lt;div class="empty-icon"&gt;&lt;i class="fas fa-folder-open"&gt;&lt;/i&gt;&lt;/div&gt;</span></div></div><div><div><div>219</div></div><div><span><span>        </span></span><span>&lt;h3&gt;📁 暂无文件&lt;/h3&gt;</span></div></div><div><div><div>220</div></div><div><span><span>        </span></span><span>&lt;p&gt;上传您的第一个文件开始使用吧！&lt;/p&gt;</span></div></div><div><div><div>221</div></div><div><span><span>      </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>222</div></div><div><span><span>    </span></span><span>`</span><span>;</span></div></div><div><div><div>223</div></div><div>
</div></div><div><div><div>224</div></div><div><span>  </span><span>return</span><span> </span><span>`</span></div></div><div><div><div>225</div></div><div><span><span>  </span></span><span>&lt;!DOCTYPE html&gt;</span></div></div><div><div><div>226</div></div><div><span><span>  </span></span><span>&lt;html lang="zh-CN"&gt;</span></div></div><div><div><div>227</div></div><div><span><span>  </span></span><span>&lt;head&gt;</span></div></div><div><div><div>228</div></div><div><span><span>    </span></span><span>&lt;meta charset="UTF-8" /&gt;</span></div></div><div><div><div>229</div></div><div><span><span>    </span></span><span>&lt;meta name="viewport" content="width=device-width, initial-scale=1.0"/&gt;</span></div></div><div><div><div>230</div></div><div><span><span>    </span></span><span>&lt;title&gt;☁️ 我的云存储&lt;/title&gt;</span></div></div><div><div><div>231</div></div><div><span><span>    </span></span><span>&lt;link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&amp;display=swap" rel="stylesheet"&gt;</span></div></div><div><div><div>232</div></div><div><span><span>    </span></span><span>&lt;link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"&gt;</span></div></div><div><div><div>233</div></div><div><span><span>    </span></span><span>&lt;style&gt;</span></div></div><div><div><div>234</div></div><div><span><span>      </span></span><span>:root {</span></div></div><div><div><div>235</div></div><div><span><span>        </span></span><span>--primary: #6366f1;</span></div></div><div><div><div>236</div></div><div><span><span>        </span></span><span>--primary-hover: #4f46e5;</span></div></div><div><div><div>237</div></div><div><span><span>        </span></span><span>--danger: #ef4444;</span></div></div><div><div><div>238</div></div><div><span><span>        </span></span><span>--text: #1f2937;</span></div></div><div><div><div>239</div></div><div><span><span>        </span></span><span>--text-light: #6b7280;</span></div></div><div><div><div>240</div></div><div><span><span>        </span></span><span>--bg: #f8fafc;</span></div></div><div><div><div>241</div></div><div><span><span>        </span></span><span>--card-bg: #ffffff;</span></div></div><div><div><div>242</div></div><div><span><span>        </span></span><span>--border: #e5e7eb;</span></div></div><div><div><div>243</div></div><div><span><span>        </span></span><span>--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);</span></div></div><div><div><div>244</div></div><div><span><span>        </span></span><span>--input-bg: #ffffff;</span></div></div><div><div><div>245</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>246</div></div><div><span><span>      </span></span><span>[data-theme="dark"] {</span></div></div><div><div><div>247</div></div><div><span><span>        </span></span><span>--text: #f9fafb;</span></div></div><div><div><div>248</div></div><div><span><span>        </span></span><span>--text-light: #9ca3af;</span></div></div><div><div><div>249</div></div><div><span><span>        </span></span><span>--bg: #111827;</span></div></div><div><div><div>250</div></div><div><span><span>        </span></span><span>--card-bg: #1f2937;</span></div></div><div><div><div>251</div></div><div><span><span>        </span></span><span>--border: #374151;</span></div></div><div><div><div>252</div></div><div><span><span>        </span></span><span>--input-bg: #374151;</span></div></div><div><div><div>253</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>254</div></div><div><span><span>      </span></span><span>* { margin: 0; padding: 0; box-sizing: border-box; }</span></div></div><div><div><div>255</div></div><div><span><span>      </span></span><span>body {</span></div></div><div><div><div>256</div></div><div><span><span>        </span></span><span>font-family: 'Inter', sans-serif;</span></div></div><div><div><div>257</div></div><div><span><span>        </span></span><span>background-color: var(--bg);</span></div></div><div><div><div>258</div></div><div><span><span>        </span></span><span>color: var(--text);</span></div></div><div><div><div>259</div></div><div><span><span>        </span></span><span>line-height: 1.6;</span></div></div><div><div><div>260</div></div><div><span><span>        </span></span><span>transition: background-color 0.3s, color 0.3s;</span></div></div><div><div><div>261</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>262</div></div><div><span><span>      </span></span><span>.container { max-width: 1200px; margin: 0 auto; padding: 2rem 1rem; }</span></div></div><div><div><div>263</div></div><div><span><span>      </span></span><span>.header {</span></div></div><div><div><div>264</div></div><div><span><span>        </span></span><span>display: flex;</span></div></div><div><div><div>265</div></div><div><span><span>        </span></span><span>justify-content: space-between;</span></div></div><div><div><div>266</div></div><div><span><span>        </span></span><span>align-items: center;</span></div></div><div><div><div>267</div></div><div><span><span>        </span></span><span>margin-bottom: 2rem;</span></div></div><div><div><div>268</div></div><div><span><span>        </span></span><span>flex-wrap: wrap;</span></div></div><div><div><div>269</div></div><div><span><span>        </span></span><span>gap: 1rem;</span></div></div><div><div><div>270</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>271</div></div><div><span><span>      </span></span><span>.header h1 {</span></div></div><div><div><div>272</div></div><div><span><span>        </span></span><span>font-size: 1.8rem;</span></div></div><div><div><div>273</div></div><div><span><span>        </span></span><span>font-weight: 700;</span></div></div><div><div><div>274</div></div><div><span><span>        </span></span><span>color: var(--primary);</span></div></div><div><div><div>275</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>276</div></div><div><span><span>      </span></span><span>.theme-toggle {</span></div></div><div><div><div>277</div></div><div><span><span>        </span></span><span>background: var(--card-bg);</span></div></div><div><div><div>278</div></div><div><span><span>        </span></span><span>border: 1px solid var(--border);</span></div></div><div><div><div>279</div></div><div><span><span>        </span></span><span>color: var(--text);</span></div></div><div><div><div>280</div></div><div><span><span>        </span></span><span>padding: 0.5rem 1rem;</span></div></div><div><div><div>281</div></div><div><span><span>        </span></span><span>border-radius: 6px;</span></div></div><div><div><div>282</div></div><div><span><span>        </span></span><span>cursor: pointer;</span></div></div><div><div><div>283</div></div><div><span><span>        </span></span><span>font-size: 0.9rem;</span></div></div><div><div><div>284</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>285</div></div><div><span><span>      </span></span><span>.upload-area {</span></div></div><div><div><div>286</div></div><div><span><span>        </span></span><span>background: var(--card-bg);</span></div></div><div><div><div>287</div></div><div><span><span>        </span></span><span>border-radius: 12px;</span></div></div><div><div><div>288</div></div><div><span><span>        </span></span><span>padding: 2rem;</span></div></div><div><div><div>289</div></div><div><span><span>        </span></span><span>margin-bottom: 2rem;</span></div></div><div><div><div>290</div></div><div><span><span>        </span></span><span>box-shadow: var(--shadow);</span></div></div><div><div><div>291</div></div><div><span><span>        </span></span><span>border: 1px solid var(--border);</span></div></div><div><div><div>292</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>293</div></div><div><span><span>      </span></span><span>.controls {</span></div></div><div><div><div>294</div></div><div><span><span>        </span></span><span>background: var(--card-bg);</span></div></div><div><div><div>295</div></div><div><span><span>        </span></span><span>border-radius: 12px;</span></div></div><div><div><div>296</div></div><div><span><span>        </span></span><span>padding: 1.5rem;</span></div></div><div><div><div>297</div></div><div><span><span>        </span></span><span>margin-bottom: 2rem;</span></div></div><div><div><div>298</div></div><div><span><span>        </span></span><span>box-shadow: var(--shadow);</span></div></div><div><div><div>299</div></div><div><span><span>        </span></span><span>border: 1px solid var(--border);</span></div></div><div><div><div>300</div></div><div><span><span>        </span></span><span>display: flex;</span></div></div><div><div><div>301</div></div><div><span><span>        </span></span><span>flex-wrap: wrap;</span></div></div><div><div><div>302</div></div><div><span><span>        </span></span><span>gap: 1rem;</span></div></div><div><div><div>303</div></div><div><span><span>        </span></span><span>align-items: center;</span></div></div><div><div><div>304</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>305</div></div><div><span><span>      </span></span><span>.search-box {</span></div></div><div><div><div>306</div></div><div><span><span>        </span></span><span>flex: 1;</span></div></div><div><div><div>307</div></div><div><span><span>        </span></span><span>min-width: 200px;</span></div></div><div><div><div>308</div></div><div><span><span>        </span></span><span>padding: 0.5rem;</span></div></div><div><div><div>309</div></div><div><span><span>        </span></span><span>border: 1px solid var(--border);</span></div></div><div><div><div>310</div></div><div><span><span>        </span></span><span>border-radius: 6px;</span></div></div><div><div><div>311</div></div><div><span><span>        </span></span><span>background: var(--input-bg);</span></div></div><div><div><div>312</div></div><div><span><span>        </span></span><span>color: var(--text);</span></div></div><div><div><div>313</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>314</div></div><div><span><span>      </span></span><span>.sort-select {</span></div></div><div><div><div>315</div></div><div><span><span>        </span></span><span>padding: 0.5rem;</span></div></div><div><div><div>316</div></div><div><span><span>        </span></span><span>border: 1px solid var(--border);</span></div></div><div><div><div>317</div></div><div><span><span>        </span></span><span>border-radius: 6px;</span></div></div><div><div><div>318</div></div><div><span><span>        </span></span><span>background: var(--input-bg);</span></div></div><div><div><div>319</div></div><div><span><span>        </span></span><span>color: var(--text);</span></div></div><div><div><div>320</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>321</div></div><div><span><span>      </span></span><span>.file-list {</span></div></div><div><div><div>322</div></div><div><span><span>        </span></span><span>background: var(--card-bg);</span></div></div><div><div><div>323</div></div><div><span><span>        </span></span><span>border-radius: 12px;</span></div></div><div><div><div>324</div></div><div><span><span>        </span></span><span>box-shadow: var(--shadow);</span></div></div><div><div><div>325</div></div><div><span><span>        </span></span><span>border: 1px solid var(--border);</span></div></div><div><div><div>326</div></div><div><span><span>        </span></span><span>overflow: hidden;</span></div></div><div><div><div>327</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>328</div></div><div><span><span>      </span></span><span>.file-list-header {</span></div></div><div><div><div>329</div></div><div><span><span>        </span></span><span>display: grid;</span></div></div><div><div><div>330</div></div><div><span><span>        </span></span><span>grid-template-columns: 0.3fr 3fr 1fr 1fr 1fr;</span></div></div><div><div><div>331</div></div><div><span><span>        </span></span><span>gap: 1rem;</span></div></div><div><div><div>332</div></div><div><span><span>        </span></span><span>padding: 1rem;</span></div></div><div><div><div>333</div></div><div><span><span>        </span></span><span>font-weight: 600;</span></div></div><div><div><div>334</div></div><div><span><span>        </span></span><span>background: #f9fafb;</span></div></div><div><div><div>335</div></div><div><span><span>        </span></span><span>border-bottom: 1px solid var(--border);</span></div></div><div><div><div>336</div></div><div><span><span>        </span></span><span>align-items: center;</span></div></div><div><div><div>337</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>338</div></div><div><span><span>      </span></span><span>[data-theme="dark"] .file-list-header {</span></div></div><div><div><div>339</div></div><div><span><span>        </span></span><span>background: #374151;</span></div></div><div><div><div>340</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>341</div></div><div><span><span>      </span></span><span>.file-item {</span></div></div><div><div><div>342</div></div><div><span><span>        </span></span><span>display: grid;</span></div></div><div><div><div>343</div></div><div><span><span>        </span></span><span>grid-template-columns: 0.3fr 3fr 1fr 1fr 1fr 1fr;</span></div></div><div><div><div>344</div></div><div><span><span>        </span></span><span>gap: 1rem;</span></div></div><div><div><div>345</div></div><div><span><span>        </span></span><span>padding: 1rem;</span></div></div><div><div><div>346</div></div><div><span><span>        </span></span><span>align-items: center;</span></div></div><div><div><div>347</div></div><div><span><span>        </span></span><span>border-bottom: 1px solid var(--border);</span></div></div><div><div><div>348</div></div><div><span><span>        </span></span><span>transition: background-color 0.15s;</span></div></div><div><div><div>349</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>350</div></div><div><span><span>      </span></span><span>.file-item:hover {</span></div></div><div><div><div>351</div></div><div><span><span>        </span></span><span>background-color: #f9fafb;</span></div></div><div><div><div>352</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>353</div></div><div><span><span>      </span></span><span>[data-theme="dark"] .file-item:hover {</span></div></div><div><div><div>354</div></div><div><span><span>        </span></span><span>background-color: #374151;</span></div></div><div><div><div>355</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>356</div></div><div><span><span>      </span></span><span>.file-item:last-child { border-bottom: none; }</span></div></div><div><div><div>357</div></div><div><span><span>      </span></span><span>.file-checkbox {</span></div></div><div><div><div>358</div></div><div><span><span>        </span></span><span>display: flex;</span></div></div><div><div><div>359</div></div><div><span><span>        </span></span><span>justify-content: center;</span></div></div><div><div><div>360</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>361</div></div><div><span><span>      </span></span><span>.file-icon {</span></div></div><div><div><div>362</div></div><div><span><span>        </span></span><span>display: flex;</span></div></div><div><div><div>363</div></div><div><span><span>        </span></span><span>align-items: center;</span></div></div><div><div><div>364</div></div><div><span><span>        </span></span><span>justify-content: center;</span></div></div><div><div><div>365</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>366</div></div><div><span><span>      </span></span><span>.file-name-cell {</span></div></div><div><div><div>367</div></div><div><span><span>        </span></span><span>min-width: 0;</span></div></div><div><div><div>368</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>369</div></div><div><span><span>      </span></span><span>.file-name {</span></div></div><div><div><div>370</div></div><div><span><span>        </span></span><span>font-weight: 500;</span></div></div><div><div><div>371</div></div><div><span><span>        </span></span><span>color: var(--text);</span></div></div><div><div><div>372</div></div><div><span><span>        </span></span><span>word-break: break-all;</span></div></div><div><div><div>373</div></div><div><span><span>        </span></span><span>overflow-wrap: break-word;</span></div></div><div><div><div>374</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>375</div></div><div><span><span>      </span></span><span>.file-name.link {</span></div></div><div><div><div>376</div></div><div><span><span>        </span></span><span>text-decoration: none;</span></div></div><div><div><div>377</div></div><div><span><span>        </span></span><span>color: var(--primary);</span></div></div><div><div><div>378</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>379</div></div><div><span><span>      </span></span><span>.file-name.link:hover {</span></div></div><div><div><div>380</div></div><div><span><span>        </span></span><span>text-decoration: underline;</span></div></div><div><div><div>381</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>382</div></div><div><span><span>      </span></span><span>.file-size, .file-date {</span></div></div><div><div><div>383</div></div><div><span><span>        </span></span><span>color: var(--text-light);</span></div></div><div><div><div>384</div></div><div><span><span>        </span></span><span>font-size: 0.9rem;</span></div></div><div><div><div>385</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>386</div></div><div><span><span>      </span></span><span>.file-actions {</span></div></div><div><div><div>387</div></div><div><span><span>        </span></span><span>min-width: 120px;</span></div></div><div><div><div>388</div></div><div><span><span>        </span></span><span>display: flex;</span></div></div><div><div><div>389</div></div><div><span><span>        </span></span><span>justify-content: flex-end;</span></div></div><div><div><div>390</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>391</div></div><div><span><span>      </span></span><span>.actions-wrapper {</span></div></div><div><div><div>392</div></div><div><span><span>        </span></span><span>display: flex;</span></div></div><div><div><div>393</div></div><div><span><span>        </span></span><span>gap: 0.5rem;</span></div></div><div><div><div>394</div></div><div><span><span>        </span></span><span>flex-wrap: wrap;</span></div></div><div><div><div>395</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>396</div></div><div><span><span>      </span></span><span>.btn {</span></div></div><div><div><div>397</div></div><div><span><span>        </span></span><span>padding: 0.4rem 0.8rem;</span></div></div><div><div><div>398</div></div><div><span><span>        </span></span><span>border-radius: 6px;</span></div></div><div><div><div>399</div></div><div><span><span>        </span></span><span>font-size: 0.85rem;</span></div></div><div><div><div>400</div></div><div><span><span>        </span></span><span>font-weight: 500;</span></div></div><div><div><div>401</div></div><div><span><span>        </span></span><span>cursor: pointer;</span></div></div><div><div><div>402</div></div><div><span><span>        </span></span><span>text-decoration: none;</span></div></div><div><div><div>403</div></div><div><span><span>        </span></span><span>display: inline-flex;</span></div></div><div><div><div>404</div></div><div><span><span>        </span></span><span>align-items: center;</span></div></div><div><div><div>405</div></div><div><span><span>        </span></span><span>gap: 0.3rem;</span></div></div><div><div><div>406</div></div><div><span><span>        </span></span><span>border: 1px solid transparent;</span></div></div><div><div><div>407</div></div><div><span><span>        </span></span><span>transition: all 0.15s ease;</span></div></div><div><div><div>408</div></div><div><span><span>        </span></span><span>white-space: nowrap;</span></div></div><div><div><div>409</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>410</div></div><div><span><span>      </span></span><span>.btn-sm { padding: 0.25rem 0.6rem; font-size: 0.8rem; }</span></div></div><div><div><div>411</div></div><div><span><span>      </span></span><span>.btn-primary {</span></div></div><div><div><div>412</div></div><div><span><span>        </span></span><span>background: var(--primary);</span></div></div><div><div><div>413</div></div><div><span><span>        </span></span><span>color: white;</span></div></div><div><div><div>414</div></div><div><span><span>        </span></span><span>border-color: var(--primary);</span></div></div><div><div><div>415</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>416</div></div><div><span><span>      </span></span><span>.btn-primary:hover {</span></div></div><div><div><div>417</div></div><div><span><span>        </span></span><span>background: var(--primary-hover);</span></div></div><div><div><div>418</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>419</div></div><div><span><span>      </span></span><span>.btn-danger {</span></div></div><div><div><div>420</div></div><div><span><span>        </span></span><span>background: var(--danger);</span></div></div><div><div><div>421</div></div><div><span><span>        </span></span><span>color: white;</span></div></div><div><div><div>422</div></div><div><span><span>        </span></span><span>border-color: var(--danger);</span></div></div><div><div><div>423</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>424</div></div><div><span><span>      </span></span><span>.btn-danger:hover {</span></div></div><div><div><div>425</div></div><div><span><span>        </span></span><span>background: #dc2626;</span></div></div><div><div><div>426</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>427</div></div><div><span><span>      </span></span><span>.link { color: var(--primary); }</span></div></div><div><div><div>428</div></div><div><span><span>      </span></span><span>.empty-state {</span></div></div><div><div><div>429</div></div><div><span><span>        </span></span><span>text-align: center;</span></div></div><div><div><div>430</div></div><div><span><span>        </span></span><span>padding: 3rem;</span></div></div><div><div><div>431</div></div><div><span><span>        </span></span><span>color: var(--text-light);</span></div></div><div><div><div>432</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>433</div></div><div><span><span>      </span></span><span>.empty-icon {</span></div></div><div><div><div>434</div></div><div><span><span>        </span></span><span>font-size: 3rem;</span></div></div><div><div><div>435</div></div><div><span><span>        </span></span><span>margin-bottom: 1rem;</span></div></div><div><div><div>436</div></div><div><span><span>        </span></span><span>color: var(--border);</span></div></div><div><div><div>437</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>438</div></div><div><span><span>      </span></span><span>.bulk-actions {</span></div></div><div><div><div>439</div></div><div><span><span>        </span></span><span>background: var(--card-bg);</span></div></div><div><div><div>440</div></div><div><span><span>        </span></span><span>border-radius: 12px;</span></div></div><div><div><div>441</div></div><div><span><span>        </span></span><span>padding: 1rem;</span></div></div><div><div><div>442</div></div><div><span><span>        </span></span><span>margin-bottom: 1rem;</span></div></div><div><div><div>443</div></div><div><span><span>        </span></span><span>box-shadow: var(--shadow);</span></div></div><div><div><div>444</div></div><div><span><span>        </span></span><span>border: 1px solid var(--border);</span></div></div><div><div><div>445</div></div><div><span><span>        </span></span><span>display: flex;</span></div></div><div><div><div>446</div></div><div><span><span>        </span></span><span>justify-content: space-between;</span></div></div><div><div><div>447</div></div><div><span><span>        </span></span><span>align-items: center;</span></div></div><div><div><div>448</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>449</div></div><div><span><span>      </span></span><span>.bulk-actions button {</span></div></div><div><div><div>450</div></div><div><span><span>        </span></span><span>padding: 0.5rem 1rem;</span></div></div><div><div><div>451</div></div><div><span><span>        </span></span><span>border-radius: 6px;</span></div></div><div><div><div>452</div></div><div><span><span>        </span></span><span>font-weight: 500;</span></div></div><div><div><div>453</div></div><div><span><span>        </span></span><span>cursor: pointer;</span></div></div><div><div><div>454</div></div><div><span><span>        </span></span><span>border: 1px solid transparent;</span></div></div><div><div><div>455</div></div><div><span><span>        </span></span><span>transition: all 0.15s;</span></div></div><div><div><div>456</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>457</div></div><div><span><span>      </span></span><span>.bulk-delete-btn {</span></div></div><div><div><div>458</div></div><div><span><span>        </span></span><span>background: var(--danger);</span></div></div><div><div><div>459</div></div><div><span><span>        </span></span><span>color: white;</span></div></div><div><div><div>460</div></div><div><span><span>        </span></span><span>border-color: var(--danger);</span></div></div><div><div><div>461</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>462</div></div><div><span><span>      </span></span><span>.bulk-delete-btn:hover:not(:disabled) {</span></div></div><div><div><div>463</div></div><div><span><span>        </span></span><span>background: #dc2626;</span></div></div><div><div><div>464</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>465</div></div><div><span><span>      </span></span><span>.bulk-delete-btn:disabled {</span></div></div><div><div><div>466</div></div><div><span><span>        </span></span><span>background: var(--border);</span></div></div><div><div><div>467</div></div><div><span><span>        </span></span><span>color: var(--text-light);</span></div></div><div><div><div>468</div></div><div><span><span>        </span></span><span>cursor: not-allowed;</span></div></div><div><div><div>469</div></div><div><span><span>        </span></span><span>border-color: var(--border);</span></div></div><div><div><div>470</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>471</div></div><div>
</div></div><div><div><div>472</div></div><div><span><span>      </span></span><span>/* 移动端优化 */</span></div></div><div><div><div>473</div></div><div><span><span>      </span></span><span>@media (max-width: 768px) {</span></div></div><div><div><div>474</div></div><div><span><span>        </span></span><span>.container {</span></div></div><div><div><div>475</div></div><div><span><span>          </span></span><span>padding: 1rem 0.5rem;</span></div></div><div><div><div>476</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>477</div></div><div>
</div></div><div><div><div>478</div></div><div><span><span>        </span></span><span>.header {</span></div></div><div><div><div>479</div></div><div><span><span>          </span></span><span>flex-direction: column;</span></div></div><div><div><div>480</div></div><div><span><span>          </span></span><span>text-align: center;</span></div></div><div><div><div>481</div></div><div><span><span>          </span></span><span>gap: 0.5rem;</span></div></div><div><div><div>482</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>483</div></div><div>
</div></div><div><div><div>484</div></div><div><span><span>        </span></span><span>.header h1 {</span></div></div><div><div><div>485</div></div><div><span><span>          </span></span><span>font-size: 1.5rem;</span></div></div><div><div><div>486</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>487</div></div><div>
</div></div><div><div><div>488</div></div><div><span><span>        </span></span><span>.controls {</span></div></div><div><div><div>489</div></div><div><span><span>          </span></span><span>flex-direction: column;</span></div></div><div><div><div>490</div></div><div><span><span>          </span></span><span>align-items: stretch;</span></div></div><div><div><div>491</div></div><div><span><span>          </span></span><span>gap: 0.75rem;</span></div></div><div><div><div>492</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>493</div></div><div>
</div></div><div><div><div>494</div></div><div><span><span>        </span></span><span>.search-box,</span></div></div><div><div><div>495</div></div><div><span><span>        </span></span><span>.sort-select {</span></div></div><div><div><div>496</div></div><div><span><span>          </span></span><span>min-width: unset;</span></div></div><div><div><div>497</div></div><div><span><span>          </span></span><span>width: 100%;</span></div></div><div><div><div>498</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>499</div></div><div>
</div></div><div><div><div>500</div></div><div><span><span>        </span></span><span>.file-list-header {</span></div></div><div><div><div>501</div></div><div><span><span>          </span></span><span>display: none;</span></div></div><div><div><div>502</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>503</div></div><div>
</div></div><div><div><div>504</div></div><div><span><span>        </span></span><span>.file-item {</span></div></div><div><div><div>505</div></div><div><span><span>          </span></span><span>grid-template-columns: 1fr;</span></div></div><div><div><div>506</div></div><div><span><span>          </span></span><span>gap: 0.75rem;</span></div></div><div><div><div>507</div></div><div><span><span>          </span></span><span>padding: 1rem;</span></div></div><div><div><div>508</div></div><div><span><span>          </span></span><span>align-items: flex-start;</span></div></div><div><div><div>509</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>510</div></div><div>
</div></div><div><div><div>511</div></div><div><span><span>        </span></span><span>.file-checkbox {</span></div></div><div><div><div>512</div></div><div><span><span>          </span></span><span>justify-content: flex-start;</span></div></div><div><div><div>513</div></div><div><span><span>          </span></span><span>margin-right: 0.5rem;</span></div></div><div><div><div>514</div></div><div><span><span>          </span></span><span>margin-left: 0.5rem;</span></div></div><div><div><div>515</div></div><div><span><span>          </span></span><span>display: flex; /* 显示复选框 */</span></div></div><div><div><div>516</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>517</div></div><div>
</div></div><div><div><div>518</div></div><div><span><span>        </span></span><span>.file-item &gt; div {</span></div></div><div><div><div>519</div></div><div><span><span>          </span></span><span>width: 100%;</span></div></div><div><div><div>520</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>521</div></div><div>
</div></div><div><div><div>522</div></div><div><span><span>        </span></span><span>.file-name-cell {</span></div></div><div><div><div>523</div></div><div><span><span>          </span></span><span>order: -2;</span></div></div><div><div><div>524</div></div><div><span><span>          </span></span><span>min-width: unset;</span></div></div><div><div><div>525</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>526</div></div><div>
</div></div><div><div><div>527</div></div><div><span><span>        </span></span><span>.file-name {</span></div></div><div><div><div>528</div></div><div><span><span>          </span></span><span>font-size: 1.1rem;</span></div></div><div><div><div>529</div></div><div><span><span>          </span></span><span>word-break: break-word;</span></div></div><div><div><div>530</div></div><div><span><span>          </span></span><span>margin-bottom: 0.5rem;</span></div></div><div><div><div>531</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>532</div></div><div>
</div></div><div><div><div>533</div></div><div><span><span>        </span></span><span>.file-icon {</span></div></div><div><div><div>534</div></div><div><span><span>          </span></span><span>order: -1;</span></div></div><div><div><div>535</div></div><div><span><span>          </span></span><span>font-size: 2rem;</span></div></div><div><div><div>536</div></div><div><span><span>          </span></span><span>margin-bottom: 0.5rem;</span></div></div><div><div><div>537</div></div><div><span><span>          </span></span><span>justify-content: flex-start;</span></div></div><div><div><div>538</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>539</div></div><div>
</div></div><div><div><div>540</div></div><div><span><span>        </span></span><span>.file-icon i {</span></div></div><div><div><div>541</div></div><div><span><span>          </span></span><span>font-size: 2rem !important;</span></div></div><div><div><div>542</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>543</div></div><div>
</div></div><div><div><div>544</div></div><div><span><span>        </span></span><span>.file-icon img {</span></div></div><div><div><div>545</div></div><div><span><span>          </span></span><span>max-width: 80px !important;</span></div></div><div><div><div>546</div></div><div><span><span>          </span></span><span>max-height: 80px !important;</span></div></div><div><div><div>547</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>548</div></div><div>
</div></div><div><div><div>549</div></div><div><span><span>        </span></span><span>.file-size,</span></div></div><div><div><div>550</div></div><div><span><span>        </span></span><span>.file-date {</span></div></div><div><div><div>551</div></div><div><span><span>          </span></span><span>order: 1;</span></div></div><div><div><div>552</div></div><div><span><span>          </span></span><span>font-size: 0.85rem;</span></div></div><div><div><div>553</div></div><div><span><span>          </span></span><span>margin-bottom: 0.5rem;</span></div></div><div><div><div>554</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>555</div></div><div>
</div></div><div><div><div>556</div></div><div><span><span>        </span></span><span>.file-actions {</span></div></div><div><div><div>557</div></div><div><span><span>          </span></span><span>order: 2;</span></div></div><div><div><div>558</div></div><div><span><span>          </span></span><span>min-width: unset;</span></div></div><div><div><div>559</div></div><div><span><span>          </span></span><span>justify-content: flex-start;</span></div></div><div><div><div>560</div></div><div><span><span>          </span></span><span>margin-top: 0.5rem;</span></div></div><div><div><div>561</div></div><div><span><span>          </span></span><span>width: 100%;</span></div></div><div><div><div>562</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>563</div></div><div>
</div></div><div><div><div>564</div></div><div><span><span>        </span></span><span>.actions-wrapper {</span></div></div><div><div><div>565</div></div><div><span><span>          </span></span><span>justify-content: flex-start;</span></div></div><div><div><div>566</div></div><div><span><span>          </span></span><span>width: 100%;</span></div></div><div><div><div>567</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>568</div></div><div>
</div></div><div><div><div>569</div></div><div><span><span>        </span></span><span>.btn {</span></div></div><div><div><div>570</div></div><div><span><span>          </span></span><span>flex: 1;</span></div></div><div><div><div>571</div></div><div><span><span>          </span></span><span>justify-content: center;</span></div></div><div><div><div>572</div></div><div><span><span>          </span></span><span>min-width: 0;</span></div></div><div><div><div>573</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>574</div></div><div>
</div></div><div><div><div>575</div></div><div><span><span>        </span></span><span>.btn-sm {</span></div></div><div><div><div>576</div></div><div><span><span>          </span></span><span>flex: 1;</span></div></div><div><div><div>577</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>578</div></div><div>
</div></div><div><div><div>579</div></div><div><span><span>        </span></span><span>.bulk-actions {</span></div></div><div><div><div>580</div></div><div><span><span>          </span></span><span>flex-direction: column;</span></div></div><div><div><div>581</div></div><div><span><span>          </span></span><span>gap: 0.75rem;</span></div></div><div><div><div>582</div></div><div><span><span>          </span></span><span>align-items: stretch;</span></div></div><div><div><div>583</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>584</div></div><div>
</div></div><div><div><div>585</div></div><div><span><span>        </span></span><span>.bulk-actions button {</span></div></div><div><div><div>586</div></div><div><span><span>          </span></span><span>width: 100%;</span></div></div><div><div><div>587</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>588</div></div><div>
</div></div><div><div><div>589</div></div><div><span><span>        </span></span><span>/* 移动端显示复选框 */</span></div></div><div><div><div>590</div></div><div><span><span>        </span></span><span>.file-checkbox {</span></div></div><div><div><div>591</div></div><div><span><span>          </span></span><span>display: flex;</span></div></div><div><div><div>592</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>593</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>594</div></div><div>
</div></div><div><div><div>595</div></div><div><span><span>      </span></span><span>@media (max-width: 480px) {</span></div></div><div><div><div>596</div></div><div><span><span>        </span></span><span>.file-item {</span></div></div><div><div><div>597</div></div><div><span><span>          </span></span><span>padding: 0.75rem;</span></div></div><div><div><div>598</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>599</div></div><div>
</div></div><div><div><div>600</div></div><div><span><span>        </span></span><span>.btn {</span></div></div><div><div><div>601</div></div><div><span><span>          </span></span><span>padding: 0.5rem;</span></div></div><div><div><div>602</div></div><div><span><span>          </span></span><span>font-size: 0.8rem;</span></div></div><div><div><div>603</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>604</div></div><div>
</div></div><div><div><div>605</div></div><div><span><span>        </span></span><span>.btn-sm {</span></div></div><div><div><div>606</div></div><div><span><span>          </span></span><span>padding: 0.4rem;</span></div></div><div><div><div>607</div></div><div><span><span>          </span></span><span>font-size: 0.75rem;</span></div></div><div><div><div>608</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>609</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>610</div></div><div><span><span>    </span></span><span>&lt;/style&gt;</span></div></div><div><div><div>611</div></div><div><span><span>  </span></span><span>&lt;/head&gt;</span></div></div><div><div><div>612</div></div><div><span><span>  </span></span><span>&lt;body&gt;</span></div></div><div><div><div>613</div></div><div><span><span>    </span></span><span>&lt;div class="container"&gt;</span></div></div><div><div><div>614</div></div><div><span><span>      </span></span><span>&lt;div class="header"&gt;</span></div></div><div><div><div>615</div></div><div><span><span>        </span></span><span>&lt;h1&gt;☁️ 我的云存储&lt;/h1&gt;</span></div></div><div><div><div>616</div></div><div><span><span>        </span></span><span>&lt;button class="theme-toggle" onclick="toggleTheme()"&gt;🌙 切换主题&lt;/button&gt;</span></div></div><div><div><div>617</div></div><div><span><span>      </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>618</div></div><div>
</div></div><div><div><div>619</div></div><div><span><span>      </span></span><span>&lt;div class="upload-area"&gt;</span></div></div><div><div><div>620</div></div><div><span><span>        </span></span><span>&lt;form class="upload-form" id="uploadForm" enctype="multipart/form-data" method="POST" action="/upload"&gt;</span></div></div><div><div><div>621</div></div><div><span><span>          </span></span><span>&lt;input type="file" id="fileInput" name="file" multiple /&gt;</span></div></div><div><div><div>622</div></div><div><span><span>          </span></span><span>&lt;label for="fileInput" style="display: inline-block; background: var(--primary); color: white; padding: 0.7rem 1.2rem; border-radius: 6px; cursor: pointer; margin-bottom: 1rem;"&gt;📁 选择文件&lt;/label&gt;</span></div></div><div><div><div>623</div></div><div><span><span>          </span></span><span>&lt;button type="submit" style="background: var(--primary); color: white; padding: 0.7rem 1.2rem; border-radius: 6px; cursor: pointer;"&gt;上传文件&lt;/button&gt;</span></div></div><div><div><div>624</div></div><div><span><span>        </span></span><span>&lt;/form&gt;</span></div></div><div><div><div>625</div></div><div><span><span>      </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>626</div></div><div>
</div></div><div><div><div>627</div></div><div><span><span>      </span></span><span>&lt;div class="controls"&gt;</span></div></div><div><div><div>628</div></div><div><span><span>        </span></span><span>&lt;input type="text" class="search-box" placeholder="🔍 搜索文件..." name="search" value="</span><span>${</span><span>currentSearch</span><span>}</span><span>" /&gt;</span></div></div><div><div><div>629</div></div><div><span><span>        </span></span><span>&lt;select class="sort-select" name="sort"&gt;</span></div></div><div><div><div>630</div></div><div><span><span>          </span></span><span>&lt;option value="name" </span><span>${</span><span>currentSort</span><span> </span><span>===</span><span> </span><span>'</span><span>name</span><span>'</span><span> </span><span>?</span><span> </span><span>'</span><span>selected</span><span>'</span><span> </span><span>:</span><span> </span><span><span>''</span><span>}</span></span><span>&gt;📝 名称&lt;/option&gt;</span></div></div><div><div><div>631</div></div><div><span><span>          </span></span><span>&lt;option value="size" </span><span>${</span><span>currentSort</span><span> </span><span>===</span><span> </span><span>'</span><span>size</span><span>'</span><span> </span><span>?</span><span> </span><span>'</span><span>selected</span><span>'</span><span> </span><span>:</span><span> </span><span><span>''</span><span>}</span></span><span>&gt;📦 大小&lt;/option&gt;</span></div></div><div><div><div>632</div></div><div><span><span>          </span></span><span>&lt;option value="date" </span><span>${</span><span>currentSort</span><span> </span><span>===</span><span> </span><span>'</span><span>date</span><span>'</span><span> </span><span>?</span><span> </span><span>'</span><span>selected</span><span>'</span><span> </span><span>:</span><span> </span><span><span>''</span><span>}</span></span><span>&gt;📅 修改时间&lt;/option&gt;</span></div></div><div><div><div>633</div></div><div><span><span>        </span></span><span>&lt;/select&gt;</span></div></div><div><div><div>634</div></div><div><span><span>        </span></span><span>&lt;select class="sort-select" name="order"&gt;</span></div></div><div><div><div>635</div></div><div><span><span>          </span></span><span>&lt;option value="asc" </span><span>${</span><span>currentOrder</span><span> </span><span>===</span><span> </span><span>'</span><span>asc</span><span>'</span><span> </span><span>?</span><span> </span><span>'</span><span>selected</span><span>'</span><span> </span><span>:</span><span> </span><span><span>''</span><span>}</span></span><span>&gt;⬆️ 升序&lt;/option&gt;</span></div></div><div><div><div>636</div></div><div><span><span>          </span></span><span>&lt;option value="desc" </span><span>${</span><span>currentOrder</span><span> </span><span>===</span><span> </span><span>'</span><span>desc</span><span>'</span><span> </span><span>?</span><span> </span><span>'</span><span>selected</span><span>'</span><span> </span><span>:</span><span> </span><span><span>''</span><span>}</span></span><span>&gt;⬇️ 降序&lt;/option&gt;</span></div></div><div><div><div>637</div></div><div><span><span>        </span></span><span>&lt;/select&gt;</span></div></div><div><div><div>638</div></div><div><span><span>      </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>639</div></div><div>
</div></div><div><div><div>640</div></div><div><span><span>      </span></span><span>&lt;div class="bulk-actions"&gt;</span></div></div><div><div><div>641</div></div><div><span><span>        </span></span><span>&lt;button type="button" class="btn btn-primary" onclick="toggleSelectAll()" style="margin-right: 1rem;"&gt;✅ 全选&lt;/button&gt;</span></div></div><div><div><div>642</div></div><div><span><span>        </span></span><span>&lt;button type="button" class="btn bulk-delete-btn" id="bulkDeleteBtn" onclick="bulkDelete()" disabled&gt;🗑️ 批量删除&lt;/button&gt;</span></div></div><div><div><div>643</div></div><div><span><span>      </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>644</div></div><div>
</div></div><div><div><div>645</div></div><div><span><span>      </span></span><span>&lt;div class="file-list"&gt;</span></div></div><div><div><div>646</div></div><div><span><span>        </span></span><span>&lt;div class="file-list-header"&gt;</span></div></div><div><div><div>647</div></div><div><span><span>          </span></span><span>&lt;div&gt;&lt;/div&gt;</span></div></div><div><div><div>648</div></div><div><span><span>          </span></span><span>&lt;div&gt;文件名&lt;/div&gt;</span></div></div><div><div><div>649</div></div><div><span><span>          </span></span><span>&lt;div&gt;大小&lt;/div&gt;</span></div></div><div><div><div>650</div></div><div><span><span>          </span></span><span>&lt;div&gt;修改时间&lt;/div&gt;</span></div></div><div><div><div>651</div></div><div><span><span>          </span></span><span>&lt;div&gt;操作&lt;/div&gt;</span></div></div><div><div><div>652</div></div><div><span><span>        </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>653</div></div><div><span>        </span><span>${</span><span>fileItems</span><span>}</span></div></div><div><div><div>654</div></div><div><span><span>      </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>655</div></div><div><span><span>    </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>656</div></div><div>
</div></div><div><div><div>657</div></div><div><span><span>    </span></span><span>&lt;script&gt;</span></div></div><div><div><div>658</div></div><div><span><span>      </span></span><span>function toggleTheme() {</span></div></div><div><div><div>659</div></div><div><span><span>        </span></span><span>const html = document.documentElement;</span></div></div><div><div><div>660</div></div><div><span><span>        </span></span><span>const current = html.getAttribute('data-theme');</span></div></div><div><div><div>661</div></div><div><span><span>        </span></span><span>const next = current === 'dark' ? 'light' : 'dark';</span></div></div><div><div><div>662</div></div><div><span><span>        </span></span><span>html.setAttribute('data-theme', next);</span></div></div><div><div><div>663</div></div><div><span><span>        </span></span><span>localStorage.setItem('theme', next);</span></div></div><div><div><div>664</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>665</div></div><div><span><span>      </span></span><span>(function() {</span></div></div><div><div><div>666</div></div><div><span><span>        </span></span><span>const html = document.documentElement;</span></div></div><div><div><div>667</div></div><div><span><span>        </span></span><span>const saved = localStorage.getItem('theme') || 'light';</span></div></div><div><div><div>668</div></div><div><span><span>        </span></span><span>html.setAttribute('data-theme', saved);</span></div></div><div><div><div>669</div></div><div><span><span>      </span></span><span>})();</span></div></div><div><div><div>670</div></div><div>
</div></div><div><div><div>671</div></div><div><span><span>      </span></span><span>document.getElementById('uploadForm').addEventListener('submit', async (e) =&gt; {</span></div></div><div><div><div>672</div></div><div><span><span>        </span></span><span>e.preventDefault();</span></div></div><div><div><div>673</div></div><div><span><span>        </span></span><span>const fileInput = document.getElementById('fileInput');</span></div></div><div><div><div>674</div></div><div><span><span>        </span></span><span>const files = fileInput.files;</span></div></div><div><div><div>675</div></div><div><span><span>        </span></span><span>if (!files.length) {</span></div></div><div><div><div>676</div></div><div><span><span>          </span></span><span>alert('请选择文件');</span></div></div><div><div><div>677</div></div><div><span><span>          </span></span><span>return;</span></div></div><div><div><div>678</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>679</div></div><div><span><span>        </span></span><span>const formData = new FormData();</span></div></div><div><div><div>680</div></div><div><span><span>        </span></span><span>for (let i = 0; i &lt; files.length; i++) formData.append('file', files[i]);</span></div></div><div><div><div>681</div></div><div><span><span>        </span></span><span>const res = await fetch('/upload', { method: 'POST', body: formData });</span></div></div><div><div><div>682</div></div><div><span><span>        </span></span><span>if (res.ok) {</span></div></div><div><div><div>683</div></div><div><span><span>          </span></span><span>alert('上传成功');</span></div></div><div><div><div>684</div></div><div><span><span>          </span></span><span>location.reload();</span></div></div><div><div><div>685</div></div><div><span><span>        </span></span><span>} else {</span></div></div><div><div><div>686</div></div><div><span><span>          </span></span><span>alert('上传失败');</span></div></div><div><div><div>687</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>688</div></div><div><span><span>      </span></span><span>});</span></div></div><div><div><div>689</div></div><div>
</div></div><div><div><div>690</div></div><div><span><span>      </span></span><span>function toggleSelectAll() {</span></div></div><div><div><div>691</div></div><div><span><span>        </span></span><span>const checkboxes = document.querySelectorAll('input[name="fileKey"]');</span></div></div><div><div><div>692</div></div><div><span><span>        </span></span><span>const bulkDeleteBtn = document.getElementById('bulkDeleteBtn');</span></div></div><div><div><div>693</div></div><div><span><span>        </span></span><span>let checked = false;</span></div></div><div><div><div>694</div></div><div><span><span>        </span></span><span>checkboxes.forEach(cb =&gt; {</span></div></div><div><div><div>695</div></div><div><span><span>          </span></span><span>if (!checked) checked = !cb.checked;</span></div></div><div><div><div>696</div></div><div><span><span>          </span></span><span>cb.checked = !cb.checked;</span></div></div><div><div><div>697</div></div><div><span><span>        </span></span><span>});</span></div></div><div><div><div>698</div></div><div><span><span>        </span></span><span>updateBulkDeleteButton();</span></div></div><div><div><div>699</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>700</div></div><div>
</div></div><div><div><div>701</div></div><div><span><span>      </span></span><span>function updateBulkDeleteButton() {</span></div></div><div><div><div>702</div></div><div><span><span>        </span></span><span>const checkboxes = document.querySelectorAll('input[name="fileKey"]:checked');</span></div></div><div><div><div>703</div></div><div><span><span>        </span></span><span>const bulkDeleteBtn = document.getElementById('bulkDeleteBtn');</span></div></div><div><div><div>704</div></div><div><span><span>        </span></span><span>bulkDeleteBtn.disabled = checkboxes.length === 0;</span></div></div><div><div><div>705</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>706</div></div><div>
</div></div><div><div><div>707</div></div><div><span><span>      </span></span><span>function bulkDelete() {</span></div></div><div><div><div>708</div></div><div><span><span>        </span></span><span>const checkboxes = document.querySelectorAll('input[name="fileKey"]:checked');</span></div></div><div><div><div>709</div></div><div><span><span>        </span></span><span>if (checkboxes.length === 0) {</span></div></div><div><div><div>710</div></div><div><span><span>          </span></span><span>alert('请至少选择一个文件');</span></div></div><div><div><div>711</div></div><div><span><span>          </span></span><span>return;</span></div></div><div><div><div>712</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>713</div></div><div><span><span>        </span></span><span>const keys = Array.from(checkboxes).map(cb =&gt; decodeURIComponent(cb.value));</span></div></div><div><div><div>714</div></div><div><span><span>        </span></span><span>if (!confirm('确定删除选中的 ' + keys.length + ' 个文件/文件夹吗？')) return;</span></div></div><div><div><div>715</div></div><div><span><span>        </span></span><span>const formData = new FormData();</span></div></div><div><div><div>716</div></div><div><span><span>        </span></span><span>keys.forEach(key =&gt; formData.append('fileKey', key));</span></div></div><div><div><div>717</div></div><div><span><span>        </span></span><span>fetch('/delete', {</span></div></div><div><div><div>718</div></div><div><span><span>          </span></span><span>method: 'POST',</span></div></div><div><div><div>719</div></div><div><span><span>          </span></span><span>body: formData</span></div></div><div><div><div>720</div></div><div><span><span>        </span></span><span>}).then(res =&gt; res.json()).then(json =&gt; {</span></div></div><div><div><div>721</div></div><div><span><span>          </span></span><span>if (json.results &amp;&amp; json.results.every(r =&gt; r.success)) {</span></div></div><div><div><div>722</div></div><div><span><span>            </span></span><span>alert('批量删除成功');</span></div></div><div><div><div>723</div></div><div><span><span>            </span></span><span>location.reload();</span></div></div><div><div><div>724</div></div><div><span><span>          </span></span><span>} else {</span></div></div><div><div><div>725</div></div><div><span><span>            </span></span><span>alert('部分文件删除失败，请重试');</span></div></div><div><div><div>726</div></div><div><span><span>          </span></span><span>}</span></div></div><div><div><div>727</div></div><div><span><span>        </span></span><span>}).catch(err =&gt; {</span></div></div><div><div><div>728</div></div><div><span><span>          </span></span><span>alert('删除失败：' + err.message);</span></div></div><div><div><div>729</div></div><div><span><span>        </span></span><span>});</span></div></div><div><div><div>730</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>731</div></div><div>
</div></div><div><div><div>732</div></div><div><span><span>      </span></span><span>function deleteSingleFile(filename) {</span></div></div><div><div><div>733</div></div><div><span><span>        </span></span><span>if (confirm('确定删除 "' + filename + '"？')) {</span></div></div><div><div><div>734</div></div><div><span><span>          </span></span><span>const formData = new FormData();</span></div></div><div><div><div>735</div></div><div><span><span>          </span></span><span>formData.append('fileKey', filename);</span></div></div><div><div><div>736</div></div><div><span><span>          </span></span><span>fetch('/delete', {</span></div></div><div><div><div>737</div></div><div><span><span>            </span></span><span>method: 'POST',</span></div></div><div><div><div>738</div></div><div><span><span>            </span></span><span>body: formData</span></div></div><div><div><div>739</div></div><div><span><span>          </span></span><span>}).then(res =&gt; res.json()).then(json =&gt; {</span></div></div><div><div><div>740</div></div><div><span><span>            </span></span><span>if (json.results &amp;&amp; json.results[0] &amp;&amp; json.results[0].success) {</span></div></div><div><div><div>741</div></div><div><span><span>              </span></span><span>alert('删除成功');</span></div></div><div><div><div>742</div></div><div><span><span>              </span></span><span>location.reload();</span></div></div><div><div><div>743</div></div><div><span><span>            </span></span><span>} else {</span></div></div><div><div><div>744</div></div><div><span><span>              </span></span><span>alert('删除失败，请重试');</span></div></div><div><div><div>745</div></div><div><span><span>            </span></span><span>}</span></div></div><div><div><div>746</div></div><div><span><span>          </span></span><span>}).catch(err =&gt; {</span></div></div><div><div><div>747</div></div><div><span><span>            </span></span><span>alert('删除失败：' + err.message);</span></div></div><div><div><div>748</div></div><div><span><span>          </span></span><span>});</span></div></div><div><div><div>749</div></div><div><span><span>          </span></span><span>return false;</span></div></div><div><div><div>750</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>751</div></div><div><span><span>        </span></span><span>return false;</span></div></div><div><div><div>752</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>753</div></div><div>
</div></div><div><div><div>754</div></div><div><span><span>      </span></span><span>document.addEventListener('change', (e) =&gt; {</span></div></div><div><div><div>755</div></div><div><span><span>        </span></span><span>if (e.target &amp;&amp; e.target.name === 'fileKey') {</span></div></div><div><div><div>756</div></div><div><span><span>          </span></span><span>updateBulkDeleteButton();</span></div></div><div><div><div>757</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>758</div></div><div><span><span>      </span></span><span>});</span></div></div><div><div><div>759</div></div><div>
</div></div><div><div><div>760</div></div><div><span><span>      </span></span><span>const formControls = document.querySelectorAll('.controls input, .controls select');</span></div></div><div><div><div>761</div></div><div><span><span>      </span></span><span>formControls.forEach(control =&gt; {</span></div></div><div><div><div>762</div></div><div><span><span>        </span></span><span>control.addEventListener('change', () =&gt; {</span></div></div><div><div><div>763</div></div><div><span><span>          </span></span><span>const search = document.querySelector('.search-box').value;</span></div></div><div><div><div>764</div></div><div><span><span>          </span></span><span>const sort = document.querySelector('.sort-select:nth-of-type(1)').value;</span></div></div><div><div><div>765</div></div><div><span><span>          </span></span><span>const order = document.querySelector('.sort-select:nth-of-type(2)').value;</span></div></div><div><div><div>766</div></div><div><span><span>          </span></span><span>const params = new URLSearchParams(window.location.search);</span></div></div><div><div><div>767</div></div><div><span><span>          </span></span><span>params.set('search', search);</span></div></div><div><div><div>768</div></div><div><span><span>          </span></span><span>params.set('sort', sort);</span></div></div><div><div><div>769</div></div><div><span><span>          </span></span><span>params.set('order', order);</span></div></div><div><div><div>770</div></div><div><span><span>          </span></span><span>window.location.search = params.toString();</span></div></div><div><div><div>771</div></div><div><span><span>        </span></span><span>});</span></div></div><div><div><div>772</div></div><div><span><span>      </span></span><span>});</span></div></div><div><div><div>773</div></div><div><span><span>    </span></span><span>&lt;/script&gt;</span></div></div><div><div><div>774</div></div><div><span><span>  </span></span><span>&lt;/body&gt;</span></div></div><div><div><div>775</div></div><div><span>&lt;/html&gt;</span></div></div><div><div><div>776</div></div><div><span><span>  </span></span><span>`</span><span>;</span></div></div><div><div><div>777</div></div><div><span>}</span></div></div><div><div><div>778</div></div><div>
</div></div><div><div><div>779</div></div><div><span>function</span><span> </span><span>buildLoginPage</span><span><span>(</span><span>errorMsg</span><span>) {</span></span></div></div><div><div><div>780</div></div><div><span>  </span><span>return</span><span> </span><span>`</span></div></div><div><div><div>781</div></div><div><span>&lt;!DOCTYPE html&gt;</span></div></div><div><div><div>782</div></div><div><span>&lt;html lang="zh-CN"&gt;</span></div></div><div><div><div>783</div></div><div><span>&lt;head&gt;</span></div></div><div><div><div>784</div></div><div><span><span>  </span></span><span>&lt;meta charset="UTF-8" /&gt;</span></div></div><div><div><div>785</div></div><div><span><span>  </span></span><span>&lt;meta name="viewport" content="width=device-width, initial-scale=1.0"/&gt;</span></div></div><div><div><div>786</div></div><div><span><span>  </span></span><span>&lt;title&gt;登录 - 我的云存储&lt;/title&gt;</span></div></div><div><div><div>787</div></div><div><span><span>  </span></span><span>&lt;link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&amp;display=swap" rel="stylesheet"&gt;</span></div></div><div><div><div>788</div></div><div><span><span>  </span></span><span>&lt;link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"&gt;</span></div></div><div><div><div>789</div></div><div><span><span>  </span></span><span>&lt;style&gt;</span></div></div><div><div><div>790</div></div><div><span><span>    </span></span><span>:root {</span></div></div><div><div><div>791</div></div><div><span><span>      </span></span><span>--primary: #6366f1;</span></div></div><div><div><div>792</div></div><div><span><span>      </span></span><span>--primary-hover: #4f46e5;</span></div></div><div><div><div>793</div></div><div><span><span>      </span></span><span>--danger: #ef4444;</span></div></div><div><div><div>794</div></div><div><span><span>      </span></span><span>--text: #1f2937;</span></div></div><div><div><div>795</div></div><div><span><span>      </span></span><span>--text-light: #6b7280;</span></div></div><div><div><div>796</div></div><div><span><span>      </span></span><span>--bg: #f8fafc;</span></div></div><div><div><div>797</div></div><div><span><span>      </span></span><span>--card-bg: #ffffff;</span></div></div><div><div><div>798</div></div><div><span><span>      </span></span><span>--border: #e5e7eb;</span></div></div><div><div><div>799</div></div><div><span><span>      </span></span><span>--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);</span></div></div><div><div><div>800</div></div><div><span><span>      </span></span><span>--input-bg: #ffffff;</span></div></div><div><div><div>801</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>802</div></div><div><span><span>    </span></span><span>[data-theme="dark"] {</span></div></div><div><div><div>803</div></div><div><span><span>      </span></span><span>--text: #f9fafb;</span></div></div><div><div><div>804</div></div><div><span><span>      </span></span><span>--text-light: #9ca3af;</span></div></div><div><div><div>805</div></div><div><span><span>      </span></span><span>--bg: #111827;</span></div></div><div><div><div>806</div></div><div><span><span>      </span></span><span>--card-bg: #1f2937;</span></div></div><div><div><div>807</div></div><div><span><span>      </span></span><span>--border: #374151;</span></div></div><div><div><div>808</div></div><div><span><span>      </span></span><span>--input-bg: #374151;</span></div></div><div><div><div>809</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>810</div></div><div><span><span>    </span></span><span>* { margin: 0; padding: 0; box-sizing: border-box; }</span></div></div><div><div><div>811</div></div><div><span><span>    </span></span><span>body {</span></div></div><div><div><div>812</div></div><div><span><span>      </span></span><span>font-family: 'Inter', sans-serif;</span></div></div><div><div><div>813</div></div><div><span><span>      </span></span><span>background-color: var(--bg);</span></div></div><div><div><div>814</div></div><div><span><span>      </span></span><span>color: var(--text);</span></div></div><div><div><div>815</div></div><div><span><span>      </span></span><span>line-height: 1.6;</span></div></div><div><div><div>816</div></div><div><span><span>      </span></span><span>transition: background-color 0.3s, color 0.3s;</span></div></div><div><div><div>817</div></div><div><span><span>      </span></span><span>min-height: 100vh;</span></div></div><div><div><div>818</div></div><div><span><span>      </span></span><span>display: flex;</span></div></div><div><div><div>819</div></div><div><span><span>      </span></span><span>align-items: center;</span></div></div><div><div><div>820</div></div><div><span><span>      </span></span><span>justify-content: center;</span></div></div><div><div><div>821</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>822</div></div><div><span><span>    </span></span><span>.login-container {</span></div></div><div><div><div>823</div></div><div><span><span>      </span></span><span>background: var(--card-bg);</span></div></div><div><div><div>824</div></div><div><span><span>      </span></span><span>border-radius: 12px;</span></div></div><div><div><div>825</div></div><div><span><span>      </span></span><span>padding: 2.5rem;</span></div></div><div><div><div>826</div></div><div><span><span>      </span></span><span>box-shadow: var(--shadow);</span></div></div><div><div><div>827</div></div><div><span><span>      </span></span><span>border: 1px solid var(--border);</span></div></div><div><div><div>828</div></div><div><span><span>      </span></span><span>width: 100%;</span></div></div><div><div><div>829</div></div><div><span><span>      </span></span><span>max-width: 400px;</span></div></div><div><div><div>830</div></div><div><span><span>      </span></span><span>text-align: center;</span></div></div><div><div><div>831</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>832</div></div><div><span><span>    </span></span><span>.login-title {</span></div></div><div><div><div>833</div></div><div><span><span>      </span></span><span>font-size: 1.8rem;</span></div></div><div><div><div>834</div></div><div><span><span>      </span></span><span>font-weight: 700;</span></div></div><div><div><div>835</div></div><div><span><span>      </span></span><span>color: var(--primary);</span></div></div><div><div><div>836</div></div><div><span><span>      </span></span><span>margin-bottom: 1.5rem;</span></div></div><div><div><div>837</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>838</div></div><div><span><span>    </span></span><span>.login-icon {</span></div></div><div><div><div>839</div></div><div><span><span>      </span></span><span>font-size: 3rem;</span></div></div><div><div><div>840</div></div><div><span><span>      </span></span><span>margin-bottom: 1.5rem;</span></div></div><div><div><div>841</div></div><div><span><span>      </span></span><span>color: var(--primary);</span></div></div><div><div><div>842</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>843</div></div><div><span><span>    </span></span><span>.login-form {</span></div></div><div><div><div>844</div></div><div><span><span>      </span></span><span>display: flex;</span></div></div><div><div><div>845</div></div><div><span><span>      </span></span><span>flex-direction: column;</span></div></div><div><div><div>846</div></div><div><span><span>      </span></span><span>gap: 1.5rem;</span></div></div><div><div><div>847</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>848</div></div><div><span><span>    </span></span><span>.form-group {</span></div></div><div><div><div>849</div></div><div><span><span>      </span></span><span>display: flex;</span></div></div><div><div><div>850</div></div><div><span><span>      </span></span><span>flex-direction: column;</span></div></div><div><div><div>851</div></div><div><span><span>      </span></span><span>gap: 0.5rem;</span></div></div><div><div><div>852</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>853</div></div><div><span><span>    </span></span><span>.form-label {</span></div></div><div><div><div>854</div></div><div><span><span>      </span></span><span>font-weight: 500;</span></div></div><div><div><div>855</div></div><div><span><span>      </span></span><span>color: var(--text);</span></div></div><div><div><div>856</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>857</div></div><div><span><span>    </span></span><span>.form-input {</span></div></div><div><div><div>858</div></div><div><span><span>      </span></span><span>padding: 0.75rem;</span></div></div><div><div><div>859</div></div><div><span><span>      </span></span><span>border: 1px solid var(--border);</span></div></div><div><div><div>860</div></div><div><span><span>      </span></span><span>border-radius: 6px;</span></div></div><div><div><div>861</div></div><div><span><span>      </span></span><span>background: var(--input-bg);</span></div></div><div><div><div>862</div></div><div><span><span>      </span></span><span>color: var(--text);</span></div></div><div><div><div>863</div></div><div><span><span>      </span></span><span>font-size: 1rem;</span></div></div><div><div><div>864</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>865</div></div><div><span><span>    </span></span><span>.form-input:focus {</span></div></div><div><div><div>866</div></div><div><span><span>      </span></span><span>outline: none;</span></div></div><div><div><div>867</div></div><div><span><span>      </span></span><span>border-color: var(--primary);</span></div></div><div><div><div>868</div></div><div><span><span>      </span></span><span>box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);</span></div></div><div><div><div>869</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>870</div></div><div><span><span>    </span></span><span>.login-btn {</span></div></div><div><div><div>871</div></div><div><span><span>      </span></span><span>padding: 0.75rem;</span></div></div><div><div><div>872</div></div><div><span><span>      </span></span><span>background: var(--primary);</span></div></div><div><div><div>873</div></div><div><span><span>      </span></span><span>color: white;</span></div></div><div><div><div>874</div></div><div><span><span>      </span></span><span>border: none;</span></div></div><div><div><div>875</div></div><div><span><span>      </span></span><span>border-radius: 6px;</span></div></div><div><div><div>876</div></div><div><span><span>      </span></span><span>font-size: 1rem;</span></div></div><div><div><div>877</div></div><div><span><span>      </span></span><span>font-weight: 500;</span></div></div><div><div><div>878</div></div><div><span><span>      </span></span><span>cursor: pointer;</span></div></div><div><div><div>879</div></div><div><span><span>      </span></span><span>transition: background-color 0.15s;</span></div></div><div><div><div>880</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>881</div></div><div><span><span>    </span></span><span>.login-btn:hover {</span></div></div><div><div><div>882</div></div><div><span><span>      </span></span><span>background: var(--primary-hover);</span></div></div><div><div><div>883</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>884</div></div><div><span><span>    </span></span><span>.login-btn:disabled {</span></div></div><div><div><div>885</div></div><div><span><span>      </span></span><span>background: var(--border);</span></div></div><div><div><div>886</div></div><div><span><span>      </span></span><span>color: var(--text-light);</span></div></div><div><div><div>887</div></div><div><span><span>      </span></span><span>cursor: not-allowed;</span></div></div><div><div><div>888</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>889</div></div><div><span><span>    </span></span><span>.error-message {</span></div></div><div><div><div>890</div></div><div><span><span>      </span></span><span>background: #fef2f2;</span></div></div><div><div><div>891</div></div><div><span><span>      </span></span><span>color: var(--danger);</span></div></div><div><div><div>892</div></div><div><span><span>      </span></span><span>padding: 0.75rem;</span></div></div><div><div><div>893</div></div><div><span><span>      </span></span><span>border-radius: 6px;</span></div></div><div><div><div>894</div></div><div><span><span>      </span></span><span>border: 1px solid #fecaca;</span></div></div><div><div><div>895</div></div><div><span><span>      </span></span><span>margin-bottom: 1rem;</span></div></div><div><div><div>896</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>897</div></div><div><span><span>    </span></span><span>[data-theme="dark"] .error-message {</span></div></div><div><div><div>898</div></div><div><span><span>      </span></span><span>background: #371e1e;</span></div></div><div><div><div>899</div></div><div><span><span>      </span></span><span>border-color: #7f1d1d;</span></div></div><div><div><div>900</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>901</div></div><div><span><span>    </span></span><span>.theme-toggle {</span></div></div><div><div><div>902</div></div><div><span><span>      </span></span><span>position: absolute;</span></div></div><div><div><div>903</div></div><div><span><span>      </span></span><span>top: 1rem;</span></div></div><div><div><div>904</div></div><div><span><span>      </span></span><span>right: 1rem;</span></div></div><div><div><div>905</div></div><div><span><span>      </span></span><span>background: var(--card-bg);</span></div></div><div><div><div>906</div></div><div><span><span>      </span></span><span>border: 1px solid var(--border);</span></div></div><div><div><div>907</div></div><div><span><span>      </span></span><span>color: var(--text);</span></div></div><div><div><div>908</div></div><div><span><span>      </span></span><span>padding: 0.5rem 1rem;</span></div></div><div><div><div>909</div></div><div><span><span>      </span></span><span>border-radius: 6px;</span></div></div><div><div><div>910</div></div><div><span><span>      </span></span><span>cursor: pointer;</span></div></div><div><div><div>911</div></div><div><span><span>      </span></span><span>font-size: 0.9rem;</span></div></div><div><div><div>912</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>913</div></div><div><span><span>    </span></span><span>.login-footer {</span></div></div><div><div><div>914</div></div><div><span><span>      </span></span><span>margin-top: 1.5rem;</span></div></div><div><div><div>915</div></div><div><span><span>      </span></span><span>font-size: 0.85rem;</span></div></div><div><div><div>916</div></div><div><span><span>      </span></span><span>color: var(--text-light);</span></div></div><div><div><div>917</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>918</div></div><div><span><span>    </span></span><span>.login-footer a {</span></div></div><div><div><div>919</div></div><div><span><span>      </span></span><span>color: var(--primary);</span></div></div><div><div><div>920</div></div><div><span><span>      </span></span><span>text-decoration: none;</span></div></div><div><div><div>921</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>922</div></div><div><span><span>    </span></span><span>.login-footer a:hover {</span></div></div><div><div><div>923</div></div><div><span><span>      </span></span><span>text-decoration: underline;</span></div></div><div><div><div>924</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>925</div></div><div>
</div></div><div><div><div>926</div></div><div><span><span>    </span></span><span>/* 移动端优化 */</span></div></div><div><div><div>927</div></div><div><span><span>    </span></span><span>@media (max-width: 480px) {</span></div></div><div><div><div>928</div></div><div><span><span>      </span></span><span>.login-container {</span></div></div><div><div><div>929</div></div><div><span><span>        </span></span><span>margin: 1rem;</span></div></div><div><div><div>930</div></div><div><span><span>        </span></span><span>padding: 1.5rem;</span></div></div><div><div><div>931</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>932</div></div><div>
</div></div><div><div><div>933</div></div><div><span><span>      </span></span><span>.theme-toggle {</span></div></div><div><div><div>934</div></div><div><span><span>        </span></span><span>position: relative;</span></div></div><div><div><div>935</div></div><div><span><span>        </span></span><span>top: auto;</span></div></div><div><div><div>936</div></div><div><span><span>        </span></span><span>right: auto;</span></div></div><div><div><div>937</div></div><div><span><span>        </span></span><span>margin-bottom: 1rem;</span></div></div><div><div><div>938</div></div><div><span><span>      </span></span><span>}</span></div></div><div><div><div>939</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>940</div></div><div><span><span>  </span></span><span>&lt;/style&gt;</span></div></div><div><div><div>941</div></div><div><span>&lt;/head&gt;</span></div></div><div><div><div>942</div></div><div><span>&lt;body&gt;</span></div></div><div><div><div>943</div></div><div><span><span>  </span></span><span>&lt;button class="theme-toggle" onclick="toggleTheme()"&gt;🌙 切换主题&lt;/button&gt;</span></div></div><div><div><div>944</div></div><div>
</div></div><div><div><div>945</div></div><div><span><span>  </span></span><span>&lt;div class="login-container"&gt;</span></div></div><div><div><div>946</div></div><div><span><span>    </span></span><span>&lt;div class="login-icon"&gt;</span></div></div><div><div><div>947</div></div><div><span><span>      </span></span><span>&lt;i class="fas fa-lock"&gt;&lt;/i&gt;</span></div></div><div><div><div>948</div></div><div><span><span>    </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>949</div></div><div>
</div></div><div><div><div>950</div></div><div><span><span>    </span></span><span>&lt;h1 class="login-title"&gt;请登录&lt;/h1&gt;</span></div></div><div><div><div>951</div></div><div>
</div></div><div><div><div>952</div></div><div><span>    </span><span>${</span><span>errorMsg</span><span> </span><span>?</span><span> </span><span>`&lt;div class="error-message"&gt;⚠️ </span><span>${</span><span>escapeHtml</span><span><span>(</span><span>errorMsg</span><span>)</span></span><span>}</span><span>&lt;/div&gt;`</span><span> </span><span>:</span><span> </span><span><span>''</span><span>}</span></span></div></div><div><div><div>953</div></div><div>
</div></div><div><div><div>954</div></div><div><span><span>    </span></span><span>&lt;form class="login-form" method="POST" action="/login"&gt;</span></div></div><div><div><div>955</div></div><div><span><span>      </span></span><span>&lt;div class="form-group"&gt;</span></div></div><div><div><div>956</div></div><div><span><span>        </span></span><span>&lt;label class="form-label" for="password"&gt;访问密码&lt;/label&gt;</span></div></div><div><div><div>957</div></div><div><span><span>        </span></span><span>&lt;input</span></div></div><div><div><div>958</div></div><div><span><span>          </span></span><span>type="password"</span></div></div><div><div><div>959</div></div><div><span><span>          </span></span><span>id="password"</span></div></div><div><div><div>960</div></div><div><span><span>          </span></span><span>name="password"</span></div></div><div><div><div>961</div></div><div><span><span>          </span></span><span>class="form-input"</span></div></div><div><div><div>962</div></div><div><span><span>          </span></span><span>placeholder="请输入访问密码"</span></div></div><div><div><div>963</div></div><div><span><span>          </span></span><span>required</span></div></div><div><div><div>964</div></div><div><span><span>          </span></span><span>autocomplete="current-password"</span></div></div><div><div><div>965</div></div><div><span><span>        </span></span><span>/&gt;</span></div></div><div><div><div>966</div></div><div><span><span>      </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>967</div></div><div>
</div></div><div><div><div>968</div></div><div><span><span>      </span></span><span>&lt;button type="submit" class="login-btn"&gt;</span></div></div><div><div><div>969</div></div><div><span><span>        </span></span><span>&lt;i class="fas fa-sign-in-alt" style="margin-right: 0.5rem;"&gt;&lt;/i&gt;</span></div></div><div><div><div>970</div></div><div><span><span>        </span></span><span>登录</span></div></div><div><div><div>971</div></div><div><span><span>      </span></span><span>&lt;/button&gt;</span></div></div><div><div><div>972</div></div><div><span><span>    </span></span><span>&lt;/form&gt;</span></div></div><div><div><div>973</div></div><div>
</div></div><div><div><div>974</div></div><div><span><span>    </span></span><span>&lt;div class="login-footer"&gt;</span></div></div><div><div><div>975</div></div><div><span><span>      </span></span><span>&lt;p&gt;请输入正确的访问密码以继续使用云存储服务&lt;/p&gt;</span></div></div><div><div><div>976</div></div><div><span><span>    </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>977</div></div><div><span><span>  </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>978</div></div><div>
</div></div><div><div><div>979</div></div><div><span><span>  </span></span><span>&lt;script&gt;</span></div></div><div><div><div>980</div></div><div><span><span>    </span></span><span>function toggleTheme() {</span></div></div><div><div><div>981</div></div><div><span><span>      </span></span><span>const html = document.documentElement;</span></div></div><div><div><div>982</div></div><div><span><span>      </span></span><span>const current = html.getAttribute('data-theme');</span></div></div><div><div><div>983</div></div><div><span><span>      </span></span><span>const next = current === 'dark' ? 'light' : 'dark';</span></div></div><div><div><div>984</div></div><div><span><span>      </span></span><span>html.setAttribute('data-theme', next);</span></div></div><div><div><div>985</div></div><div><span><span>      </span></span><span>localStorage.setItem('theme', next);</span></div></div><div><div><div>986</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>987</div></div><div><span><span>    </span></span><span>(function() {</span></div></div><div><div><div>988</div></div><div><span><span>      </span></span><span>const html = document.documentElement;</span></div></div><div><div><div>989</div></div><div><span><span>      </span></span><span>const saved = localStorage.getItem('theme') || 'light';</span></div></div><div><div><div>990</div></div><div><span><span>      </span></span><span>html.setAttribute('data-theme', saved);</span></div></div><div><div><div>991</div></div><div><span><span>    </span></span><span>})();</span></div></div><div><div><div>992</div></div><div>
</div></div><div><div><div>993</div></div><div><span><span>    </span></span><span>function escapeHtml(unsafe) {</span></div></div><div><div><div>994</div></div><div><span><span>      </span></span><span>return unsafe</span></div></div><div><div><div>995</div></div><div><span><span>        </span></span><span>.replace(/&amp;/g, "&amp;amp;")</span></div></div><div><div><div>996</div></div><div><span><span>        </span></span><span>.replace(/&lt;/g, "&amp;lt;")</span></div></div><div><div><div>997</div></div><div><span><span>        </span></span><span>.replace(/&gt;/g, "&amp;gt;")</span></div></div><div><div><div>998</div></div><div><span><span>        </span></span><span>.replace(/"/g, "&amp;quot;")</span></div></div><div><div><div>999</div></div><div><span><span>        </span></span><span>.replace(/'/g, "&amp;#039;");</span></div></div><div><div><div>1000</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>1001</div></div><div><span><span>  </span></span><span>&lt;/script&gt;</span></div></div><div><div><div>1002</div></div><div><span>&lt;/body&gt;</span></div></div><div><div><div>1003</div></div><div><span>&lt;/html&gt;</span></div></div><div><div><div>1004</div></div><div><span>`</span><span>;</span></div></div><div><div><div>1005</div></div><div><span>}</span></div></div><div><div><div>1006</div></div><div>
</div></div><div><div><div>1007</div></div><div><span>function</span><span> </span><span>buildErrorResponse</span><span><span>(</span><span>error</span><span>) {</span></span></div></div><div><div><div>1008</div></div><div><span>  </span><span>return</span><span> </span><span>new</span><span> </span><span>Response</span><span>(</span><span>`</span></div></div><div><div><div>1009</div></div><div><span><span>  </span></span><span>&lt;!DOCTYPE html&gt;</span></div></div><div><div><div>1010</div></div><div><span><span>  </span></span><span>&lt;html&gt;</span></div></div><div><div><div>1011</div></div><div><span><span>  </span></span><span>&lt;body style="font-family: sans-serif; text-align: center; padding: 2rem; background: #f5f5f5;"&gt;</span></div></div><div><div><div>1012</div></div><div><span><span>    </span></span><span>&lt;div style="background: white; padding: 2rem; border-radius: 8px;"&gt;</span></div></div><div><div><div>1013</div></div><div><span><span>      </span></span><span>&lt;h2&gt;发生错误&lt;/h2&gt;</span></div></div><div><div><div>1014</div></div><div><span><span>      </span></span><span>&lt;p&gt;</span><span>${</span><span>error</span><span>.</span><span>message</span><span> </span><span>||</span><span> </span><span>error</span><span>}</span><span>&lt;/p&gt;</span></div></div><div><div><div>1015</div></div><div><span><span>      </span></span><span>&lt;a href="/" style="color: #6366f1;"&gt;返回首页&lt;/a&gt;</span></div></div><div><div><div>1016</div></div><div><span><span>    </span></span><span>&lt;/div&gt;</span></div></div><div><div><div>1017</div></div><div><span><span>  </span></span><span>&lt;/body&gt;</span></div></div><div><div><div>1018</div></div><div><span>&lt;/html&gt;</span></div></div><div><div><div>1019</div></div><div><span><span>  </span></span><span>`</span><span>, { </span><span>status</span><span>:</span><span> </span><span>500</span><span>, </span><span>headers</span><span>:</span><span> { </span><span>"Content-Type"</span><span>:</span><span> </span><span>"text/html"</span><span> } });</span></div></div><div><div><div>1020</div></div><div><span>}</span></div></div><div><div><div>1021</div></div><div>
</div></div><div><div><div>1022</div></div><div><span>function</span><span> </span><span>formatFileSize</span><span><span>(</span><span>bytes</span><span>) {</span></span></div></div><div><div><div>1023</div></div><div><span>  </span><span>if</span><span> (</span><span>!</span><span><span>bytes</span><span> </span></span><span>||</span><span><span> </span><span>bytes</span><span> </span></span><span>===</span><span> </span><span>0</span><span>) </span><span>return</span><span> </span><span>"0 B"</span><span>;</span></div></div><div><div><div>1024</div></div><div><span>  </span><span>const</span><span> </span><span>k</span><span> </span><span>=</span><span> </span><span>1024</span><span>;</span></div></div><div><div><div>1025</div></div><div><span>  </span><span>const</span><span> </span><span>sizes</span><span> </span><span>=</span><span> [</span><span>"B"</span><span>, </span><span>"KB"</span><span>, </span><span>"MB"</span><span>, </span><span>"GB"</span><span>, </span><span>"TB"</span><span>];</span></div></div><div><div><div>1026</div></div><div><span>  </span><span>const</span><span> </span><span>i</span><span> </span><span>=</span><span><span> </span><span>Math</span><span>.</span></span><span>floor</span><span><span>(</span><span>Math</span><span>.</span></span><span>log</span><span><span>(</span><span>bytes</span><span>) </span></span><span>/</span><span><span> </span><span>Math</span><span>.</span></span><span>log</span><span><span>(</span><span>k</span><span>));</span></span></div></div><div><div><div>1027</div></div><div><span>  </span><span>return</span><span> </span><span>parseFloat</span><span><span>((</span><span>bytes</span><span> </span></span><span>/</span><span><span> </span><span>Math</span><span>.</span></span><span>pow</span><span><span>(</span><span>k</span><span>, </span><span>i</span><span>)).</span></span><span>toFixed</span><span>(</span><span>2</span><span>)) </span><span>+</span><span> </span><span>" "</span><span> </span><span>+</span><span><span> </span><span>sizes</span><span>[</span><span>i</span><span>];</span></span></div></div><div><div><div>1028</div></div><div><span>}</span></div></div><div><div><div>1029</div></div><div>
</div></div><div><div><div>1030</div></div><div><span>function</span><span> </span><span>escapeHtml</span><span><span>(</span><span>unsafe</span><span>) {</span></span></div></div><div><div><div>1031</div></div><div><span>  </span><span>return</span><span><span> </span><span>unsafe</span></span></div></div><div><div><div>1032</div></div><div><span><span>    </span></span><span>.</span><span>replace</span><span>(</span><span>/&amp;/</span><span>g</span><span>, </span><span>"&amp;amp;"</span><span>)</span></div></div><div><div><div>1033</div></div><div><span><span>    </span></span><span>.</span><span>replace</span><span>(</span><span>/&lt;/</span><span>g</span><span>, </span><span>"&amp;lt;"</span><span>)</span></div></div><div><div><div>1034</div></div><div><span><span>    </span></span><span>.</span><span>replace</span><span>(</span><span>/&gt;/</span><span>g</span><span>, </span><span>"&amp;gt;"</span><span>)</span></div></div><div><div><div>1035</div></div><div><span><span>    </span></span><span>.</span><span>replace</span><span>(</span><span>/"/</span><span>g</span><span>, </span><span>"&amp;quot;"</span><span>)</span></div></div><div><div><div>1036</div></div><div><span><span>    </span></span><span>.</span><span>replace</span><span>(</span><span>/'/</span><span>g</span><span>, </span><span>"&amp;#039;"</span><span>);</span></div></div><div><div><div>1037</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section>]]></content>
    </entry>
    <entry>
      <id>https://www.5al.top/posts/cloudflare-preferred/</id>
      <title type="text">Cloudflare 优选 IP 教程</title>
      <published>2025-10-23T16:00:00.000Z</published>
      <updated>2025-10-23T16:00:00.000Z</updated>
      <author><name>星觅海</name></author>
      <link rel="alternate" href="https://www.5al.top/posts/cloudflare-preferred/"/>
      <summary type="text">用两个托管在 Cloudflare 的域名配合自定义主机名做优选 IP，附多站点配置方法。</summary>
      <content type="html"><![CDATA[<blockquote><p>用我的两个域名来演示</p><p>主力域名：xmhai.cn　辅助域名：jasmiam.top</p><p>我的两个域名都托管在 Cloudflare，主力域名用来访问，辅助域名用来优选</p><p>优选域我用的是 cloudflare.182682.xyz</p><p>本站是部署在 Netlify 上的</p></blockquote>
<section><h3>🚀 快速开始<a href="#-快速开始"><span>#</span></a></h3><section><h4>前提条件<a href="#前提条件"><span>#</span></a></h4><ul>
<li>拥有 <a href="https://dash.cloudflare.com/" target="_blank">Cloudflare 账号</a></li>
<li>Cloudflare 账号已绑定支付方式</li>
<li>拥有两个域名</li>
<li>辅助域名必须托管在Cloudflare上</li>
</ul></section><section><h4>辅助域名dns配置<a href="#辅助域名dns配置"><span>#</span></a></h4>

<table><thead><tr><th>类型</th><th>名称</th><th>目标</th><th>代理状态</th></tr></thead><tbody><tr><td>cname</td><td>cdn</td><td>cloudflare.182682.xyz</td><td>关</td></tr><tr><td>cname或A</td><td>@</td><td>网址或ip</td><td>开</td></tr></tbody></table><p></p><figure><img src="https://img.xmhai.cn/v2/iaBzi9V.png" alt="Screenshot_2025-10-24-04-50-31-809_mark.via.png" /><figcaption>Screenshot_2025-10-24-04-50-31-809_mark.via.png</figcaption></figure>
<figure><img src="https://img.xmhai.cn/v2/EVNlWSs.png" alt="Screenshot_2025-10-24-04-51-11-410_mark.via.png" /><figcaption>Screenshot_2025-10-24-04-51-11-410_mark.via.png</figcaption></figure><p></p></section><section><h4>辅助域名自定义主机名<a href="#辅助域名自定义主机名"><span>#</span></a></h4><p>添加一个默认回退源，我填的是jasmiam.top，因为我的辅助域名的dns记录是@</p><p>添加自定义主机名<strong>xmhai.cn</strong>，自定义源服务器选<strong>默认源服务器</strong></p><p></p><figure><img src="https://img.xmhai.cn/v2/l5MfHvx.png" alt="Screenshot_2025-10-24-04-56-16-885_mark.via.png" /><figcaption>Screenshot_2025-10-24-04-56-16-885_mark.via.png</figcaption></figure>
<figure><img src="https://img.xmhai.cn/v2/xgyeaYu.png" alt="Screenshot_2025-10-24-04-55-12-042_mark.via.png" /><figcaption>Screenshot_2025-10-24-04-55-12-042_mark.via.png</figcaption></figure><p></p></section><section><h4>主域名dns配置<a href="#主域名dns配置"><span>#</span></a></h4>

<table><thead><tr><th>类型</th><th>名称</th><th>目标</th><th>代理状态</th></tr></thead><tbody><tr><td>cname</td><td>@</td><td>cdn.jasmiam.top</td><td>关</td></tr></tbody></table><p></p><figure><img src="https://img.xmhai.cn/v2/e8cQr9R.png" alt="Screenshot_2025-10-24-05-15-18-220_mark.via.png" /><figcaption>Screenshot_2025-10-24-05-15-18-220_mark.via.png</figcaption></figure><p></p><blockquote><p>然后访问<strong>xmhai.cn</strong>就可以了</p></blockquote></section></section>
<section><h3>如何配置多个站点？<a href="#如何配置多个站点"><span>#</span></a></h3><ul>
<li>假如我要用<strong>page.xmhai.cn</strong>来访问</li>
</ul><section><h4>辅助域名dns配置<a href="#辅助域名dns配置-1"><span>#</span></a></h4>

<table><thead><tr><th>类型</th><th>名称</th><th>目标</th><th>代理状态</th></tr></thead><tbody><tr><td>cname或A</td><td>page</td><td>网址或ip</td><td>开</td></tr></tbody></table><p></p><figure><img src="https://img.xmhai.cn/v2/lbQQsz0.png" alt="Screenshot_2025-10-24-05-02-25-332_mark.via.png" /><figcaption>Screenshot_2025-10-24-05-02-25-332_mark.via.png</figcaption></figure><p></p></section><section><h4>辅助域名自定义主机名<a href="#辅助域名自定义主机名-1"><span>#</span></a></h4><p>自定义主机名填page.xmhai.cn，自定义源服务器选<strong>自定义源服务器</strong>填写page.jasmiam.top</p><p></p><figure><img src="https://img.xmhai.cn/v2/yJhkvUy.png" alt="Screenshot_2025-10-24-05-04-27-128_mark.via.png" /><figcaption>Screenshot_2025-10-24-05-04-27-128_mark.via.png</figcaption></figure><p></p></section><section><h4>主域名dns配置<a href="#主域名dns配置-1"><span>#</span></a></h4>

<table><thead><tr><th>类型</th><th>名称</th><th>目标</th><th>代理状态</th></tr></thead><tbody><tr><td>cname</td><td>page</td><td>cdn.jasmiam.top</td><td>关</td></tr></tbody></table><p></p><figure><img src="https://img.xmhai.cn/v2/TuMnQiY.png" alt="Screenshot_2025-10-24-05-14-03-648_mark.via.png" /><figcaption>Screenshot_2025-10-24-05-14-03-648_mark.via.png</figcaption></figure><p></p><blockquote><p>然后访问<strong>page.xmhai.cn</strong>就行了</p></blockquote></section></section>]]></content>
    </entry>
    <entry>
      <id>https://www.5al.top/posts/202509101940/</id>
      <title type="text">第一篇文章</title>
      <published>2025-09-10T11:40:00.000Z</published>
      <updated>2025-09-10T11:40:00.000Z</updated>
      <author><name>星觅海</name></author>
      <link rel="alternate" href="https://www.5al.top/posts/202509101940/"/>
      <summary type="text"></summary>
      <content type="html"><![CDATA[<p>测试一下😁不用管</p>
<section><h3>测试<a href="#测试"><span>#</span></a></h3><p></p><figure><img src="https://q1.qlogo.cn/g?b=qq&amp;nk=1498934815&amp;s=640" alt="测试" /><figcaption>测试</figcaption></figure><p></p></section>]]></content>
    </entry>
</feed>
