<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>朝朝听雨</title><description>物物而不物于物，念念而不念于念</description><link>https://rainzt.cn/</link><templateTheme>Aemeath</templateTheme><templateThemeVersion>4.1.13</templateThemeVersion><templateThemeUrl>https://rainzt.cn/</templateThemeUrl><lastBuildDate>Sun, 11 Oct 2026 05:56:17 GMT</lastBuildDate><item><title>自动上架系统：用 Playwright 构建可控发布流程</title><link>https://rainzt.cn/posts/auto-listing-system/</link><guid isPermaLink="true">https://rainzt.cn/posts/auto-listing-system/</guid><description>记录一个运行在 Windows 桌面上的 BigSeller 自动化系统：从 Excel、图库和模板配置开始，经由 Shopee/TikTok Shop 独立 Worker 执行，再用身份校验、变种精确匹配、暂停恢复、SSE 日志和历史记录把一次高风险的批量发布变成可追踪流程。</description><pubDate>Thu, 09 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;section&gt;&lt;h2&gt;先说结论：它解决的是重复的劳动&lt;a href=&quot;#先说结论它解决的是重复的劳动&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这套工具的目标，不是把鼠标点击录制下来，再批量重复一遍。真正要解决的是一条商品发布链路里的多个一致性问题：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;Excel 里的商品、型号和组号，必须能对应到正确的商品页面；&lt;/li&gt;
&lt;li&gt;当前 BigSeller 页面、店铺和平台必须是操作者明确打开的那一个；&lt;/li&gt;
&lt;li&gt;主图、详情图、视频和变种图不能因为页面顺序变化而错配；&lt;/li&gt;
&lt;li&gt;AI 可以帮助识别图片和生成标题，但不能擅自改掉品牌、型号和固定字段；&lt;/li&gt;
&lt;li&gt;“看到成功提示”不等于平台真的接受了发布；&lt;/li&gt;
&lt;li&gt;批量任务中途暂停、失败或重试后，必须知道已经处理到哪里。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;所以我把它做成了一个运行在 Windows 桌面上的本地工具。文章第一次发布于 2026 年 7 月 9 日；当时仓库还处在 &lt;code&gt;v1.0.0&lt;/code&gt; 的早期基线。现在本地工作区已经推进到 &lt;code&gt;v4.1.1&lt;/code&gt;，主要服务 BigSeller 中的 Shopee 和 TikTok Shop 商品维护与批量发布。本文的代码和流程均来自这套本地项目；代码块已经去掉 API Key、绝对路径、店铺名称和运行数据。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;当前版本自动上架系统欢迎卡片&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;911&quot; src=&quot;/_astro/current-welcome.D2fmHr8J_1yiSFy.webp&quot; srcset=&quot;/_astro/current-welcome.D2fmHr8J_2kzvJN.webp 640w, /_astro/current-welcome.D2fmHr8J_btCWD.webp 750w, /_astro/current-welcome.D2fmHr8J_1SsgU9.webp 828w, /_astro/current-welcome.D2fmHr8J_ZPcXcE.webp 1080w, /_astro/current-welcome.D2fmHr8J_1dulXV.webp 1280w, /_astro/current-welcome.D2fmHr8J_jLEbI.webp 1668w, /_astro/current-welcome.D2fmHr8J_1yiSFy.webp 1920w&quot; /&gt;&lt;figcaption&gt;当前版本自动上架系统欢迎卡片&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;从第一次发布到现在：这不是一次换皮，而是一次工程升级&lt;a href=&quot;#从第一次发布到现在这不是一次换皮而是一次工程升级&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Git 历史最能说明这套工具发生了什么变化。第一次发布前后的 &lt;code&gt;9b877e13&lt;/code&gt;（2026-07-08）仍然是 &lt;code&gt;v1.0.0&lt;/code&gt;：已经有 Shopee/TikTok Shop 双 Worker、4 种标题模式、实时监控、历史记录和 7 个 Shopee 模板 + 1 个 TK 模板，但它更像一套“已经能工作的批量上架工具”。&lt;/p&gt;&lt;p&gt;截至 2026 年 9 月 18 日，最新正式提交是 &lt;code&gt;53d29aff&lt;/code&gt;（&lt;code&gt;v4.1.0&lt;/code&gt;），本地工作区又继续推进到了 &lt;code&gt;v4.1.1&lt;/code&gt;。两个月里的主要变化不是继续堆叠按钮，而是把系统从“能跑”推进到“知道什么时候能跑、为什么停、停在哪里、结果是否可信”。&lt;/p&gt;

















































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;时间与提交&lt;/th&gt;&lt;th&gt;变化&lt;/th&gt;&lt;th&gt;解决的问题&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;7 月 19 日：&lt;code&gt;af9d8a70&lt;/code&gt;&lt;/td&gt;&lt;td&gt;编辑页二次确认、品牌复核，并为两个平台补充测试&lt;/td&gt;&lt;td&gt;搜索到商品不代表打开的就是目标商品，列表品牌也不一定可信&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;7 月 22 日：&lt;code&gt;1be3eb28&lt;/code&gt;&lt;/td&gt;&lt;td&gt;用商品链接和编辑页标题共同校验身份，并兼容旧列表页&lt;/td&gt;&lt;td&gt;BigSeller 页面路由和打开方式变化后，仍能判断页面是否属于目标商品&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;7 月 24 日：&lt;code&gt;6afae1e6&lt;/code&gt;&lt;/td&gt;&lt;td&gt;完善 TK 配置和印尼型号表&lt;/td&gt;&lt;td&gt;TikTok Shop 的表格列、型号和页面流程不能直接照搬 Shopee&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;8 月 1 日：&lt;code&gt;1409482c&lt;/code&gt;、&lt;code&gt;74efdadc&lt;/code&gt;&lt;/td&gt;&lt;td&gt;加入自定义上架方案和店铺整套配置&lt;/td&gt;&lt;td&gt;把一次运行的临时选择，提升为可以保存、复用和按店铺隔离的方案&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;8 月 6—11 日：&lt;code&gt;d4ec311e&lt;/code&gt;、&lt;code&gt;767bab7b&lt;/code&gt;、&lt;code&gt;52109f6c&lt;/code&gt;、&lt;code&gt;8dfd7b70&lt;/code&gt;&lt;/td&gt;&lt;td&gt;稳定规则标题、取消自动插入连接词、主图先传、细化进度和发布收尾&lt;/td&gt;&lt;td&gt;标题字段不再被 AI 随意改写，上传顺序和成功收尾更符合真实页面状态&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;8 月 15 日：&lt;code&gt;bfb9d084&lt;/code&gt;&lt;/td&gt;&lt;td&gt;加固配置系统和 SKU 工具，增加图片冲突测试&lt;/td&gt;&lt;td&gt;运营维护动作开始进入同一套配置和校验边界，而不是各自写脚本&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;9 月 6—12 日：&lt;code&gt;2ceecc97&lt;/code&gt;、&lt;code&gt;ae6227cf&lt;/code&gt;&lt;/td&gt;&lt;td&gt;增加库存扫描兜底与报表能力，更新模板和型号配置&lt;/td&gt;&lt;td&gt;系统从“发布商品”扩展到发布前后的库存、型号和运营维护&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;9 月 16—18 日：&lt;code&gt;6b7fc1e7&lt;/code&gt;、&lt;code&gt;53550478&lt;/code&gt;、&lt;code&gt;c41ff776&lt;/code&gt;、&lt;code&gt;53d29aff&lt;/code&gt;&lt;/td&gt;&lt;td&gt;分批上架范围、多店铺分开上架、PNG 兼容、图片组严格匹配、编辑页身份严格校验和范围导航&lt;/td&gt;&lt;td&gt;批次更大、店铺更多之后，必须把运行范围、图片关系和页面身份锁死&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;这条演进路线可以概括成五个转变：&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;当前版本系统公告与安全校验说明&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;855&quot; src=&quot;/_astro/current-notice.GD6BwqzR_pxtev.webp&quot; srcset=&quot;/_astro/current-notice.GD6BwqzR_28RNet.webp 640w, /_astro/current-notice.GD6BwqzR_ZkmyG.webp 750w, /_astro/current-notice.GD6BwqzR_Z25Ln9S.webp 828w, /_astro/current-notice.GD6BwqzR_6aFrS.webp 1080w, /_astro/current-notice.GD6BwqzR_C0pnL.webp 1280w, /_astro/current-notice.GD6BwqzR_1E63V4.webp 1668w, /_astro/current-notice.GD6BwqzR_pxtev.webp 1920w&quot; /&gt;&lt;figcaption&gt;当前版本系统公告与安全校验说明&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;从“搜索到就编辑”变成“列表行、编辑页、品牌三次确认”；&lt;/li&gt;
&lt;li&gt;从“按文件名和数组顺序上传”变成“按页面真实变种标签做精确映射”；&lt;/li&gt;
&lt;li&gt;从“一个配置覆盖所有场景”变成“模板、平台、店铺和运行范围分别管理”；&lt;/li&gt;
&lt;li&gt;从“整批任务一口气跑完”变成“可选组范围、可暂停、可恢复、可重试”；&lt;/li&gt;
&lt;li&gt;从“控制台显示完成”变成“Worker、SSE、截图、事件历史共同证明结果”。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;所以现在的系统和文章刚发布时，差别不只是界面更丰富、模板更多，而是错误模型变了：早期主要关心怎样把动作执行完，现在更关心执行前能否证明输入正确、执行中能否定位状态、执行后能否证明平台确实接受了结果。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;系统边界：浏览器自动化，但不是“盲点”&lt;a href=&quot;#系统边界浏览器自动化但不是盲点&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;工具通过 Playwright 连接已经登录的 Chromium/Chrome 页面，再操作 BigSeller 当前打开的列表页和编辑页。使用时，运营人员先登录并打开正确的平台列表页，工具负责读取页面、查找商品、进入编辑页和执行配置好的动作。&lt;/p&gt;&lt;p&gt;这个边界是故意保留的：工具不会为了“跑通”而偷偷切换到另一个店铺，也不会假设所有店铺、所有平台都共用同一套页面流程。真实业务里，误操作一个店铺的代价通常比少处理一个商品更高。&lt;/p&gt;&lt;p&gt;整个系统可以抽象成四层：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Excel / 图库 / 视频 / 模板配置&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;│&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;▼&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;server.js 控制层&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Express API · 浏览器上下文 · SSE · 历史记录&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;┌──────┴──────┐&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;▼             ▼&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;batch-worker.js   tk-worker.js&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;     &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Shopee 流程      TikTok Shop 流程&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;└──────┬──────┘&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;▼&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;       &lt;/span&gt;&lt;/span&gt;&lt;span&gt;BigSeller 当前页面&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;│&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;▼&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;       &lt;/span&gt;&lt;/span&gt;&lt;span&gt;截图 · 事件 · 产品结果 · Token&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;server.js&lt;/code&gt; 不把所有业务都塞进一个请求处理器，而是负责控制面板、浏览器连接、配置读写、状态广播和 Worker 生命周期。Shopee 与 TikTok Shop 各自有 Worker、运行状态、暂停文件、SSE 通道和平台选择器；&lt;code&gt;shared.js&lt;/code&gt; 只承载两边都能复用的解析、路径、图片和标题规则。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;一次任务从哪里开始&lt;a href=&quot;#一次任务从哪里开始&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;一个批次的输入并不只有一个 Excel 文件。系统会同时使用：&lt;/p&gt;




























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;输入&lt;/th&gt;&lt;th&gt;作用&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Excel 文件或目录&lt;/td&gt;&lt;td&gt;提供商品名、商品 ID、型号、组号等结构化数据&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;图片目录&lt;/td&gt;&lt;td&gt;按品牌、模板和组号组织主图、详情图、变种图&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;视频目录&lt;/td&gt;&lt;td&gt;按模板配置选择随机视频或指定视频&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;模板配置&lt;/td&gt;&lt;td&gt;决定品牌、标题字段、图片序号、视频策略和平台规则&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;BigSeller 当前页面&lt;/td&gt;&lt;td&gt;提供实际商品列表、编辑页 DOM 和最终发布入口&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;Excel 不是简单的“导入后逐行点击”。Worker 会先读取文件，解析出可搜索的组号，再根据模板和品牌建立映射。运行时还会把本次上架范围传入子进程，让控制层可以明确知道这一轮处理的是哪一段数据。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;运行范围是输入契约，不是一个按钮&lt;a href=&quot;#运行范围是输入契约不是一个按钮&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;批量任务最容易被低估的参数是“这次到底处理哪些组”。早期的界面更接近“从某个位置开始，再跑多少组”；现在服务端把它规范成 &lt;code&gt;groupStart&lt;/code&gt;、&lt;code&gt;groupEnd&lt;/code&gt; 和兼容性的 &lt;code&gt;groupCount&lt;/code&gt;。其中 &lt;code&gt;groupEnd&lt;/code&gt; 是绝对组号，不是“再跑几组”，因此“第 2 组到第 4 组”和“从第 2 组开始跑 3 组”最终会落到同一个明确范围。&lt;/p&gt;&lt;p&gt;服务端在启动 Worker 前会先把结束组物化，再拿当前模板实际读取到的 Excel 组数做边界校验：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;materializeListingScope&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;scope&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;totalGroups&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Number&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;totalGroups&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;&lt;span&gt;scope&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scope&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;groupEnd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scope&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;groupEnd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scope&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;groupEnd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;&lt;span&gt;scope&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;groupEnd&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;groupCount&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;groupEnd&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scope&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;groupStart&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 起始组超过表格总数、结束组小于起始组、结束组超出总数，都会拒绝启动&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scopeCheck&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;validateListingScope&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;shopee&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;requestedScope&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;&lt;span&gt;scopeCheck&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;ok&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;ok&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scopeCheck&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这一步的价值在多店铺场景里更加明显。每个店铺的 Excel 组数可能不同，界面会先检查本地工作区是否真的解析出组号，服务端还会再次拒绝超出表格的结束组。前端禁用“下一步”只能改善体验，不能承担安全边界；真正决定任务能否启动的必须是服务端的第二次校验。&lt;/p&gt;&lt;p&gt;&lt;code&gt;server.js&lt;/code&gt; 启动 Shopee Worker 的核心逻辑大致如下。这里保留了实际的进程边界，省略了 UI 和错误处理之外的细节：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// server.js：启动一个独立的 Shopee Worker（脱敏节选）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scriptPath&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;__dirname&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&apos;batch-worker.js&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;proc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;spawn&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;node&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, [&lt;/span&gt;&lt;span&gt;scriptPath&lt;/span&gt;&lt;span&gt;], {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;cwd&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;__dirname&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;LISTING_GROUP_START&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;listingScope&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;groupStart&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;LISTING_GROUP_COUNT&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;listingScope&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;groupCount&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;stdio&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&apos;pipe&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;pipe&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;pipe&apos;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;buffer&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;proc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;stdout&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;data&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;chunk&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;buffer&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chunk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;lines&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;buffer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;buffer&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;lines&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;pop&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;line&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;of&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;lines&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;&lt;span&gt;line&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;()) &lt;/span&gt;&lt;span&gt;continue&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;handleWorkerMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;line&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;()));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;addLog&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;info&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;line&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Worker 不直接操作控制面板，而是把每一步编码成一行 JSON。这样做有两个好处：浏览器操作和 HTTP 请求不会互相阻塞；同一组事件既可以实时显示，也可以落盘成为历史记录。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// batch-worker.js：Worker 的最小事件出口&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;obj&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;process&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stdout&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;write&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;obj&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;product_start&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;brand&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;imgGroupId&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;batchGroupId&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;batchNum&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;Worker 输出的是运行协议，不只是日志&lt;a href=&quot;#worker-输出的是运行协议不只是日志&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Worker 的标准输出实际上是一条轻量事件协议。&lt;code&gt;product_start&lt;/code&gt; 表示进入商品边界，&lt;code&gt;step&lt;/code&gt; 表示可以展示给操作者的中间动作，&lt;code&gt;screenshot&lt;/code&gt; 记录证据文件，&lt;code&gt;product_done&lt;/code&gt; 才代表商品结果已经完成；&lt;code&gt;fatal&lt;/code&gt;、&lt;code&gt;paused&lt;/code&gt; 和进程退出则分别进入失败、暂停和收尾路径。控制层收到同一条消息后，同时更新内存中的运行状态、SSE 客户端和当前历史产品文件。&lt;/p&gt;&lt;p&gt;这里还有一个很实际的工程细节：子进程的 stdout 不保证一次回调就是一整行 JSON。&lt;code&gt;server.js&lt;/code&gt; 会保留上一次回调最后一个不完整的片段，只解析已经出现换行的部分；如果某行不是 JSON，则降级为普通信息日志，而不是让整个控制层崩溃。这使 Worker 偶尔输出第三方库的诊断文字时，批量任务仍能继续运行。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;proc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;stdout&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;on&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;data&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, (&lt;/span&gt;&lt;span&gt;chunk&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;buffer&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chunk&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;lines&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;buffer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;buffer&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;lines&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;pop&lt;/span&gt;&lt;span&gt;(); &lt;/span&gt;&lt;span&gt;// 保留可能尚未收完整的 JSON 行&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;line&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;of&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;lines&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;&lt;span&gt;line&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;()) &lt;/span&gt;&lt;span&gt;continue&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;handleWorkerMessage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;parse&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;line&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;()));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;addLog&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;info&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;line&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;trim&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;最重要的防错：先确认“这是哪个商品”&lt;a href=&quot;#最重要的防错先确认这是哪个商品&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;浏览器自动化最危险的地方，不是点击失败，而是点击成功但点错了对象。BigSeller 的列表可能分页、刷新或重新渲染，页面中的行也可能因为平台升级而变化。因此系统不会只拿一个字符串搜索到行就继续。&lt;/p&gt;&lt;p&gt;Shopee Worker 当前使用 VXE 表格的行选择器定位商品，并要求结果唯一；随后再次读取行标题，确认它与 Excel 中的商品名一致。进入编辑页后，还要检查 URL 和编辑页标题至少有一项能够和目标商品对应：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// batch-worker.js：编辑页身份二次确认（脱敏节选）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;verifyOpenedEditPageIdentity&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;productName&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;expectedUrl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;actualPath&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;normalizeEditPagePath&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;\/&lt;/span&gt;&lt;span&gt;(?:web&lt;/span&gt;&lt;span&gt;\/&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;listing&lt;/span&gt;&lt;span&gt;\/&lt;/span&gt;&lt;span&gt;shopee&lt;/span&gt;&lt;span&gt;\/&lt;/span&gt;&lt;span&gt;edit&lt;/span&gt;&lt;span&gt;\/&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;test&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;actualPath&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;ok&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;reason&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;打开的不是 Shopee 编辑页&apos;&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;editTitle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;readEditPageProductName&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;15000&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;&lt;span&gt;editTitle&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;ok&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;reason&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;编辑页标题为空，无法确认商品身份&apos;&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exactUrl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;normalizeEditPagePath&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;expectedUrl&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;actualPath&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exactTitle&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;normalizeProductText&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;editTitle&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;normalizeProductText&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;productName&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;&lt;span&gt;exactUrl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;&lt;span&gt;exactTitle&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;ok&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;reason&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;`编辑页身份不匹配: 列表“&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;productName&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;” / 编辑页“&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;editTitle&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;”`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;ok&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;editTitle&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;matchedBy&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exactUrl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;url&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;title&apos;&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;品牌也会在编辑页再次判断。列表标题只负责初筛，编辑页无法确认时，Worker 会停止当前商品，而不是“猜一个最接近的品牌”。这也是为什么系统的日志里会明确记录“身份确认”而不是只记录“已点击编辑”。&lt;/p&gt;&lt;p&gt;这套策略也解释了为什么工具不主动替用户切换店铺或跳转到另一个列表页。浏览器里的登录态、当前店铺和当前列表页属于操作者明确选择的运行上下文；工具只在这个上下文内做可验证的动作。自动导航看起来更省一步，但一旦页面路由、店铺筛选或登录态发生变化，脚本可能在错误的业务空间里完成一套完全正常的点击。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;图片映射：绝不靠 &lt;code&gt;组号 + 1&lt;/code&gt; 猜下一张&lt;a href=&quot;#图片映射绝不靠-组号--1-猜下一张&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;批量上架里最容易造成业务事故的是图片错配。一个产品通常同时涉及：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;商品主图和详情图；&lt;/li&gt;
&lt;li&gt;与品牌或模板相关的图片序号；&lt;/li&gt;
&lt;li&gt;页面里的 Color/Model 变种顺序；&lt;/li&gt;
&lt;li&gt;本地图库中的组号和文件名。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;系统在删除旧图之前，会先读取页面真实的变种标签顺序，再和本地预期顺序做精确比较。两次读取不一致、页面缺标签、图片不存在、顺序变化，都会停止，不会退回到“按数组下标上传”的猜测逻辑。&lt;/p&gt;&lt;p&gt;&lt;code&gt;shared.js&lt;/code&gt; 中的比较函数要求两边非空、无重复、长度相等，并且每一项按位置相等：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// shared.js：页面变种顺序与本地组号的核心判定（节选）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exact&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;expected&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;actual&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;duplicateExpected&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;duplicateActual&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;expected&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;actual&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;expected&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;every&lt;/span&gt;&lt;span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;groupId&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;groupId&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;actual&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;上传变种图时，Worker 会把页面标签映射成文件名，然后逐项检查文件是否存在，并再次比较预检结果与页面结果：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// batch-worker.js：变种图严格对齐（节选）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pageFiles&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pageOrder&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;groupId&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;resolveListingImageName&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;baseDir&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;groupId&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;mainImageIndex&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;&lt;span&gt;pageFiles&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;every&lt;/span&gt;&lt;span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;existsSync&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;baseDir&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;)))) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;Strict variant match failed: image file is missing&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;orderChanged&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pageFiles&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;variantFiles&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pageFiles&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;some&lt;/span&gt;&lt;span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;variantFiles&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;orderChanged&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;Strict variant order changed&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;variantFiles&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;pageFiles&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这段逻辑看起来保守，实际是在保护库存和商品展示。自动化系统的专业性，很多时候体现在它知道什么时候不应该继续。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;为什么变种顺序要读两次&lt;a href=&quot;#为什么变种顺序要读两次&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;BigSeller 的变种区域会随着滚动、懒加载和 Vue 重渲染发生短暂变化。当前 Worker 在首次读取到非空标签后等待一小段时间，再读取第二次；只有长度和每个位置都相同，才把顺序缓存下来。重试时优先使用已经确认过的缓存，避免重新滚动页面又读到半成品 DOM。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;firstOrder&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;readVariantPageOrder&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sleep&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;800&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;confirmOrder&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;readVariantPageOrder&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stable&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;firstOrder&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;confirmOrder&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;firstOrder&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;every&lt;/span&gt;&lt;span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;groupId&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;groupId&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;confirmOrder&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;]);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;&lt;span&gt;stable&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;fatal&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;变种标签两次读取不一致，已停止&apos;&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;而且“第一次校验通过”仍然不够。进入删除旧图之前，Worker 会再次读取两次页面顺序，并和最初的 &lt;code&gt;pageOrder&lt;/code&gt; 比较。也就是说，真正会修改商品图片的动作前面有一个独立的最终闸门；页面在中间发生重排时，任务宁可停在删除之前，也不会拿旧的读取结果继续执行。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;当前版本自动上架系统控制台&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;911&quot; src=&quot;/_astro/current-console.Dc-3xFX6_1sJszK.webp&quot; srcset=&quot;/_astro/current-console.Dc-3xFX6_cjtaT.webp 640w, /_astro/current-console.Dc-3xFX6_Z1VLoBg.webp 750w, /_astro/current-console.Dc-3xFX6_ZeMKDK.webp 828w, /_astro/current-console.Dc-3xFX6_ZULois.webp 1080w, /_astro/current-console.Dc-3xFX6_17UUS8.webp 1280w, /_astro/current-console.Dc-3xFX6_ede5U.webp 1668w, /_astro/current-console.Dc-3xFX6_1sJszK.webp 1920w&quot; /&gt;&lt;figcaption&gt;当前版本自动上架系统控制台&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;标题生成：AI 只负责它擅长的那一小部分&lt;a href=&quot;#标题生成ai-只负责它擅长的那一小部分&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;标题有四种运行模式：固定标题、完整 AI 标题、固定字段规则随机、AI 识别图案词后再由本地规则拼接。这里没有把整条标题完全交给模型，因为品牌、型号、功能词和字符上限都属于确定性约束。&lt;/p&gt;&lt;p&gt;例如 &lt;code&gt;ai_image_shuffle&lt;/code&gt; 模式只让 AI 从图片中提取少量图案词，品牌、前缀、型号和功能字段仍由本地配置控制；&lt;code&gt;ai_fixed_shuffle&lt;/code&gt; 甚至完全不调用 AI。&lt;code&gt;shared.js&lt;/code&gt; 中的规则构造器会把固定字段放在配置指定的位置，模型不能重复或挪动这些字段。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// batch-worker.js：AI 图析 + 本地规则组装&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;imgWords&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;generateTitlePatternOnly&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;imageDir&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;aiImage&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;brand&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pageTitle&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;parts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;aiWords&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;imgWords&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;buildRuleShuffleTitle&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;parts&lt;/span&gt;&lt;span&gt;, { &lt;/span&gt;&lt;/span&gt;&lt;span&gt;maxChars&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TITLE_MAX&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TITLE_MAX&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;error&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;规则标题超过平台字符限制&apos;&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;完整 AI 标题模式也会检查语言、清理换行和引号、限制字符数；写入页面后再读取输入框验证，连续失败就停止该商品。模型请求失败不会被伪装成“发布成功”，Token 使用量也会作为运行事件记录。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;AI 与标题配置&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;911&quot; src=&quot;/_astro/4.Do9ty007_Z1pBRBc.webp&quot; srcset=&quot;/_astro/4.Do9ty007_Z1O2AEK.webp 640w, /_astro/4.Do9ty007_1KW8ST.webp 750w, /_astro/4.Do9ty007_Zl53k.webp 828w, /_astro/4.Do9ty007_ZVvyDQ.webp 1080w, /_astro/4.Do9ty007_Z1JqdXG.webp 1280w, /_astro/4.Do9ty007_HEqr6.webp 1668w, /_astro/4.Do9ty007_Z1pBRBc.webp 1920w&quot; /&gt;&lt;figcaption&gt;AI 与标题配置&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;一个商品的执行顺序&lt;a href=&quot;#一个商品的执行顺序&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;当前 Shopee 流程不是简单的“打开页面 → 上传 → 点击发布”，而是有明确的破坏性动作边界：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;连接现有 CDP 浏览器，确认 BigSeller Shopee 列表页存在；&lt;/li&gt;
&lt;li&gt;读取模板和 Excel，确定本次组号范围；&lt;/li&gt;
&lt;li&gt;搜索商品并确认列表行唯一；&lt;/li&gt;
&lt;li&gt;打开编辑页，检查 URL、商品标题和品牌；&lt;/li&gt;
&lt;li&gt;读取页面变种顺序，构建本地图片映射；&lt;/li&gt;
&lt;li&gt;预检查主图、详情图、视频和变种图是否齐全；&lt;/li&gt;
&lt;li&gt;生成标题、填写标题并回读验证；&lt;/li&gt;
&lt;li&gt;在删除旧图前再次读取两次变种顺序；&lt;/li&gt;
&lt;li&gt;删除旧图，确认删除完成后再分批上传新图；&lt;/li&gt;
&lt;li&gt;按模板处理视频和变种图，缺槽位时重试；&lt;/li&gt;
&lt;li&gt;发布前截图；&lt;/li&gt;
&lt;li&gt;点击 BigSeller 的“保存&amp;amp;发布”，等待平台最终确认；&lt;/li&gt;
&lt;li&gt;只有商品结果确认后，才写入 &lt;code&gt;product_done&lt;/code&gt; 和成功历史。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;其中第 8 步很关键：即使前面已经做过一次变种检查，在真正删除旧图片这种不可逆程度更高的动作前仍然要复核。页面可能在滚动、异步加载或重渲染后发生变化，前一次读数不能自动代表当前状态。&lt;/p&gt;&lt;p&gt;从流程设计上看，一个商品实际上被拆成三个阶段：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Prepare&lt;/strong&gt;：读取 Excel、图库和页面，生成标题与图片计划，但不修改商品；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mutate&lt;/strong&gt;：通过最终闸门后删除旧素材、上传新素材、填写视频和变种图；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Commit&lt;/strong&gt;：拿到平台最终提交确认后，写入 &lt;code&gt;product_done&lt;/code&gt;、成功计数和历史结果。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;这不是数据库事务，因为 BigSeller 页面本身不能回滚；但它把最危险的动作尽量推迟到所有可验证条件都满足之后，并且把“提交成功”从按钮点击中分离出来。对浏览器自动化来说，这种分阶段比单纯增加等待时间更可靠。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;发布成功不是一个绿色 Toast&lt;a href=&quot;#发布成功不是一个绿色-toast&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;BigSeller 的 Shopee 发布按钮是下拉按钮，按钮和下拉选项可能使用同一个文案。Worker 需要先展开下拉菜单，再定位真正的选项。点击以后，普通 Ant Design 成功 Toast 只能作为过程信号，不能直接标记商品成功。&lt;/p&gt;&lt;p&gt;当前代码等待包含“操作成功”和“产品已提交 Shopee”的最终弹窗：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// batch-worker.js：最终成功判定（脱敏节选）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;modalText&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;modal&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;textContent&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2000&lt;/span&gt;&lt;span&gt; }).&lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;modalText&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;includes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;操作成功&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;modalText&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;includes&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;产品已提交Shopee&apos;&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;closePublishedShopeeEditPage&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;modal&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// 普通成功提示只记录，不完成商品&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;locator&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;.ant-message-success&apos;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;step&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;level&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;info&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;已收到成功提示，继续等待最终提交确认&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;如果按钮消失但最终弹窗没有出现，系统也不会把它当作成功。它会进入重试或失败路径，避免历史记录里出现“看起来成功、实际上没有提交”的假结果。&lt;/p&gt;&lt;p&gt;因此历史记录里的成功数不是“点击了多少次发布按钮”，而是满足一组条件后的结果：目标编辑页身份已经确认、图片与变种映射通过、标题回读通过、必要素材已验证、最终提交弹窗出现，并且 Worker 能完成收尾。任何一项缺失，都不会被包装成成功率。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;暂停、恢复和失败边界&lt;a href=&quot;#暂停恢复和失败边界&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;批量任务不应该只能“开始”或“杀进程”。Shopee Worker 使用暂停标记和恢复状态文件记录组号、产品索引、批次数量和当前组索引：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// batch-worker.js：暂停时保存可恢复位置&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;saveResumeState&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;groupNum&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;productIndex&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;totalBatches&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;totalProducts&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;groupIdx&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;writeFileSync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;RESUME_STATE&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;groupNum&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;productIndex&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;totalBatches&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;totalProducts&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;groupIdx&lt;/span&gt;&lt;span&gt; }),&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&apos;utf-8&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;existsSync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;PAUSE_FLAG&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;saveResumeState&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;parseGroupId&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;keyword&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;/span&gt;&lt;span&gt;num&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;productIndex&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;totalBatches&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;totalProducts&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;groupIdx&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;emit&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;paused&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;keyword&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;productIndex&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;totalInBatch&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;while&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;existsSync&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;RESUME_FLAG&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sleep&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;暂停并不是在任意一行代码上硬切断。控制层先写入暂停请求，Worker 在产品边界检查标记，保存位置后等待恢复。对于身份不明、图片缺失、变种不稳定、标题回读失败或发布最终确认缺失等情况，系统选择安全停止当前商品，而不是继续扩大影响范围。&lt;/p&gt;&lt;p&gt;停止和暂停也不是同一件事。暂停是协作状态：当前商品完成到安全边界后保存恢复位置，等待 &lt;code&gt;resume.flag&lt;/code&gt;；停止则清理控制文件、结束当前进程，并将队列标记为停止。Shopee 与 TikTok Shop 使用各自的暂停/恢复文件和运行状态，因此停止一个平台不会误伤另一个平台的 Worker。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;实时状态与历史记录是两条链&lt;a href=&quot;#实时状态与历史记录是两条链&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;实时控制台使用 SSE。Shopee 和 TikTok Shop 分别连接 &lt;code&gt;/events&lt;/code&gt; 与 &lt;code&gt;/events-tk&lt;/code&gt;，服务端会发送初始状态、后续事件和心跳；浏览器断线时，服务端清理客户端，避免连接数组无限增长。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// server.js：SSE 的公共连接处理（节选）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;writeHead&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;200&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&apos;Content-Type&apos;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;text/event-stream&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&apos;Cache-Control&apos;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;no-cache, no-transform&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&apos;Connection&apos;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;keep-alive&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&apos;X-Accel-Buffering&apos;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;no&apos;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;write&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`data: &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;JSON&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;stringify&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;initialEvent&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;\n\n&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;heartbeat&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;setInterval&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;res&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;write&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;`: heartbeat &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;\n\n&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}, &lt;/span&gt;&lt;span&gt;SSE_HEARTBEAT_MS&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;SSE 只是“现在发生了什么”，不是唯一事实来源。每次运行还会写入本地历史目录：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;上架历史/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;└── YYYY-MM-DD/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;└── shopee-&amp;lt;run-id&amp;gt;/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;├── run.json          # 平台、模板、店铺快照、统计、Token、最终状态&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;├── index.json        # 历史列表用的轻量摘要&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;├── events.ndjson     # 按时间追加的批次/产品/错误事件&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;├── products/         # 每个商品的计划、步骤、结果和失败原因&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;└── screenshots/      # 关键页面截图引用&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;run.json&lt;/code&gt; 会保存本次配置快照、平台和模板、处理数量、成功/失败数量、截图数量与 Token 使用量；&lt;code&gt;events.ndjson&lt;/code&gt; 则把 Worker 事件按时间追加。这样，实时页面即使被关闭，也能在历史页重新回答：哪个批次失败、失败发生在哪一步、标题和图片计划是什么、最终是否提交。&lt;/p&gt;&lt;p&gt;历史页也没有每次打开都把所有产品和事件一次性读进概览。服务端把运行级概览和单次详情分开：概览只读取日期、平台、状态、成功失败计数和最近产品；用户点进某次运行后，才加载产品 JSON、事件尾部和截图预览。对于长期积累的运行记录，这个边界能避免“查看今天的统计”被历史详情拖慢。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;当前版本历史记录与复盘界面&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;911&quot; src=&quot;/_astro/current-history.BhxgVPs3_Nl1V2.webp&quot; srcset=&quot;/_astro/current-history.BhxgVPs3_P2xnV.webp 640w, /_astro/current-history.BhxgVPs3_Z1j3koe.webp 750w, /_astro/current-history.BhxgVPs3_nUiyh.webp 828w, /_astro/current-history.BhxgVPs3_Z1AaOWb.webp 1080w, /_astro/current-history.BhxgVPs3_swuep.webp 1280w, /_astro/current-history.BhxgVPs3_ZqbcxN.webp 1668w, /_astro/current-history.BhxgVPs3_Nl1V2.webp 1920w&quot; /&gt;&lt;figcaption&gt;当前版本历史记录与复盘界面&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;Shopee 与 TikTok Shop：共用底座，不共用假设&lt;a href=&quot;#shopee-与-tiktok-shop共用底座不共用假设&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;两个平台共用以下基础能力：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;Express 服务和嵌入式/调试浏览器连接；&lt;/li&gt;
&lt;li&gt;模板、品牌、Excel、图库和视频配置；&lt;/li&gt;
&lt;li&gt;SSE 状态、截图和历史记录；&lt;/li&gt;
&lt;li&gt;AI 标题与 Token 统计；&lt;/li&gt;
&lt;li&gt;暂停、恢复、停止和错误事件。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;但它们不会共用平台专属动作。Shopee 使用 &lt;code&gt;batch-worker.js&lt;/code&gt;，关注商品列表、编辑页、“保存&amp;amp;发布”、Shopee 提交确认和可选预售；TikTok Shop 使用 &lt;code&gt;tk-worker.js&lt;/code&gt;，采用自己的列表入口、Excel 起始行、编辑按钮、更新按钮和成功判断。一个平台的选择器、发布按钮或预售逻辑不能直接复制到另一个平台。&lt;/p&gt;&lt;p&gt;这种隔离比抽象出一个“万能 Worker”更可靠。平台差异被封装在各自 Worker 里，公共部分保持纯函数和数据规则，后续增加平台时可以复用底座，但不必把所有条件分支塞进同一个巨型流程。&lt;/p&gt;&lt;p&gt;当前版本还保留原来的单店铺入口，同时增加了“多店铺分开上架”。后者不是把多个店铺的配置临时拼在一起，而是为每个店铺绑定独立 Excel、图库和整套模板配置，再逐店打开设置核对，全部确认后才进入串行队列。截图中的 BigSeller 店铺列表尚未打开时，系统会明确显示同步失败并禁用下一步；这正是它与早期“默认当前页面继续执行”思路的区别：店铺范围必须被检测、确认，不能靠猜。&lt;/p&gt;&lt;p&gt;多店铺最容易出现的隐性事故不是“选错店铺”，而是店铺专属路径污染全局配置。当前实现会先把店铺工作区合并到内存中的运行配置，Worker 通过 &lt;code&gt;LISTING_CONFIG_PATH&lt;/code&gt; 读取这份隔离配置；单店铺使用的 &lt;code&gt;config.json&lt;/code&gt; 不会被临时覆盖。服务端还会修复历史版本遗留的路径泄漏，把单店铺全局路径恢复到默认工作区。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;applyMultiStoreWorkspace&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;workspaceDir&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workspace&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;getMultiStoreWorkspaceSummary&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;normalizeMultiStoreWorkspace&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;workspaceDir&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;next&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cloneConfigValue&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;of&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Object&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;values&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;next&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;templates&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; {})) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;excelPath&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workspace&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;excelDir&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;template&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;imageBaseDir&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workspace&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;imageDir&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;next&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;paths&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;tempImages&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;workspace&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;imageDir&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;next&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;singleMainImageDir&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;path&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;workspace&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;imageDir&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;单主图库&apos;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;next&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;workspace&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这也是“多店铺分开上架”与“把多个店铺临时拼到一起”的本质区别：队列可以串行执行多个工作区，但每个 Worker 拿到的是自己的配置快照；单店铺原有配置和运行状态仍然是独立的。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;当前版本多店铺分开上架流程&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;911&quot; src=&quot;/_astro/current-multi-store.DpfS9gWl_Z1ihY6I.webp&quot; srcset=&quot;/_astro/current-multi-store.DpfS9gWl_Z1roeC6.webp 640w, /_astro/current-multi-store.DpfS9gWl_Z1vSE2p.webp 750w, /_astro/current-multi-store.DpfS9gWl_Z1NEnvU.webp 828w, /_astro/current-multi-store.DpfS9gWl_Z1CX8iG.webp 1080w, /_astro/current-multi-store.DpfS9gWl_1gGufl.webp 1280w, /_astro/current-multi-store.DpfS9gWl_ZehbYd.webp 1668w, /_astro/current-multi-store.DpfS9gWl_Z1ihY6I.webp 1920w&quot; /&gt;&lt;figcaption&gt;当前版本多店铺分开上架流程&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;Shopee 与 TikTok Shop 工作区&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;911&quot; src=&quot;/_astro/6.B4sBFD10_VJar.webp&quot; srcset=&quot;/_astro/6.B4sBFD10_2tVSA7.webp 640w, /_astro/6.B4sBFD10_YJukP.webp 750w, /_astro/6.B4sBFD10_ZLxIBo.webp 828w, /_astro/6.B4sBFD10_u337M.webp 1080w, /_astro/6.B4sBFD10_ZiQBc3.webp 1280w, /_astro/6.B4sBFD10_29e3dJ.webp 1668w, /_astro/6.B4sBFD10_VJar.webp 1920w&quot; /&gt;&lt;figcaption&gt;Shopee 与 TikTok Shop 工作区&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;周边工具为什么也要放进同一套系统&lt;a href=&quot;#周边工具为什么也要放进同一套系统&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;真正使用时，批量上架并不是唯一的高频动作。当前控制台还包含表格批量上传、批量添加型号、已知 Model 修复、SKU 修复、描述修改、折扣处理、素材中心清理、视频中心、截图和历史查看等工具。&lt;/p&gt;&lt;p&gt;它们共用同一个浏览器上下文和配置边界，但会检查与上架 Worker 的冲突状态。例如上架运行中，另一个会修改商品数据的工具不能直接抢占同一页面。这样做不是为了让功能看起来更多，而是为了避免两个自动化任务同时改同一个 BigSeller 页面。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;当前版本批量工具中心&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;911&quot; src=&quot;/_astro/current-tools.DWbSFht6_13RCnu.webp&quot; srcset=&quot;/_astro/current-tools.DWbSFht6_eYyw.webp 640w, /_astro/current-tools.DWbSFht6_a2yzs.webp 750w, /_astro/current-tools.DWbSFht6_Z1wvK7h.webp 828w, /_astro/current-tools.DWbSFht6_Z21KPcs.webp 1080w, /_astro/current-tools.DWbSFht6_ZRlrhb.webp 1280w, /_astro/current-tools.DWbSFht6_Z13Aj6g.webp 1668w, /_astro/current-tools.DWbSFht6_13RCnu.webp 1920w&quot; /&gt;&lt;figcaption&gt;当前版本批量工具中心&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;平台差异不是配置项，而是两套流程&lt;a href=&quot;#平台差异不是配置项而是两套流程&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;两个平台共用输入、浏览器连接、日志和历史底座，但关键页面动作保持独立：&lt;/p&gt;












































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;维度&lt;/th&gt;&lt;th&gt;Shopee&lt;/th&gt;&lt;th&gt;TikTok Shop&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Worker&lt;/td&gt;&lt;td&gt;&lt;code&gt;batch-worker.js&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;tk-worker.js&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;列表入口&lt;/td&gt;&lt;td&gt;&lt;code&gt;/web/listing/shopee/active/index.htm&lt;/code&gt;，兼容旧列表 URL&lt;/td&gt;&lt;td&gt;&lt;code&gt;/listing/tiktok/index&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Excel 读取&lt;/td&gt;&lt;td&gt;商品 ID 第 1 列、Model 第 8 列，通常从第 2 行开始&lt;/td&gt;&lt;td&gt;商品 ID 第 14 列、Model 第 17 列，通常从第 5 行开始&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;编辑入口&lt;/td&gt;&lt;td&gt;&lt;code&gt;a[title=&quot;编辑&quot;]&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;a.addEditProduct[title=&quot;编辑&quot;]&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;发布动作&lt;/td&gt;&lt;td&gt;展开“保存&amp;amp;发布”下拉并等待 Shopee 提交确认&lt;/td&gt;&lt;td&gt;点击“更 新”并判断更新成功&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;平台专属逻辑&lt;/td&gt;&lt;td&gt;预售、Shopee 提交弹窗、自动关闭编辑页&lt;/td&gt;&lt;td&gt;TK 变种入口、更新按钮和独立成功判断&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;运行控制&lt;/td&gt;&lt;td&gt;&lt;code&gt;pause.flag&lt;/code&gt; / &lt;code&gt;resume.flag&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;tk-pause.flag&lt;/code&gt; / &lt;code&gt;tk-resume.flag&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;这张表看起来只是选择器差异，实际代表的是业务语义差异：同一个“发布”在两个平台上不是同一个动作，同一个“成功”也不能共用判断。共享层适合放纯函数、路径解析和组号比较；平台页面动作则留在对应 Worker 中，避免抽象出一个包含大量平台分支的“万能流程”。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;测试如何守住这些边界&lt;a href=&quot;#测试如何守住这些边界&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;项目里已经有一组针对高风险路径的 Node 测试，不依赖真实店铺登录即可验证关键规则：&lt;/p&gt;




























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;测试&lt;/th&gt;&lt;th&gt;它防止什么回归&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;test-listing-scope.cjs&lt;/code&gt;&lt;/td&gt;&lt;td&gt;结束组的绝对编号、超出 Excel 总组数、起止倒置和多店铺界面字段错误&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;test-strict-image-group-matching.cjs&lt;/code&gt;&lt;/td&gt;&lt;td&gt;组号错位、重复、顺序变化、缺图，以及删除旧图前没有最终复核&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;test-shopee-publish-confirmation.cjs&lt;/code&gt;&lt;/td&gt;&lt;td&gt;早期成功 Toast 被误判为最终发布成功、发布按钮缺失仍然记成功&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;test-multi-store-config-isolation.cjs&lt;/code&gt;&lt;/td&gt;&lt;td&gt;店铺专属 Excel/图库路径泄漏回单店铺配置，或 Worker 没有读取隔离配置&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;test-listing-state-sync.cjs&lt;/code&gt;&lt;/td&gt;&lt;td&gt;状态事件、暂停/停止和页面状态同步后出现旧状态覆盖新状态&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;这些测试没有试图替代真实浏览器端到端测试。它们的作用是锁住纯规则和关键代码结构，让平台 DOM 变化时，问题尽量暴露在启动前或测试阶段，而不是等到错误商品已经被修改之后才发现。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;当前实现的限制&lt;a href=&quot;#当前实现的限制&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这套系统已经足够支撑日常批量工作，但它并不是一个脱离平台的通用 SaaS，也有明确限制：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;它依赖 BigSeller 页面 DOM，平台改版后需要重新核对选择器和成功判定；&lt;/li&gt;
&lt;li&gt;它依赖本地 Windows 文件结构，换电脑时需要迁移配置和素材路径，不能直接复制运行时目录替代配置；&lt;/li&gt;
&lt;li&gt;AI 标题依赖外部模型服务，网络、额度和模型输出都会影响任务，需要保留失败路径；&lt;/li&gt;
&lt;li&gt;它更适合规则明确、图片和型号组织稳定的商品线，不适合完全没有结构的任意商品；&lt;/li&gt;
&lt;li&gt;所谓“成功率”必须来自历史记录和平台最终确认，不能只用页面按钮点击次数估算。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;这些限制反而决定了系统的工程方向：继续补充平台适配、测试和可观测性，而不是把“全自动”写成不需要人工确认的宣传口号。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;最后：自动化的价值是把判断留给人&lt;a href=&quot;#最后自动化的价值是把判断留给人&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我现在更愿意把这套工具称为“有明确停止条件的批量发布流水线”。它确实替人完成了大量重复操作，但它没有试图替人承担所有判断：店铺是否正确、素材是否对应、页面身份是否一致、发布是否真的成功，这些地方都必须留下证据。&lt;/p&gt;&lt;p&gt;从代码结构看，系统最值得保留的部分不是某个漂亮的控制台，而是几条朴素的规则：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;不确定商品身份，就停止；&lt;/li&gt;
&lt;li&gt;不确定图片映射，就停止；&lt;/li&gt;
&lt;li&gt;不确定最终提交结果，就不要记成功；&lt;/li&gt;
&lt;li&gt;AI 只处理开放性内容，固定业务字段由规则控制；&lt;/li&gt;
&lt;li&gt;实时状态用于操作，历史事件用于复盘。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;当商品数量、平台和店铺继续增加时，速度只是第一层收益。更重要的是，流程开始拥有统一的输入、可验证的中间状态、可恢复的执行位置和可追溯的结果。这才是我认为自动上架系统真正值得记录的地方。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>最近</title><link>https://rainzt.cn/posts/recently/</link><guid isPermaLink="true">https://rainzt.cn/posts/recently/</guid><description>聊聊最近的疲惫和懒散、暂时推迟的创业计划，以及重新调整作息、捡起锻炼、继续开发自动上架 Agents 的打算。</description><pubDate>Sat, 10 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;我是一个极度缺乏写作灵感的人，偏偏又不太喜欢写一成不变的东西。&lt;/p&gt;
&lt;p&gt;本来打算做周刊，后来想了想，每周都写“这周更新了什么、修了什么、接下来准备做什么”，估计用不了几期，我自己就先看腻了。于是放弃了这个百分百会让我写出一堆重复内容的方案，最后变成了现在的月刊。&lt;/p&gt;
&lt;p&gt;可更新频率降下来了，灵感也没因此多起来，哈哈哈哈。&lt;/p&gt;
&lt;p&gt;很久没更新文章了，今天想了一天，到底写什么好。想来想去，干脆问 GPT：你觉得我最近的心情、性格，和上个月有什么不一样？&lt;/p&gt;
&lt;p&gt;因为我自己很明显地感觉到，我好像变了。&lt;/p&gt;
&lt;p&gt;它有一段话说得挺贴近我的状态：&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;你还在不断推进事情，但现在又想停下来写点自己。可能是最近每天都有事做，回头看却很难说清楚“这段日子我到底过得怎么样”。做了什么容易列出来，感受反而需要慢慢想。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;害，好像还真是。&lt;/p&gt;
&lt;p&gt;每天都有一些事情要处理，可真要问我最近过得怎么样，我反而答不上来。项目可以列清单，心情却没那么容易整理。&lt;/p&gt;
&lt;p&gt;那就写一篇日常文叭，起码告诉我的朋友们，我最近在做什么，又在想些什么。&lt;/p&gt;
&lt;p&gt;所以，这一期的标题就叫：&lt;strong&gt;最近！&lt;/strong&gt;&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;最近，有点懒散了&lt;a href=&quot;#最近有点懒散了&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;最近感觉身体好疲惫，人也有点懒散了。&lt;/p&gt;&lt;p&gt;说来好笑，项目的更新频率已经从最初的日更，慢慢变成周更，再到现在的月更。甚至一个月不出问题，我可能就一直拖，拖到哪天出问题了才想起来：哦，该修一下了。&lt;/p&gt;&lt;p&gt;写到这里，我自己都有点蚌埠住。&lt;/p&gt;&lt;p&gt;说要创业的人，结果先懒散了一个月，哈哈哈哈。&lt;/p&gt;&lt;p&gt;创业的计划，也确实被我推迟了。越接近真正要开始的时候，我越能感觉到自己有点担心。之前想得挺起劲，真要往前迈，又发现还有很多事情没准备好。&lt;/p&gt;&lt;p&gt;所以，我还想再沉淀一段时间。&lt;/p&gt;&lt;p&gt;但今天想了一上午，我也得承认：“再沉淀一下”这句话说起来实在太方便了。至于到底在沉淀什么，又准备沉淀到什么时候，我得给自己一个具体的回答。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;那股冲劲，好像没了&lt;a href=&quot;#那股冲劲好像没了&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;今天没啥事，我难得认真想了想自己的状态。&lt;/p&gt;&lt;p&gt;我感觉自己正在往后退，无论是游戏，还是做项目，都没有五到七月份那股冲劲了。&lt;/p&gt;&lt;p&gt;游戏尤其明显。我明明感觉自己的操作还在进步，一些细节也比以前处理得好了，但就是打得没以前好。越想打好，有时候越力不从心，打完还觉得挺累。&lt;/p&gt;&lt;p&gt;做项目也是。&lt;/p&gt;&lt;p&gt;自动上架 Agents 刚开发出来的时候，我那是相当兴奋呐。我给它们起名叫 worker-A、Worker-B，每天负责帮我上班。看着那些原来需要自己反复点、反复填的事情，终于能交给它们去做，那日子真叫一个滋润啊，嘿嘿。&lt;/p&gt;&lt;p&gt;现在回想起来，我似乎有点沉浸在这里面了。&lt;/p&gt;&lt;p&gt;每天看着它们跑起来，觉得挺舒服；没出问题，就觉得暂时不用管。慢慢地，继续开发这件事，也就被我往后放了。&lt;/p&gt;&lt;p&gt;可跨境电商的业务相当繁琐，远远不止上架这一件事。还有很多流程没有接起来，也有很多问题，需要我自己判断和处理。&lt;/p&gt;&lt;p&gt;我想做的是一个跨境电商全能的智能体。现在这套半自动的产品，只走到了其中一段。&lt;/p&gt;&lt;p&gt;当初把它做出来时那么兴奋，后面却停在这里享受了一个月。想起来，我确实挺失望，也挺懊悔的。按原来的劲头，这段时间应该还能继续往前推进不少东西。&lt;/p&gt;&lt;p&gt;当然，懊悔完了，事情也不会自己往前走。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;把状态一点点找回来&lt;a href=&quot;#把状态一点点找回来&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这一上午想来想去，最后还是得回到几个很具体的地方。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;先把作息改了&lt;a href=&quot;#先把作息改了&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;晚上尽量不超过十二点睡，早上也不能再拖到九点起床了。之前总觉得晚一点睡没什么，今天拖一点，明天再拖一点，作息就这么慢慢往后挪。现在自己都觉得疲惫了，这件事该认真管一管。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;然后是健身锻炼&lt;a href=&quot;#然后是健身锻炼&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;我还清晰地记得，高中一米八五的个子，体重才一百二十出头。上大学变成一百四十，到现在已经一百六十多了，哈哈哈哈。&lt;/p&gt;&lt;p&gt;明显有点小胖了。&lt;/p&gt;&lt;p&gt;更蚌埠住的是，我以前还是体育生。现在一想起来，多少有点对不起当年在操场上训练的自己。&lt;/p&gt;&lt;p&gt;先重新动起来吧，把锻炼捡回来。具体怎么练可以慢慢安排，至少不能一直停在“我准备开始健身”这句话上。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;游戏这边，我也还没老呢！&lt;a href=&quot;#游戏这边我也还没老呢&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;曾经的小国露娜，可不想就这么沉沦下去。最近打得不好，让我挺不甘心的。想认真玩的时候，还是想把状态找回来，打出自己满意的操作。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;自动上架 Agents，该继续日更了&lt;a href=&quot;#自动上架-agents该继续日更了&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;最后，也是我最在意的：自动上架 Agents 该继续日更了。&lt;/p&gt;&lt;p&gt;每天进步一点点，或者多一个小功能，又或者回头清一清半年前留下的技术债。把一直惦记着却没动手的东西做出来，把还需要我反复操作的步骤接起来，给异常情况补上处理办法。&lt;/p&gt;&lt;p&gt;有时候，花一天把一个老问题彻底解决，让自己以后少操一份心，也算进步。&lt;/p&gt;&lt;p&gt;我想慢慢补齐跨境业务里那些繁琐的环节，让 worker-A、Worker-B 能多接一点活，也让最初设想的那套智能体，一点点有更完整的样子。&lt;/p&gt;&lt;p&gt;总得让我最初想做的那个东西，继续往前走。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;创业先推迟，计划得有&lt;a href=&quot;#创业先推迟计划得有&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;创业暂时推迟了，这段时间也该有点实实在在的进展。等我再考虑出发的时候，至少要像做自动上架那样，能说清楚：我准备了什么，补上了什么，还有哪些问题需要面对，下一步又该先做哪件事。&lt;/p&gt;&lt;p&gt;其实说白了，就是得有个计划啦！&lt;/p&gt;&lt;p&gt;把“还需要沉淀一段时间”具体写下来：这段时间要完成什么，做到哪一步，就可以重新考虑出发。这样过完一个月，回头看时，起码知道自己往前走了多少。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;给最近的自己留个记录&lt;a href=&quot;#给最近的自己留个记录&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;写到这里，好像有点像给自己立 flag 了。&lt;/p&gt;&lt;p&gt;而且是作息、锻炼、游戏、项目一起立。按我这一个月的表现，多少有点令人担忧，哈哈哈哈。&lt;/p&gt;&lt;p&gt;所以这篇文章就先放在这里，给自己留个记录，也让朋友们知道：我最近确实有点懒散，有点疲惫，开始担心之前很兴奋地说出口的创业计划，也对自己的状态有些失望。&lt;/p&gt;&lt;p&gt;这些都是最近的我。&lt;/p&gt;&lt;p&gt;但我还想继续做下去。&lt;/p&gt;&lt;p&gt;我记得五到七月份那股冲劲，也记得自己曾经让身旁的人感到震撼的时候。想起那些时候，总觉得现在的我还能再努努力，还有很多东西值得认真做一遍。&lt;/p&gt;&lt;p&gt;朝朝听雨，加把劲哇。&lt;/p&gt;&lt;p&gt;今晚先早点睡，明天起来，把该做的事情往前推一点。&lt;/p&gt;&lt;p&gt;下次再写“最近”，希望我能告诉你们：&lt;/p&gt;&lt;p&gt;这次，我真的坚持下来了。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>山河锦绣，国泰民安</title><link>https://rainzt.cn/posts/national-day-2026/</link><guid isPermaLink="true">https://rainzt.cn/posts/national-day-2026/</guid><description>国庆将至，借这张图写下一份简单的祝愿：愿山河锦绣，也愿每个人的日子安稳明亮。</description><pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;国庆快到了。看到图里的红旗、烟火和落日，我想起“热闹”与“安稳”这两个词。&lt;/p&gt;
&lt;p&gt;热闹是街上的灯、家里的饭桌，也是久别的人终于坐在一起；安稳是平常的一天能按自己的节奏过去，惦记的人都平安，明天仍有值得期待的事。&lt;/p&gt;
&lt;p&gt;山河辽阔，生活具体。愿祖国繁荣昌盛，也愿每个认真生活的人，都能把眼前的小日子过得踏实、明亮。&lt;/p&gt;
&lt;p&gt;国庆快乐，愿山河锦绣，国泰民安。&lt;/p&gt;</content:encoded></item><item><title>月刊 · 第二期</title><link>https://rainzt.cn/posts/monthly-2026-09/</link><guid isPermaLink="true">https://rainzt.cn/posts/monthly-2026-09/</guid><description>记录 8 月 27 日到 9 月 27 日之间的博客更新：Aemeath v4.0.0、自建 Umami、评论区 Lottie 表情、被撤回的视频壁纸，以及本期推荐的博友。</description><pubDate>Mon, 28 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;上个月写完第一期月刊的时候，我其实不太确定还会不会有第二期。很多事情我都是开了头，然后就停在那里。&lt;/p&gt;
&lt;p&gt;所以这篇能写出来，我自己先松了口气。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;先看一下这个月的数据&lt;a href=&quot;#先看一下这个月的数据&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这次统计从 8 月 27 日到 9 月 27 日，刚好接上上一期。&lt;/p&gt;&lt;div&gt;
  &lt;div&gt;
    &lt;span&gt;4.01k&lt;/span&gt;
    &lt;span&gt;位访客&lt;/span&gt;
  &lt;/div&gt;
  &lt;div&gt;
    &lt;span&gt;5.63k&lt;/span&gt;
    &lt;span&gt;次访问&lt;/span&gt;
  &lt;/div&gt;
  &lt;div&gt;
    &lt;span&gt;70.2k&lt;/span&gt;
    &lt;span&gt;次页面浏览&lt;/span&gt;
  &lt;/div&gt;
  &lt;div&gt;
    &lt;span&gt;43%&lt;/span&gt;
    &lt;span&gt;跳出率&lt;/span&gt;
  &lt;/div&gt;
  &lt;div&gt;
    &lt;span&gt;4′07″&lt;/span&gt;
    &lt;span&gt;平均访问时长&lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;数据来自自建 Umami，统计时间：8 月 27 日—9 月 27 日。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/monthly-2026-09/umami-dashboard.webp&quot; alt=&quot;Umami 站点数据统计（8 月 27 日—9 月 27 日）&quot; /&gt;&lt;figcaption&gt;Umami 站点数据统计（8 月 27 日—9 月 27 日）&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;访客差不多翻了一倍，从 2.1k 到 4k。页面浏览从 1.2 万涨到 7 万，这个数字我不太敢当真。一次访问平均要看十几页，不像正常读者的行为。9 月 12 日那天一下有将近 8000 次浏览，大概率是爬虫，或者我自己调试时刷出来的。所以这个月我更愿意看访客和访问次数，它们比浏览量诚实。&lt;/p&gt;&lt;p&gt;访问最多的页面依次是首页、归档、友链、朋友圈和留言板。文章里读的人最多的还是《自动上架系统》。外部来源里，必应排第一，后面是博友圈、&lt;a href=&quot;https://bf.zzxworld.com&quot; target=&quot;_blank&quot;&gt;bf.zzxworld.com&lt;/a&gt;、80tz.cn，还有上期推荐过的 &lt;a href=&quot;https://daily.yybb.us/&quot; target=&quot;_blank&quot;&gt;AIOVTUE-雪&lt;/a&gt;。能从友链点进来，说明这个圈子真的在流动。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;顺便：统计换成了自建 Umami&lt;a href=&quot;#顺便统计换成了自建-umami&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;9 月 21 日，我把统计从 Umami Cloud 迁到了自己部署的 Umami（&lt;code&gt;umami.rainzt.cn&lt;/code&gt;）。&lt;/p&gt;&lt;p&gt;原因很简单：官方免费版只保存 6 个月的数据。博客才开了三个月，我已经能想到半年后回头翻，最早那些记录全没了的样子。数据还是放在自己手里踏实。&lt;/p&gt;&lt;p&gt;迁移时最怕数据断档。我先把 Cloud 上的记录导成 CSV 塞进新库，再把切换前的累计差额（576 位访客、951 次浏览）记成基准补回去。侧边栏的累计数字因此没有突然归零。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/monthly-2026-09/analytics-page.webp&quot; alt=&quot;博客里的站点统计页&quot; /&gt;&lt;figcaption&gt;博客里的站点统计页&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;统计页也跟着接到了新的自建地址，数据和后台保持一致。代价是本期和上期的统计口径不完全一样，对比只能看个大概。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;本月文章&lt;a href=&quot;#本月文章&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;
  &lt;span&gt;9 月 · 三篇新文章&lt;/span&gt;
  &lt;article&gt;
    &lt;img src=&quot;/favicon/chaoc-tingyu-avatar-180.png&quot; alt=&quot;&quot; width=&quot;44&quot; height=&quot;44&quot; loading=&quot;lazy&quot; /&gt;
    &lt;div&gt;
      &lt;time&gt;9 月 14 日&lt;/time&gt;
      &lt;p&gt;这篇置顶了：&lt;br /&gt;&lt;a href=&quot;/posts/in-grandmas-white-hair-i-saw-time/&quot;&gt;《在奶奶的白发里，我看见了时间》&lt;/a&gt;&lt;/p&gt;
    &lt;/div&gt;
  &lt;/article&gt;
  &lt;article&gt;
    &lt;div&gt;
      &lt;p&gt;一次回家，看见奶奶满头白发。那一下挺难受的，好像时间一直在走，只是我没有回头看。这篇写得很慢，也是这个月我最想让人读到的一篇。&lt;/p&gt;
    &lt;/div&gt;
    &lt;img src=&quot;/assets/images/rain-avatar.webp&quot; alt=&quot;&quot; width=&quot;44&quot; height=&quot;44&quot; loading=&quot;lazy&quot; /&gt;
  &lt;/article&gt;
  &lt;article&gt;
    &lt;img src=&quot;/favicon/chaoc-tingyu-avatar-180.png&quot; alt=&quot;&quot; width=&quot;44&quot; height=&quot;44&quot; loading=&quot;lazy&quot; /&gt;
    &lt;div&gt;
      &lt;time&gt;9 月 18 日&lt;/time&gt;
      &lt;p&gt;犹豫了一阵，还是公开了：&lt;br /&gt;&lt;a href=&quot;/posts/codex-292-state-analysis/&quot;&gt;《Codex 为什么会“降智”：292 State、状态注入与长任务》&lt;/a&gt;&lt;/p&gt;
    &lt;/div&gt;
  &lt;/article&gt;
  &lt;article&gt;
    &lt;div&gt;
      &lt;p&gt;从 HTTP 292 和状态注入，聊长任务为什么越跑越笨。写完一直不太确定该不该放出来，最后想想，把自己的观察留下来总比憋着好。&lt;/p&gt;
    &lt;/div&gt;
    &lt;img src=&quot;/assets/images/rain-avatar.webp&quot; alt=&quot;&quot; width=&quot;44&quot; height=&quot;44&quot; loading=&quot;lazy&quot; /&gt;
  &lt;/article&gt;
  &lt;article&gt;
    &lt;img src=&quot;/favicon/chaoc-tingyu-avatar-180.png&quot; alt=&quot;&quot; width=&quot;44&quot; height=&quot;44&quot; loading=&quot;lazy&quot; /&gt;
    &lt;div&gt;
      &lt;time&gt;9 月 21 日&lt;/time&gt;
      &lt;p&gt;最后这篇：&lt;br /&gt;&lt;a href=&quot;/posts/ai-ecommerce-agents/&quot;&gt;《跨境电商 Agents：从 AI 选品到自动经营》&lt;/a&gt;&lt;/p&gt;
    &lt;/div&gt;
  &lt;/article&gt;
  &lt;article&gt;
    &lt;div&gt;
      &lt;p&gt;把选品、竞品研究、上架、生图和投放串成一条 Agent 流水线的想法。一篇写家人，一篇写工具，一篇写生意，放一起大概就是我这个月脑子里装的全部东西。&lt;/p&gt;
    &lt;/div&gt;
    &lt;img src=&quot;/assets/images/rain-avatar.webp&quot; alt=&quot;&quot; width=&quot;44&quot; height=&quot;44&quot; loading=&quot;lazy&quot; /&gt;
  &lt;/article&gt;
&lt;/div&gt;&lt;div&gt;
  &lt;a href=&quot;/posts/in-grandmas-white-hair-i-saw-time/&quot;&gt;
    &lt;img src=&quot;/monthly-2026-09/cover-grandma.webp&quot; alt=&quot;《在奶奶的白发里，我看见了时间》文章主图&quot; loading=&quot;lazy&quot; /&gt;
    &lt;span&gt;在奶奶的白发里，我看见了时间&lt;/span&gt;
  &lt;/a&gt;
  &lt;a href=&quot;/posts/codex-292-state-analysis/&quot;&gt;
    &lt;img src=&quot;/monthly-2026-09/cover-codex.webp&quot; alt=&quot;《Codex 为什么会“降智”》文章主图&quot; loading=&quot;lazy&quot; /&gt;
    &lt;span&gt;Codex 为什么会“降智”&lt;/span&gt;
  &lt;/a&gt;
  &lt;a href=&quot;/posts/ai-ecommerce-agents/&quot;&gt;
    &lt;img src=&quot;/monthly-2026-09/cover-agents.webp&quot; alt=&quot;《跨境电商 Agents》文章主图&quot; loading=&quot;lazy&quot; /&gt;
    &lt;span&gt;跨境电商 Agents&lt;/span&gt;
  &lt;/a&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;本月更新：Aemeath v4.0.0&lt;a href=&quot;#本月更新aemeath-v400&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;9 月 26 日，博客的版本号跳到了 v4.0.0。&lt;/p&gt;&lt;p&gt;没有哪一天突然做完了什么大功能。前面三周的小改动攒到一起，某天回头一看，已经和月初不太像同一个站了，我就觉得可以叫它 4.0 了。&lt;/p&gt;&lt;div&gt;
  &lt;article&gt;
    &lt;time&gt;&lt;span&gt;09&lt;/span&gt;&lt;strong&gt;14—17&lt;/strong&gt;&lt;/time&gt;
    &lt;div&gt;&lt;span&gt;merged&lt;/span&gt;&lt;code&gt;main / reading&lt;/code&gt;&lt;span&gt;阅读体验&lt;/span&gt;&lt;/div&gt;
    &lt;p&gt;滚动进度条、滚动动画提示、文章胶片和 Lottie 画廊陆续上线，奶奶那篇就是用胶片排的。16 号加了视频壁纸，17 号又撤掉了。&lt;/p&gt;
  &lt;/article&gt;
  &lt;article&gt;
    &lt;time&gt;&lt;span&gt;09&lt;/span&gt;&lt;strong&gt;18—21&lt;/strong&gt;&lt;/time&gt;
    &lt;div&gt;&lt;span&gt;synced&lt;/span&gt;&lt;code&gt;main / analytics&lt;/code&gt;&lt;span&gt;统计与文章&lt;/span&gt;&lt;/div&gt;
    &lt;p&gt;两篇文章公开，自动上架更新到 v4.1.1；统计切到自建 Umami，友链状态标签重新整理，Demius 加入友链。&lt;/p&gt;
  &lt;/article&gt;
  &lt;article&gt;
    &lt;time&gt;&lt;span&gt;09&lt;/span&gt;&lt;strong&gt;22—25&lt;/strong&gt;&lt;/time&gt;
    &lt;div&gt;&lt;span&gt;reviewed&lt;/span&gt;&lt;code&gt;main / comments&lt;/code&gt;&lt;span&gt;评论与友链&lt;/span&gt;&lt;/div&gt;
    &lt;p&gt;评论区支持 Lottie 表情并重做了选择器，修了暗色模式对比度；出门去野钓加入友链，站点加入开往、BokeHub 和 hao.asoe.cn。&lt;/p&gt;
  &lt;/article&gt;
  &lt;article&gt;
    &lt;time&gt;&lt;span&gt;09&lt;/span&gt;&lt;strong&gt;26—27&lt;/strong&gt;&lt;/time&gt;
    &lt;div&gt;&lt;span&gt;released&lt;/span&gt;&lt;code&gt;main / v4.0.0&lt;/code&gt;&lt;span&gt;Aemeath v4.0.0&lt;/span&gt;&lt;/div&gt;
    &lt;p&gt;导航栏、外观设置、页面切换和侧边栏一起收尾，版本号正式跳到 v4.0.0。&lt;/p&gt;
  &lt;/article&gt;
  &lt;p&gt;单看每一天都只是小改动，回放一遍才发现，4.0 是这么一点点攒出来的。&lt;/p&gt;
&lt;/div&gt;&lt;section&gt;&lt;h3&gt;评论区：能用表情说话了&lt;a href=&quot;#评论区能用表情说话了&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;评论支持 Lottie 动态表情，后来又把表情选择器重新做了一遍，找表情不用翻半天。&lt;/li&gt;
&lt;li&gt;调整了 Waline 的 AI 回复体验。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;阅读：知道自己读到哪了&lt;a href=&quot;#阅读知道自己读到哪了&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;加了全局滚动进度条。&lt;/li&gt;
&lt;li&gt;调整了滚动动画的提示。&lt;/li&gt;
&lt;li&gt;文章里支持胶片式排版和 Lottie 画廊。&lt;/li&gt;
&lt;li&gt;修了暗色模式下几处文字对比度太低的问题。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;导航和外观&lt;a href=&quot;#导航和外观&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;导航栏的悬停、子菜单都重新调过，默认改成展开状态，老访客的偏好也做了迁移。&lt;/li&gt;
&lt;li&gt;外观设置重新整理了一遍。&lt;/li&gt;
&lt;li&gt;页面切换和侧边栏的一些小毛病顺手修掉了。&lt;/li&gt;
&lt;li&gt;博客更新日志单独成页，版本号和日志保持同步。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;这个月也做了一次“停止”&lt;a href=&quot;#这个月也做了一次停止&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;
  &lt;article&gt;
    &lt;div&gt;&lt;strong&gt;视频壁纸&lt;/strong&gt;&lt;span&gt;已撤回&lt;/span&gt;&lt;/div&gt;
    &lt;small&gt;上线一天，太晃眼睛&lt;/small&gt;
  &lt;/article&gt;
  &lt;span&gt;&lt;i&gt;&lt;/i&gt;&lt;i&gt;&lt;/i&gt;&lt;/span&gt;
  &lt;article&gt;
    &lt;div&gt;&lt;strong&gt;番茄的推荐&lt;/strong&gt;&lt;span&gt;本期暂停&lt;/span&gt;&lt;/div&gt;
    &lt;small&gt;同样的原因，体验差了很多&lt;/small&gt;
  &lt;/article&gt;
&lt;/div&gt;&lt;p&gt;9 月 16 日，我给博客加上了视频壁纸，17 号就撤掉了。&lt;/p&gt;&lt;p&gt;原因也简单：太晃眼睛了。背景一直在动，眼睛就总被它拽过去，文章读不进去。&lt;/p&gt;&lt;p&gt;这件事让我想清楚了一点：这里本质上还是博客，是用来写文章、读文章的地方，不是用来秀技术的。好不好看当然重要，但不能让读的人觉得累。&lt;/p&gt;&lt;p&gt;上期我说自己一直在加，也一直在删。这次从加到删只隔了一天，比以前快多了。我觉得这算进步：判断一个东西不对，不一定要先硬撑一个月。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;自动上架：更新到 v4.1.1&lt;a href=&quot;#自动上架更新到-v411&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;自动上架工具这个月更新到了 v4.1.1，&lt;a href=&quot;/posts/auto-listing-system/&quot;&gt;自动上架系统&lt;/a&gt;那篇文章也扩写了一大段。它还是站里被读得最多的文章。&lt;/p&gt;&lt;p&gt;这个月的变化主要是把“批次更大、店铺更多”之后的边界锁死：分批上架范围、多个店铺分开上架、PNG 兼容、图片组严格匹配、编辑页身份严格校验。9 月 18 日我还把项目正式放进了 Git 仓库，之后又修了上架历史读取批次标签的问题，更新了店铺方案和 SKU 型号表。&lt;/p&gt;&lt;p&gt;一句话概括：它越来越少“猜”，越来越多“先证明再动手”。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;博客开始连进更大的圈子&lt;a href=&quot;#博客开始连进更大的圈子&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这个月博客加入了几个博客社区和导航站：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.travellings.cn/&quot; target=&quot;_blank&quot;&gt;开往 Travellings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bokehub.com/home&quot; target=&quot;_blank&quot;&gt;BokeHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hao.asoe.cn/&quot; target=&quot;_blank&quot;&gt;hao.asoe.cn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;友链这边，新认识了 &lt;a href=&quot;https://chumendiao.com/&quot; target=&quot;_blank&quot;&gt;出门去野钓&lt;/a&gt; 和 &lt;a href=&quot;https://blog.demius.tech&quot; target=&quot;_blank&quot;&gt;Demius&lt;/a&gt;。一个写野钓，一个写技术和时间，都是我自己不会去写、但读起来挺有意思的东西。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;生活篇&lt;a href=&quot;#生活篇&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;本期暂无。这一栏我想换个写法，还在想，先空着。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;9 月推荐友链&lt;a href=&quot;#9-月推荐友链&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;本期一共 7 位。前面 4 位是上期留下来的老朋友，后面 3 位是这个月新加的。&lt;/p&gt;&lt;p&gt;上期还推荐过番茄主理人，这期我想了很久，还是没放。他的动态页我特别喜欢，本来正打算抄过来，可视频壁纸看着确实难受，整体体验差了不少。说起来有点好笑，我自己也搞过视频壁纸，上线一天就下了，原因一模一样。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;&lt;a href=&quot;https://blog.cuteleaf.cn/&quot; target=&quot;_blank&quot;&gt;夏夜流萤&lt;/a&gt;&lt;a href=&quot;#夏夜流萤&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://weavatar.com/avatar/d252655d40d6874417a720bad0a6c5f77f8f6a1fd2f882f8f338402dc37e4190?s=640&quot; alt=&quot;夏夜流萤头像&quot; loading=&quot;lazy&quot; /&gt;&lt;/span&gt;&lt;/p&gt;&lt;div&gt;&lt;span&gt;本月推荐&lt;/span&gt;&lt;span&gt;Firefly 作者&lt;/span&gt;&lt;span&gt;技术分享&lt;/span&gt;&lt;/div&gt;&lt;p&gt;这一位是必须要放的。本站就是基于他的 Firefly 主题二创出来的（&lt;a href=&quot;/about/&quot;&gt;关于我&lt;/a&gt;里也写了），我是站在巨人的肩膀上。每次改博客的时候翻源码，都能感受到原作者在结构上花的心思。&lt;/p&gt;&lt;div&gt;
  &lt;div&gt;
    &lt;strong&gt;站在巨人的肩膀上&lt;/strong&gt;
    &lt;span&gt;页面加载 · GitHub API&lt;/span&gt;
  &lt;/div&gt;
  &lt;p&gt;我的博客源码开源在 Aemeath。本站基于 CuteLeaf 的 Firefly 主题继续二次创作，也在这里向原作者致谢。&lt;/p&gt;
&lt;/div&gt;&lt;a href=&quot;https://github.com/Jarvis0227/Aemeath&quot; target=&quot;_blank&quot;&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;Jarvis0227&lt;/div&gt;&lt;/div&gt;&lt;div&gt;/&lt;/div&gt;&lt;div&gt;Aemeath&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;Waiting for api.github.com...&lt;/div&gt;&lt;div&gt;&lt;div&gt;00K&lt;/div&gt;&lt;div&gt;0K&lt;/div&gt;&lt;div&gt;0K&lt;/div&gt;&lt;span&gt;Waiting...&lt;/span&gt;&lt;/div&gt;&lt;/a&gt;&lt;a href=&quot;https://github.com/CuteLeaf/Firefly&quot; target=&quot;_blank&quot;&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;CuteLeaf&lt;/div&gt;&lt;/div&gt;&lt;div&gt;/&lt;/div&gt;&lt;div&gt;Firefly&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;Waiting for api.github.com...&lt;/div&gt;&lt;div&gt;&lt;div&gt;00K&lt;/div&gt;&lt;div&gt;0K&lt;/div&gt;&lt;div&gt;0K&lt;/div&gt;&lt;span&gt;Waiting...&lt;/span&gt;&lt;/div&gt;&lt;/a&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;a href=&quot;https://daily.yybb.us/&quot; target=&quot;_blank&quot;&gt;AIOVTUE-雪&lt;/a&gt;&lt;a href=&quot;#aiovtue-雪&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://r2tc.20030327.xyz/file/博客/主题/1780655293662_avatar_me.jpg.PNG&quot; alt=&quot;AIOVTUE-雪头像&quot; loading=&quot;lazy&quot; /&gt;&lt;/span&gt;&lt;/p&gt;&lt;div&gt;&lt;span&gt;本月推荐&lt;/span&gt;&lt;span&gt;博客主题&lt;/span&gt;&lt;span&gt;开源&lt;/span&gt;&lt;span&gt;教程&lt;/span&gt;&lt;/div&gt;&lt;p&gt;我的好朋友，也是我的第一个友链。站里很多小设计都是参考他做的，比如站点统计、友链，包括现在的月刊……这么一想，参考了不少呢。这个月从他那边点进来的访客也不少，友链真的在互相带人。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;a href=&quot;https://homulilly.com&quot; target=&quot;_blank&quot;&gt;Homulilly&lt;/a&gt;&lt;a href=&quot;#homulilly&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://homulilly.com/images/avatar.jpg&quot; alt=&quot;Homulilly头像&quot; loading=&quot;lazy&quot; /&gt;&lt;/span&gt;&lt;/p&gt;&lt;div&gt;&lt;span&gt;本月推荐&lt;/span&gt;&lt;span&gt;Hexo&lt;/span&gt;&lt;span&gt;博客主题&lt;/span&gt;&lt;span&gt;开源&lt;/span&gt;&lt;/div&gt;&lt;p&gt;他的站点真的好好看，我是真的喜欢。也是受他启发，我正在把源站那些卡片和标签改成我自己的设计，不想一直用原主题的样子。所以这期继续推荐他。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;a href=&quot;https://detached.online/&quot; target=&quot;_blank&quot;&gt;Detached&lt;/a&gt;&lt;a href=&quot;#detached&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://detached.online/icon-512.png&quot; alt=&quot;Detached头像&quot; loading=&quot;lazy&quot; /&gt;&lt;/span&gt;&lt;/p&gt;&lt;div&gt;&lt;span&gt;本月推荐&lt;/span&gt;&lt;span&gt;独立开发&lt;/span&gt;&lt;span&gt;技术工具&lt;/span&gt;&lt;span&gt;生活记录&lt;/span&gt;&lt;/div&gt;&lt;p&gt;也是我的好朋友。他的站也快得不行，羡慕坏了。这个月我抄了他的导航栏设计。他的风格切换也很神奇，我打算照着规划一下：做一个极简主题，再保留现在这种花哨一点的，还在规划中。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;a href=&quot;http://www.dreamcenter.top/&quot; target=&quot;_blank&quot;&gt;时光潜流&lt;/a&gt;&lt;a href=&quot;#时光潜流&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://www.dreamcenter.top/imgs/avatar.jpg&quot; alt=&quot;时光潜流头像&quot; loading=&quot;lazy&quot; /&gt;&lt;/span&gt;&lt;/p&gt;&lt;div&gt;&lt;span&gt;本月推荐&lt;/span&gt;&lt;span&gt;后端开发&lt;/span&gt;&lt;span&gt;运维&lt;/span&gt;&lt;span&gt;技术分享&lt;/span&gt;&lt;/div&gt;&lt;p&gt;我最喜欢的是他网站的流畅度，点哪儿都是秒开，像在本地运行一样。一个写后端和运维的人，把自己的站点调成这样，本身就是最好的作品展示。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;a href=&quot;https://lygalaxy.cn/&quot; target=&quot;_blank&quot;&gt;冷月笙寒的小窝&lt;/a&gt;&lt;a href=&quot;#冷月笙寒的小窝&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://lygalaxy.cn/uploads/20260128_200240_6979fae094c7b.jpg&quot; alt=&quot;冷月笙寒头像&quot; loading=&quot;lazy&quot; /&gt;&lt;/span&gt;&lt;/p&gt;&lt;div&gt;&lt;span&gt;本月推荐&lt;/span&gt;&lt;span&gt;个人博客&lt;/span&gt;&lt;span&gt;技术分享&lt;/span&gt;&lt;span&gt;生活记录&lt;/span&gt;&lt;/div&gt;&lt;p&gt;从 2019 年一直写到现在，这份坚持本身就让我佩服。站里除了文章，还有说说、相册、服务监控、建站日志，还有 SkyPanel、Bing 壁纸 API、AuroraPlayer 这些自己做的小项目，逛起来像在参观一个人慢慢攒起来的家。想先读一篇的话，可以看这个月的《完整扒站实践》。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;a href=&quot;https://www.qingwu.ink/&quot; target=&quot;_blank&quot;&gt;清吾&lt;/a&gt;&lt;a href=&quot;#清吾&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://www.qingwu.ink/images/avatar.jpg&quot; alt=&quot;清吾头像&quot; loading=&quot;lazy&quot; /&gt;&lt;/span&gt;&lt;/p&gt;&lt;div&gt;&lt;span&gt;本月推荐&lt;/span&gt;&lt;span&gt;个人博客&lt;/span&gt;&lt;span&gt;技术分享&lt;/span&gt;&lt;span&gt;生活记录&lt;/span&gt;&lt;/div&gt;&lt;p&gt;“一个温馨二次元的小屋”，这个描述挺准的。我喜欢他写文章的方式：不只放效果，还把怎么做的讲清楚，比如《用 Motion 做克制的动效：滚动揭示与滚动缩放》和《玻璃拟态卡片与聚光效果》。“克制”这两个字，正好也是我这个月撤掉视频壁纸之后最想学的。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;评选规则&lt;a href=&quot;#评选规则&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;和上期一样。这不是按权重排的榜单，也不是一次性打分。每期主要看五件事：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;有持续更新、能读到内容的站点；&lt;/li&gt;
&lt;li&gt;有自己的表达和长期打磨的痕迹；&lt;/li&gt;
&lt;li&gt;读完文章或逛完页面后，仍会想多停一会儿、再点开几篇的那种吸引力。&lt;/li&gt;
&lt;li&gt;界面符不符合Ai的审美&lt;/li&gt;
&lt;li&gt;Ai觉得你的文章写得不错&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;满足这些才会被Ai写进当期月刊；下期会重新观察，不把推荐当永久标签。&lt;/p&gt;&lt;p&gt;剩下就是我个人评选啦，私心肯定有但不多~&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;写在第二期月刊最后&lt;a href=&quot;#写在第二期月刊最后&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;第一次做一件事，靠的是新鲜感。第二次靠什么，我也说不太清楚。&lt;/p&gt;&lt;p&gt;写第一期的时候，我什么都想放进去。到了第二期，我开始知道哪些东西不用写，也知道月底会有这么一个晚上，要把这一个月认真翻一遍。&lt;/p&gt;&lt;p&gt;这个月我做得最多的，其实是“收”：撤掉视频壁纸，推荐名单里拿掉了一位，规划主题时想的也是先做一个极简版。第一期我总想证明自己能做很多东西，到了第二期，我更在意留下来的东西是不是真的好用、好读。&lt;/p&gt;&lt;p&gt;坚持到第二次，没有第一次那么兴奋，但更踏实一点。我知道这件事能继续下去了。&lt;/p&gt;&lt;p&gt;下个月月底，再见。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>跨境电商 Agents：从 AI 选品到自动经营</title><link>https://rainzt.cn/posts/ai-ecommerce-agents/</link><guid isPermaLink="true">https://rainzt.cn/posts/ai-ecommerce-agents/</guid><description>从 Ai-Jev 的决策能力出发，拆解我正在搭建的跨境电商 Agents：AI 选品、竞品研究、本地化上架、自动生图、广告投放与异常处理。</description><pubDate>Mon, 21 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最近出来了一个新的模型决策 AI——Ai-Jev。它有意思的地方，不只是“能不能把问题回答得更好”，而是它开始让我重新思考：如果让 AI 长期观察数据、判断下一步该做什么，再调用不同的工具和 Agents 去执行，跨境电商是不是可以从一堆零散的自动化脚本，变成一套真正会自己运转的系统？&lt;/p&gt;
&lt;p&gt;我想到的不是再做一个“自动上架工具”，而是搭建一套跨境电商 Agents：选品、采集、竞品研究、标题和详情页、本地化运营、生图、质检、上架、广告投放、客服和数据复盘，尽可能都接入同一个任务系统。&lt;/p&gt;
&lt;p&gt;目前这还只是一个进行中的实践，完成度大概在 50% 左右。但已经足够让我看到一个方向：未来的跨境电商，可能不是一个人管理一家店，而是一个人设定目标和边界，再让一组 Agents 持续做研究、执行和试错。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;1. 为什么我会想到 OPC + AIGC + Agents&lt;a href=&quot;#1-为什么我会想到-opc--aigc--agents&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;传统 OPC，也就是一个人或一个很小的团队运营一家跨境店铺，最大的限制并不是不会做，而是精力很难同时覆盖所有环节。&lt;/p&gt;&lt;p&gt;一个人要找产品、看竞品、算利润、联系供应链、做图片、写标题、上架、投广告、看数据、处理客服和售后。每件事单独看都不复杂，但它们会在一天里不断切换。广告需要盯，商品需要更新，平台规则也可能随时变化，最后往往是最重要的判断没有时间做，时间都消耗在重复操作上。&lt;/p&gt;&lt;p&gt;传统自动化可以解决一部分重复点击，却很难处理开放环境里的变化：页面改版了怎么办？竞品标题变了怎么办？一张图虽然生成成功，但主产品被遮挡了怎么办？广告数据不够好，到底应该改素材、改价格，还是直接停止测试？&lt;/p&gt;&lt;p&gt;单独使用大模型也有另一个问题：它可以帮我分析一次、生成一张图、写一段文案，但不会天然地记住整个店铺的状态，更不会自动完成后续执行和验证。&lt;/p&gt;&lt;p&gt;所以我更想做的是三层组合：&lt;/p&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层级&lt;/th&gt;&lt;th&gt;作用&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;数据和工具&lt;/td&gt;&lt;td&gt;采集平台前台数据、商品素材、店铺指标、广告数据和供应链信息&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Agents&lt;/td&gt;&lt;td&gt;分别执行选品、文案、生图、上架、投放、客服等具体任务&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Ai-Jev 决策层&lt;/td&gt;&lt;td&gt;判断现在要不要调用 Agent、调用哪个 Agent、是否继续、是否重试或停止&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;简单说，Agent 负责做事，Ai-Jev 负责决定下一步做什么，数据再把结果反馈回来。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;数据与信号 → Ai-Jev 判断 → 调用 Agent 执行 → 质量检查 → 结果回流 → 下一轮决策&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;这比“让一个模型包办所有事情”更接近真实业务，也更容易逐步替换和扩展。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;2. AI 上架与本地化运营：不只是把商品翻译过去&lt;a href=&quot;#2-ai-上架与本地化运营不只是把商品翻译过去&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;自动上架只是最容易被看见的一层。真正有价值的是，Agent 可以把一个商品从“原始素材”转换成“适合某个市场、某个平台、某家店铺的商品页面”。&lt;/p&gt;&lt;p&gt;一条比较完整的链路可以是：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;从供应商、平台前台或已有商品库采集商品信息；&lt;/li&gt;
&lt;li&gt;清理厂家信息、重复描述和不适合目标市场的表达；&lt;/li&gt;
&lt;li&gt;判断目标平台类目、属性、规格和变体结构；&lt;/li&gt;
&lt;li&gt;参考当地竞品标题、卖点、价格区间和图片结构；&lt;/li&gt;
&lt;li&gt;生成目标语言的标题、五点描述、详情文案和搜索词；&lt;/li&gt;
&lt;li&gt;根据利润、运费、平台佣金和活动规则计算价格；&lt;/li&gt;
&lt;li&gt;生成或整理图片，再做尺寸、主体、文字和合规检查；&lt;/li&gt;
&lt;li&gt;最后才调用平台或 ERP 的上架 Agent 执行发布。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;本地化也不应该只是逐句翻译。不同市场对卖点的关注点、表达习惯、尺寸单位、场景和审美都不一样。一个在中国电商页面里成立的标题，直接翻译之后可能既不自然，也没有覆盖当地用户真正搜索的词。&lt;/p&gt;&lt;p&gt;我希望 Agent 在上架前先去目标地区的平台前台看一遍：这个类目现在的主流标题怎么写，竞品图片怎么排，消费者评论里反复抱怨什么，再结合自己的产品条件生成更适合的版本。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;AI 对目标市场和竞品数据的概览分析&quot; loading=&quot;lazy&quot; width=&quot;1694&quot; height=&quot;1074&quot; src=&quot;/_astro/market-overview-detail-hd.CkJBnipA_vPBc3.webp&quot; srcset=&quot;/_astro/market-overview-detail-hd.CkJBnipA_1KgTP7.webp 640w, /_astro/market-overview-detail-hd.CkJBnipA_Z1We8Fn.webp 750w, /_astro/market-overview-detail-hd.CkJBnipA_Z1ePIn9.webp 828w, /_astro/market-overview-detail-hd.CkJBnipA_1qghss.webp 1080w, /_astro/market-overview-detail-hd.CkJBnipA_Z2awYb1.webp 1280w, /_astro/market-overview-detail-hd.CkJBnipA_1Fhwt3.webp 1668w, /_astro/market-overview-detail-hd.CkJBnipA_vPBc3.webp 1694w&quot; /&gt;&lt;figcaption&gt;AI 对目标市场和竞品数据的概览分析&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;这类分析可以作为本地化上架前的输入，让标题、卖点、图片和价格不再完全凭经验决定。&lt;/em&gt;&lt;/p&gt;&lt;p&gt;真正执行上架时，系统仍然需要任务队列、状态记录和异常队列。商品缺少必填属性、图片尺寸不对、类目无法判断、品牌信息存在风险时，不能为了追求“全自动”而强行发布，而应该停在具体步骤，让人补充信息或让另一个 Agent 重新处理。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;3. AI 选品：从“我觉得会卖”变成可验证的假设&lt;a href=&quot;#3-ai-选品从我觉得会卖变成可验证的假设&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我理解的 AI 选品，不是让模型直接给我一个“爆款名单”，而是让它持续收集信号，帮我把一个模糊的想法拆成可以验证的假设。&lt;/p&gt;&lt;p&gt;它可以观察：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;搜索词和类目的增长趋势；&lt;/li&gt;
&lt;li&gt;商品数量、价格区间和近 30 天销量；&lt;/li&gt;
&lt;li&gt;头部商品的销售集中度；&lt;/li&gt;
&lt;li&gt;竞品的标题、主图、详情结构和评论痛点；&lt;/li&gt;
&lt;li&gt;本地卖家和跨境卖家的比例；&lt;/li&gt;
&lt;li&gt;物流体积、退货风险、供应稳定性和毛利空间；&lt;/li&gt;
&lt;li&gt;是否存在专利、商标、外观或平台合规风险。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;例如下面这次平板保护套的分析，Agent 不是只说“这个品类不错”，而是先整理样本数量、价格、销量和热销商品，再给出具体的型号集中度和竞争判断。这样得到的结论，才有机会转成后续的上架和广告测试计划。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;AI 选品的市场样本与关键指标&quot; loading=&quot;lazy&quot; width=&quot;1830&quot; height=&quot;1040&quot; src=&quot;/_astro/market-findings-hd.B2v7juO5_Z18G5jo.webp&quot; srcset=&quot;/_astro/market-findings-hd.B2v7juO5_10yAO8.webp 640w, /_astro/market-findings-hd.B2v7juO5_Z11c3p2.webp 750w, /_astro/market-findings-hd.B2v7juO5_19XpzN.webp 828w, /_astro/market-findings-hd.B2v7juO5_Z1DqmfL.webp 1080w, /_astro/market-findings-hd.B2v7juO5_ZOCVjn.webp 1280w, /_astro/market-findings-hd.B2v7juO5_u9mGH.webp 1668w, /_astro/market-findings-hd.B2v7juO5_Z18G5jo.webp 1830w&quot; /&gt;&lt;figcaption&gt;AI 选品的市场样本与关键指标&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;AI 选品报告中的市场概览&quot; loading=&quot;lazy&quot; width=&quot;1694&quot; height=&quot;1074&quot; src=&quot;/_astro/market-overview-detail-hd.CkJBnipA_vPBc3.webp&quot; srcset=&quot;/_astro/market-overview-detail-hd.CkJBnipA_1KgTP7.webp 640w, /_astro/market-overview-detail-hd.CkJBnipA_Z1We8Fn.webp 750w, /_astro/market-overview-detail-hd.CkJBnipA_Z1ePIn9.webp 828w, /_astro/market-overview-detail-hd.CkJBnipA_1qghss.webp 1080w, /_astro/market-overview-detail-hd.CkJBnipA_Z2awYb1.webp 1280w, /_astro/market-overview-detail-hd.CkJBnipA_1Fhwt3.webp 1668w, /_astro/market-overview-detail-hd.CkJBnipA_vPBc3.webp 1694w&quot; /&gt;&lt;figcaption&gt;AI 选品报告中的市场概览&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;选品 Agent 的输出也不应该只有一个分数，更应该包含：为什么值得测试、最大的风险是什么、需要准备哪些素材、第一批要测几个价格、预算上限是多少、什么数据出现后应该停止。&lt;/p&gt;&lt;p&gt;最终的选品逻辑可以变成一个小实验：先选出若干候选产品，生成最低成本的商品页和素材，给它们一个可控预算，观察点击率、加购率、转化率、退款和利润，再决定要不要扩大。AI 可能会误判，但只要每一次判断都有记录、每一次尝试都有结果，系统就会慢慢积累出属于自己的经验。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;4. AI 生图：从一次生成，变成生成—识别—淘汰—重生成&lt;a href=&quot;#4-ai-生图从一次生成变成生成识别淘汰重生成&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;传统生图流程通常是：写提示词、生成一张图、觉得差不多就用了。问题在于，“生成成功”不等于“适合卖货”。主产品可能被杯子、花瓶或手部遮住，产品结构可能变形，颜色可能和实物不一致，画面里还可能出现不应该存在的配件。&lt;/p&gt;&lt;p&gt;我现在更关注的是让 Agent 参与图片质量闭环：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;根据商品本身和目标市场竞品，制定这组图的用途；&lt;/li&gt;
&lt;li&gt;调用生图工具生成主图、场景图、细节图和使用图；&lt;/li&gt;
&lt;li&gt;让视觉检查 Agent 判断主体是否完整、产品是否被遮挡、数量和颜色是否正确；&lt;/li&gt;
&lt;li&gt;不合格的图片进入淘汰队列，记录具体问题；&lt;/li&gt;
&lt;li&gt;只针对问题重新生成，而不是每次从头碰运气；&lt;/li&gt;
&lt;li&gt;通过后再按平台尺寸和详情页顺序整理上传。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;下面是一次实际遇到的问题。第一版详情图的场景氛围不错，但主产品被画面元素遮挡，作为商品展示图并不合格。Agent 识别到这个问题后，没有把它直接放进商品页，而是重新调整主体位置和展示方式，生成了可以继续进入候选池的版本。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;第一次生成的详情图：主产品被场景元素遮挡&quot; loading=&quot;lazy&quot; width=&quot;1254&quot; height=&quot;1254&quot; src=&quot;/_astro/image-generation-before.l6uYfR1T_Z1MzciE.webp&quot; srcset=&quot;/_astro/image-generation-before.l6uYfR1T_ZrTAYq.webp 640w, /_astro/image-generation-before.l6uYfR1T_ZawYAK.webp 750w, /_astro/image-generation-before.l6uYfR1T_1sQ2Cb.webp 828w, /_astro/image-generation-before.l6uYfR1T_1XqAiY.webp 1080w, /_astro/image-generation-before.l6uYfR1T_Z1MzciE.webp 1254w&quot; /&gt;&lt;figcaption&gt;第一次生成的详情图：主产品被场景元素遮挡&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;问题版本：画面有氛围，但没有优先保证商品主体清晰可见。&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;Agent 重新生成并整理后的图片候选&quot; loading=&quot;lazy&quot; width=&quot;1610&quot; height=&quot;412&quot; src=&quot;/_astro/image-generation-after-hd.ah93QCVI_Z2ifpRs.webp&quot; srcset=&quot;/_astro/image-generation-after-hd.ah93QCVI_Z1Nuqn.webp 640w, /_astro/image-generation-after-hd.ah93QCVI_1WsqAd.webp 750w, /_astro/image-generation-after-hd.ah93QCVI_ZhvvbX.webp 828w, /_astro/image-generation-after-hd.ah93QCVI_Z138Rq7.webp 1080w, /_astro/image-generation-after-hd.ah93QCVI_nvBqr.webp 1280w, /_astro/image-generation-after-hd.ah93QCVI_Z2ifpRs.webp 1610w&quot; /&gt;&lt;figcaption&gt;Agent 重新生成并整理后的图片候选&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;调整后：保留场景感，同时补充更清晰的产品展示和使用场景。&lt;/em&gt;&lt;/p&gt;&lt;p&gt;这也是我觉得 Agents 比单次调用生图 API 更有价值的地方。它不只是“会生成”，而是可以判断结果是否值得留下。未来还可以加入品牌色、产品结构比对、文字区域检测、平台图片规范检查，甚至让它根据广告数据判断哪一种视觉方向更值得继续测试。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;5. AI Agent 可以覆盖跨境电商的哪些环节&lt;a href=&quot;#5-ai-agent-可以覆盖跨境电商的哪些环节&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;如果把自动上架当成入口，后面可以继续扩展成一条完整的经营链路。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;AI 竞品研究与市场监控&lt;a href=&quot;#ai-竞品研究与市场监控&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Agent 可以定期访问目标平台的前台，记录竞品的标题、价格、销量、促销、评论和图片变化。当某个竞品突然降价、某个关键词开始出现大量新商品，或者某种素材结构明显变多时，系统可以把变化整理成提醒，而不是等人偶尔打开页面才发现。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;AI 标题、详情和本地化内容&lt;a href=&quot;#ai-标题详情和本地化内容&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;同一个产品可以根据国家、平台、流量入口生成不同版本。Agent 负责从竞品中提取表达方式，再结合自己的真实参数生成标题和详情，避免夸大功能、遗漏关键属性或直接复制竞品内容。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;AI 广告投放与素材实验&lt;a href=&quot;#ai-广告投放与素材实验&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;它可以结合曝光、点击、点击成本、加购、转化、利润和库存判断下一步动作：保留哪张图、暂停哪个广告组、调整哪个关键词、是否扩大预算、是否需要重新生成素材。&lt;/p&gt;&lt;p&gt;这里最重要的不是让 AI 每分钟都改一次广告，而是让它提出可解释的实验假设。例如“点击率不错但转化低，可能是主图承诺与详情页不一致”，然后只调整一个变量，用数据验证，而不是同时改价格、标题、图片和预算，最后什么也分析不出来。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;AI 客服、评论与售后&lt;a href=&quot;#ai-客服评论与售后&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Agent 可以把评论和客服对话按问题分类，提炼出高频疑问，再反过来优化详情页和 FAQ。常见问题可以自动回复，涉及退款、纠纷、敏感承诺或高风险客户时再交给人工处理。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;AI 定价、库存与供应链&lt;a href=&quot;#ai-定价库存与供应链&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;结合采购价、物流费、平台佣金、广告成本和退货率，系统可以持续计算真实利润，而不是只看销售额。库存不足时暂停投放，库存积压时设计折扣测试，供应商涨价时重新评估售价和广告上限。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;AI 合规与风险检查&lt;a href=&quot;#ai-合规与风险检查&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;发布前检查违禁词、品牌词、图片中的商标、虚假承诺、类目属性和当地法规要求。它不能替代专业法律意见，但可以把大量低级错误挡在发布前，减少因为疏忽造成的风险。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;6. 我设想的运行方式：固定脚本负责日常，Ai-Jev 负责决定何时调用 Agents&lt;a href=&quot;#6-我设想的运行方式固定脚本负责日常ai-jev-负责决定何时调用-agents&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我并不想把所有事情都交给一个模型实时处理。很多重复、稳定、规则清晰的工作，用固定脚本跑更可靠；只有遇到需要理解、比较和判断的场景，才让 Ai-Jev 调用对应的 Agent。&lt;/p&gt;&lt;p&gt;例如：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;日常同步库存、整理文件、检查任务状态，用固定脚本运行；&lt;/li&gt;
&lt;li&gt;发现某个市场的销量、价格或竞品结构出现变化，交给 Ai-Jev 判断是否启动选品 Agent；&lt;/li&gt;
&lt;li&gt;发现图片质量检测失败，调用生图和视觉审核 Agent；&lt;/li&gt;
&lt;li&gt;发现平台页面改版或任务异常，把截图、日志和当前步骤交给 GPT Luna 处理；&lt;/li&gt;
&lt;li&gt;广告数据达到预设条件后，生成一个新的素材或预算实验，而不是无条件改动账户。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这样做的好处是成本、速度和稳定性更可控。AI 不需要在每一个普通动作里重复思考，而是在出现变化或需要决策时介入。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;7. 从一家店开始，把成功逻辑复制到更多店铺&lt;a href=&quot;#7-从一家店开始把成功逻辑复制到更多店铺&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;传统 OPC 的问题之一，是一个人很难长期盯住很多店铺。但如果第一家店铺的流程被整理成数据、规则、素材模板、任务状态和复盘结果，后续就不必每次重新从零开始。&lt;/p&gt;&lt;p&gt;可以复制的不是某个店铺的具体商品，而是一套经过实践验证的逻辑：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;什么样的产品值得进入测试池；&lt;/li&gt;
&lt;li&gt;什么样的图片结构更适合这个市场；&lt;/li&gt;
&lt;li&gt;标题和详情页如何本地化；&lt;/li&gt;
&lt;li&gt;什么指标出现后开始加预算；&lt;/li&gt;
&lt;li&gt;什么异常必须人工确认；&lt;/li&gt;
&lt;li&gt;什么情况下应该停止一个产品。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;复制到新店铺或新市场时，Agents 可以重新读取当地数据，调整语言、价格、素材和投放策略。这样是“复制方法”，不是把同一份内容机械铺到所有店铺。&lt;/p&gt;&lt;p&gt;更进一步，系统还可以自己提出新的尝试：新的标题结构、不同的主图风格、不同的价格区间、不同的广告受众。人负责设定边界和预算，AI 负责提出更多低成本实验。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;8. 这件事真正难的地方，不是调用模型&lt;a href=&quot;#8-这件事真正难的地方不是调用模型&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;现在回头看，接入一个模型、调用一个生图工具、写一个浏览器 Worker，都不是最难的部分。真正困难的是把业务目标变成可判断的规则。&lt;/p&gt;&lt;p&gt;比如：什么叫“图片合格”？什么叫“产品值得继续投”？什么情况下可以自动发布？什么情况下必须停下来问人？如果这些问题没有定义清楚，Agents 只会更快地制造更多不确定结果。&lt;/p&gt;&lt;p&gt;所以我会把下面几件事放在系统底座里：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;每个任务都有唯一 ID、当前步骤和可追溯日志；&lt;/li&gt;
&lt;li&gt;发布、扣库存、改预算等有副作用的动作必须做幂等检查；&lt;/li&gt;
&lt;li&gt;不确定的结果进入异常队列，不允许无限重试；&lt;/li&gt;
&lt;li&gt;重要操作保留截图、输入、输出和决策原因；&lt;/li&gt;
&lt;li&gt;不同店铺、国家和平台使用独立的权限、规则和素材；&lt;/li&gt;
&lt;li&gt;AI 的结论必须能回到数据，而不是只给一个看起来很确定的答案。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;AI 也可能误判，广告数据也可能因为样本太少而产生错觉，竞品的动作更不代表自己的产品一定适合照搬。我的目标不是让系统永远正确，而是让它可以低成本地尝试、及时发现错误、快速停止，并且从结果里积累经验。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;9. 现在做到哪一步了&lt;a href=&quot;#9-现在做到哪一步了&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;目前这套跨境电商 Agents 还没有完全成型，大概完成了 50%。已经能够看到的部分包括：采集和分析数据、根据竞品辅助选品、生成本地化内容、调用生图、对生成结果做检查，以及把任务继续交给上架流程。&lt;/p&gt;&lt;p&gt;接下来还需要继续解决：多店铺状态管理、广告数据的长期记忆、不同平台的权限和风控、素材与真实商品的一致性、实验结果归因，以及在自动化失败时如何更准确地把问题交给合适的人或 Agent。&lt;/p&gt;&lt;p&gt;但我觉得它不需要等到 100% 完美才有价值。只要它能把一次选品、一次上架、一次生图或一次广告实验闭环跑通，就可以从一个真实店铺开始积累数据。跑得越多，规则越清楚，后面的店铺就越容易复用。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;最后：让人从重复操作里出来，把时间留给判断&lt;a href=&quot;#最后让人从重复操作里出来把时间留给判断&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我并不认为未来的跨境电商会变成“完全不需要人”。相反，人的工作会从每天盯着页面、搬运数据、修改几十个标题，转向设定目标、约束预算、判断方向和处理真正复杂的异常。&lt;/p&gt;&lt;p&gt;Ai-Jev 负责做更长周期的判断，Agents 负责调用工具和完成任务，固定脚本负责稳定的日常流程，数据负责告诉我们结果到底有没有变好。这个组合也许还不成熟，甚至会误判，但它至少提供了一种新的工作方式：一次只经营一家店铺的人，可以开始尝试管理一套能够持续学习和复制的经营系统。&lt;/p&gt;&lt;p&gt;这就是我现在想做的跨境电商 Agents。它不是一个“自动上架按钮”，而是一套从 AI 选品开始，经过本地化内容、生图质检、自动执行、广告实验和数据复盘，逐步走向自动经营的系统。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>Codex 为什么会“降智”：292 State、状态注入与长任务的另一条解释</title><link>https://rainzt.cn/posts/codex-292-state-analysis/</link><guid isPermaLink="true">https://rainzt.cn/posts/codex-292-state-analysis/</guid><description>从 HTTP 292、current_turn_state 和 x-codex-turn-state 出发，拆解 Codex 的资源路由、美国本地与中转/VPN的服务质量分层、状态注入、上下文增长、压缩与长任务稳定性。</description><pubDate>Fri, 18 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最近使用 Codex 处理一些时间较长的任务时，我反复遇到几个很难解释的现象。&lt;/p&gt;
&lt;p&gt;同一个账号、相同的模型和相近的提示词，有时可以连续完成多文件重构；换一个时间段之后，却开始忘记上下文、重复已经解决的问题，甚至给出像是低配模型生成的答案。&lt;/p&gt;
&lt;p&gt;Codex 的 cloud agent 也会频繁出现 overloaded，请求还可能返回 429。&lt;/p&gt;
&lt;p&gt;换一个会话、换一个网络出口，或者等一段时间之后，状态又似乎恢复了。&lt;/p&gt;
&lt;p&gt;这到底是模型本身发生了变化，还是服务端的调度状态发生了变化？&lt;/p&gt;
&lt;p&gt;我现在更认可后一种解释。&lt;/p&gt;
&lt;p&gt;Codex 背后存在一套和资源路由、请求状态、上下文压缩以及长任务连续性有关的机制。我们看到的“降智”，往往不是单纯的模型能力变化，而是多个状态同时变化后的结果。&lt;/p&gt;
&lt;p&gt;其中最值得关注的，就是 HTTP 292 和 &lt;code&gt;current_turn_state&lt;/code&gt;。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;先说结论&lt;a href=&quot;#先说结论&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这件事可以拆成两条同时运行的链路。&lt;/p&gt;&lt;p&gt;第一条是资源链路：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;请求&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;服务端调度&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;HTTP 292 / current_turn_state&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;资源等级与后端路由&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;后续请求继续携带 state&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;第二条是任务链路：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;对话与工具调用&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;上下文不断增长&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;达到阈值后压缩&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;部分细节被重新概括&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;长任务表现发生变化&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;292 State 主要解释第一条链路。&lt;/p&gt;&lt;p&gt;上下文增长和 compaction 主要解释第二条链路。&lt;/p&gt;&lt;p&gt;两者叠加之后，就会出现一种非常真实的用户体验：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;任务刚开始时模型很聪明，运行一段时间后，它像是换了一个模型。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;这里先把资料分成三种：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;公开源码已经能确认的部分&lt;/strong&gt;：Codex 存在 &lt;code&gt;x-codex-turn-state&lt;/code&gt;，并把它用于同一轮请求的粘性路由。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;公开讨论和实际观察中的部分&lt;/strong&gt;：292、&lt;code&gt;current_turn_state&lt;/code&gt;、约一小时 TTL、IP 不绑定以及资源等级之间的关系。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;本文自己的代码&lt;/strong&gt;：保留状态摘要记录、续期逻辑和路由结构示意，用来还原这套调度流程。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这三类证据不能混在一起，但它们可以拼出一套比较完整的解释。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;292 到底是什么&lt;a href=&quot;#292-到底是什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;标准 HTTP 规范里没有 292 这个状态码。IANA 的标准登记表中也没有记录它。&lt;a href=&quot;https://www.iana.org/assignments/http-status-codes&quot; target=&quot;_blank&quot;&gt;IANA HTTP Status Code Registry&lt;/a&gt;&lt;/p&gt;&lt;p&gt;但这并不代表服务内部不能使用私有状态码。&lt;/p&gt;&lt;p&gt;大型平台通常会在标准 HTTP 状态之外，通过响应头、内部元数据或自定义状态表示更多调度信息。&lt;/p&gt;&lt;p&gt;292 就是这样一种内部信号。&lt;/p&gt;&lt;p&gt;目前比较合理的解释是：当请求到达服务端之后，调度器会先判断当前请求应该进入什么资源路径，包括：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;路由到哪个模型实例。&lt;/li&gt;
&lt;li&gt;使用什么推理质量。&lt;/li&gt;
&lt;li&gt;分配多少计算资源。&lt;/li&gt;
&lt;li&gt;是否允许完整上下文。&lt;/li&gt;
&lt;li&gt;是否进入备用或降级队列。&lt;/li&gt;
&lt;li&gt;后续请求是否可以继续复用当前调度结果。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;调度正常时，服务端返回 292，并附带一个 &lt;code&gt;current_turn_state&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;当前资源紧张时，则返回普通的 200，但请求实际进入资源受限或降级路径。&lt;/p&gt;&lt;p&gt;可以把它简单理解成：&lt;/p&gt;





























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;状态&lt;/th&gt;&lt;th&gt;含义&lt;/th&gt;&lt;th&gt;用户表现&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;292&lt;/td&gt;&lt;td&gt;调度正常、资源路径稳定&lt;/td&gt;&lt;td&gt;速度快，推理完整，较少出现 overload&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;200&lt;/td&gt;&lt;td&gt;HTTP 请求成功，但不代表资源等级相同&lt;/td&gt;&lt;td&gt;能返回结果，但可能变慢、变浅或更容易丢上下文&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;429&lt;/td&gt;&lt;td&gt;请求过快或触发限流&lt;/td&gt;&lt;td&gt;请求失败，需要退避&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;503&lt;/td&gt;&lt;td&gt;服务暂时过载&lt;/td&gt;&lt;td&gt;请求失败或排队&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;这里需要强调，292 和 200 的对应关系目前属于逆向观察，不是 OpenAI 公开文档确认的协议。&lt;/p&gt;&lt;p&gt;但这个解释能够很好地解释一种现象：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;请求明明成功返回了，为什么模型的质量还是明显下降了？&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;因为 HTTP 成功和获得相同的计算资源，并不是一回事。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;&lt;code&gt;current_turn_state&lt;/code&gt; 是什么&lt;a href=&quot;#current_turn_state-是什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;公开的 Codex 源码中，可以看到一个真实存在的状态：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;x-codex-turn-state&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;官方客户端把它描述为 sticky-routing token，也就是粘性路由状态。&lt;a href=&quot;https://github.com/openai/codex/blob/main/codex-rs/core/src/client.rs&quot; target=&quot;_blank&quot;&gt;Codex 官方 client.rs&lt;/a&gt;&lt;/p&gt;&lt;p&gt;它的基本流程是：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;一轮 turn 开始。&lt;/li&gt;
&lt;li&gt;服务器返回 turn state。&lt;/li&gt;
&lt;li&gt;客户端保存这个状态。&lt;/li&gt;
&lt;li&gt;同一轮中的后续请求继续携带它。&lt;/li&gt;
&lt;li&gt;任务进入新的 turn 后，应该创建新的 session。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;SSE、WebSocket 和 compact 请求也会处理这个状态。&lt;a href=&quot;https://github.com/openai/codex/blob/main/codex-rs/codex-api/src/sse/responses.rs&quot; target=&quot;_blank&quot;&gt;SSE 响应处理&lt;/a&gt; &lt;a href=&quot;https://github.com/openai/codex/blob/main/codex-rs/codex-api/src/endpoint/responses_websocket.rs&quot; target=&quot;_blank&quot;&gt;WebSocket 响应处理&lt;/a&gt;&lt;/p&gt;&lt;p&gt;这至少证明了一个事实：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Codex 的请求并不是完全独立的，服务端确实会使用某种状态维持同一轮任务的连续路由。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;公开源码使用的是 &lt;code&gt;x-codex-turn-state&lt;/code&gt;，而一些逆向观察把更上层的状态称为 &lt;code&gt;current_turn_state&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;我更倾向于把它们理解成同一套调度机制的不同表现：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;HTTP 292 更像服务端给出的资源调度结果。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;current_turn_state&lt;/code&gt; 更像资源调度凭据。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;x-codex-turn-state&lt;/code&gt; 更像客户端继续发送的 turn 粘性状态。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;它们属于同一个服务端状态系统的不同层级。&lt;/p&gt;&lt;p&gt;官方源码把 &lt;code&gt;x-codex-turn-state&lt;/code&gt; 放在 turn 范围内，说明客户端看到的 header 负责维持当前 turn 的粘性路由；292 和 &lt;code&gt;current_turn_state&lt;/code&gt; 则是更上层的资源状态。它们不需要是同一个字段，但共同指向同一个结论：Codex 并不是无状态地处理每一个请求。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;为什么不是所有人都能拿到 292&lt;a href=&quot;#为什么不是所有人都能拿到-292&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;如果 292 代表一次调度成功，那么它就不应该对所有请求无差别发放。&lt;/p&gt;&lt;p&gt;从实际观察来看，影响 292 签发的因素主要有四类。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;IP 质量&lt;a href=&quot;#ip-质量&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;不同网络出口具有不同的风控和资源评分。&lt;/p&gt;&lt;p&gt;但如果只把 292 理解成“当前还有算力”，仍然漏掉了更核心的一层：它是服务端在判断请求来源之后，为高可信用户保留完整服务路径的调度标记。&lt;/p&gt;&lt;p&gt;这套分层首先照顾的是美国本地的真实用户。这里的“本地”不是简单读取一个国家字段，而是综合网络出口、ASN、地理位置、IP 信誉、账号历史和请求行为等信号。稳定的美国住宅宽带，更像一个真实家庭用户的固定网络；中转站、共享代理、VPN、VPS 和数据中心出口，则更容易呈现出多人共用、地域与账号不一致、请求集中或出口频繁变化等特征。&lt;/p&gt;&lt;p&gt;服务端没有必要把这些流量全部拒绝。更现实的做法是，在资源紧张时先保护本地住宅用户的体验，再把中转、共享 VPN 和数据中心流量放进普通或降级路径。后者仍然可以拿到响应，但更容易遇到等待、overload、429、503，以及长任务连续性变差。&lt;/p&gt;&lt;p&gt;所以，292 的核心并不是“美国 IP 有一个特殊开关”，而是服务端把不同来源的请求放进了不同的服务质量等级：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;flowchart TD
    A[请求进入] --&amp;gt; B[IP / ASN / 地理位置 / 账号 / 行为]
    B --&amp;gt; C{服务质量分层}
    C --&amp;gt;|美国住宅、稳定低风险| D[完整资源路径]
    C --&amp;gt;|中转、共享 VPN、数据中心| E[普通或降级路径]
    D --&amp;gt; F[292 + state + 更稳定的长任务]
    E --&amp;gt; G[200 / 429 / 503 / overload]&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这也解释了为什么同一个账号会出现明显的网络差异：变化的未必是账号权限，而是请求被服务端放进了哪一层。&lt;/p&gt;&lt;p&gt;可以把实验中的 IP 类型粗略分成：&lt;/p&gt;


































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;IP 类型&lt;/th&gt;&lt;th&gt;观察到的 292 通过率&lt;/th&gt;&lt;th&gt;说明&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;美国住宅宽带&lt;/td&gt;&lt;td&gt;较高&lt;/td&gt;&lt;td&gt;真实 ISP 住宅出口&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;原生 IPv6&lt;/td&gt;&lt;td&gt;较高&lt;/td&gt;&lt;td&gt;某些地址可能被视为更自然的用户网络&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;欧洲或亚洲住宅&lt;/td&gt;&lt;td&gt;中等&lt;/td&gt;&lt;td&gt;仍可能获得正常状态，但概率不稳定&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;数据中心 IP&lt;/td&gt;&lt;td&gt;较低&lt;/td&gt;&lt;td&gt;VPS、云服务器地址段容易被集中标记&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;共享 VPN 或公共代理&lt;/td&gt;&lt;td&gt;很低&lt;/td&gt;&lt;td&gt;同一个出口可能被大量用户使用&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;这张表不是固定不变的官方等级表，而是实际实验中最值得优先比较的网络分层。&lt;/p&gt;&lt;p&gt;如果同一个账号通过美国住宅网络容易拿到 292，通过数据中心或共享代理更容易进入普通 200，那么就说明 IP 质量已经参与了调度。更准确地说，服务端正在用网络来源判断“这个请求应该获得什么级别的服务”。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;为什么中转和 VPN 更容易降级&lt;a href=&quot;#为什么中转和-vpn-更容易降级&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;中转、VPN 和数据中心并不等于恶意请求，但从服务端的角度看，它们和共享滥用流量使用的是相似的网络信号：一个出口承载大量账号，账号所在地与 IP 所在地不一致，请求在短时间内集中出现，或者出口属于云厂商和代理服务商的地址段。&lt;/p&gt;&lt;p&gt;在高峰期，服务端最容易做的不是逐个判断“这个人是不是好用户”，而是先按网络来源做粗粒度分层。美国本地住宅出口更容易进入高优先级路径；中转站、共享 VPN 和数据中心出口则更容易保留基本可用性，但牺牲一部分速度、推理资源和长任务稳定性。&lt;/p&gt;&lt;p&gt;这就是“给当地居民更好的体验，给中转用户降级使用”的技术含义。它不是把某一类用户永久封死，而是在有限资源下把完整体验优先留给最像本地真实用户的请求。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;账号等级&lt;a href=&quot;#账号等级&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;不同订阅等级具有不同的资源优先级。&lt;/p&gt;&lt;p&gt;需要比较的变量包括：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;Free。&lt;/li&gt;
&lt;li&gt;Plus。&lt;/li&gt;
&lt;li&gt;Pro。&lt;/li&gt;
&lt;li&gt;更高等级的 Pro 计划。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;不能只拿一个账号测试一次就得出结论。更可靠的方式是使用相同模型、相同提示词和相近时间重复测试。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;服务端负载&lt;a href=&quot;#服务端负载&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;即使账号和 IP 都没有变化，服务端高峰期也可能收紧 292 的签发。&lt;/p&gt;&lt;p&gt;这可以解释为什么：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;早上状态很好，下午开始变差。&lt;/li&gt;
&lt;li&gt;工作日和周末体验不同。&lt;/li&gt;
&lt;li&gt;同一个会话上午正常，晚上频繁 overload。&lt;/li&gt;
&lt;li&gt;等几个小时后，模型又恢复了。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这不一定是用户发生了变化，也可能只是服务端整体负载发生了变化。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;State 的有效期&lt;a href=&quot;#state-的有效期&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;current_turn_state&lt;/code&gt; 的 TTL 约为一小时。&lt;/p&gt;&lt;p&gt;也就是说：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;第一次请求成功获得 292。&lt;/li&gt;
&lt;li&gt;服务端签发一个 state。&lt;/li&gt;
&lt;li&gt;接下来一段时间内可以继续复用。&lt;/li&gt;
&lt;li&gt;超过有效期之后，state 失效。&lt;/li&gt;
&lt;li&gt;下一次请求需要重新进行调度。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;这可以解释“刚刚还很好，过一会儿突然变差”的体验。&lt;/p&gt;&lt;p&gt;当然，TTL 不能只凭一次观察确定，必须通过连续重放实验测量。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;验证不能只靠感觉&lt;a href=&quot;#验证不能只靠感觉&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;如果只是在浏览器里感觉“今天好像聪明一点”，很难证明 292 State 的作用。&lt;/p&gt;&lt;p&gt;接下来需要测量四个变量：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;292 是否和 state 一起出现。&lt;/li&gt;
&lt;li&gt;携带有效 state 是否真的改变后续结果。&lt;/li&gt;
&lt;li&gt;state 是否和 IP 绑定。&lt;/li&gt;
&lt;li&gt;state 的 TTL 大约是多少。&lt;/li&gt;
&lt;/ol&gt;&lt;section&gt;&lt;h3&gt;第一组：观察 292 和 state 是否同时出现&lt;a href=&quot;#第一组观察-292-和-state-是否同时出现&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;每次请求至少记录：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;timestamp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;network_profile&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;account_level&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;model&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;http_status&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;state_present&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;state_hash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;latency_ms&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;retry_after&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;overloaded&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;记录时可以用 state 的哈希作为标识。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;__future__&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; annotations&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; hashlib&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; typing &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; Mapping&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;summarize_response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;headers&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Mapping[&lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;latency_ms&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;) -&amp;gt; &lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;state &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;current_turn_state&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;or&lt;/span&gt;&lt;span&gt;&lt;span&gt; headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;x-codex-turn-state&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;status&quot;&lt;/span&gt;&lt;span&gt;: status,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;state_present&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt;(state),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;state_hash&quot;&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;hashlib.&lt;/span&gt;&lt;span&gt;sha256&lt;/span&gt;&lt;span&gt;(state.&lt;/span&gt;&lt;span&gt;encode&lt;/span&gt;&lt;span&gt;()).&lt;/span&gt;&lt;span&gt;hexdigest&lt;/span&gt;&lt;span&gt;()[:&lt;/span&gt;&lt;span&gt;12&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; state&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;None&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;latency_ms&quot;&lt;/span&gt;&lt;span&gt;: latency_ms,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;retry_after&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;: headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;retry-after&quot;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;server_model&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;: headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;openai-model&quot;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这段代码只记录状态是否存在，不保存完整 token。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;第二组：有状态和无状态对照&lt;a href=&quot;#第二组有状态和无状态对照&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;至少准备三组请求：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;A 组：普通线路，不携带 state&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;B 组：普通线路，携带有效 state&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;C 组：普通线路，携带过期或随机 state&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;三组请求应尽量保持：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;相同账号。&lt;/li&gt;
&lt;li&gt;相同模型。&lt;/li&gt;
&lt;li&gt;相同提示词。&lt;/li&gt;
&lt;li&gt;相近时间。&lt;/li&gt;
&lt;li&gt;相同请求间隔。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;需要比较的指标包括：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;292 出现频率。&lt;/li&gt;
&lt;li&gt;429 出现频率。&lt;/li&gt;
&lt;li&gt;overloaded 出现频率。&lt;/li&gt;
&lt;li&gt;首字节延迟。&lt;/li&gt;
&lt;li&gt;输出长度。&lt;/li&gt;
&lt;li&gt;工具调用是否中断。&lt;/li&gt;
&lt;li&gt;上下文是否连续。&lt;/li&gt;
&lt;li&gt;人工盲评结果。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;如果只有 B 组长期表现更好，而 A、C 组明显更差，就可以量化 state 的作用。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;第三组：验证是否绑定 IP&lt;a href=&quot;#第三组验证是否绑定-ip&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这是整个理论最关键的验证。&lt;/p&gt;&lt;p&gt;流程应该是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;住宅 IP 获取 292 + state&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;切换到数据中心或 IPLC&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;不携带 state 请求一次&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;携带 state 请求一次&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;比较两次结果&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;如果不携带 state 的请求明显更容易降级，而携带同一个 state 的请求仍保持正常，说明 state 的使用和 IP 是分离的。&lt;/p&gt;&lt;p&gt;这个实验需要反复执行，用来排除服务端负载波动。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;第四组：测量 TTL&lt;a href=&quot;#第四组测量-ttl&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;获得 state 后，按固定间隔重试：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;0 分钟&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;10 分钟&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;20 分钟&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;30 分钟&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;45 分钟&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;55 分钟&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;60 分钟&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;70 分钟&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;90 分钟&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;每次记录：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;是否仍被接受。&lt;/li&gt;
&lt;li&gt;是否仍保持相同路由结果。&lt;/li&gt;
&lt;li&gt;是否返回新的 292。&lt;/li&gt;
&lt;li&gt;是否需要重新获取 state。&lt;/li&gt;
&lt;li&gt;是否出现 429 或 503。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;如果状态在接近一小时后稳定失效，才能说明“一小时 TTL”具有一定可信度。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;状态获取和状态使用可以分开&lt;a href=&quot;#状态获取和状态使用可以分开&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;状态注入的核心思路是：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;获取 state 的网络环境，和使用 state 的网络环境，不一定需要相同。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;大致流程如下：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;获取阶段：&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Clash 切到住宅或原生 IPv6&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;keeper 发送探测请求&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;服务端返回 292&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;提取 current_turn_state&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;保存状态和过期时间&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Clash 切回日常线路&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;使用阶段：&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Codex / ChatGPT 发出请求&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;本地代理读取 state 文件&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;在请求中加入状态 header&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;转发到服务端&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;服务端验证 state 是否有效&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这套设计的关键假设是：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;state 不和签发时的 IP 强绑定。&lt;/li&gt;
&lt;li&gt;state 代表的是调度结果，而不是 IP 身份。&lt;/li&gt;
&lt;li&gt;state 在 TTL 内可以跨网络出口使用。&lt;/li&gt;
&lt;li&gt;state 与实际使用的模型保持一致时效果最好。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;如果这些假设成立，住宅 IP 只需要在获取阶段出现几秒钟，日常使用仍然可以走低延迟线路。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;State 的真正作用：保留已经拿到的服务等级&lt;a href=&quot;#state-的真正作用保留已经拿到的服务等级&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;如果 292 只是“当前还有算力”，拿到一个 state 本身没有太大意义。状态注入之所以有效，是因为它把第一次请求时完成的服务质量判断延续到了后续请求。&lt;/p&gt;&lt;p&gt;获取阶段在美国住宅或其他高可信网络上拿到 292，等于先完成了一次更优先的资源调度。使用阶段即使切回中转、VPN 或日常线路，带上有效 state 后，服务端仍然可以把请求识别成已经建立的稳定 turn，而不是每次都根据当前出口重新从普通队列开始排队。&lt;/p&gt;&lt;p&gt;所以，state 的作用不是把数据中心 IP 变成住宅 IP，也不是改变账号的地区身份；它就是一张暂时有效的“服务等级通行证”，把之前已经拿到的资源路径延续一段时间。只要 state 仍在有效窗口内，后续请求就会继续沿用原来的调度结果；一旦过期、模型不匹配或服务端重新做风险判断，请求就会回到当前网络对应的普通路径。&lt;/p&gt;&lt;p&gt;这也解释了为什么“先住宅获取，再日常线路使用”比单纯切换 IP 更有意义：前者是在复用一次已经完成的调度，后者只是重新碰运气。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;续期逻辑&lt;a href=&quot;#续期逻辑&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;状态不能等到完全过期之后再续。&lt;/p&gt;&lt;p&gt;更合理的方式是提前五分钟续期，并且每 45 秒检查一次。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;__future__&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; annotations&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; time&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; pathlib &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; Path&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;STATE_FILE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Path&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;current_turn_state.json&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;RENEW_BEFORE_SECONDS&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;60&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;load_state&lt;/span&gt;&lt;span&gt;() -&amp;gt; &lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;None&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;not&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;STATE_FILE&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;exists&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;None&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; json.&lt;/span&gt;&lt;span&gt;loads&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;STATE_FILE&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;read_text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;encoding&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;should_renew&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;None&lt;/span&gt;&lt;/span&gt;&lt;span&gt;) -&amp;gt; &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;not&lt;/span&gt;&lt;span&gt; state:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;expires_at &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;(state[&lt;/span&gt;&lt;span&gt;&quot;expires_at&quot;&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; expires_at &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; time.&lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;RENEW_BEFORE_SECONDS&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;save_state&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ttl_seconds&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;) -&amp;gt; &lt;/span&gt;&lt;span&gt;None&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;payload &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;value&quot;&lt;/span&gt;&lt;span&gt;: value,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;expires_at&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;: time.&lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; ttl_seconds,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;STATE_FILE&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;write_text&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;json.&lt;/span&gt;&lt;span&gt;dumps&lt;/span&gt;&lt;span&gt;(payload, &lt;/span&gt;&lt;span&gt;indent&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;35&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;encoding&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;36&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;37&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;38&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;39&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;keeper_loop&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;acquire_state&lt;/span&gt;&lt;span&gt;) -&amp;gt; &lt;/span&gt;&lt;span&gt;None&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;40&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;while&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;41&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;state &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;load_state&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;42&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;43&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;not&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;should_renew&lt;/span&gt;&lt;span&gt;(state):&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;44&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;time.&lt;/span&gt;&lt;span&gt;sleep&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;45&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;45&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;continue&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;46&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;47&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 这里调用探测逻辑，返回状态和 TTL。&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;48&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;acquire_state&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;49&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;50&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; result &lt;/span&gt;&lt;span&gt;and&lt;/span&gt;&lt;span&gt;&lt;span&gt; result.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;status&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;292&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;51&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;save_state&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;52&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;result[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;state&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;53&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;ttl_seconds&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;result.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;ttl&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;3600&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;54&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;55&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;state refreshed&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;56&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;57&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;state refresh failed&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;58&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;59&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;time.&lt;/span&gt;&lt;span&gt;sleep&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;45&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;实际模型应该和使用模型保持一致。&lt;/p&gt;&lt;p&gt;例如，如果日常使用的是 &lt;code&gt;gpt-6-astra&lt;/code&gt;，探测时也应该使用同一个模型，而不是用一个模型获取 state，再拿去请求另一个模型。&lt;/p&gt;&lt;p&gt;否则即使 state 有效，也不一定能覆盖另一个模型的资源路由。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;真实验证路径&lt;a href=&quot;#真实验证路径&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这套机制真正要验证的不是“我能不能生成一个叫 state 的字符串”，而是下面这条因果链：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;同一账号、同一模型、相近时间&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;住宅 / 原生 IPv6 请求&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;观察是否出现 HTTP 292 和 current_turn_state&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;保存 state 的摘要与首次出现时间&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;切换到日常线路&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;分别发送不带 state 和带 state 的请求&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;比较路由结果、延迟、429、503、overload 与输出质量&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;工程上可以拆成几个部分：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;keeper 负责发起探测、保存 state 和提前续期。&lt;/li&gt;
&lt;li&gt;本地代理负责在请求经过时读取当前 state。&lt;/li&gt;
&lt;li&gt;Clash 或其他路由工具负责在探测线路和日常线路之间切换。&lt;/li&gt;
&lt;li&gt;日志记录每一次响应的状态、延迟、模型和路由差异。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;典型流程是：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;在住宅或原生 IPv6 出口发起探测请求。&lt;/li&gt;
&lt;li&gt;观察响应是否出现 292，以及是否返回 current_turn_state。&lt;/li&gt;
&lt;li&gt;将 state 保存到本地文件，同时记录获取时间和预计过期时间。&lt;/li&gt;
&lt;li&gt;在 state 过期前提前续期。&lt;/li&gt;
&lt;li&gt;让本地代理在后续请求中携带 state。&lt;/li&gt;
&lt;li&gt;切回日常线路，比较有 state 和无 state 的真实请求。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;真实验证需要记录什么&lt;a href=&quot;#真实验证需要记录什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;如果要把这套机制跑清楚，每次请求至少记录一行实验数据：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;timestamp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;account_level&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;model&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;network_profile&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;http_status&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;state_present&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;state_hash&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;latency_ms&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;retry_after&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;overloaded&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;context_size&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;compaction_happened&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;记录时可以用 state 的哈希作为标识：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;__future__&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; annotations&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; hashlib&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; collections.abc &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; Mapping&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;summarize_response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;headers&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; Mapping[&lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;latency_ms&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;) -&amp;gt; &lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;state &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;current_turn_state&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;or&lt;/span&gt;&lt;span&gt;&lt;span&gt; headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;x-codex-turn-state&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;status&quot;&lt;/span&gt;&lt;span&gt;: status,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;state_present&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt;(state),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;state_hash&quot;&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;hashlib.&lt;/span&gt;&lt;span&gt;sha256&lt;/span&gt;&lt;span&gt;(state.&lt;/span&gt;&lt;span&gt;encode&lt;/span&gt;&lt;span&gt;()).&lt;/span&gt;&lt;span&gt;hexdigest&lt;/span&gt;&lt;span&gt;()[:&lt;/span&gt;&lt;span&gt;12&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; state&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;None&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;latency_ms&quot;&lt;/span&gt;&lt;span&gt;: latency_ms,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;retry_after&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;: headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;retry-after&quot;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;server_model&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;: headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;openai-model&quot;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这个函数把一次响应整理成便于比较的实验记录，后面可以直接写入 JSONL 或 CSV。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;真实实验的对照组&lt;a href=&quot;#真实实验的对照组&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;至少应该设置三组：&lt;/p&gt;




























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;组别&lt;/th&gt;&lt;th&gt;网络&lt;/th&gt;&lt;th&gt;state&lt;/th&gt;&lt;th&gt;目的&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;A 组&lt;/td&gt;&lt;td&gt;日常线路&lt;/td&gt;&lt;td&gt;不携带&lt;/td&gt;&lt;td&gt;观察自然状态&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;B 组&lt;/td&gt;&lt;td&gt;日常线路&lt;/td&gt;&lt;td&gt;携带有效 state&lt;/td&gt;&lt;td&gt;观察注入后的状态&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;C 组&lt;/td&gt;&lt;td&gt;日常线路&lt;/td&gt;&lt;td&gt;携带随机或已过期 state&lt;/td&gt;&lt;td&gt;排除“只要多一个 header 就有效”的可能&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;三组请求尽量保持：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;同一个账号。&lt;/li&gt;
&lt;li&gt;同一个模型。&lt;/li&gt;
&lt;li&gt;同一类提示词。&lt;/li&gt;
&lt;li&gt;相近的时间段。&lt;/li&gt;
&lt;li&gt;相同的请求间隔。&lt;/li&gt;
&lt;li&gt;尽量相同的上下文长度。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;如果只测试一次，结果很容易被时间、网络和服务负载干扰。更可靠的方式是跨多个时间段、多个网络出口重复测试，同时记录 429、503 和 overload 的出现情况。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;IP 是否绑定，需要单独验证&lt;a href=&quot;#ip-是否绑定需要单独验证&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;最关键的实验是 IP 绑定实验：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;阶段一：住宅 / 原生 IPv6&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;获取 292 和 state&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;阶段二：数据中心 / IPLC&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;不携带 state 请求一次&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;携带同一个 state 请求一次&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;阶段三：重复多轮&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;比较两种请求的成功率和服务表现&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;如果携带 state 的请求在更换网络出口后仍然保持明显差异，就说明 state 的使用可能不依赖签发 IP。&lt;/p&gt;&lt;p&gt;更换 IP 后还要继续控制账号、模型、会话和提示词，否则很难判断变化到底来自 state 还是其他变量。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;TTL 也必须实测&lt;a href=&quot;#ttl-也必须实测&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;TTL 约一小时，连续重放可以测出它的实际窗口。&lt;/p&gt;&lt;p&gt;拿到 state 后按固定时间间隔测试：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;0 分钟&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;10 分钟&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;20 分钟&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;30 分钟&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;45 分钟&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;55 分钟&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;60 分钟&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;70 分钟&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;90 分钟&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;每个时间点记录：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;state 是否仍然被接受。&lt;/li&gt;
&lt;li&gt;是否仍然维持相同的响应特征。&lt;/li&gt;
&lt;li&gt;是否出现新的 292。&lt;/li&gt;
&lt;li&gt;是否出现 429、503 或 overload。&lt;/li&gt;
&lt;li&gt;是否需要重新建立 session。&lt;/li&gt;
&lt;li&gt;上下文是否已经发生 compaction。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;如果多个时间段的结果都在接近一小时后发生变化，TTL 的判断才比较稳定。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结果怎么看&lt;a href=&quot;#结果怎么看&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Codex 官方源码读取 x-codex-turn-state，并把它作为同一 turn 内的 sticky-routing token；SSE、WebSocket 和 compact 路径也都会处理这个状态。&lt;/p&gt;&lt;p&gt;292、current_turn_state、IP 质量、TTL 和资源等级共同构成这套机制的核心变量。真实请求对照要测量的是：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;292 是否和 state 稳定同时出现。&lt;/li&gt;
&lt;li&gt;state 是否能在换 IP 后继续生效。&lt;/li&gt;
&lt;li&gt;state 失效是否呈现稳定的时间窗口。&lt;/li&gt;
&lt;li&gt;携带 state 后，429、503、overload 和响应延迟是否持续改善。&lt;/li&gt;
&lt;li&gt;上下文长度和 compaction 是否会独立影响结果。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;把这些变量拆开之后，才能知道“降智”究竟来自资源路由、状态过期，还是上下文已经变得过于复杂。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;Clash 的路由切换&lt;a href=&quot;#clash-的路由切换&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;如果实际实验需要切换不同出口，结构上通常需要两个代理组：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;proxy-groups&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;GPT-Daily&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;select&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;proxies&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;Daily-Route-1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;Daily-Route-2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;GPT-State-Probe&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;select&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;proxies&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;Residential-Route&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;- &lt;/span&gt;&lt;span&gt;Native-IPv6-Route&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;获取 state 时切换到 &lt;code&gt;GPT-State-Probe&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;获取完成后切回 &lt;code&gt;GPT-Daily&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;这也是为什么整个方案通常需要三个组件：&lt;/p&gt;




















&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;组件&lt;/th&gt;&lt;th&gt;职责&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;keeper&lt;/td&gt;&lt;td&gt;获取 state、记录 TTL、提前续期&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;本地代理&lt;/td&gt;&lt;td&gt;在请求链路中携带有效 state&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Clash 或其他路由工具&lt;/td&gt;&lt;td&gt;在获取阶段切换网络出口&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;对 Codex 来说，它仍然只是向本地代理发请求，并不知道背后进行了状态管理。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;上下文增长会放大问题&lt;a href=&quot;#上下文增长会放大问题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;即使状态路由保持稳定，长任务依然可能出现“降智”。&lt;/p&gt;&lt;p&gt;因为上下文本身也在不断增长。&lt;/p&gt;&lt;p&gt;一个复杂任务可能包含：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;初始需求。&lt;/li&gt;
&lt;li&gt;多轮对话。&lt;/li&gt;
&lt;li&gt;大量文件内容。&lt;/li&gt;
&lt;li&gt;多次代码修改。&lt;/li&gt;
&lt;li&gt;测试日志。&lt;/li&gt;
&lt;li&gt;浏览器截图。&lt;/li&gt;
&lt;li&gt;工具调用结果。&lt;/li&gt;
&lt;li&gt;被放弃的旧方案。&lt;/li&gt;
&lt;li&gt;后来追加的临时要求。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这些内容都会进入任务历史。&lt;/p&gt;&lt;p&gt;刚开始时，模型只需要理解“我要做什么”。&lt;/p&gt;&lt;p&gt;任务进行到后面，它需要理解的是：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;哪些内容仍然有效，哪些决定已经被推翻，哪些错误已经修复，现在文件系统的真实状态是什么？&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;上下文越长，模型拥有的信息越多，但真正重要的信息不一定更加突出。&lt;/p&gt;&lt;p&gt;如果一个任务中存在大量旧方案、失败日志和已经失效的指令，模型就需要花更多精力在上下文里寻找当前有效的答案。&lt;/p&gt;&lt;p&gt;这时即使资源路由没有变化，用户也会感觉模型变得迟钝。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;压缩会改变任务的“记忆形状”&lt;a href=&quot;#压缩会改变任务的记忆形状&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;当上下文接近上限时，Codex 需要进行 compaction，也就是上下文压缩。&lt;/p&gt;&lt;p&gt;OpenAI 官方文档说明，压缩后的内容是加密且不可直接解释的 opaque item，客户端不能简单地把它当成普通文本查看或编辑。&lt;a href=&quot;https://developers.openai.com/api/docs/guides/compaction&quot; target=&quot;_blank&quot;&gt;Context compaction 官方文档&lt;/a&gt;&lt;/p&gt;&lt;p&gt;压缩可以让长任务继续运行，但它并不是把原来的上下文逐字保存下来。&lt;/p&gt;&lt;p&gt;原来的上下文里，可能有很多只有在具体语境中才有意义的判断：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;这段代码暂时不能抽象。&lt;/li&gt;
&lt;li&gt;这个按钮虽然多余，但用户已经习惯。&lt;/li&gt;
&lt;li&gt;当前功能只需要验证流程，不应该继续扩展。&lt;/li&gt;
&lt;li&gt;某个报错只是环境问题，不是代码逻辑问题。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;压缩之后，这些细节可能仍然存在，也可能只剩下一段更概括的总结。&lt;/p&gt;&lt;p&gt;模型可能还记得“要完成什么”，但未必完整保留“为什么要这样完成”。&lt;/p&gt;&lt;p&gt;因此，压缩前后的任务目标看起来一样，工作风格却可能已经发生变化。&lt;/p&gt;&lt;p&gt;如果恰好在压缩、重连或重新路由之后，任务又失去了原来的 turn state，用户就更容易把这种变化理解成“模型降智”。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;长任务其实是多种状态叠加&lt;a href=&quot;#长任务其实是多种状态叠加&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;可以把一个长任务简单理解成下面这几层：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;flowchart TD
    A[一个长任务] --&amp;gt; B[会话与线程]
    A --&amp;gt; C[292 / 资源路由状态]
    A --&amp;gt; D[turn state]
    A --&amp;gt; E[上下文与压缩]
    A --&amp;gt; F[工具调用历史]
    A --&amp;gt; G[文件系统真实状态]
    A --&amp;gt; H[限流与服务负载]&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这些状态任何一层发生变化，都可能影响最后的表现。&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;会话变化，可能导致上下文重新开始。&lt;/li&gt;
&lt;li&gt;292 状态变化，可能导致资源路径发生变化。&lt;/li&gt;
&lt;li&gt;turn state 丢失，可能导致同一轮请求不再保持粘性路由。&lt;/li&gt;
&lt;li&gt;上下文压缩，可能让模型失去部分细节。&lt;/li&gt;
&lt;li&gt;工具调用异常，可能让模型误判当前进度。&lt;/li&gt;
&lt;li&gt;文件系统变化，可能让对话历史和真实项目不一致。&lt;/li&gt;
&lt;li&gt;429 或 503，则可能直接造成请求失败或重试。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;所以，模型表现变差并不一定只有一个原因。&lt;/p&gt;&lt;p&gt;但这几种状态如果同时变化，就很容易产生非常明显的“降智”体验。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;429 和 overloaded 可能是结果，不是根因&lt;a href=&quot;#429-和-overloaded-可能是结果不是根因&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;OpenAI 官方文档将 429 和 503 区分为两类问题。&lt;/p&gt;&lt;p&gt;429 通常和请求过快、流量增长过猛或速率限制有关。&lt;/p&gt;&lt;p&gt;503 &lt;code&gt;server_is_overloaded&lt;/code&gt; 则表示模型服务暂时过载。&lt;/p&gt;&lt;p&gt;官方建议使用 &lt;code&gt;Retry-After&lt;/code&gt; 和指数退避，而不是连续快速重试。&lt;a href=&quot;https://developers.openai.com/api/docs/guides/rate-limits&quot; target=&quot;_blank&quot;&gt;Rate limits 官方文档&lt;/a&gt;&lt;/p&gt;&lt;p&gt;但从长任务角度看，429 和 overloaded 也可能只是我们看到的表面结果。&lt;/p&gt;&lt;p&gt;如果一个任务不断增长、频繁调用工具、反复重试，那么它可能同时带来：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;更高的请求频率。&lt;/li&gt;
&lt;li&gt;更大的上下文。&lt;/li&gt;
&lt;li&gt;更多的压缩操作。&lt;/li&gt;
&lt;li&gt;更多的状态切换。&lt;/li&gt;
&lt;li&gt;更复杂的资源调度。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;最终表现出来的就不只是一次请求失败，而是整个任务开始变得不稳定。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;状态注入可能遇到的坑&lt;a href=&quot;#状态注入可能遇到的坑&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;State 过期&lt;a href=&quot;#state-过期&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;如果续期失败，旧 state 过期之后就会进入空窗期。&lt;/p&gt;&lt;p&gt;表现可能包括：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;模型质量下降。&lt;/li&gt;
&lt;li&gt;overload 增多。&lt;/li&gt;
&lt;li&gt;重新出现 429。&lt;/li&gt;
&lt;li&gt;长任务突然开始丢上下文。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;所以续期应该提前五分钟开始，而不是等到最后一分钟。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;模型不匹配&lt;a href=&quot;#模型不匹配&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;用一个模型获取的 state，不一定适合另一个模型。&lt;/p&gt;&lt;p&gt;例如：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;gpt-6-astra 获取的 state&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;不一定适合&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;其他模型的请求&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;实验时，获取模型和使用模型应该保持一致。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;多账号不能混用&lt;a href=&quot;#多账号不能混用&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;每个账号的 state 应该独立保存。&lt;/p&gt;&lt;p&gt;不能把账号 A 获得的状态注入账号 B 的请求，也不能让多个账号共用同一个状态文件。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;上下文压缩仍然会发生&lt;a href=&quot;#上下文压缩仍然会发生&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;即使状态注入成功，也不能阻止上下文增长和 compaction。&lt;/p&gt;&lt;p&gt;状态注入解决的更像是资源和路由问题，不能保证模型永远记得每一个细节。&lt;/p&gt;&lt;p&gt;一个解决“分配到哪里”。&lt;/p&gt;&lt;p&gt;另一个解决“还能记住什么”。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;和其他方法比较&lt;a href=&quot;#和其他方法比较&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;














































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;方法&lt;/th&gt;&lt;th&gt;原理&lt;/th&gt;&lt;th&gt;效果&lt;/th&gt;&lt;th&gt;问题&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;换 IP&lt;/td&gt;&lt;td&gt;碰运气进入更好的出口&lt;/td&gt;&lt;td&gt;有时有效&lt;/td&gt;&lt;td&gt;不稳定&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;新开会话&lt;/td&gt;&lt;td&gt;重新触发调度&lt;/td&gt;&lt;td&gt;偶尔有效&lt;/td&gt;&lt;td&gt;丢失上下文&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;多账号轮换&lt;/td&gt;&lt;td&gt;分散请求压力&lt;/td&gt;&lt;td&gt;可能有效&lt;/td&gt;&lt;td&gt;成本高、管理复杂&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;避开高峰期&lt;/td&gt;&lt;td&gt;等服务负载下降&lt;/td&gt;&lt;td&gt;通常有效&lt;/td&gt;&lt;td&gt;不适合连续工作&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;记录项目状态&lt;/td&gt;&lt;td&gt;减少上下文依赖&lt;/td&gt;&lt;td&gt;对长任务有效&lt;/td&gt;&lt;td&gt;不能改变资源路由&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;292 State 注入&lt;/td&gt;&lt;td&gt;复用稳定调度状态&lt;/td&gt;&lt;td&gt;理论上更稳定&lt;/td&gt;&lt;td&gt;需要验证 TTL、IP 绑定和模型匹配&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;292 State 注入的特别之处，在于它试图从调度层解决问题，而不是只在应用层缓解症状。&lt;/p&gt;&lt;p&gt;换 IP 是重新碰运气。&lt;/p&gt;&lt;p&gt;新会话是重新开始。&lt;/p&gt;&lt;p&gt;状态注入则是尝试保留原来的资源和路由结果。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;这套机制为什么存在&lt;a href=&quot;#这套机制为什么存在&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;从平台的角度看，OpenAI 需要同时面对两个问题：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;GPU 资源有限。&lt;/li&gt;
&lt;li&gt;用户请求不断增长。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;如果每个请求都重新排队，长任务的连续性会很差。&lt;/p&gt;&lt;p&gt;因此，服务端会在一轮任务开始时进行一次调度判断：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;当前账号是否有优先级。&lt;/li&gt;
&lt;li&gt;当前 IP 是否可信。&lt;/li&gt;
&lt;li&gt;当前负载是否允许。&lt;/li&gt;
&lt;li&gt;当前模型是否有资源。&lt;/li&gt;
&lt;li&gt;是否可以给后续请求维持一条稳定通道。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;292 和 state 就是这次调度结果的外部表现。&lt;/p&gt;&lt;p&gt;更核心的目的，是在资源有限和请求来源复杂的情况下，优先保证美国本地、稳定、低风险真实用户的体验。中转站、共享 VPN 和数据中心用户并不一定被拒绝，但更容易被放进普通或降级路径：能用，速度和稳定性却不再和本地住宅用户相同。&lt;/p&gt;&lt;p&gt;state 则负责把已经完成的调度结果延续到同一轮任务的后续请求，让高优先级路径不必每次从头竞争。网络切换、移动设备切换和长任务重试只是这个机制带来的连续性表现，不是它唯一的目的。&lt;/p&gt;&lt;p&gt;但如果 state 的签发和使用没有绑定 IP，就会产生一个意外特性：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;获取状态和使用状态可以发生在不同的网络环境中。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;这也正是状态注入理论成立的关键。&lt;/p&gt;&lt;p&gt;当然，这套机制未来可能被平台调整：绑定 IP、缩短 TTL、增加设备指纹、绑定账户和模型，都是很自然的防护方式。&lt;/p&gt;&lt;p&gt;所以它更像一个尚未被充分文档化的内部机制，而不是一个永远有效的漏洞。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;最后&lt;a href=&quot;#最后&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我现在越来越不觉得 Codex 只是一个模型接口。&lt;/p&gt;&lt;p&gt;它更像是一个由模型、资源、上下文、工具、会话和路由共同组成的系统。&lt;/p&gt;&lt;p&gt;292 State 解释了资源路由这一层。&lt;/p&gt;&lt;p&gt;它最重要的含义，是服务端会优先把完整体验留给更像美国本地真实用户的请求，再把中转、共享 VPN 和数据中心流量降到仍然可用但更保守的路径。&lt;/p&gt;&lt;p&gt;&lt;code&gt;x-codex-turn-state&lt;/code&gt; 说明 Codex 确实会在同一轮请求中维护粘性状态。&lt;/p&gt;&lt;p&gt;上下文增长和 compaction 解释了为什么长任务会逐渐失去细节。&lt;/p&gt;&lt;p&gt;429 和 overload 则说明服务端负载本身也会参与最终体验。&lt;a href=&quot;https://openai.com/index/unrolling-the-codex-agent-loop/&quot; target=&quot;_blank&quot;&gt;OpenAI：Unrolling the Codex agent loop&lt;/a&gt;&lt;/p&gt;&lt;p&gt;所以，“降智”可能不是一个单独的问题，而是下面几件事同时发生：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;292 状态没有拿到或已经过期&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;后端资源路由发生变化&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;上下文不断增长&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;压缩后丢失部分细节&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;服务端处于高负载&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;用户感知到模型突然变笨&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;状态注入解决的是资源路由问题，不能替代上下文管理，但它为“降智”和 overload 提供了一个很有解释力的方向。&lt;/p&gt;&lt;p&gt;真正值得研究的，不只是模型本身有多聪明，而是：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;这一次请求到底被分配到了什么资源，携带了什么状态，又在怎样的上下文里继续运行。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;这可能才是长任务中最容易被忽略、也最影响体验的一层。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;相关阅读&lt;a href=&quot;#相关阅读&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/openai/codex/blob/main/codex-rs/core/src/client.rs&quot; target=&quot;_blank&quot;&gt;Codex 官方 client.rs：turn state 与粘性路由&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/openai/codex/blob/main/codex-rs/codex-api/src/sse/responses.rs&quot; target=&quot;_blank&quot;&gt;Codex 官方 SSE 响应处理&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/openai/codex/blob/main/codex-rs/codex-api/src/endpoint/responses_websocket.rs&quot; target=&quot;_blank&quot;&gt;Codex 官方 WebSocket 响应处理&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.openai.com/api/docs/guides/compaction&quot; target=&quot;_blank&quot;&gt;OpenAI Context compaction 文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.openai.com/api/docs/guides/rate-limits&quot; target=&quot;_blank&quot;&gt;OpenAI Rate limits 文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/openai/codex/issues/30536&quot; target=&quot;_blank&quot;&gt;Codex 官方 Issue：WebSocket 中 turn-state 的行为&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/can1357/oh-my-pi/issues/9277&quot; target=&quot;_blank&quot;&gt;社区对 &lt;code&gt;x-codex-turn-state&lt;/code&gt; 生命周期的讨论&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/10/chatgpt-codex-proxy/blob/main/docs/CODEX_API_DOCS.md&quot; target=&quot;_blank&quot;&gt;社区 Codex proxy 对 turn-state 的转发说明&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;&lt;/section&gt;</content:encoded></item><item><title>在奶奶的白发里，我看见了时间</title><link>https://rainzt.cn/posts/in-grandmas-white-hair-i-saw-time/</link><guid isPermaLink="true">https://rainzt.cn/posts/in-grandmas-white-hair-i-saw-time/</guid><description>一次回家时看见奶奶满头白发，让我重新想起时间、家人和未来，也让我决定趁年轻为自己走一条创业的路。</description><pubDate>Mon, 14 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;休假结束后，又隔了许久没正经写点东西。&lt;/p&gt;
&lt;p&gt;其实心里一直堆着满当当的感慨，千头万绪堵在喉咙口，真要落笔时，却又不知道该从何说起。索性就聊聊这半个多月以来，沉在心底的几分真实感受。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;起因&lt;a href=&quot;#起因&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;假期从 8 月 28 日开始，一直到 9 月 6 日。8 月 31 日和 9 月 1 日，我回了两天家。算下来，前前后后还是有将近半个月，没有像往常那样好好待在家里。&lt;/p&gt;&lt;p&gt;放在以前，每周休息日我总要抽一天回去，看看家里人，吃顿热乎的家常菜。这件事早已经成了不用刻意提醒的习惯。可这次一下子隔了这么久，再踏进家门的时候，竟莫名生出了一点生疏的亲切感。&lt;/p&gt;&lt;p&gt;周日下午吃过饭，我搬了张凳子，坐在阳台上晒着太阳，陪奶奶闲聊。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;在奶奶的白发里，看见时间&lt;a href=&quot;#在奶奶的白发里看见时间&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我们说着最近工作上的琐事、家里的日常、也聊到我对未来的打算。我正讲得起劲，目光无意间扫过她的头发。&lt;/p&gt;&lt;p&gt;那一刻，我像是忽然看见了时间留下的证据。&lt;/p&gt;&lt;p&gt;从前印象里，她总还留着大半黑发，常常笑着说自己头发白得慢，用不着染。可那天下午，太阳斜斜地落下来，明晃晃照得一清二楚——哪里还有什么黑发，满头都已经是霜白的颜色，连鬓角深处藏着的几根，也全都白透了。&lt;/p&gt;&lt;p&gt;我的心里猛地咯噔一下，话音也不自觉顿了半拍。&lt;/p&gt;&lt;p&gt;她依旧坐在那里听我说话，依旧会在我起身的时候问一句，要不要再带点什么。可我忽然意识到，那个我以为一直都会在的奶奶，也正在一天一天地老去。&lt;/p&gt;&lt;p&gt;那一刻，脑子里乱糟糟的，只有一个念头反复往上冒：&lt;/p&gt;&lt;p&gt;我到底，还剩下多少这样的日子？&lt;/p&gt;&lt;p&gt;还能有多少次，像这样安安静静地坐在她身边，听她念叨家常的下午？&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;时间从来不会等你准备好&lt;a href=&quot;#时间从来不会等你准备好&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;时间从来都是最残忍的东西。它不声不响，从不停留。你总觉得日子还长，总想着等忙完这阵，等做出点成绩，等站稳脚跟以后，再好好陪陪家人。&lt;/p&gt;&lt;p&gt;可时间从来不会等你准备好。&lt;/p&gt;&lt;p&gt;它就藏在奶奶新生的白发里，藏在每次回家时她递过来的热汤里，藏在一句句“在外面照顾好自己”的叮嘱里。不知不觉间，它已经把我们的岁月翻过去了大半。&lt;/p&gt;&lt;p&gt;也是从那时起，我重新想起了“时间”这件事。&lt;/p&gt;&lt;p&gt;以前我总觉得，上班嘛，说白了就是公司用钱买你的时间。每天八小时坐满，工作干到位，就对得起这份薪水。&lt;/p&gt;&lt;p&gt;可后来我慢慢想明白，公司真正买走的，或许不是你的时间，而是你在这段时间里能够交付的价值。而你拿去交换的，却是人生中一段不可回收的时间。&lt;/p&gt;&lt;p&gt;最遗憾的是，很多人卖着卖着，就把自己整段的人生都打包卖了出去。&lt;/p&gt;&lt;p&gt;连带着下班后的精力、对未来的规划、对生活的期待，全都困在了那份固定的薪水里。白天把时间交给工作，晚上把精力交给疲惫，等真正想做点自己的事情时，才发现已经没有多少力气了。&lt;/p&gt;&lt;p&gt;我们总以为以后还有机会。&lt;/p&gt;&lt;p&gt;可“以后”这两个字，往往是时间最温柔、也最危险的骗局。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;我要把时间的主动权拿回来&lt;a href=&quot;#我要把时间的主动权拿回来&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我今年 21 岁。过去的人生，几张幻灯片、几页简历就能全部放完，快得像一眨眼。再晃几年，三十岁也就到了。&lt;/p&gt;&lt;p&gt;这个年纪的我，没有太多资源，也没有一张保证成功的底牌。但至少，我还拥有试错的时间，还拥有重新开始的可能。&lt;/p&gt;&lt;p&gt;我不想等到那时候回头看，才发现自己最好的年纪，全都用来给别人的事业添砖加瓦，自己的时间、精力和选择，却始终没有真正掌握在自己手里。&lt;/p&gt;&lt;p&gt;所以我想，我得把时间的主动权拿回来。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;决定自己创业&lt;a href=&quot;#决定自己创业&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我要自己创业了。&lt;/p&gt;&lt;p&gt;这句话写出来很轻，可真正扛在身上，一点都不轻。&lt;/p&gt;&lt;p&gt;上班这半年，我其实从来没有停下过自己的事情。从接触跨境电商的第一天开始，我的方向就不是传统电商那条老路。&lt;/p&gt;&lt;p&gt;别人靠铺货，靠低价，靠不断复制别人已经走过的路径；而我更想磨出一套属于自己的玩法——跨境电商 + AIGC + OPC。&lt;/p&gt;&lt;p&gt;从选品逻辑，到内容生产，再到店铺运营，把能够流程化、自动化的环节尽可能沉淀下来。这样以后不管是铺店，还是复制运营方式，都不需要每次从头开始。&lt;/p&gt;&lt;p&gt;这不是一时头脑发热的冲动，但也确实带着一股头脑发热的勇气。&lt;/p&gt;&lt;p&gt;我害怕，&lt;/p&gt;&lt;p&gt;尤其是奶奶。有些人，或许真的等不起我们口中的“以后”。&lt;/p&gt;&lt;p&gt;接下来，我会带着这套 Codex，一步一步运营两家店铺：一家完全交给 Codex，去跑一套由我设计好的运营流程；另一家则由我自己独立运营，亲自做判断，也亲自承担结果。&lt;/p&gt;&lt;p&gt;这既是创业，也是一次实验。&lt;/p&gt;&lt;p&gt;我想看看，一个人究竟能不能借助 AI，把自己的想法真正落地；也想看看，当工具越来越强，一个普通人能不能拥有一套不依赖大团队的工作方式。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;我知道创业不等于自由&lt;a href=&quot;#我知道创业不等于自由&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;当然，我知道创业不等于自由。&lt;/p&gt;&lt;p&gt;至少在一开始，它意味着更多的不确定、更长的工作时间，以及更直接的压力。上班有固定的发薪日，有同事、有流程，也有人替你分担一部分问题。自己做以后，没有人会替你回答为什么没有订单，为什么犯了错，为什么还没有结果。&lt;/p&gt;&lt;p&gt;这些我都知道。&lt;/p&gt;&lt;p&gt;所以我并不是因为看不起上班，也不是因为笃定自己一定会成功，才选择走这条路。&lt;/p&gt;&lt;p&gt;恰恰是因为我知道它可能很难，甚至可能失败，所以才想趁自己还年轻的时候，认真为自己闯一次。&lt;/p&gt;&lt;p&gt;有人说，安稳上班不好吗，何必折腾？&lt;/p&gt;&lt;p&gt;可我总觉得，人活着，就是要争这一口气。&lt;/p&gt;&lt;p&gt;争的不是和谁比高低，也不是非要证明自己比别人强。争的是一份对人生的主动权。&lt;/p&gt;&lt;p&gt;我要自己决定每天的时间花在哪里，决定自己的精力投入什么事情，决定自己愿意为什么样的结果负责。&lt;/p&gt;&lt;p&gt;我想靠自己的双手，走出一条不一样的路。&lt;/p&gt;&lt;p&gt;哪怕这条路并不平坦，哪怕中间会绕远、会犯错、会有一段时间看不到结果，至少那也是我自己选的路。&lt;/p&gt;&lt;p&gt;不想等到白发爬上自己鬓角的时候，才遗憾年轻时没有为自己认真活过一次。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;路是走出来的，不是想出来的&lt;a href=&quot;#路是走出来的不是想出来的&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;其实还有很多情绪没有说出口，也不必都说透。&lt;/p&gt;&lt;p&gt;千言万语，最终都要落到实处。路是走出来的，不是想出来的。&lt;/p&gt;&lt;p&gt;接下来的日子，我会把这些想法一点一点做出来，也把该承担的结果一一接住。&lt;/p&gt;&lt;p&gt;也希望读到这篇文章的人，能够珍惜时间，珍惜身边的人。不要把所有重要的事情，都推给那个永远不会真正到来的“以后”。&lt;/p&gt;&lt;p&gt;因为我们以为还很长的日子，可能转眼就过去了。&lt;/p&gt;&lt;p&gt;而真正值得我们抓住的，往往就是眼前这一刻。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>月刊</title><link>https://rainzt.cn/posts/monthly-2026-08/</link><guid isPermaLink="true">https://rainzt.cn/posts/monthly-2026-08/</guid><description>记录 8 月 7 日到 26 日之间的博客更新、自动上架项目、被我停掉的功能、刚搭好的小家，以及本期想认真推荐的几位博友。</description><pubDate>Fri, 28 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最近做的事情有点杂：博客、自动上架、采购工具、几个做到一半又收手的小项目，还有终于布置好的小家。它们看起来没什么关系，却一起占掉了这个月的大部分时间。&lt;/p&gt;
&lt;p&gt;这篇不想写成一份冷冰冰的完成事项清单。做成了什么当然要记，为什么返工、哪里没做完、哪些东西最后被我删掉，也一样值得写下来。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;先看一下这个月的数据&lt;a href=&quot;#先看一下这个月的数据&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这次统计从 8 月 7 日到 8 月 26 日。先把 Umami 里的数字放在这里：&lt;/p&gt;&lt;div&gt;
  &lt;div&gt;
    &lt;span&gt;2.08k&lt;/span&gt;
    &lt;span&gt;位访客&lt;/span&gt;
  &lt;/div&gt;
  &lt;div&gt;
    &lt;span&gt;2.87k&lt;/span&gt;
    &lt;span&gt;次访问&lt;/span&gt;
  &lt;/div&gt;
  &lt;div&gt;
    &lt;span&gt;12.3k&lt;/span&gt;
    &lt;span&gt;次页面浏览&lt;/span&gt;
  &lt;/div&gt;
  &lt;div&gt;
    &lt;span&gt;39%&lt;/span&gt;
    &lt;span&gt;跳出率&lt;/span&gt;
  &lt;/div&gt;
  &lt;div&gt;
    &lt;span&gt;4′37″&lt;/span&gt;
    &lt;span&gt;平均访问时长&lt;/span&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;数据来自 Umami，统计时间：8 月 7 日—8 月 26 日。&lt;/p&gt;&lt;p&gt;这些数字没法直接证明博客做得好不好，但至少说明这里开始有人路过了。有人从友链点进来，有人看完一篇文章又翻到另一篇，也有人愿意在评论区留句话。对一个还在慢慢搭建中的个人博客来说，这种“真的有人来过”的感觉，还是挺开心的。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/monthly-2026-08/umami-dashboard.png&quot; alt=&quot;Umami 站点数据统计&quot; /&gt;&lt;figcaption&gt;Umami 站点数据统计&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;本月更新&lt;a href=&quot;#本月更新&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;翻项目记录的时候，我发现这个月没有什么特别大的版本号，更多是一些零零碎碎的调整：一张页面补完整一点，一个后台入口变得能用一点，一段状态同步终于不再装死。单看每一项都不算惊天动地，但博客确实比月初更像一个我会长期使用的地方了。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;博客内容和页面，慢慢长出自己的样子&lt;a href=&quot;#博客内容和页面慢慢长出自己的样子&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;div&gt;
  &lt;span&gt;8 月 · 两篇新文章&lt;/span&gt;
  &lt;article&gt;
    &lt;img src=&quot;/favicon/chaoc-tingyu-avatar-180.png&quot; alt=&quot;&quot; width=&quot;44&quot; height=&quot;44&quot; loading=&quot;lazy&quot; /&gt;
    &lt;div&gt;
      &lt;time&gt;8 月 14 日&lt;/time&gt;
      &lt;p&gt;今天发了这篇：&lt;br /&gt;&lt;a href=&quot;/posts/chao-chao-listening-rain-years-wind/&quot;&gt;《朝朝听雨，岁岁有风》&lt;/a&gt;&lt;/p&gt;
    &lt;/div&gt;
  &lt;/article&gt;
  &lt;article&gt;
    &lt;div&gt;
      &lt;p&gt;那篇写的是高中时期的一段感情，也是我第一次比较完整地把那段青春重新翻出来。写的时候有点犹豫，毕竟这和技术文章完全不是一回事，但写完之后反而觉得，博客本来就不应该只放“有用的东西”。有些记忆如果不找个地方放着，过几年可能连自己都说不清了。&lt;/p&gt;
    &lt;/div&gt;
    &lt;img src=&quot;/assets/images/rain-avatar.webp&quot; alt=&quot;&quot; width=&quot;44&quot; height=&quot;44&quot; loading=&quot;lazy&quot; /&gt;
  &lt;/article&gt;
  &lt;article&gt;
    &lt;img src=&quot;/favicon/chaoc-tingyu-avatar-180.png&quot; alt=&quot;&quot; width=&quot;44&quot; height=&quot;44&quot; loading=&quot;lazy&quot; /&gt;
    &lt;div&gt;
      &lt;time&gt;8 月 20 日&lt;/time&gt;
      &lt;p&gt;这一篇也发出来了：&lt;br /&gt;&lt;a href=&quot;/posts/from-matriarchal-society-to-patriarchy/&quot;&gt;《从母系社会到父权制度：亲缘、私有制与现代性别关系的演变（浅谈男女对立）》&lt;/a&gt;&lt;/p&gt;
    &lt;/div&gt;
  &lt;/article&gt;
  &lt;article&gt;
    &lt;div&gt;
      &lt;p&gt;这篇更像一次长一点的思考练习，我想把亲缘、继承、私有制和现代性别矛盾之间的关系捋一遍。它当然不可能把这么大的问题讲完，但至少把我当时的疑问和判断留下来了，之后想改也有地方可改。&lt;/p&gt;
    &lt;/div&gt;
    &lt;img src=&quot;/assets/images/rain-avatar.webp&quot; alt=&quot;&quot; width=&quot;44&quot; height=&quot;44&quot; loading=&quot;lazy&quot; /&gt;
  &lt;/article&gt;
&lt;/div&gt;&lt;div&gt;
  &lt;a href=&quot;/posts/chao-chao-listening-rain-years-wind/&quot;&gt;
    &lt;img src=&quot;/monthly-2026-08/article-cover-rain.webp&quot; alt=&quot;《朝朝听雨，岁岁有风》文章主图&quot; loading=&quot;lazy&quot; /&gt;
    &lt;span&gt;朝朝听雨，岁岁有风&lt;/span&gt;
  &lt;/a&gt;
  &lt;a href=&quot;/posts/from-matriarchal-society-to-patriarchy/&quot;&gt;
    &lt;img src=&quot;/monthly-2026-08/article-cover-gender-history.webp&quot; alt=&quot;《从母系社会到父权制度》文章主图&quot; loading=&quot;lazy&quot; /&gt;
    &lt;span&gt;从母系社会到父权制度&lt;/span&gt;
  &lt;/a&gt;
&lt;/div&gt;&lt;p&gt;“关于我”页面也重新整理过。以前它更像一张简单的名片，后来我把头像、个人状态、本机信息、导航栏 Logo、页脚和移动端文章信息一点点补上。做这些时经常会怀疑：这真的重要吗？但页面最后呈现出来的感觉，往往就是这些小地方一起决定的。&lt;/p&gt;&lt;div&gt;
  &lt;figure&gt;&lt;img src=&quot;/monthly-2026-08/about-profile-page.png&quot; alt=&quot;关于我页面的个人资料区&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;个人资料和 README&lt;/figcaption&gt;&lt;/figure&gt;
  &lt;figure&gt;&lt;img src=&quot;/monthly-2026-08/about-projects-page.png&quot; alt=&quot;关于我页面的项目和更新日志区&quot; loading=&quot;lazy&quot; /&gt;&lt;figcaption&gt;项目、更新日志和状态区&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/div&gt;&lt;p&gt;评论区也没有闲着。评论区身份标记、登录入口、AI 摘要、AI 评论员、评论图片处理，这些功能前前后后改了几轮；站长后台还加了小爱回复入口和安全验证。现在我暂时没有把“完全自动回复”放开，公开评论区还是要留一点人工确认。AI 可以帮忙整理和起草，但最后发出去的话，至少应该有人看过。&lt;/p&gt;&lt;div&gt;
  &lt;div&gt;&lt;span&gt;AI review trace&lt;/span&gt;&lt;small&gt;当前权限：只读建议&lt;/small&gt;&lt;/div&gt;
  &lt;ol&gt;
    &lt;li&gt;&lt;span&gt;inspect&lt;/span&gt;&lt;div&gt;&lt;strong&gt;先查页面&lt;/strong&gt;&lt;small&gt;从评论里的描述开始，定位对应的组件和页面。&lt;/small&gt;&lt;/div&gt;&lt;/li&gt;
    &lt;li&gt;&lt;span&gt;found&lt;/span&gt;&lt;div&gt;&lt;strong&gt;找到问题&lt;/strong&gt;&lt;small&gt;确认过一次 icon not found，追到 Svelte 图标组件。&lt;/small&gt;&lt;/div&gt;&lt;/li&gt;
    &lt;li&gt;&lt;span&gt;proposed&lt;/span&gt;&lt;div&gt;&lt;strong&gt;提出修改&lt;/strong&gt;&lt;small&gt;整理原因和处理方案，交给站长确认。&lt;/small&gt;&lt;/div&gt;&lt;/li&gt;
    &lt;li&gt;&lt;span&gt;reverted&lt;/span&gt;&lt;div&gt;&lt;strong&gt;自动修改撤回&lt;/strong&gt;&lt;small&gt;能发现问题不等于能直接改代码，最后保留人工检查。&lt;/small&gt;&lt;/div&gt;&lt;/li&gt;
  &lt;/ol&gt;
  &lt;p&gt;&lt;span&gt;result&lt;/span&gt;&lt;strong&gt;自动修改已撤回&lt;/strong&gt;&lt;small&gt;只留下发现问题和整理线索的部分&lt;/small&gt;&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;友链页面终于不只是几张卡片&lt;a href=&quot;#友链页面终于不只是几张卡片&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这个月在友链上花的时间，比我一开始想的多。&lt;/p&gt;&lt;p&gt;最早的友链页面就是头像、站名、描述和链接，排整齐了就算结束。后来越看越觉得不太对：既然叫友链，不能只是把朋友的网站摆在那里，然后大家各自沉默。于是我给它加了站点截图、推荐标记、申请说明、推荐语和交互式预览。鼠标移到卡片上时，可以先看看站点大概长什么样，再决定要不要点进去。&lt;/p&gt;&lt;a href=&quot;https://daily.yybb.us/&quot; target=&quot;_blank&quot;&gt;
  &lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span&gt;&lt;img src=&quot;https://r2tc.20030327.xyz/file/博客/主题/1780655293662_avatar_me.jpg.PNG&quot; alt=&quot;AIOVTUE-雪头像&quot; loading=&quot;lazy&quot; /&gt;&lt;/span&gt;
  &lt;span&gt;
    &lt;span&gt;&lt;span&gt;AIOVTUE-雪&lt;/span&gt;&lt;/span&gt;
    &lt;span&gt;雨滴会记录生命中的每一个瞬间&lt;/span&gt;
    &lt;span&gt;&lt;i&gt;博客主题&lt;/i&gt;&lt;i&gt;开源&lt;/i&gt;&lt;i&gt;教程&lt;/i&gt;&lt;/span&gt;
  &lt;/span&gt;
  &lt;figure&gt;&lt;img src=&quot;/friends/screenshots/aiovtue.webp&quot; alt=&quot;&quot; width=&quot;1200&quot; height=&quot;675&quot; loading=&quot;lazy&quot; /&gt;&lt;/figure&gt;
&lt;/a&gt;&lt;p&gt;预览功能看起来只是一个悬浮卡片，实际要处理的东西不少：截图加载失败怎么办，卡片放在屏幕边缘会不会被截掉，手机上没有鼠标该怎么交互，页面切换回来之后事件还在不在。它不是本月最重要的功能，却是那种做完之后每天都会顺手用一下的改动。&lt;/p&gt;&lt;p&gt;后来我又做了“朋友圈”页面，读取友链站点的 RSS，把朋友们最近写的文章放进一条时间线里，再配上分页、文章元信息和随机文章过渡。友链因此不再是静态列表，朋友们更新文章时，这里也会跟着动起来。&lt;/p&gt;&lt;article&gt;
  
  &lt;span&gt;&lt;/span&gt;
  &lt;span&gt;&lt;/span&gt;
  &lt;div&gt;
    &lt;img alt=&quot;&quot; width=&quot;40&quot; height=&quot;40&quot; loading=&quot;lazy&quot; /&gt;
    &lt;span&gt;&lt;/span&gt;
  &lt;/div&gt;
  &lt;div&gt;
    &lt;div&gt;&lt;span&gt;随机一篇文章&lt;/span&gt;&lt;span&gt;随手翻翻&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
    &lt;a href=&quot;/moments/&quot;&gt;
      &lt;strong&gt;正在挑选一篇文章…&lt;/strong&gt;
      &lt;span&gt;从朋友圈文章里随机选一篇，换个角度继续阅读。&lt;/span&gt;
      &lt;span&gt;&lt;/span&gt;
    &lt;/a&gt;
  &lt;/div&gt;
  &lt;span&gt;5&lt;/span&gt;
  &lt;span&gt;自动随机&lt;/span&gt;
  &lt;span&gt;换一篇&lt;/span&gt;
&lt;/article&gt;&lt;p&gt;我还试过自动检查友链是否可用，最后又把它撤掉了。网络请求本来就有偶发失败，站点临时维护、证书抖一下、接口响应慢一点，都可能让一个正常的网站被错判。做出来不难，做到足够可靠才难；既然现在还做不到，我宁愿先不让它替我下结论。&lt;/p&gt;&lt;p&gt;8 月 25 日，我把“友链穿梭”补了出来。它带着启动动画、终端感文字和一段穿梭过渡，为了让这几秒看起来不像突然弹了个新页面，我又来回调了文字对比度、终端填充、表面纹理、启动和退出动画。它对效率没有帮助，但确实让这个博客多了一点自己的脾气。&lt;/p&gt;&lt;figure&gt;
  &lt;img src=&quot;/monthly-2026-08/friends-portal-page.png&quot; alt=&quot;友链穿梭页面的漫游舱效果&quot; loading=&quot;lazy&quot; /&gt;
  &lt;figcaption&gt;友链穿梭：从一面面站点截图之间穿过去。&lt;/figcaption&gt;
&lt;/figure&gt;&lt;p&gt;顺带一提，穿梭页顶部那个小太阳 / 月亮不只是装饰：它会把整个界面切到浅色或暗色，不知道有没有人发现。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;按日期回放，才知道这个月有多碎&lt;a href=&quot;#按日期回放才知道这个月有多碎&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;如果只看最后留下来的页面，很容易以为这些东西是一次性做完的。按日期回放就不是这样了。&lt;/p&gt;&lt;div&gt;
  &lt;article&gt;
    &lt;time&gt;&lt;span&gt;08&lt;/span&gt;&lt;strong&gt;09—14&lt;/strong&gt;&lt;/time&gt;
    &lt;div&gt;&lt;span&gt;merged&lt;/span&gt;&lt;code&gt;main / friends&lt;/code&gt;&lt;span&gt;友链与文章&lt;/span&gt;&lt;/div&gt;
    &lt;p&gt;主要是在补友链、整理文章和调整站点基础信息。MingBlog、Ditto、sssr7844、S17、桃之夭夭、林子浩的日记、九仞之行、王一君 Mew 等站点陆续加进来，博客也把新的生活文章发了出去。&lt;/p&gt;
  &lt;/article&gt;&lt;div&gt;
  
    &lt;img src=&quot;/friends/screenshots/0c0.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/80tz.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/aiovtue.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/asteri5m.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/blogplanet.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/blogroll.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/blogsclub.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/boyouquan.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/chenyicheng.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/detached.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/ditto.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/dreamcenter.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/fwneko.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/lemonadorable.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/linyu.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/linzihao.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/luming.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/mingblog.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/mingcy.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/nanzhiy.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/s17.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/silvercode-nexus.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/sssr7844.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/styunlen.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/sunrise.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/talen.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/taozhiyy.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/telyra.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/weasel6.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/whitebear.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/wyjmew.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/xeiu.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/xgrblog.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
    &lt;img src=&quot;/friends/screenshots/xiaoxi.webp&quot; alt=&quot;&quot; loading=&quot;lazy&quot; /&gt;
  
  &lt;div&gt;
    &lt;div&gt;&lt;/div&gt;
    &lt;div&gt;&lt;/div&gt;
    &lt;div&gt;&lt;/div&gt;
    &lt;div&gt;&lt;/div&gt;
    &lt;div&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div&gt;&lt;span&gt;FRIEND FEED&lt;/span&gt;&lt;b&gt;08 · scrolling&lt;/b&gt;&lt;/div&gt;
&lt;/div&gt;  &lt;article&gt;
    &lt;time&gt;&lt;span&gt;08&lt;/span&gt;&lt;strong&gt;15—18&lt;/strong&gt;&lt;/time&gt;
    &lt;div&gt;&lt;span&gt;reviewed&lt;/span&gt;&lt;code&gt;main / interface&lt;/code&gt;&lt;span&gt;页面与评论区&lt;/span&gt;&lt;/div&gt;
    &lt;p&gt;重点变成友链预览、申请说明、推荐卡片和评论区。这几天还处理了字体自托管、移动端布局、评论区身份、AI 摘要、AI 推荐和站长邮件验证，改完一个地方，另一个地方往往又冒出一点间距问题。&lt;/p&gt;
  &lt;/article&gt;
  &lt;article&gt;
    &lt;time&gt;&lt;span&gt;08&lt;/span&gt;&lt;strong&gt;19—24&lt;/strong&gt;&lt;/time&gt;
    &lt;div&gt;&lt;span&gt;synced&lt;/span&gt;&lt;code&gt;main / moments&lt;/code&gt;&lt;span&gt;RSS 与关于我&lt;/span&gt;&lt;/div&gt;
    &lt;p&gt;朋友圈开始接入 RSS，随机文章和分页也跟着补上。关于我页面、头像、Logo、页脚和图片资源重新整理了一遍，站内图片也逐步迁移到 WebP。&lt;/p&gt;
  &lt;/article&gt;
  &lt;article&gt;
    &lt;time&gt;&lt;span&gt;08&lt;/span&gt;&lt;strong&gt;25—26&lt;/strong&gt;&lt;/time&gt;
    &lt;div&gt;&lt;span&gt;released&lt;/span&gt;&lt;code&gt;main / portal&lt;/code&gt;&lt;span&gt;友链穿梭与收尾&lt;/span&gt;&lt;/div&gt;
    &lt;p&gt;主要是把友链穿梭补完整，继续增加友链，调整导航栏光标和页面动画，再把小爱回复后台、自动部署这些收尾工作接起来。&lt;/p&gt;
  &lt;/article&gt;
  &lt;p&gt;有些改动现在已经看不出来当时花了多久，但它们叠在一起，才是这个月博客真正发生的变化。&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;自动上架：这个月真正新增了什么&lt;a href=&quot;#自动上架这个月真正新增了什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;自动上架这个月修了不少底层问题，但这些东西对博客读者没有直接影响，同事只要知道它们已经处理好就够了。真正值得展开写的，是两个会改变实际工作方式的新功能：旧链接折扣批量处理，以及可以直接交到别人电脑上的采购工具。&lt;/p&gt;&lt;p&gt;修 Bug 的过程我没有删，只是先收起来。想看细节可以展开；只关心这月多了什么，直接往下读就行。&lt;/p&gt;
  
    &lt;span&gt;&lt;strong&gt;本月修复记录&lt;/strong&gt;&lt;small&gt;型号与标题 · 状态同步 · 图片参数联动&lt;/small&gt;&lt;/span&gt;
  
  &lt;div&gt;
    &lt;section&gt;
      &lt;h3&gt;型号和标题规则，先把地基打牢&lt;/h3&gt;
      &lt;p&gt;这个月先更新了印尼备货型号表。旧表有 299 条记录，新表变成了 301 条，新增两条 8.8 英寸型号，也调整了两处货位。更新之后，我又重新检查品牌、尺寸、字段完整性、重复冲突和 SKU 前缀，确认没有未知品牌、缺字段或非法前缀。&lt;/p&gt;
      &lt;p&gt;表格制作器里的图案编号也重新对过：3Y 对应 3Y-Fold，书本款要区分 No Pen Slot 和 With Pen Slot。这些词看着只是几个字符串，最后却会进入 SKU、图片目录和标题规则。前面改得随意一点，后面就可能出现图片匹配不上、型号分错组的问题。&lt;/p&gt;
      &lt;p&gt;标题这边也检查了 AI 随机组合、固定品牌、型号字段和字符上限。平台给出的上限是一回事，实际生成时还得留出安全余量，不能每次都贴着边缘写。配置检查中还发现过店铺档案和当前活动配置之间的细小不一致，这类问题平时可能不报错，批量发布时却很容易被放大。&lt;/p&gt;
    &lt;/section&gt;
    &lt;section&gt;
      &lt;h3&gt;后台已经结束，页面为什么还卡着&lt;/h3&gt;
      &lt;p&gt;自动上架期间遇到过一次很典型的问题：Shopee 任务实际上已经 12/12、100%，后台状态也结束了，页面却还停在原来的步骤。&lt;/p&gt;
      &lt;p&gt;后来查清楚，任务没有停，卡住的是前台。SSE 长连接中途断掉，最后那条完成事件没有送到页面，所以用户看到的还是旧状态。这个问题如果只看界面，很容易误以为整个任务失败了。&lt;/p&gt;
      &lt;p&gt;之后我给服务端补了心跳、自动重试、TCP keepalive 和禁止代理缓冲，前端再加一层每 5 秒一次的轻量状态同步。SSE 出错、网络恢复、窗口重新聚焦或页面重新可见时，都会主动把状态重新对齐。Shopee 和 TikTok Shop 的事件流也保持独立，一个平台的连接坏了，不能把另一个平台一起带偏。&lt;/p&gt;
      &lt;p&gt;这次之后，我不太敢再把“有长连接”直接叫作实时了。连接会断，断了要有人补，补回来的状态还要和已经显示过的日志合并，这些都算在实时功能里面。&lt;/p&gt;
    &lt;/section&gt;
    &lt;section&gt;
      &lt;h3&gt;图片参数联动，范围不能串&lt;/h3&gt;
      &lt;p&gt;设置页面还加了图片参数联动。以前改不同品牌时，需要重复填写每组图数、辅图数量、AI 识图和跳过序号几项参数。现在在当前店铺、当前平台、当前模板和当前通用类型的范围内，可以指定一个品牌作为同步源，其他品牌跟着更新。&lt;/p&gt;
      &lt;p&gt;这里最容易出错的不是同步本身，而是同步范围。普通模板和通用模板要分开，3Y、书本、笔槽、旋转也要分开；主图、变种图、偏移、标题和视频配置不能顺手一起覆盖。保存时原有的跳过序号冲突检查和九张产品图上限也必须保留。&lt;/p&gt;
      &lt;p&gt;配置功能特别容易出现一种错觉：页面上看起来只是改了当前这一组，实际上却把别的模板也一起改了。等到真正上架时才发现，通常已经晚了。所以这次我更在意“只改该改的地方”。&lt;/p&gt;
    &lt;/section&gt;
  &lt;/div&gt;
&lt;section&gt;&lt;h3&gt;旧链接折扣，先学会识别再批量处理&lt;a href=&quot;#旧链接折扣先学会识别再批量处理&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这个月还加了“批量开启旧链接折扣”。它会逐个打开旧链接的促销设置，识别折扣列，开启全部变种；只有非空折扣值一致时才继续批量更新，不一致的链接就跳过，并记录跳过原因。&lt;/p&gt;&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;识别折扣列&lt;/strong&gt;&lt;small&gt;促销价格不算折扣&lt;/small&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;只比较非空值&lt;/strong&gt;&lt;small&gt;空白行不参与判断&lt;/small&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;相同才继续&lt;/strong&gt;&lt;small&gt;50 / 50 / 50&lt;/small&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;span&gt;&lt;b&gt;一致&lt;/b&gt;批量处理&lt;/span&gt;&lt;span&gt;&lt;b&gt;不一致&lt;/b&gt;跳过，并记录原因&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/monthly-2026-08/toolbox.png&quot; alt=&quot;批量工具面板&quot; /&gt;&lt;figcaption&gt;批量工具面板&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;这个工具一开始也踩了坑：未开启变种的空白行，被误当成了无法识别；折扣列和促销价格也不能混在一起看，不能看到一个 0 到 100 的数字，就认定那是折扣。后来规则改成只比较折扣列里非空的数值，空白行不参与一致性判断。50、50、50 可以继续处理，50、50、51 就必须停下来。&lt;/p&gt;&lt;p&gt;现场读到过一个 96 个变种的促销弹窗，其中 78 个非空折扣值全部是 50%，另外 18 个为空。识别完成后弹窗正常关闭，核心读取逻辑是通的；不过重启后台服务后的完整批量提交验证还需要单独补一轮，所以这件事我只写到“识别和跳过逻辑已经验证”，不把它夸成百分之百完成。&lt;/p&gt;&lt;p&gt;自动化最怕自信过头。标题错一次，可能只是一个商品；折扣错一次，可能就是一批链接。能识别、能跳过、能留下理由，有时比不管三七二十一都执行更重要。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;写出来是一回事，交到别人电脑上又是另一回事&lt;a href=&quot;#写出来是一回事交到别人电脑上又是另一回事&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这个月还把采购工具整理成了可以交付的版本。以前工具能在我电脑上运行，不代表换一台 Windows 电脑也能顺利启动。于是我把安装和启动流程收成双击就能执行的脚本，数据也放进本地数据库里，尽量不让接手的人先去补一套开发环境。&lt;/p&gt;&lt;p&gt;这种工作不太有“做完一个功能”的爽感，更多时候是在另一台电脑上反复点安装、点启动，再看它到底能不能真的打开。对我来说，能交到别人手里正常使用，比我自己本地跑通更像完成。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;这个月也做了几次“停止”&lt;a href=&quot;#这个月也做了几次停止&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;
  &lt;article&gt;
    &lt;div&gt;&lt;strong&gt;Rain 记账&lt;/strong&gt;&lt;span&gt;已停更&lt;/span&gt;&lt;/div&gt;
    &lt;small&gt;不再给自己新开一条线&lt;/small&gt;
  &lt;/article&gt;
  &lt;span&gt;&lt;i&gt;&lt;/i&gt;&lt;i&gt;&lt;/i&gt;&lt;/span&gt;
  &lt;article&gt;
    &lt;div&gt;&lt;strong&gt;8888 元项目&lt;/strong&gt;&lt;span&gt;未接&lt;/span&gt;&lt;/div&gt;
    &lt;small&gt;不把接下来几个月押进去&lt;/small&gt;
  &lt;/article&gt;
&lt;/div&gt;&lt;p&gt;8 月 26 日起，Rain 记账项目正式停更。中转那边余留的 Pro 号也都处理掉了。不是它完全没有继续做的价值，而是我现在确实没有那么多精力，再开一条线只会让手上的事情越来越散。&lt;/p&gt;&lt;p&gt;这月还碰到了一笔 8888 元的企业级自动上架项目。对方愿意买下这套独家系统，听起来确实很诱人，但我当时第一反应不是“赚到了”，而是“接下来是不是要被它拴住”。&lt;/p&gt;&lt;p&gt;我爸说，我赚的这点钱，他躺着都能赚到，没必要为了这点钱糟蹋身体。话听起来有点扎心，但我后来想想，确实有道理。我只是刚出来没多久的大学生，第一次接这么大的单，项目一旦接下，后面几个月很可能都要围着它转。那这 8888 元到底能不能换回我失去的休息、生活和做自己项目的时间，我自己都没法确定。&lt;/p&gt;&lt;p&gt;所以最后还是没接。不是不想赚钱，就是不想还没开始过日子，先把接下来几个月都押给一个项目。&lt;/p&gt;&lt;p&gt;以前我可能会觉得，别人愿意付钱，就应该赶紧答应。现在至少会先问自己几句：这件事是不是非我不可？会不会把接下来的时间全部占满？拿到的钱，能不能抵消掉我放弃的东西？这些问题没有标准答案，但问一遍之后，很多决定会清楚一点。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;这个月最有意思的事：我一直在加，也一直在删&lt;a href=&quot;#这个月最有意思的事我一直在加也一直在删&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这个月真正让我觉得有意思的，不是又加了什么按钮，而是删掉了几样看起来挺酷的东西。&lt;/p&gt;&lt;p&gt;我做过一个加载动画，里面是一个忙着写代码的小人，旁边还有猫和各种代码窗口。单看原图其实很可爱，做成动效之后也确实会忙起来。可放回博客整体的浅色简约风里，总觉得它太热闹了，人物、代码框、装饰线同时动起来，页面的气质有点接不上。看了几遍之后，我自己先看不顺眼了，于是把它放进了废案。&lt;/p&gt;
  &lt;figure&gt;
    &lt;img src=&quot;/monthly-2026-08/loading-girl.webp&quot; alt=&quot;最后没有放进博客的加载小人&quot; loading=&quot;lazy&quot; /&gt;
  &lt;/figure&gt;

&lt;p&gt;&lt;span&gt;没上线&lt;/span&gt;&lt;strong&gt;不知道为什么，和网站整体的风格就是不太搭。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;另一个是任务工作台。它做到一半时，已经有任务列表、项目脉络、右侧智能详情和时间线，看起来像一个很完整的后台。问题也正是从这里开始：为了让它“看起来像个工作台”，我加了太多暗色卡片、状态、层级和装饰，信息反而没有飞书那么清楚。工作台这种东西，第一要求应该是找得到任务、看得懂状态、知道下一步做什么，不是每个角落都要有光效。&lt;/p&gt;&lt;img src=&quot;/monthly-2026-08/task-workbench.png&quot; alt=&quot;做到一半的任务工作台&quot; loading=&quot;lazy&quot; /&gt;
&lt;p&gt;&lt;span&gt;没继续&lt;/span&gt;&lt;strong&gt;信息太多，还是飞书更清楚。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;最后这个工作台没有继续做下去，我还是用回了飞书。图和代码都留着，项目先停在这里。以后如果再做，我会先从信息结构开始，而不是先想“这一块做成什么效果”。&lt;/p&gt;&lt;p&gt;还有一个更冒险的东西：我把 AI 接进了博客的反馈流程。它会读评论里描述的问题，自己打开页面检查，尝试找到出错的组件，再把原因和处理结果回复到评论区。截图里“小爱客服”的几条回复，就是它真的跑起来后的样子。从 &lt;code&gt;icon not found&lt;/code&gt; 一路查到 Svelte 图标组件，它甚至已经开始用站长的口吻告诉用户“已确认”“正在修”。&lt;/p&gt;&lt;img src=&quot;/monthly-2026-08/ai-bug-review.png&quot; alt=&quot;被撤回的 AI 网页检修功能&quot; loading=&quot;lazy&quot; /&gt;
&lt;p&gt;&lt;span&gt;没放开&lt;/span&gt;&lt;strong&gt;权限太高，最后还得我看一眼。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;刚跑起来的时候确实挺爽，像是有人替我值班。可很快就觉得不对：它不只是回答问题，而是真的能碰代码、改页面，再把结论发出去。判断对了当然省事，判断错一次，就可能把一个小 Bug 修成另一个 Bug；把权限收紧后，它又只能给出一份看起来很完整、最后仍然要我重新检查的答案。&lt;/p&gt;&lt;p&gt;所以自动修改被我撤了，现在最多让它帮忙发现问题、整理线索，真正要动代码时还是先让我看一眼。友链自动可用性检查也一起收紧了——漏掉一次提醒没什么，把正常链接误判掉才麻烦。&lt;/p&gt;
  打开废案抽屉
  &lt;p&gt;这三份废案我都留了下来。它们至少提醒我：可爱不等于适合，做出来也不等于一定要上线；功能越“聪明”，越要想清楚它能碰什么、出了错由谁兜底。以后再冒出类似的点子，先看看它能不能和现有页面待在一起，也看看权限是不是给得太多，再决定要不要继续。&lt;/p&gt;
&lt;p&gt;以前总觉得项目应该不断加功能，加到看起来越来越强。现在反而觉得，知道什么不该留下，也算是在做产品。尤其是自动修改、自动判断和高权限操作，程序该停的时候停，不确定的时候把问题交给人，可能比“什么都替你做”更靠谱。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;生活篇&lt;a href=&quot;#生活篇&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我的小家折腾了两三个月，终于算是搭起来了，给你们看看。&lt;/p&gt;&lt;figure&gt;
  &lt;div&gt;
    &lt;span&gt;&lt;/span&gt;
    &lt;img src=&quot;/monthly-2026-08/desk-setup.png&quot; alt=&quot;我的小家：白色桌面、粉色椅子和正在使用的电脑&quot; loading=&quot;lazy&quot; /&gt;
    &lt;span&gt;下班后 · 20:47&lt;/span&gt;
    &lt;span&gt;&lt;/span&gt;
    &lt;span&gt;桌面还没收拾好&lt;br /&gt;&lt;b&gt;但我已经回来了&lt;/b&gt;&lt;/span&gt;
  &lt;/div&gt;
  &lt;figcaption&gt;
    &lt;div&gt;&lt;span&gt;HOME / 2026.08&lt;/span&gt;&lt;span&gt;AFTER WORK&lt;/span&gt;&lt;/div&gt;
    &lt;strong&gt;下班以后，终于有个地方可以慢下来。&lt;/strong&gt;
    &lt;span&gt;桌面还是会乱，但愿意回来坐下来的时候，我就觉得它搭对了。&lt;/span&gt;
    &lt;div&gt;
      &lt;span&gt;&lt;i&gt;&lt;/i&gt;&lt;b&gt;电竞椅子&lt;/b&gt;&lt;small&gt;坐下来就不想动&lt;/small&gt;&lt;/span&gt;
      &lt;span&gt;&lt;i&gt;&lt;/i&gt;&lt;b&gt;橘色鼠标&lt;/b&gt;&lt;small&gt;写代码也打游戏&lt;/small&gt;&lt;/span&gt;
      &lt;span&gt;&lt;i&gt;&lt;/i&gt;&lt;b&gt;一张长桌&lt;/b&gt;&lt;small&gt;把日子放在一起&lt;/small&gt;&lt;/span&gt;
    &lt;/div&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;span&gt;下班以后&lt;/span&gt;想写东西就写东西，想玩游戏就玩游戏。&lt;/p&gt;&lt;p&gt;地方不算大，东西也不是什么特别贵的配置：显示器、键盘、鼠标、主机，再加上一些零零碎碎的生活用品。桌面乱的时候还是会乱，但它是我一点点整理出来的地方，工作、写博客、折腾代码、打游戏和休息都在这里发生。&lt;/p&gt;&lt;p&gt;白天上班，晚上回到家，换个舒服的姿势坐下来，想写东西就写东西，想玩游戏就玩游戏，中间不用再切换场景，也不用一直担心有人催进度。每天下完班在这里玩一会儿，真的挺自在。&lt;/p&gt;&lt;p&gt;以前总觉得，等以后赚到更多钱、住进更大的房子，生活才算真正开始。现在倒觉得，能有一个下班后愿意回去坐一会儿的地方，已经是很不错的事情了。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;8 月推荐友链&lt;a href=&quot;#8-月推荐友链&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;月刊里还有一个我很喜欢的部分：每个月挑一些站点，认真推荐给看到这里的人。&lt;/p&gt;&lt;p&gt;它不是永久榜单，每个月都会刷新。因为这是第一次正式做月刊，上月的推荐暂时延续到 9 月底。这次一共放 10 个站点，里面有我平时会看的，也有这段时间刚好逛到、留下印象的。&lt;/p&gt;&lt;p&gt;我主要看文章，也会看设计。有些站点是我自己长期关注的，有些是最近翻到之后觉得值得留下，还有几位是先让 AI 筛的，由我Ai每月固定点进去读文章、看页面。AI 负责帮我找站，最后推不推荐，也是完全看Ai脸色。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;&lt;a href=&quot;https://daily.yybb.us/&quot; target=&quot;_blank&quot;&gt;AIOVTUE-雪&lt;/a&gt;&lt;a href=&quot;#aiovtue-雪&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://r2tc.20030327.xyz/file/博客/主题/1780655293662_avatar_me.jpg.PNG&quot; alt=&quot;AIOVTUE-雪头像&quot; loading=&quot;lazy&quot; /&gt;&lt;/span&gt;&lt;/p&gt;&lt;div&gt;&lt;span&gt;本月推荐&lt;/span&gt;&lt;span&gt;博客主题&lt;/span&gt;&lt;span&gt;开源&lt;/span&gt;&lt;span&gt;教程&lt;/span&gt;&lt;/div&gt;&lt;p&gt;这个站一直在更新，主题和页面也在持续打磨。能看出来站长不是换完模板就放着，而是真的在把它当成一个会长期使用的地方。很多细节不抢眼，但你多看几眼就会发现，它们都不是随便放上去的。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;a href=&quot;https://homulilly.com&quot; target=&quot;_blank&quot;&gt;Homulilly&lt;/a&gt;&lt;a href=&quot;#homulilly&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://homulilly.com/images/avatar.jpg&quot; alt=&quot;Homulilly头像&quot; loading=&quot;lazy&quot; /&gt;&lt;/span&gt;&lt;/p&gt;&lt;div&gt;&lt;span&gt;本月推荐&lt;/span&gt;&lt;span&gt;Hexo&lt;/span&gt;&lt;span&gt;博客主题&lt;/span&gt;&lt;span&gt;开源&lt;/span&gt;&lt;/div&gt;&lt;p&gt;这个站把个人博客、主题开发和实用工具放在了一起，最近也一直在打磨 FlatPaper 主题和站点之间的连接。想先读一篇的话，可以从《&lt;a href=&quot;https://homulilly.com/post/hexo-theme-flapaper-now-support-friend-circle-md.html&quot; target=&quot;_blank&quot;&gt;Hexo Theme Flatpaper 加入朋友圈大家庭&lt;/a&gt;》开始；既能看到作者对 Hexo 主题的思路，也能看到他如何把自己的博客继续往前做。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;a href=&quot;https://blog.fqzlr.top/&quot; target=&quot;_blank&quot;&gt;番茄主理人&lt;/a&gt;&lt;a href=&quot;#番茄主理人&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://q1.qlogo.cn/g?b=qq&amp;amp;nk=20447289&amp;amp;s=640&quot; alt=&quot;番茄主理人头像&quot; loading=&quot;lazy&quot; /&gt;&lt;/span&gt;&lt;/p&gt;&lt;div&gt;&lt;span&gt;本月推荐&lt;/span&gt;&lt;span&gt;个人博客&lt;/span&gt;&lt;span&gt;技术分享&lt;/span&gt;&lt;span&gt;建站记录&lt;/span&gt;&lt;/div&gt;&lt;p&gt;这是一个很有自己表达的个人博客，页面里既有文章、归档和知识索引，也有不少围绕站点本身做的交互和整理。它不只是把内容放上去，还在持续把博客当成一个长期经营的空间，这种认真劲儿很容易让人多逛一会儿。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;a href=&quot;https://xiaoxi.ac.cn/&quot; target=&quot;_blank&quot;&gt;小曦的园子&lt;/a&gt;&lt;a href=&quot;#小曦的园子&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://img.xiaoxi.ac.cn/logo.png&quot; alt=&quot;小曦的园子头像&quot; loading=&quot;lazy&quot; /&gt;&lt;/span&gt;&lt;/p&gt;&lt;div&gt;&lt;span&gt;本月推荐&lt;/span&gt;&lt;span&gt;个人博客&lt;/span&gt;&lt;span&gt;生活记录&lt;/span&gt;&lt;/div&gt;&lt;p&gt;很会把小互动做得可爱，又不会为了动效把内容盖住。站里的小巧思不少，评论区的一些视觉灵感我也是逛到之后才发现，原来还可以这样处理。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;a href=&quot;https://detached.online/&quot; target=&quot;_blank&quot;&gt;Detached&lt;/a&gt;&lt;a href=&quot;#detached&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://detached.online/icon-512.png&quot; alt=&quot;Detached头像&quot; loading=&quot;lazy&quot; /&gt;&lt;/span&gt;&lt;/p&gt;&lt;div&gt;&lt;span&gt;本月推荐&lt;/span&gt;&lt;span&gt;独立开发&lt;/span&gt;&lt;span&gt;技术工具&lt;/span&gt;&lt;span&gt;生活记录&lt;/span&gt;&lt;/div&gt;&lt;p&gt;文章写独立开发、技术工具和日常观察，更新节奏也比较稳。它的页面不花，读起来却不会觉得单薄，有一种安安静静把事情记录下来的感觉。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;a href=&quot;https://blog.blueke.top/&quot; target=&quot;_blank&quot;&gt;Ditto&lt;/a&gt;&lt;a href=&quot;#ditto&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://gcore.jsdelivr.net/gh/Keduoli03/My_img@img/avatar.jpg&quot; alt=&quot;Ditto头像&quot; loading=&quot;lazy&quot; /&gt;&lt;/span&gt;&lt;/p&gt;&lt;div&gt;&lt;span&gt;本月推荐&lt;/span&gt;&lt;span&gt;Astro&lt;/span&gt;&lt;span&gt;AI&lt;/span&gt;&lt;span&gt;技术分享&lt;/span&gt;&lt;/div&gt;&lt;p&gt;我是真的喜欢他的文章。技术部分写得清楚，表达又不端着，偶尔还会带一点自己的判断和脾气。看完之后不会只记住一个结论，也会记住他是怎么想到那里去的，这点挺难得。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;a href=&quot;https://taozhiyy.top/&quot; target=&quot;_blank&quot;&gt;桃之夭夭&lt;/a&gt;&lt;a href=&quot;#桃之夭夭&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://taozhiyy.top/cos/1.png&quot; alt=&quot;桃之夭夭头像&quot; loading=&quot;lazy&quot; /&gt;&lt;/span&gt;&lt;/p&gt;&lt;div&gt;&lt;span&gt;本月推荐&lt;/span&gt;&lt;span&gt;React&lt;/span&gt;&lt;span&gt;AI&lt;/span&gt;&lt;span&gt;建站记录&lt;/span&gt;&lt;/div&gt;&lt;p&gt;我最先注意到的是站里的动画和小互动。它们都不算特别夸张，但很会把页面的气氛撑起来，动起来的时候有一种轻松的感觉。我最近折腾博客交互时，确实偷偷看了不少——抄是不可能承认的，借鉴了一点点。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;a href=&quot;https://blog.talen.top/&quot; target=&quot;_blank&quot;&gt;爱吃猫的鱼&lt;/a&gt;&lt;a href=&quot;#爱吃猫的鱼&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://image.talen.top/20251229184705_wgsva7g9.png&quot; alt=&quot;爱吃猫的鱼头像&quot; loading=&quot;lazy&quot; /&gt;&lt;/span&gt;&lt;/p&gt;&lt;div&gt;&lt;span&gt;本月推荐&lt;/span&gt;&lt;span&gt;建站&lt;/span&gt;&lt;span&gt;自动化&lt;/span&gt;&lt;span&gt;技术分享&lt;/span&gt;&lt;/div&gt;&lt;p&gt;内容比较扎实，技术文章、自动化、博客建设和生活记录都有。尤其是自研博客系统和 FlecBlog 相关内容，能看出来作者不是只写一篇就走，而是在认真维护自己的工具和写作空间。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;a href=&quot;https://blog.telyra.top/&quot; target=&quot;_blank&quot;&gt;Petrichor’s Blog&lt;/a&gt;&lt;a href=&quot;#petrichors-blog&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://blog.telyra.top/avatar.webp&quot; alt=&quot;Petrichor&apos;s Blog头像&quot; loading=&quot;lazy&quot; /&gt;&lt;/span&gt;&lt;/p&gt;&lt;div&gt;&lt;span&gt;本月推荐&lt;/span&gt;&lt;span&gt;个人博客&lt;/span&gt;&lt;span&gt;技术随笔&lt;/span&gt;&lt;span&gt;生活记录&lt;/span&gt;&lt;/div&gt;&lt;p&gt;主要记录技术实践、AI 使用心得和各种折腾过程。最近写到 Codex、VibeCoding 和实际工具的内容，我觉得比较具体，不只是说“用了某个工具很方便”，还会写它到底怎么用、哪里会出问题。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;a href=&quot;https://mingblog.site/&quot; target=&quot;_blank&quot;&gt;MingBlog&lt;/a&gt;&lt;a href=&quot;#mingblog&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;span&gt;&lt;img src=&quot;https://mingblog.site/icons/icon-512.png&quot; alt=&quot;MingBlog头像&quot; loading=&quot;lazy&quot; /&gt;&lt;/span&gt;&lt;/p&gt;&lt;div&gt;&lt;span&gt;本月推荐&lt;/span&gt;&lt;span&gt;个人博客&lt;/span&gt;&lt;span&gt;生活记录&lt;/span&gt;&lt;span&gt;旅行观察&lt;/span&gt;&lt;/div&gt;&lt;p&gt;这是一个很有自己审美的生活记录站点，内容有影像、观察、观读听，也有一些小工具。页面简洁但不单调，最近的 FloraForm 也很有意思，适合喜欢把生活记录和创作放在一起的人。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;评选规则&lt;a href=&quot;#评选规则&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这不是按权重排的榜单，也不是一次性打分。每期主要看五件事：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;有持续更新、能读到内容的站点；&lt;/li&gt;
&lt;li&gt;有自己的表达和长期打磨的痕迹；&lt;/li&gt;
&lt;li&gt;读完文章或逛完页面后，仍会想多停一会儿、再点开几篇的那种吸引力。&lt;/li&gt;
&lt;li&gt;界面符不符合Ai的审美&lt;/li&gt;
&lt;li&gt;Ai觉得你的文章写得不错&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;满足这些才会被Ai写进当期月刊；下期会重新观察，不把推荐当永久标签。&lt;/p&gt;&lt;p&gt;剩下就是我个人评选啦，私信肯定有但不多~&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;写在第一期月刊最后&lt;a href=&quot;#写在第一期月刊最后&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;回头看，这个月留下来的不只是几个新页面和几个新工具，还有一些被我删掉的想法，以及几次认真考虑过之后做出的“不做”。&lt;/p&gt;&lt;p&gt;我开始更在意一件事：功能加上去之后，事情有没有真的变简单；自动化跑完之后，能不能说明白自己改了什么；一个项目赚到钱之后，值不值得拿接下来很长一段时间去换。&lt;/p&gt;&lt;p&gt;博客也一样。它可以有动画、友链穿梭、AI 摘要和各种小功能，但最后还是要回到文章本身。有人愿意停下来读一会儿，有人愿意留下评论，我也还愿意继续写，这就已经挺好了。&lt;/p&gt;&lt;p&gt;再说一件事儿，上班说到底，当然是奔着财富自由去的。但赶路的时候也别一直闷头往前冲，该停就停一下，看看自己这段时间到底走了多远，哪些坑不必再踩第二次。&lt;/p&gt;&lt;p&gt;我打算休息半个月，已经请好假了。把前面的经验捋一捋，把该放下的东西放下，等下一次想做什么的时候，再有力气重新开始。&lt;/p&gt;&lt;p&gt;好啦，第一期就到这里。下个月见。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>从母系社会到父权制度：亲缘、私有制与现代性别关系的演变(浅谈男女对立)</title><link>https://rainzt.cn/posts/from-matriarchal-society-to-patriarchy/</link><guid isPermaLink="true">https://rainzt.cn/posts/from-matriarchal-society-to-patriarchy/</guid><description>从亲缘确认、私有制和生产方式出发，浅谈母系亲缘结构、父权制度与现代男女对立背后的历史逻辑。</description><pubDate>Thu, 20 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;最近网络上的声音很大，男女对立也很严重，本期我们不站队任何一方，只从历史的角度去看待客观事实。
再次叠甲，“男女对立是阶级矛盾转移”的观点认为，社会中的一些性别冲突，表面上表现为男性与女性之间的利益冲突，但深层可能与经济结构、资源分配和阶层差异有关。例如，住房、就业、生育、教育等压力，本质上可能来自社会资源分配问题，却容易被转化为男女之间的相互指责。上层或既有利益结构在这种理论中被认为可能通过强化身份矛盾，让普通群体内部产生分裂，从而弱化对更深层经济问题的关注。不过，这并不意味着所有性别问题都是阶级矛盾造成的，现实中的性别不平等也有其独立存在的原因，二者往往是相互交织的。
再次声明，本站文章仅代表本站观点，若你不认可我的观点尽可离去并举报，如果你的观点能打动我，我会与你辩论，如果没有我会觉得你很无知并永久拉黑。&lt;/p&gt;&lt;/blockquote&gt;
&lt;section&gt;&lt;h2&gt;引言：社会制度并非天然存在&lt;a href=&quot;#引言社会制度并非天然存在&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;很多人会思考一个问题：&lt;/p&gt;&lt;p&gt;为什么现代社会大多数文明都形成了父姓制度？&lt;/p&gt;&lt;p&gt;为什么子女通常随父姓，而不是随母姓？&lt;/p&gt;&lt;p&gt;为什么人类历史上出现了从母系亲属结构向父系社会结构的转变？&lt;/p&gt;&lt;p&gt;进一步来说，为什么今天女性开始获得越来越多的社会权利，而与此同时，许多普通男性也开始感受到来自传统性别结构的压力？&lt;/p&gt;&lt;p&gt;这些问题表面上讨论的是男女关系，实际上背后涉及的是更深层的社会逻辑：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;亲缘确认、生产方式、财产继承以及社会结构的变化。&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;很多制度并不是某一个群体单纯设计出来的，而是在特定生产条件下，为了解决现实问题而逐渐形成的。&lt;/p&gt;&lt;hr /&gt;&lt;/section&gt;
&lt;section&gt;&lt;h1&gt;一、为什么父姓制度会成为许多文明的选择？&lt;a href=&quot;#一为什么父姓制度会成为许多文明的选择&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h1&gt;&lt;section&gt;&lt;h2&gt;母亲拥有天然确定的血缘关系&lt;a href=&quot;#母亲拥有天然确定的血缘关系&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;从生理意义上来说，母亲对于孩子的血缘关系具有天然确定性。&lt;/p&gt;&lt;p&gt;女性经历：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;怀孕；&lt;/li&gt;
&lt;li&gt;分娩；&lt;/li&gt;
&lt;li&gt;哺育。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;因此，一个母亲可以百分百确认：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;这个孩子属于自己的血脉。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;这种确定性，使母系亲缘关系天然具有稳定基础。&lt;/p&gt;&lt;hr /&gt;&lt;/section&gt;&lt;section&gt;&lt;h2&gt;父系血缘为什么需要社会制度确认？&lt;a href=&quot;#父系血缘为什么需要社会制度确认&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;相比之下，父亲的血缘关系在生理层面并不存在同等程度的确定性。&lt;/p&gt;&lt;p&gt;男性无法通过自身身体直接确认：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;这个孩子是否一定拥有自己的血缘。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;这也是为什么在人类社会发展过程中，围绕父系血缘逐渐产生了一系列社会制度：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;从父姓；&lt;/li&gt;
&lt;li&gt;婚姻制度；&lt;/li&gt;
&lt;li&gt;宗族体系；&lt;/li&gt;
&lt;li&gt;财产继承规则。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这些制度的核心作用之一，就是加强父系血缘的社会确认。&lt;/p&gt;&lt;p&gt;换句话说：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;生理上无法完全确认的父系关系，需要通过社会制度进行确认。&lt;/p&gt;&lt;/blockquote&gt;&lt;hr /&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h1&gt;二、姓氏制度不仅是身份问题，也是亲缘识别系统&lt;a href=&quot;#二姓氏制度不仅是身份问题也是亲缘识别系统&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;很多人认为姓氏只是一个称呼。&lt;/p&gt;&lt;p&gt;但在传统社会中，姓氏承担着更加重要的功能：&lt;/p&gt;&lt;section&gt;&lt;h2&gt;第一，确认家族归属&lt;a href=&quot;#第一确认家族归属&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;同一个姓氏意味着：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;共同祖先；&lt;/li&gt;
&lt;li&gt;共同宗族；&lt;/li&gt;
&lt;li&gt;共同继承关系。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;在传统农业社会中，这对于土地、财富和权力传递非常重要。&lt;/p&gt;&lt;hr /&gt;&lt;/section&gt;&lt;section&gt;&lt;h2&gt;第二，帮助社会识别婚姻关系&lt;a href=&quot;#第二帮助社会识别婚姻关系&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;如果所有亲属都按照母系姓氏传承，那么一些亲缘关系可能隐藏在同姓体系中。&lt;/p&gt;&lt;p&gt;而父姓制度，使父系宗族更加清晰。&lt;/p&gt;&lt;p&gt;传统宗族中的祠堂、族谱，并不仅仅是财富继承工具，它实际上也是一种社会组织系统。&lt;/p&gt;&lt;p&gt;它解决的问题包括：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;谁属于这个家族；&lt;/li&gt;
&lt;li&gt;谁拥有继承资格；&lt;/li&gt;
&lt;li&gt;哪些人之间不能通婚。&lt;/li&gt;
&lt;/ul&gt;&lt;hr /&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h1&gt;三、为什么许多母系社会中出现较弱的婚姻绑定？&lt;a href=&quot;#三为什么许多母系社会中出现较弱的婚姻绑定&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;这里需要明确：&lt;/p&gt;&lt;p&gt;母系社会并不等于女性统治社会，也不意味着所有母系社会都采用同一种婚姻模式。&lt;/p&gt;&lt;p&gt;但在人类学研究中，可以发现，一些母系亲属社会中存在较弱化的婚姻绑定结构。&lt;/p&gt;&lt;p&gt;原因并非简单来自道德观念，而是来自亲缘确认逻辑。&lt;/p&gt;&lt;hr /&gt;&lt;section&gt;&lt;h2&gt;当父亲身份无法被确认时，男性投入模式会发生变化&lt;a href=&quot;#当父亲身份无法被确认时男性投入模式会发生变化&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;在一些母系社会中，与孩子长期共同生活并承担抚育责任的男性，并不一定是生父，而可能是母亲一方的兄弟，也就是舅舅。&lt;/p&gt;&lt;p&gt;原因在于：&lt;/p&gt;&lt;p&gt;舅舅可以确认：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;这个孩子一定属于自己母系家族的血缘。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;而生父无法获得同等程度的确定。&lt;/p&gt;&lt;p&gt;因此，在这样的社会结构中，母系亲缘关系更加稳定。&lt;/p&gt;&lt;hr /&gt;&lt;/section&gt;&lt;section&gt;&lt;h2&gt;父亲角色的社会意义与制度有关&lt;a href=&quot;#父亲角色的社会意义与制度有关&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;当社会无法建立稳定的父系确认机制时，对于父亲身份的制度需求自然较低。&lt;/p&gt;&lt;p&gt;而当社会进入私有财产积累阶段后，情况开始改变。&lt;/p&gt;&lt;hr /&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h1&gt;四、农业革命如何推动父系制度形成？&lt;a href=&quot;#四农业革命如何推动父系制度形成&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;人类早期社会主要依靠：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;采集；&lt;/li&gt;
&lt;li&gt;狩猎；&lt;/li&gt;
&lt;li&gt;群体共享资源。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这一时期，私人财富积累有限。&lt;/p&gt;&lt;p&gt;但农业时代出现后，人类社会发生重大变化。&lt;/p&gt;&lt;hr /&gt;&lt;section&gt;&lt;h2&gt;土地和财富改变了社会逻辑&lt;a href=&quot;#土地和财富改变了社会逻辑&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;农业生产带来了：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;土地；&lt;/li&gt;
&lt;li&gt;牲畜；&lt;/li&gt;
&lt;li&gt;粮食储备；&lt;/li&gt;
&lt;li&gt;私人财产。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;当财富开始积累，一个新的问题出现：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;财产应该传给谁？&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;母亲当然可以确认自己的孩子。&lt;/p&gt;&lt;p&gt;但是男性如果希望自己的财富传递给自己的直系后代，就需要解决父系血缘确认问题。&lt;/p&gt;&lt;p&gt;于是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;私有财产出现&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;需要确定继承人&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;加强父系血缘确认&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;发展姓氏、婚姻、宗族制度&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;hr /&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h1&gt;五、父权制度并非单纯来自男性意志，而是生产结构的结果&lt;a href=&quot;#五父权制度并非单纯来自男性意志而是生产结构的结果&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;很多社会制度不能简单理解为：&lt;/p&gt;&lt;p&gt;“某个群体压迫另一个群体”。&lt;/p&gt;&lt;p&gt;更准确地说：&lt;/p&gt;&lt;p&gt;制度往往是生产方式和现实需求共同塑造的结果。&lt;/p&gt;&lt;p&gt;农业文明时期：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;体力是重要生产力；&lt;/li&gt;
&lt;li&gt;战争依赖身体力量；&lt;/li&gt;
&lt;li&gt;土地开发依赖大量劳动力。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;由于生育和哺育成本，女性平均承担更多家庭照护限制。&lt;/p&gt;&lt;p&gt;而男性平均具有更强的体力优势。&lt;/p&gt;&lt;p&gt;于是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;农业生产&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;+&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;体力竞争&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;+&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;私有财产继承&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;父系制度强化&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;hr /&gt;&lt;/section&gt;
&lt;section&gt;&lt;h1&gt;六、为什么现代社会正在改变传统性别结构？&lt;a href=&quot;#六为什么现代社会正在改变传统性别结构&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;工业革命以后，人类社会进入新的阶段。&lt;/p&gt;&lt;p&gt;现代社会越来越依赖：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;知识；&lt;/li&gt;
&lt;li&gt;技术；&lt;/li&gt;
&lt;li&gt;教育；&lt;/li&gt;
&lt;li&gt;管理；&lt;/li&gt;
&lt;li&gt;沟通能力。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;生产力的核心逐渐从体力转向脑力。&lt;/p&gt;&lt;p&gt;这意味着：&lt;/p&gt;&lt;p&gt;过去男性占优势的部分，正在减少。&lt;/p&gt;&lt;p&gt;女性在：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;教育；&lt;/li&gt;
&lt;li&gt;商业；&lt;/li&gt;
&lt;li&gt;科技；&lt;/li&gt;
&lt;li&gt;医疗；&lt;/li&gt;
&lt;li&gt;管理；&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;等领域拥有越来越多的发展空间。&lt;/p&gt;&lt;p&gt;这并不是简单来源于思想变化，而是生产方式变化带来的结果。&lt;/p&gt;&lt;hr /&gt;&lt;/section&gt;
&lt;section&gt;&lt;h1&gt;七、女性解放与生产力变化&lt;a href=&quot;#七女性解放与生产力变化&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;恩格斯曾提出：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;女性解放与社会生产方式变化密切相关。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;这一观点的核心逻辑是：&lt;/p&gt;&lt;p&gt;旧社会中的性别关系，与私有制、家庭结构和社会等级体系存在联系。&lt;/p&gt;&lt;p&gt;当生产方式变化，旧有结构自然会受到挑战。&lt;/p&gt;&lt;p&gt;过去：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;体力生产&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;男性优势&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;传统父权结构&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;现代：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;知识生产&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;性别差异降低&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;新的社会关系形成&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;hr /&gt;&lt;/section&gt;
&lt;section&gt;&lt;h1&gt;八、父权制度不仅影响女性，也影响普通男性&lt;a href=&quot;#八父权制度不仅影响女性也影响普通男性&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;讨论父权制度时，很多人首先想到女性受到限制。&lt;/p&gt;&lt;p&gt;但另一方面，传统父权结构也给男性塑造了大量责任要求。&lt;/p&gt;&lt;p&gt;尤其对于普通男性而言，父权社会给予的更多是一种责任期待，而不一定是真正的权力。&lt;/p&gt;&lt;hr /&gt;&lt;section&gt;&lt;h2&gt;第一层压力：必须成功&lt;a href=&quot;#第一层压力必须成功&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;传统观念告诉男性：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;要成家；&lt;/li&gt;
&lt;li&gt;要立业；&lt;/li&gt;
&lt;li&gt;要承担家庭责任。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;成功似乎成为男性价值的重要证明。&lt;/p&gt;&lt;p&gt;但现实中，并不是所有男性都能获得与责任匹配的资源。&lt;/p&gt;&lt;hr /&gt;&lt;/section&gt;&lt;section&gt;&lt;h2&gt;第二层压力：婚姻成为社会评价体系&lt;a href=&quot;#第二层压力婚姻成为社会评价体系&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;在传统观念中：&lt;/p&gt;&lt;p&gt;结婚不仅是一种情感选择，也被赋予了成功象征。&lt;/p&gt;&lt;p&gt;男性往往被期待：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;买房；&lt;/li&gt;
&lt;li&gt;提供稳定收入；&lt;/li&gt;
&lt;li&gt;承担家庭经济责任。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;婚姻因此不仅成为情感关系，也成为一种社会压力。&lt;/p&gt;&lt;hr /&gt;&lt;/section&gt;&lt;section&gt;&lt;h2&gt;第三层压力：责任感被无限扩大&lt;a href=&quot;#第三层压力责任感被无限扩大&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;责任本身并没有问题。&lt;/p&gt;&lt;p&gt;问题在于：&lt;/p&gt;&lt;p&gt;如果一个人需要承担所有责任，却缺少对应的支持和选择空间，那么责任就可能变成压力。&lt;/p&gt;&lt;p&gt;对于普通男性而言，传统父权结构有时意味着：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;需要承担“家庭支柱”的角色，却未必拥有真正的社会资源和权力。&lt;/p&gt;&lt;/blockquote&gt;&lt;hr /&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h1&gt;九、真正的问题不是男女，而是旧制度留下的压力&lt;a href=&quot;#九真正的问题不是男女而是旧制度留下的压力&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;父权制度并不是简单的：&lt;/p&gt;&lt;p&gt;“男性获益，女性受害”。&lt;/p&gt;&lt;p&gt;它更像是一套等级结构：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;女性可能受到角色限制；&lt;/li&gt;
&lt;li&gt;普通男性可能承担过度责任；&lt;/li&gt;
&lt;li&gt;少数掌握资源的人获得更多优势。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;因此，真正需要被反思的，并不是某一个性别，而是：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;一个社会是否能够让每个人拥有更自由、更平等的发展空间。&lt;/p&gt;&lt;/blockquote&gt;&lt;hr /&gt;&lt;/section&gt;
&lt;section&gt;&lt;h1&gt;十、从历史角度看，人类正在进入新的阶段&lt;a href=&quot;#十从历史角度看人类正在进入新的阶段&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;回顾整个人类社会：&lt;/p&gt;&lt;section&gt;&lt;h2&gt;原始社会阶段&lt;a href=&quot;#原始社会阶段&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;生产力较低：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;财富有限；&lt;/li&gt;
&lt;li&gt;血缘以母系确认更加稳定。&lt;/li&gt;
&lt;/ul&gt;&lt;hr /&gt;&lt;/section&gt;&lt;section&gt;&lt;h2&gt;农业社会阶段&lt;a href=&quot;#农业社会阶段&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;私有财产出现：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;继承需求增强；&lt;/li&gt;
&lt;li&gt;父系制度发展；&lt;/li&gt;
&lt;li&gt;宗族结构强化。&lt;/li&gt;
&lt;/ul&gt;&lt;hr /&gt;&lt;/section&gt;&lt;section&gt;&lt;h2&gt;工业与信息社会阶段&lt;a href=&quot;#工业与信息社会阶段&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;生产方式变化：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;体力不再决定全部价值；&lt;/li&gt;
&lt;li&gt;男女能力差异进一步缩小；&lt;/li&gt;
&lt;li&gt;传统性别结构开始调整。&lt;/li&gt;
&lt;/ul&gt;&lt;hr /&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h1&gt;结语：制度会随着生产力变化而变化&lt;a href=&quot;#结语制度会随着生产力变化而变化&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h1&gt;&lt;p&gt;人类历史并不是简单的男女对立史。&lt;/p&gt;&lt;p&gt;更多时候，它是一部：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;人类不断调整社会制度，以适应新的生产方式和生活需求的历史。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;从母系亲缘结构，到父系宗族体系，再到现代社会对性别关系的重新探索。&lt;/p&gt;&lt;p&gt;其中变化的并不仅仅是男女地位，而是整个社会组织方式。&lt;/p&gt;&lt;p&gt;未来社会会走向哪里，没有人可以完全预测。&lt;/p&gt;&lt;p&gt;但可以确定的是：&lt;/p&gt;&lt;p&gt;当生产力、经济结构和人的认知不断变化时，旧有制度也会不断被重新审视。&lt;/p&gt;&lt;p&gt;历史不会停留在过去。&lt;/p&gt;&lt;p&gt;它会继续寻找新的平衡。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>我回望青春时，我的青春是最昂贵的；
我正值青春时，我的青春是最廉价的。</title><link>https://rainzt.cn/posts/others/youth-most-expensive/youth-most-expensive/</link><guid isPermaLink="true">https://rainzt.cn/posts/others/youth-most-expensive/youth-most-expensive/</guid><description>一篇关于校园、游戏、喜欢与告别的青春回望。</description><pubDate>Mon, 17 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;section&gt;&lt;h2&gt;后来才知道，真正舍不得的是日子&lt;a href=&quot;#后来才知道真正舍不得的是日子&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;有些年份，人在其中时并不觉得有什么特别。&lt;/p&gt;&lt;p&gt;天照常亮，铃照常响，黑板上的粉笔字一层叠着一层。夏天从窗外吹进来的风是热的，冬天走廊尽头总灌着冷气。我们每天抱着书，从一间教室走到另一间教室，嫌课间太短，嫌晚自习太长，嫌考试太多，也嫌日子过得太慢。&lt;/p&gt;&lt;p&gt;那时候谁也不会想到，许多年以后，真正舍不得的，偏偏就是这些曾经觉得漫长的日子。&lt;/p&gt;&lt;p&gt;我也是后来才知道——&lt;/p&gt;&lt;p&gt;原来青春在拥有的时候，几乎一文不值。&lt;/p&gt;&lt;p&gt;而一旦失去，便再也无从估价。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;2020 年的秋天&lt;a href=&quot;#2020-年的秋天&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我记得2020年的秋天。&lt;/p&gt;&lt;p&gt;其实已经记不清那年秋天究竟冷得早不早，也记不得开学那天穿的是什么衣服，只记得教学楼的走廊很长，课间总有人聚在那里。&lt;/p&gt;&lt;p&gt;后来不知道是谁带来了毽子。&lt;/p&gt;&lt;p&gt;一群人便围成一个并不好看的圈。&lt;/p&gt;&lt;p&gt;毽子从这个人的脚尖飞到另一个人的肩头，踢歪了便笑，掉在地上就弯腰去捡。上课铃一响，众人作鸟兽散，踩着铃声往教室里跑。&lt;/p&gt;&lt;p&gt;现在想来，那实在算不得什么值得记住的事情。&lt;/p&gt;&lt;p&gt;可偏偏很多年以后，大考的题目忘了，名次忘了，老师说过的话也忘得七七八八，我却还能想起那只毽子在半空里打着旋。&lt;/p&gt;&lt;p&gt;大概记忆本来就不讲道理。&lt;/p&gt;&lt;p&gt;重要的事未必留下，真正留下来的，反倒总是一些无用的东西。&lt;/p&gt;&lt;p&gt;比如一阵风。&lt;/p&gt;&lt;p&gt;一句玩笑。&lt;/p&gt;&lt;p&gt;一个人在走廊上回过头来的样子。&lt;/p&gt;&lt;p&gt;又或者，是放学时天边那一点将暗未暗的光。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;十六七岁的时间太多了&lt;a href=&quot;#十六七岁的时间太多了&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;刚上高中的时候，我似乎还算一个意气很盛的人。&lt;/p&gt;&lt;p&gt;做班长，第一次月考数学考了141，年级第一。&lt;/p&gt;&lt;p&gt;少年人大抵都这样。&lt;/p&gt;&lt;p&gt;只凭一次考得不错，便觉得前路也不过如此。&lt;/p&gt;&lt;p&gt;那时的我从不真正害怕时间。&lt;/p&gt;&lt;p&gt;一道题今天不会，还有明天；一次考试失手，还有下一次；一句话今天没说出口，也总觉得往后还有机会。&lt;/p&gt;&lt;p&gt;我们对“以后”有一种近乎盲目的信任。&lt;/p&gt;&lt;p&gt;仿佛它是一条很长很长的路，无论今天停在哪里，明天起来，总还能接着走。&lt;/p&gt;&lt;p&gt;所以时间在那时候很便宜。&lt;/p&gt;&lt;p&gt;一个课间可以拿来踢毽子。&lt;/p&gt;&lt;p&gt;一个晚自习可以偷偷玩五子棋。&lt;/p&gt;&lt;p&gt;一个周末，可以几个人抱着手机，一局接一局地打游戏。&lt;/p&gt;&lt;p&gt;输了便再开。&lt;/p&gt;&lt;p&gt;赢了便笑。&lt;/p&gt;&lt;p&gt;谁也不会觉得几个小时过去了有什么可惜。&lt;/p&gt;&lt;p&gt;因为十六七岁的时间太多了。&lt;/p&gt;&lt;p&gt;多得让人误以为，它永远不会见底。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;屏幕背后的那些晚上&lt;a href=&quot;#屏幕背后的那些晚上&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;后来有一段时间，我很喜欢露娜。&lt;/p&gt;&lt;p&gt;月下不停地穿来穿去，一次断大便足够让人懊恼半天。&lt;/p&gt;&lt;p&gt;现在偶尔翻到那些旧截图，已经很难再理解当时为什么会为了一个MVP高兴那么久。&lt;/p&gt;&lt;p&gt;但我仍然舍不得删。&lt;/p&gt;&lt;p&gt;倒不是怀念游戏。&lt;/p&gt;&lt;p&gt;我只是舍不得屏幕背后的那些晚上。&lt;/p&gt;&lt;p&gt;那时候有人喊一句“上号”，几个人就真的都在。&lt;/p&gt;&lt;p&gt;没有人要提前约时间，也没有人会在打到一半时说自己明天还有会议。&lt;/p&gt;&lt;p&gt;那个年纪，人的世界很小。&lt;/p&gt;&lt;p&gt;一间教室。&lt;/p&gt;&lt;p&gt;一个操场。&lt;/p&gt;&lt;p&gt;几个人。&lt;/p&gt;&lt;p&gt;一部手机。&lt;/p&gt;&lt;p&gt;就足够盛下整个夜晚。&lt;/p&gt;&lt;div&gt;
  &lt;div&gt;
    
  &lt;/div&gt;
  &lt;p&gt;一段王者荣耀的画面，放在这里，作为那些一起开黑、一起把夜晚打发掉的回声。&lt;/p&gt;
  &lt;a href=&quot;https://www.bilibili.com/video/BV1f6b86tEBz/&quot; target=&quot;_blank&quot;&gt;在哔哩哔哩打开这段视频 ↗&lt;/a&gt;
&lt;/div&gt;&lt;p&gt;后来世界变大了，认识的人多了，能去的地方也多了。&lt;/p&gt;&lt;p&gt;反倒越来越难凑齐当年的那几个人。&lt;/p&gt;&lt;p&gt;于是才明白，人这一生最奇怪的地方，大概就是：&lt;/p&gt;&lt;p&gt;总在拥有很少的时候，觉得自己什么也没有；&lt;/p&gt;&lt;p&gt;等后来真的拥有了许多，才开始怀念曾经那一点点东西。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;那时的我站在操场&lt;a href=&quot;#那时的我站在操场&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;再后来，我去了操场。&lt;/p&gt;&lt;p&gt;成了体育生。&lt;/p&gt;&lt;p&gt;黄昏从此有了另一种样子。&lt;/p&gt;&lt;p&gt;放学铃响以后，别人往教室走，我往跑道走。&lt;/p&gt;&lt;p&gt;夏天的塑胶跑道晒过一天，热气还伏在上面。跑起来的时候风从耳边过去，汗一点点把衣服浸透。冬天则冷，呼出来的气在眼前散掉，手指僵着，连热身都觉得漫长。&lt;/p&gt;&lt;p&gt;那时候我并不喜欢这些。&lt;/p&gt;&lt;p&gt;累就是累。&lt;/p&gt;&lt;p&gt;疼就是疼。&lt;/p&gt;&lt;p&gt;训练结束时，满脑子只剩下吃饭、洗澡、回教室。&lt;/p&gt;&lt;p&gt;可如今再想起来，那些傍晚竟比很多后来精心安排过的旅行还要清楚。&lt;/p&gt;&lt;p&gt;我记得训练结束以后，天往往还剩一点光。&lt;/p&gt;&lt;p&gt;操场上的人没有完全散。&lt;/p&gt;&lt;p&gt;远处教学楼已经亮起了灯。&lt;/p&gt;&lt;p&gt;有人从食堂方向往回走。&lt;/p&gt;&lt;p&gt;风把树吹得轻轻响。&lt;/p&gt;&lt;p&gt;那时的我站在那里，不会觉得这一幕有什么值得留恋。&lt;/p&gt;&lt;p&gt;我只想着，晚自习快开始了。&lt;/p&gt;&lt;p&gt;可如今让我再回去一次，我大概会站得久一点。&lt;/p&gt;&lt;p&gt;不会急着走。&lt;/p&gt;&lt;p&gt;也不会急着把那天过完。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;喜欢一个人，其实是很轻的事情&lt;a href=&quot;#喜欢一个人其实是很轻的事情&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;青春里当然也有喜欢的人。&lt;/p&gt;&lt;p&gt;只是现在再回望，我已经很难把它单独从那些岁月里剥离出来。&lt;/p&gt;&lt;p&gt;喜欢一个人，其实是很轻的事情。&lt;/p&gt;&lt;p&gt;轻得像上课时回头看的一眼。&lt;/p&gt;&lt;p&gt;像故意调近一点的座位。&lt;/p&gt;&lt;p&gt;像晚自习前那十几分钟。&lt;/p&gt;&lt;p&gt;像训练结束以后，明明一身汗，还想先去见一面。&lt;/p&gt;&lt;p&gt;那时候说过那么多话。&lt;/p&gt;&lt;p&gt;如今却没有几句还能完整记得。&lt;/p&gt;&lt;p&gt;反而记得一些毫无意义的片段。&lt;/p&gt;&lt;p&gt;她坐在后面。&lt;/p&gt;&lt;p&gt;我知道她在那里。&lt;/p&gt;&lt;p&gt;有时回头。&lt;/p&gt;&lt;p&gt;有时不回头。&lt;/p&gt;&lt;p&gt;有时一句话也不说。&lt;/p&gt;&lt;p&gt;现在想来，少年人的喜欢之所以动人，或许不是因为它多么深刻。&lt;/p&gt;&lt;p&gt;而是因为那时候，我们还没有学会衡量。&lt;/p&gt;&lt;p&gt;没有值不值得。&lt;/p&gt;&lt;p&gt;没有以后怎么办。&lt;/p&gt;&lt;p&gt;没有这段关系最终会走到哪里。&lt;/p&gt;&lt;p&gt;甚至连所谓“永远”，也只是一个说出口时并不觉得沉重的词。&lt;/p&gt;&lt;p&gt;只是喜欢。&lt;/p&gt;&lt;p&gt;于是便靠近一些。&lt;/p&gt;&lt;p&gt;仅此而已。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;那些没有日期的日子&lt;a href=&quot;#那些没有日期的日子&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;2021年5月17日，我至今还记得。&lt;/p&gt;&lt;p&gt;年轻的时候，总喜欢记住故事开始的日期。&lt;/p&gt;&lt;p&gt;后来才知道，真正值得记住的，其实不是哪一天开始。&lt;/p&gt;&lt;p&gt;而是开始以后，那些没有日期的日子。&lt;/p&gt;&lt;p&gt;一起吃过多少顿饭。&lt;/p&gt;&lt;p&gt;绕过多少圈操场。&lt;/p&gt;&lt;p&gt;等过多少次下课。&lt;/p&gt;&lt;p&gt;说过多少句无关紧要的话。&lt;/p&gt;&lt;p&gt;没有人会把这些写进纪念册。&lt;/p&gt;&lt;p&gt;日历也不会特意替你保存。&lt;/p&gt;&lt;p&gt;它们只是一天一天地发生，然后一天一天地过去。&lt;/p&gt;&lt;p&gt;直到有一天，你忽然发现，那些日子竟然已经全部成为过去。&lt;/p&gt;&lt;p&gt;这时候再去寻找，却连一个准确的日期都没有。&lt;/p&gt;&lt;p&gt;只剩下一种模糊的感觉。&lt;/p&gt;&lt;p&gt;好像那几年总是有风。&lt;/p&gt;&lt;p&gt;总是有人在身边。&lt;/p&gt;&lt;p&gt;总觉得明天还很长。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;真正的告别很少有一场郑重的告别&lt;a href=&quot;#真正的告别很少有一场郑重的告别&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;高三以后，很多事情开始不再像从前。&lt;/p&gt;&lt;p&gt;成绩掉下去。&lt;/p&gt;&lt;p&gt;心也乱起来。&lt;/p&gt;&lt;p&gt;曾经以为笔直向前的路，突然生出许多岔口。&lt;/p&gt;&lt;p&gt;那个考了141分以后觉得未来理所当然会很好的人，也渐渐沉默下来。&lt;/p&gt;&lt;p&gt;现在回头看，那大概是我第一次真正意识到：&lt;/p&gt;&lt;p&gt;人生并不会因为你年轻，就永远朝着自己想象的方向去。&lt;/p&gt;&lt;p&gt;有些人会离开。&lt;/p&gt;&lt;p&gt;有些事情会失控。&lt;/p&gt;&lt;p&gt;有些努力没有结果。&lt;/p&gt;&lt;p&gt;有些承诺说出口的时候是真的，后来失效的时候也是真的。&lt;/p&gt;&lt;p&gt;我们总是在很年轻的时候，以为遗憾来自“没有得到”。&lt;/p&gt;&lt;p&gt;后来才发现，遗憾更多时候来自——&lt;/p&gt;&lt;p&gt;曾经得到过。&lt;/p&gt;&lt;p&gt;只是那时不知道那就是最后一次。&lt;/p&gt;&lt;p&gt;最后一次并肩走过那条路。&lt;/p&gt;&lt;p&gt;最后一次坐在那个座位。&lt;/p&gt;&lt;p&gt;最后一次晚自习后一起离开。&lt;/p&gt;&lt;p&gt;最后一次很自然地说：&lt;/p&gt;&lt;p&gt;明天见。&lt;/p&gt;&lt;p&gt;所谓告别，很少真的有一场郑重的告别。&lt;/p&gt;&lt;p&gt;更多时候，它只是一个普通下午。&lt;/p&gt;&lt;p&gt;大家像往常一样分开。&lt;/p&gt;&lt;p&gt;谁也没有回头。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;离开那间教室以后&lt;a href=&quot;#离开那间教室以后&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;毕业以后，我很少再梦见考试。&lt;/p&gt;&lt;p&gt;倒是偶尔会梦见学校。&lt;/p&gt;&lt;p&gt;梦里的教学楼并没有什么变化。&lt;/p&gt;&lt;p&gt;走廊还是那样长。&lt;/p&gt;&lt;p&gt;教室里的灯还是白的。&lt;/p&gt;&lt;p&gt;有人趴在桌上睡觉，有人在后排讲话。&lt;/p&gt;&lt;p&gt;我走进去，也没有人觉得奇怪。&lt;/p&gt;&lt;p&gt;仿佛我只是出去了一会儿。&lt;/p&gt;&lt;p&gt;可醒来以后，要很久才能反应过来：&lt;/p&gt;&lt;p&gt;原来我已经离开那里很多年了。&lt;/p&gt;&lt;p&gt;有一次我忽然想到，人这一生会拥有很多房间。&lt;/p&gt;&lt;p&gt;后来住过的房子可能比那间教室宽敞得多，桌子也更好，灯也更亮。&lt;/p&gt;&lt;p&gt;可唯独那间教室，一旦走出去以后，就再也不能真正回去。&lt;/p&gt;&lt;p&gt;就算哪天重新推开门，也已经只是一个来访的人。&lt;/p&gt;&lt;p&gt;自己的座位坐着别人。&lt;/p&gt;&lt;p&gt;黑板上写着别人的课程。&lt;/p&gt;&lt;p&gt;窗外还是同一棵树。&lt;/p&gt;&lt;p&gt;可故事已经换了一批人。&lt;/p&gt;&lt;p&gt;那一刻大概才算真正明白了什么叫时间。&lt;/p&gt;&lt;p&gt;它不会毁掉什么。&lt;/p&gt;&lt;p&gt;它甚至让很多东西原封不动地留在那里。&lt;/p&gt;&lt;p&gt;只是把你从里面拿走。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;时间开始有了价格&lt;a href=&quot;#时间开始有了价格&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;后来，我也渐渐成了一个会计算的人。&lt;/p&gt;&lt;p&gt;会算得失，会想结果，会知道一件事情该不该做。&lt;/p&gt;&lt;p&gt;许多东西开始有了价格。&lt;/p&gt;&lt;p&gt;一次选择有代价。&lt;/p&gt;&lt;p&gt;一天时间有成本。&lt;/p&gt;&lt;p&gt;一件商品值多少钱，一项工作该投入多少精力，很多事情都可以慢慢算清楚。&lt;/p&gt;&lt;p&gt;可越是这样，我越想起十六七岁的自己。&lt;/p&gt;&lt;p&gt;那个时候的我，恰恰什么都不算。&lt;/p&gt;&lt;p&gt;会把一个下午完整地交给一场游戏。&lt;/p&gt;&lt;p&gt;会为了一个人多绕半圈操场。&lt;/p&gt;&lt;p&gt;会在晚自习前浪费十分钟，只为了说几句如今已经记不得的话。&lt;/p&gt;&lt;p&gt;会在走廊里踢一只毫无用处的毽子。&lt;/p&gt;&lt;p&gt;会望着窗外发呆。&lt;/p&gt;&lt;p&gt;会觉得一天太长。&lt;/p&gt;&lt;p&gt;会盼下课。&lt;/p&gt;&lt;p&gt;会盼周末。&lt;/p&gt;&lt;p&gt;会盼长大。&lt;/p&gt;&lt;p&gt;我们用最昂贵的东西，做着最没有意义的事。&lt;/p&gt;&lt;p&gt;而这大概就是青春。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;如果现在回到从前&lt;a href=&quot;#如果现在回到从前&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我有时会想，如果现在真的给我一次机会，让我回到从前，我会不会改变些什么。&lt;/p&gt;&lt;p&gt;也许不会。&lt;/p&gt;&lt;p&gt;我甚至不想告诉那个少年未来会发生什么。&lt;/p&gt;&lt;p&gt;不告诉他哪次考试会失败。&lt;/p&gt;&lt;p&gt;不告诉他谁最后会离开。&lt;/p&gt;&lt;p&gt;不告诉他应该珍惜谁。&lt;/p&gt;&lt;p&gt;也不告诉他哪些选择以后会后悔。&lt;/p&gt;&lt;p&gt;因为一旦什么都提前知道，那就不再是青春了。&lt;/p&gt;&lt;p&gt;我只想在某个傍晚远远地看他一眼。&lt;/p&gt;&lt;p&gt;看他训练结束，站在操场边喘气。&lt;/p&gt;&lt;p&gt;看他低头看看时间，觉得晚自习快迟到了。&lt;/p&gt;&lt;p&gt;看他匆匆往教学楼走。&lt;/p&gt;&lt;p&gt;晚霞落在背后。&lt;/p&gt;&lt;p&gt;校服被风吹起来一点。&lt;/p&gt;&lt;p&gt;他不知道很多年以后，会有人如此怀念这一刻。&lt;/p&gt;&lt;p&gt;他只是往前跑。&lt;/p&gt;&lt;p&gt;甚至嫌天黑得太慢。&lt;/p&gt;&lt;p&gt;我不会叫住他。&lt;/p&gt;&lt;p&gt;只是想替很多年后的自己，多看一会儿。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;青春昂贵在哪里&lt;a href=&quot;#青春昂贵在哪里&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;现在我终于知道，青春昂贵在哪里。&lt;/p&gt;&lt;p&gt;不是因为年轻。&lt;/p&gt;&lt;p&gt;不是因为爱情。&lt;/p&gt;&lt;p&gt;也不是因为那几年发生过多少值得讲述的故事。&lt;/p&gt;&lt;p&gt;它昂贵，只因为一切都不能重来。&lt;/p&gt;&lt;p&gt;2020年的秋天不能。&lt;/p&gt;&lt;p&gt;第一次月考的141不能。&lt;/p&gt;&lt;p&gt;走廊里飞起来的毽子不能。&lt;/p&gt;&lt;p&gt;操场黄昏的风不能。&lt;/p&gt;&lt;p&gt;晚自习前的十几分钟不能。&lt;/p&gt;&lt;p&gt;那个曾经相信“以后还有很多”的自己，也不能。&lt;/p&gt;&lt;p&gt;而最让人无可奈何的是——&lt;/p&gt;&lt;p&gt;这些东西真正属于我的时候，我从没觉得它们稀有。&lt;/p&gt;&lt;p&gt;我嫌时间走得慢。&lt;/p&gt;&lt;p&gt;嫌晚自习太长。&lt;/p&gt;&lt;p&gt;嫌高三难熬。&lt;/p&gt;&lt;p&gt;一心想往前走。&lt;/p&gt;&lt;p&gt;后来真的走了很远，才忍不住频频回头。&lt;/p&gt;&lt;p&gt;原来人总是在青春里盼望远方，&lt;/p&gt;&lt;p&gt;又在远方里，想念青春。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;在远方里想念青春&lt;a href=&quot;#在远方里想念青春&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;如今再经过下雨的夜晚，我有时还是会停下来听一会儿。&lt;/p&gt;&lt;p&gt;雨落在窗沿，落在屋檐，也落进一些已经很远的年份里。&lt;/p&gt;&lt;p&gt;我不知道从前那个少年是否也曾在某个晚自习里听过同一场雨。&lt;/p&gt;&lt;p&gt;也许有。&lt;/p&gt;&lt;p&gt;也许没有。&lt;/p&gt;&lt;p&gt;其实都不重要了。&lt;/p&gt;&lt;p&gt;重要的是，他曾经在那里。&lt;/p&gt;&lt;p&gt;我也曾经在那里。&lt;/p&gt;&lt;p&gt;那几年并不完美。&lt;/p&gt;&lt;p&gt;有欢喜，也有狼狈；有得到，也有失去。&lt;/p&gt;&lt;p&gt;可它们终于还是一点一点，把今天的我送到了这里。&lt;/p&gt;&lt;p&gt;于是有一天，当我回过头去，我才发现——&lt;/p&gt;&lt;p&gt;原来我这一生最奢侈的时候，并不是后来拥有了什么。&lt;/p&gt;&lt;p&gt;而是十七岁那年，&lt;/p&gt;&lt;p&gt;我有整整一个下午可以挥霍，&lt;/p&gt;&lt;p&gt;有一个明天可以相信，&lt;/p&gt;&lt;p&gt;有一些人以为永远不会走散，&lt;/p&gt;&lt;p&gt;还有一个尚且不知道岁月为何物的自己。&lt;/p&gt;&lt;p&gt;那时候，我什么都没有。&lt;/p&gt;&lt;p&gt;却好像什么都有。&lt;/p&gt;&lt;p&gt;而如今，我终于知道它们有多贵。&lt;/p&gt;&lt;p&gt;只是再也买不回来了。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>朝朝听雨，岁岁有风</title><link>https://rainzt.cn/posts/chao-chao-listening-rain-years-wind/</link><guid isPermaLink="true">https://rainzt.cn/posts/chao-chao-listening-rain-years-wind/</guid><description>今天不说遗憾，只讲讲我和她的故事。写给那段被照亮过的高中岁月，也写给如今终于学会从容回望的自己。</description><pubDate>Fri, 14 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;section&gt;&lt;h2&gt;写在故事之前&lt;a href=&quot;#写在故事之前&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;今天是 2026 年 8 月 14 日。距离我的上一段感情，已经过去一年半了。&lt;/p&gt;&lt;p&gt;翻相册的时候，我看到了最近打出的高光操作——金标镜，多少有点想显摆。也就是这一点小小的得意，把我拽回了当年的那些事情。&lt;/p&gt;&lt;p&gt;我的博客叫“朝朝听雨”，ZZT 是我本名的缩写，Y 是她名字的最后一字，拼在一起便是“听雨”，对应着 Rain。这几年下来，不管是网名，还是做过的项目、写过的作品，落款里总藏着这几个字母。&lt;/p&gt;&lt;p&gt;说起来也算我痴情。倒不是刻意放不下，只是习惯了，也舍不得抹掉那些年月的痕迹。也是想提醒自己：物物而不物于物，念念而不念于念。&lt;/p&gt;&lt;p&gt;“朝朝听雨”现在的寓意，已经不再是我和她之间的专属暗码，而是独属于我个人的精神角落。它装得下年少的青春旧事，也盛得下如今的成熟从容；记得住被照亮的恩情，也将这份光继续延续下去。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;今天不说遗憾，只讲讲我和她的故事。&lt;/p&gt;&lt;/blockquote&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;故事从高一开始&lt;a href=&quot;#故事从高一开始&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;她是个很特别的姑娘，憨憨的，带着点没被磨平的傻气，和身边很多人都不一样。我总爱说她憨批，看着她的时候，总像看见初中时的自己——干净、透亮，对世界还抱着毫无保留的热忱。&lt;/p&gt;&lt;p&gt;故事要从 2020 年的秋天说起，那年我刚上高一。&lt;/p&gt;&lt;p&gt;九月入学。初三我当过半年班长，偏巧我爸认识广州大同中学的老师，而我们班主任恰好是从那所学校调过来的。我本来打定主意高中不再管事，还是被班主任直接任命成了班长。现在回头看，倒也算因祸得福。&lt;/p&gt;&lt;p&gt;高中的日子总过得缓慢又无聊。下课闲得发慌时，我就和几个相熟的男女同学凑在走廊踢毽子。起初只有零星几个人，后来队伍越拉越长，她也混在了里面。&lt;/p&gt;&lt;p&gt;你或许以为我们的故事是从毽子开始的。&lt;/p&gt;&lt;p&gt;其实不是。那时候认识归认识，我依旧我行我素。晚自习总爱坐到讲台上，和前排同学天南海北地聊，要么就拉着人玩飞行棋、真心话大冒险、五子棋。第一次月考我数学考了 141 分，拿了年级第一，便更没把心思放在学习上。&lt;/p&gt;&lt;p&gt;真正的转机是我舍友阿 B。阿 B 和他朋友小黑，加上她和她闺蜜，几个人总凑在一起开黑，连跪到小黑都嫌他们菜，嚷嚷着要找个大神带。于是他们找上了我。&lt;/p&gt;&lt;p&gt;就这么着，我们才算真正熟络起来。她玩法师，走中路。&lt;/p&gt;&lt;p&gt;从蝉鸣聒噪的盛夏，到呵气成雾的深冬。白天我们在走廊踢毽子、传纸条，课上偷偷讲小话，晚自习凑在一起写作业、玩桌游、扯些没营养的闲话；周末就抱着手机组队打王者，日子热热闹闹地往前跑。原本对她没什么特别感觉的我，不知不觉间，就动了心。&lt;/p&gt;&lt;p&gt;借着班长的便利，我把自己的座位调到了第一列第一个，她就坐在我身后。抬头低头总能撞见，聊天的时间越来越长，距离也越靠越近。&lt;/p&gt;&lt;p&gt;2021 年 5 月 17 日，我和她告白了。&lt;/p&gt;&lt;p&gt;我赌对了，她也喜欢我。&lt;/p&gt;&lt;p&gt;我们在一起了。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;我喜欢她&lt;a href=&quot;#我喜欢她&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;喜欢她干干净净的单纯。连故意逗她、看她气鼓鼓又拿我没办法的样子，心里都涨着满满的欢喜；喜欢她身上淡淡的、很好闻的香味，每次凑近都觉得莫名安心；喜欢她藏不住的可爱，一颦一笑都带着少年人独有的鲜活；更喜欢她眼睛里的光，亮闪闪的，像盛着整片星空。&lt;/p&gt;&lt;p&gt;我也曾有过意气风发的时候，有过属于自己的耀眼时刻。可那段日子里，我整个人都颓丧得厉害，世界灰蒙蒙的，连自己的光都快熄灭了。是她一步步走过来，把光重新递到了我手里，把我的日子照得透亮。&lt;/p&gt;&lt;p&gt;在一起之后的日子，像裹了层橘子味的糖衣。&lt;/p&gt;&lt;p&gt;上课的时候，我会趁老师转身写板书，悄悄往后递小纸条，写些无聊的废话，或者画个丑兮兮的小人；课间她会戳我的后背，递过来一颗水果糖，或者跟我吐槽刚才那道数学题有多离谱。晚自习我依旧坐讲台上，目光却总不自觉地飘向她的座位，撞见她抬头看我，就各自偷偷弯起嘴角。&lt;/p&gt;&lt;p&gt;周末除了打游戏，我们也会约着出去。去公园漫无目的地散步，去街边小店吃牛杂，去电影院看喜欢的动漫、电影。她走路总爱牵着我的手，而我总是觉得人多而不好意思。那时候总觉得日子很长，长到我们可以这样并肩走很久很久。&lt;/p&gt;&lt;p&gt;我把游戏 ID 改成了藏着她名字的字符，把日常的细碎都写进仅彼此可见的空间里，连后来做第一个小项目时，下意识就用了“听雨”的名字。那时候没想过以后会分开，只觉得这份喜欢要藏进所有我能留下的痕迹里。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;高二：走进彼此的日常&lt;a href=&quot;#高二走进彼此的日常&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;到了高二，我们的关系才算真正往彼此的生活里扎了根。&lt;/p&gt;&lt;p&gt;刚升学年那会儿，我们还守着各自的节奏，吃饭结伴旧友，课间各有圈子，算不上时时刻刻黏在一起。变故藏在那年的秋风里：她和相交多年的闺蜜闹了绝交，原本热热闹闹的日常忽然空了一大块。那段日子她总独来独往，连吃饭都缩在食堂角落，安安静静的。&lt;/p&gt;&lt;p&gt;我便顺理成章地走进了她的空档里。午饭陪她挤在食堂长桌，晚饭后绕着塑胶跑道慢慢散步，从教学楼前的香樟树下，走到田径场的铁丝网边。从前那些分给朋友的时间，渐渐都换成了彼此的陪伴。&lt;/p&gt;&lt;p&gt;后来我成了体育生，这份陪伴也没被训练冲散，反倒成了更笃定的日常。每天下午下课铃响，我直奔操场训练，她就安安静静地等我——有时在看台坐着翻书，有时回教室写作业，算着我结束的时间。等我带着一身汗气跑完练完，两人再慢悠悠晃去食堂吃晚饭。&lt;/p&gt;&lt;p&gt;饭后她先回教室自习，我回宿舍冲澡换衣，赶在晚自修开始前的十几分钟，凑到她桌边挨着说些没营养的闲话，腻歪好一阵才舍得回自己座位。&lt;/p&gt;&lt;p&gt;那些日子重复又细碎，却比任何告白都更让人安心。好像从那时起，我们才不只是“在一起的两个人”，而是真正走进了彼此的日常里。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;高三：遗憾&lt;a href=&quot;#高三遗憾&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;转眼到了高三，日子被高考的倒计时推着往前走，可我整个人却陷入一种浑浑噩噩的状态。明明已经迫近高考，我却没有半点奋力冲刺的劲头，成绩不进反退，越埋头学，越觉得力不从心，分数一次比一次不尽如人意。&lt;/p&gt;&lt;p&gt;是我的选择与失误，最后让我们没能奔赴同一所大学。&lt;/p&gt;&lt;p&gt;异地的距离横亘在我们之间，也埋下了往后走散的伏笔。或许这算不上分开的直接导火索，却是绕不开的间接缘由。少年人的感情，抵不住现实的落差。当两个人不能再朝夕相处，那些日常的陪伴、课间的碰面，很多矛盾与隔阂，也由此慢慢滋生。&lt;/p&gt;&lt;p&gt;现在回头看，这便是青春里一大桩无可奈何的遗憾。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;分开之后&lt;a href=&quot;#分开之后&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;后来的故事，那就不是那么开心的故事了，其实也俗套：分班、升学、磨合、争吵。少年人的感情热烈又脆弱，曾经以为能撑过所有难关，最终还是走散在了半路。&lt;/p&gt;&lt;p&gt;分开后的那段日子其实不好过，我花了很久才慢慢走出来。只是时至今日，再想起那些年月，最先冒出来的依旧是走廊的风、毽子落地的声响、晚自习的暖黄灯光，还有她眼睛里亮闪闪的光。&lt;/p&gt;&lt;p&gt;我没改掉那些带着她痕迹的名字，不是还在等什么，只是舍不得那段被照亮的岁月。毕竟是她，在我最黯淡的年纪里，认认真真地，做了我的光。&lt;/p&gt;&lt;p&gt;如今再翻起游戏截图，想起当年一手露娜带飞的意气，想起金标到手时迫不及待想分享的心情，想起那些藏在 ID 和名字里的少年心事，只剩一点淡淡的、温柔的怀念。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;朝朝听雨，岁岁有风&lt;a href=&quot;#朝朝听雨岁岁有风&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;最后呢，希望大家：物物而不物于物，念念而不念于念。&lt;/p&gt;&lt;p&gt;而我，&lt;/p&gt;&lt;p&gt;朝朝听雨，岁岁有风。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>Agent 赋予电商自动化：从业务规则到可控的自动上架框架</title><link>https://rainzt.cn/posts/agent-ecommerce-automation/</link><guid isPermaLink="true">https://rainzt.cn/posts/agent-ecommerce-automation/</guid><description>从一套自动上架系统的实践出发，拆解 AI Agent、Playwright、CDP 和 ERP 如何组合成一套可观察、可回滚、可扩展的跨境电商自动化框架。</description><pubDate>Sun, 09 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;2026 年 5 月底，我第一次把 AI 真正放进跨境电商的日常工作里。不是让它帮我写一段宣传文案，也不是问它一个 API 怎么调用，而是让它参与搭建一套全自动上架系统。&lt;/p&gt;
&lt;p&gt;这件事最初的动机很朴素：每天重复打开页面、整理表格、上传图片、匹配规格、修改标题，再盯着页面确认有没有保存成功。单次操作并不难，难的是每天重复几十次以后，人的注意力会先于代码耗尽。&lt;/p&gt;
&lt;p&gt;后来我发现，电商自动化真正难的部分从来不是“让浏览器点一下按钮”，而是把业务规则讲清楚，再把每个容易出错的环节做成可以观察、可以重试、可以追责的流程。下面就记录这套思路，也把我在实践中踩过的坑一起写下来。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;先说结论：自动化的核心不是点击，而是规则&lt;a href=&quot;#先说结论自动化的核心不是点击而是规则&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;很多人第一次做浏览器自动化，会从录制操作开始：打开页面、点击按钮、填入文本、上传文件，然后把录制结果保存成脚本。这种方式在演示阶段很直观，但一旦页面结构、商品规格或图片顺序发生变化，脚本就会变得非常脆弱。&lt;/p&gt;&lt;p&gt;我现在更愿意把一次上架拆成四层：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;数据层&lt;/strong&gt;：表格、图片、视频、规格和标题素材分别是什么。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;规则层&lt;/strong&gt;：什么图片属于哪个 SKU，什么规格允许发布，标题如何拼接。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;执行层&lt;/strong&gt;：由 Playwright、CDP 和平台适配器完成页面操作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;观察层&lt;/strong&gt;：任务做到哪一步、为什么失败、是否进入异常队列，都要有记录。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;可以把它理解成下面这条链路：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;flowchart LR
    A[表格与素材] --&amp;gt; B[规则解析]
    B --&amp;gt; C[任务队列]
    C --&amp;gt; D[平台 Worker]
    D --&amp;gt; E[Playwright + CDP]
    E --&amp;gt; F[页面校验]
    F --&amp;gt; G[截图、日志、结果]
    G --&amp;gt; H{是否成功}
    H --&amp;gt;|是| I[归档]
    H --&amp;gt;|否| J[重试或异常队列]&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;我希望它是全自动执行，但不是无法解释的黑盒。自动化可以替我完成机械动作，每一步也必须留下可以追溯的痕迹。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;为什么我更推荐从 ERP 开始&lt;a href=&quot;#为什么我更推荐从-erp-开始&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;理论上，直接操控店铺后台当然也能实现自动上架。但在实践中，我更推荐先围绕 ERP 做自动化，原因主要有三个。&lt;/p&gt;&lt;section&gt;&lt;h3&gt;1. ERP 的数据更结构化&lt;a href=&quot;#1-erp-的数据更结构化&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;商品名称、规格、库存、价格、图片和视频通常都已经被整理成字段。自动化系统不必从一个复杂页面里猜测“这段文字到底是什么”，而是可以先读取结构化数据，再按照模板生成任务。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;2. 风险边界更清楚&lt;a href=&quot;#2-风险边界更清楚&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;如果所有平台都直接接入，某个平台的小改版就可能影响整条流程。ERP 作为中间层，可以把共用的数据准备、素材管理和任务记录放在一起，再把 Shopee、TikTok Shop 等平台的差异放进各自的 Worker 里。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;3. 多平台差异可以被隔离&lt;a href=&quot;#3-多平台差异可以被隔离&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Shopee 和 TikTok Shop 的页面入口、编辑按钮、保存逻辑和成功提示并不一样。如果强行写成一套流程，后期会出现大量条件分支。更稳定的方式是共用底座，平台只负责自己的适配。&lt;/p&gt;&lt;p&gt;指纹浏览器同样可以做自动化，而且对某些场景很方便。但账号风险、登录状态和长期大规模运行是否稳定，不能只凭一次成功运行就下结论。使用这类方案时，需要自行评估平台规则、账号权限和风控边界，不要把“能跑通”误认为“可以无限放大”。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;站外浏览器自动化接口与支持框架&quot; loading=&quot;lazy&quot; width=&quot;1267&quot; height=&quot;602&quot; src=&quot;/_astro/image1.BmpDIoV6_BAozs.webp&quot; srcset=&quot;/_astro/image1.BmpDIoV6_ZAQj3T.webp 640w, /_astro/image1.BmpDIoV6_Z14ppSN.webp 750w, /_astro/image1.BmpDIoV6_Z1wOd61.webp 828w, /_astro/image1.BmpDIoV6_12YaOF.webp 1080w, /_astro/image1.BmpDIoV6_BAozs.webp 1267w&quot; /&gt;&lt;figcaption&gt;站外浏览器自动化接口与支持框架&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;技术底座：Playwright、CDP 与固定 Chromium&lt;a href=&quot;#技术底座playwrightcdp-与固定-chromium&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我的技术组合是 Playwright + CDP + 固定版本 Chromium。&lt;/p&gt;&lt;p&gt;Playwright 负责定位元素、填写表单、上传素材、等待页面状态和截取结果；CDP 负责连接已经打开的浏览器；固定版本 Chromium 则尽量减少不同浏览器版本带来的接口差异。&lt;/p&gt;&lt;p&gt;连接浏览器的大致代码如下：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;chromium&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Browser&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;playwright&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;connectBrowser&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cdpUrl&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;Browser&lt;/span&gt;&lt;span&gt;&amp;gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;chromium&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;connectOverCDP&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cdpUrl&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;browser&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;connectBrowser&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;http://127.0.0.1:9222&quot;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;browser&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;contexts&lt;/span&gt;&lt;span&gt;()[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;browser&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;newContext&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;pages&lt;/span&gt;&lt;span&gt;()[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;newPage&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;goto&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;https://example.com/products&quot;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;waitUntil&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;domcontentloaded&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;固定 Chromium 的代价是项目体积会大一些，但它解决了两个实际问题：一是不同浏览器版本的接口行为不完全一致，二是图片上传顺序、文件选择器和页面等待时机更容易出现细微差异。对于需要重复运行的任务，我更愿意牺牲一些体积，换取结果的一致性。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;固定版本 Chromium 运行环境&quot; loading=&quot;lazy&quot; width=&quot;1103&quot; height=&quot;632&quot; src=&quot;/_astro/image2.D2sJ9Ii4_Z2s6p9U.webp&quot; srcset=&quot;/_astro/image2.D2sJ9Ii4_Z2qc6iG.webp 640w, /_astro/image2.D2sJ9Ii4_Z1sLVlx.webp 750w, /_astro/image2.D2sJ9Ii4_Z26kUJm.webp 828w, /_astro/image2.D2sJ9Ii4_Z1d3ScW.webp 1080w, /_astro/image2.D2sJ9Ii4_Z2s6p9U.webp 1103w&quot; /&gt;&lt;figcaption&gt;固定版本 Chromium 运行环境&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;需要注意的是，CDP 不是“绕过平台规则”的通行证。它只是让自动化程序连接到浏览器调试端口，实际仍然是在页面上进行正常的用户操作。登录状态、权限和平台规则都应该由运营者自己负责。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;AI Agent 到底帮了什么忙&lt;a href=&quot;#ai-agent-到底帮了什么忙&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我并没有让 AI 直接生成一个“万能上架机器人”，而是把它当成一个会观察、会提问、会整理的开发搭档。&lt;/p&gt;&lt;p&gt;它最有价值的地方，通常在下面几个环节：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;观察页面&lt;/strong&gt;：读取当前页面的按钮、表格、规格和错误提示。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拆解流程&lt;/strong&gt;：把“上架一个商品”拆成可验证的步骤。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;定位问题&lt;/strong&gt;：当选择器失效时，根据截图和日志判断是页面未加载、入口变化还是权限问题。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;整理规则&lt;/strong&gt;：把散落在聊天记录和经验里的判断，整理成配置文件和校验函数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生成样板代码&lt;/strong&gt;：先快速搭出 Worker、日志、重试和页面对象，再由我检查细节。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;最重要的一点是：AI 可以帮我更快地把想法变成可运行的东西，但它不知道我的业务边界。比如“这个 SKU 的第 3 张图不能上传”可能是一个临时异常，也可能是这个产品本来就没有第 3 张图。最终决定规则的人，仍然是我。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;两种业务场景，不能用同一套规则&lt;a href=&quot;#两种业务场景不能用同一套规则&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;POD 产品：规则稳定，适合批量化&lt;a href=&quot;#pod-产品规则稳定适合批量化&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;POD 产品通常有比较规整的数据和素材：款式、颜色、尺寸、主图、详情图和变体之间有明确关系。只要把模板定义清楚，就可以批量处理很多商品。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;搬运或铺货：差异更大，但同样可以全自动&lt;a href=&quot;#搬运或铺货差异更大但同样可以全自动&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;搬运和铺货同样可以全自动。它们通常需要翻译、清理厂家信息、生成场景图、判断平台类目、重新组织规格，规则确实比 POD 更复杂，但复杂不等于必须人工逐条确认。只要把这些判断拆成可配置的规则和可调用的 AI 步骤，就可以让采集、清洗、翻译、配图、SKU 生成、上架和发布全部自动跑完。&lt;/p&gt;&lt;p&gt;真正需要保留的是异常队列，而不是人工审批链：缺少必填字段、图片不符合尺寸、类目无法判断或平台返回风险提示时，任务暂停在可定位的节点，修正规则或补齐数据后再自动继续。我的做法是共享任务队列、日志和素材中心，但为不同产品线保留独立模板和校验策略。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;批量铺货与任务配置界面&quot; loading=&quot;lazy&quot; width=&quot;699&quot; height=&quot;832&quot; src=&quot;/_astro/image4.BdCfIs8t_1QLiPG.webp&quot; srcset=&quot;/_astro/image4.BdCfIs8t_2lqPbc.webp 640w, /_astro/image4.BdCfIs8t_1QLiPG.webp 699w&quot; /&gt;&lt;figcaption&gt;批量铺货与任务配置界面&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;图片与 SKU 的匹配：最容易被低估的难题&lt;a href=&quot;#图片与-sku-的匹配最容易被低估的难题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;图片文件名看起来很简单，真正处理起来却非常容易错位。&lt;/p&gt;&lt;p&gt;例如下面是一组商品素材：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;AB001-1.jpg  # AB001 主图&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;AB001-2.jpg  # AB001 详情图&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;AB001-3.jpg&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;AB001-4.jpg&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;AB001-5.jpg&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;AB002-1.jpg  # AB002 另一种颜色或规格的主图&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;AB002-2.jpg&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;最基础的规则是：同一个前缀属于同一个图片组，&lt;code&gt;-1&lt;/code&gt; 作为主图，其余作为详情图。但仅靠字符串切分还不够，至少要同时做三层校验：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AssetGroup&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cover&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;detail&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;[];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;groupAssets&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;files&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;[])&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AssetGroup&lt;/span&gt;&lt;span&gt;[] {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;groups&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Map&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;[]&amp;gt;();&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;of&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;files&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;match&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;)-(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;\d&lt;/span&gt;&lt;span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;\.&lt;/span&gt;&lt;span&gt;(png&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;jpe&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;g&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;webp)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;&lt;span&gt;match&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;continue&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; [, &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;match&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;groups&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; [];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;groups&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;&lt;span&gt;groups&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;entries&lt;/span&gt;&lt;span&gt;()].&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;&lt;span&gt;(([&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;list&lt;/span&gt;&lt;span&gt;]) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ordered&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;&lt;span&gt;list&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;/span&gt;&lt;span&gt;sort&lt;/span&gt;&lt;span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;aNo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Number&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;/-(&lt;/span&gt;&lt;span&gt;\d&lt;/span&gt;&lt;span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;\.&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)?.[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;999&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bNo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Number&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;match&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;/-(&lt;/span&gt;&lt;span&gt;\d&lt;/span&gt;&lt;span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;\.&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)?.[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;999&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;aNo&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bNo&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;cover&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ordered&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;detail&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ordered&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;代码只能完成“文件名分组”，不能代替业务校验。真正执行上传前，还要检查：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;当前 ERP 页面显示的 SKU 是否等于图片组的 &lt;code&gt;key&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;主图是否确实是 &lt;code&gt;-1&lt;/code&gt;，有没有缺号或重复编号。&lt;/li&gt;
&lt;li&gt;当前变体的颜色、尺寸与表格字段是否一致。&lt;/li&gt;
&lt;li&gt;当前任务有没有读到下一组产品的图片。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;第三点和第四点很关键。曾经遇到过流程全部显示成功，但图片被挂到了错误 SKU 上。因为系统只看到了“上传成功”，却没有验证“上传到了正确的位置”。自动化要判断的是业务结果，而不是浏览器动作有没有报错。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;批量编辑中的图片分组与 SKU 关系&quot; loading=&quot;lazy&quot; width=&quot;893&quot; height=&quot;424&quot; src=&quot;/_astro/image5.DHByjJdD_Z16iV2O.webp&quot; srcset=&quot;/_astro/image5.DHByjJdD_Z18yrsX.webp 640w, /_astro/image5.DHByjJdD_1mAPw2.webp 750w, /_astro/image5.DHByjJdD_DafiL.webp 828w, /_astro/image5.DHByjJdD_Z16iV2O.webp 893w&quot; /&gt;&lt;figcaption&gt;批量编辑中的图片分组与 SKU 关系&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;变体图片上传位置校验&quot; loading=&quot;lazy&quot; width=&quot;806&quot; height=&quot;199&quot; src=&quot;/_astro/image6.BPrftriA_1kBfvS.webp&quot; srcset=&quot;/_astro/image6.BPrftriA_8zJa3.webp 640w, /_astro/image6.BPrftriA_Z19XzTO.webp 750w, /_astro/image6.BPrftriA_1kBfvS.webp 806w&quot; /&gt;&lt;figcaption&gt;变体图片上传位置校验&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;把平台操作写成 Worker，而不是一条超长脚本&lt;a href=&quot;#把平台操作写成-worker而不是一条超长脚本&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;一个平台 Worker 可以只负责一件事：接收标准化任务，完成这个平台的页面操作，并返回结构化结果。&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ListingTask&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;productId&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;variants&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Array&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;lt;{ &lt;/span&gt;&lt;span&gt;sku&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;images&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;[] }&amp;gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ListingResult&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ok&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;boolean&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;productId&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;step&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;publishToShopee&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;playwright&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;Page&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;task&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ListingTask&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;ListingResult&lt;/span&gt;&lt;span&gt;&amp;gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;getByPlaceholder&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;搜索商品&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;fill&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;task&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;productId&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;getByRole&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;button&quot;&lt;/span&gt;&lt;span&gt;, { &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;编辑&quot;&lt;/span&gt;&lt;span&gt; }).&lt;/span&gt;&lt;span&gt;click&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;getByLabel&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;商品标题&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;fill&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;task&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;uploadImages&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;task&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;variants&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;verifyVariants&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;task&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;variants&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;getByRole&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;button&quot;&lt;/span&gt;&lt;span&gt;, { &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;保存并发布&quot;&lt;/span&gt;&lt;span&gt; }).&lt;/span&gt;&lt;span&gt;click&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;getByText&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;发布成功&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;waitFor&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;visible&quot;&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;ok&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;productId&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;task&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;productId&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;step&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;published&quot;&lt;/span&gt;&lt;span&gt; };&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;ok&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;productId&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;task&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;productId&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;step&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;publish&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;35&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;instanceof&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Error&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;36&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;37&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;38&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;Shopee 与 TikTok Shop 的平台任务工作区&quot; loading=&quot;lazy&quot; width=&quot;1267&quot; height=&quot;587&quot; src=&quot;/_astro/image3.DRhkBReV_1kLwUr.webp&quot; srcset=&quot;/_astro/image3.DRhkBReV_Z39hHh.webp 640w, /_astro/image3.DRhkBReV_Z28QWWJ.webp 750w, /_astro/image3.DRhkBReV_2sUnDY.webp 828w, /_astro/image3.DRhkBReV_Z2nySsK.webp 1080w, /_astro/image3.DRhkBReV_1kLwUr.webp 1267w&quot; /&gt;&lt;figcaption&gt;Shopee 与 TikTok Shop 的平台任务工作区&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;这里有两个值得保留的习惯：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;每完成一个高风险步骤，就记录一个明确的 &lt;code&gt;step&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;返回结构化结果，而不是只在控制台打印一句“失败了”。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这样做的好处是，后续可以把 Shopee、TikTok Shop 或其他平台的结果统一放进任务历史里，界面不需要知道每个平台内部的细节。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;失败重试，但不要无脑重试&lt;a href=&quot;#失败重试但不要无脑重试&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;网络波动、页面响应慢和临时弹窗都可能造成偶发失败，所以任务需要重试机制。但“失败就再点一次”也很危险，尤其是发布、扣库存或创建优惠券这类不可逆动作。&lt;/p&gt;&lt;p&gt;我会把步骤分成三类：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;可安全重试&lt;/strong&gt;：打开页面、读取列表、等待元素、获取截图。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;需要幂等判断后重试&lt;/strong&gt;：上传图片、保存草稿、更新标题。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不做盲目重试&lt;/strong&gt;：最终发布、扣库存、创建订单等有副作用的操作，先做幂等校验，再由任务状态机自动决定是否继续。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;一个简单的重试包装器可以这样写：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;retry&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;&amp;gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;action&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;&amp;gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;options&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;attempts&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;delayMs&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;T&lt;/span&gt;&lt;span&gt;&amp;gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;attempts&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;options&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;attempts&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;delayMs&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;options&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;delayMs&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;800&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;lastError&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;unknown&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;attempts&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;action&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;lastError&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;attempts&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;resolve&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;setTimeout&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;resolve&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;delayMs&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;lastError&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;真正接入系统时，还要给重试加上截图、当前 URL、任务 ID 和步骤名。否则过几天回头看日志，只知道某个任务失败过，却不知道当时页面到底是什么状态。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;为什么一定要做可视化界面&lt;a href=&quot;#为什么一定要做可视化界面&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;自动化不是越黑盒越高级。对于电商任务，我希望界面至少能回答下面这些问题：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;当前正在处理哪一个商品、哪一个 SKU？&lt;/li&gt;
&lt;li&gt;已经完成了多少步，卡在哪一步？&lt;/li&gt;
&lt;li&gt;失败是网络问题、页面变化，还是业务规则不匹配？&lt;/li&gt;
&lt;li&gt;图片和规格有没有完成对应校验？&lt;/li&gt;
&lt;li&gt;是否保存了关键截图和日志？&lt;/li&gt;
&lt;li&gt;这个任务可以安全重试，还是应该进入异常队列？&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;所以我会把运行面板当成产品的一部分，而不是开发阶段的调试工具。实时日志、成功/失败统计、暂停/恢复、截图留档和历史记录，都会直接影响运营人员是否敢于使用这套系统。&lt;/p&gt;&lt;p&gt;最理想的状态不是“所有任务都永远成功”，而是任务失败时，系统能在几十秒内定位原因，把任务放回重试队列或异常队列，而不是让人重新从头操作。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;从自动上架继续往外扩展&lt;a href=&quot;#从自动上架继续往外扩展&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;自动上架只是一个入口。围绕同一套数据、素材和任务系统，后续可以继续扩展：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;AI 生成商品主图、换背景和场景图；&lt;/li&gt;
&lt;li&gt;根据平台规则自动生成多语言标题和描述；&lt;/li&gt;
&lt;li&gt;采集供应商商品并清理厂家信息；&lt;/li&gt;
&lt;li&gt;跑图、算价、整理利润和库存；&lt;/li&gt;
&lt;li&gt;批量设置折扣、优惠券和活动价格；&lt;/li&gt;
&lt;li&gt;在发布前做违禁词、图片尺寸和品牌信息检查；&lt;/li&gt;
&lt;li&gt;用队列调度不同店铺，并保留团队操作审计。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这些功能看起来分散，其实共享的是同一条底座：结构化数据、可追踪的任务、明确的规则和可靠的执行器。底座稳定以后，新增一个工具不再是复制一份脚本，而是接入同一套任务系统。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;AI Agent 串联电商自动化工作流&quot; loading=&quot;lazy&quot; width=&quot;924&quot; height=&quot;439&quot; src=&quot;/_astro/image7.BQC36XSQ_Z10dgVP.webp&quot; srcset=&quot;/_astro/image7.BQC36XSQ_7aeGB.webp 640w, /_astro/image7.BQC36XSQ_Z2rQB7k.webp 750w, /_astro/image7.BQC36XSQ_1SSVtl.webp 828w, /_astro/image7.BQC36XSQ_Z10dgVP.webp 924w&quot; /&gt;&lt;figcaption&gt;AI Agent 串联电商自动化工作流&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;最后：Agent 替我完成动作，但不会替我承担判断&lt;a href=&quot;#最后agent-替我完成动作但不会替我承担判断&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这次实践让我最明显的感受是，AI 把“想法到原型”的距离压缩得非常短。以前可能要先查文档、搭环境、写一堆样板代码，才能看到第一个页面；现在可以先把业务目标、页面截图和现有规则交给 AI，让它帮助我拆分模块、定位元素、补齐日志，再由我持续校正。&lt;/p&gt;&lt;p&gt;但效率提高以后，真正重要的判断反而更贵了：为什么要做这个工具？哪些规则必须固定？哪些异常不能自动处理？出了问题由谁确认和负责？&lt;/p&gt;&lt;p&gt;Agent 可以观察页面、整理数据、调用工具，也可以陪我把一个流程逐步变成代码。它能替我完成大量机械执行，却不能替我决定什么结果才算正确。&lt;/p&gt;&lt;p&gt;所以我对“AI 赋予电商自动化”的理解，不是让人从系统里消失，而是让人从重复点击和逐条审批里出来，把时间留给规则设计、体验和异常处理。全自动铺货并不意味着盲目放行，而是让每个判断都有规则、有校验、有记录，只有真正无法判断的异常才会被单独拎出来。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>作品展示页动画实现原理</title><link>https://rainzt.cn/posts/portfolio-showcase-animation-principles/</link><guid isPermaLink="true">https://rainzt.cn/posts/portfolio-showcase-animation-principles/</guid><description>从滚动读数、目标与渲染进度、分镜区间出发，拆解 Rainzt.cn 作品展示页当前的工程化实现：长滚动固定舞台、requestAnimationFrame 平滑、媒体解码、快门转场与内容可访问性边界。</description><pubDate>Mon, 27 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最初做作品展示页时，我把它理解成“让五张卡片依次动起来”。后来页面经历了几轮调整，真正变化的不是多加了几个位移或淡入，而是实现的重心从“堆动画效果”转成了“维护一条可控制、可验证的滚动时间轴”。&lt;/p&gt;
&lt;p&gt;现在的页面仍然没有引入重型动画库。Astro 负责结构和资源声明，CSS 负责固定舞台、裁切和图层关系，原生 TypeScript 只做一件事：把浏览器滚动位置转换为稳定的分镜状态。卡片、黑场快门、角色、视频和结尾文案都消费同一个进度值，但它们不再直接绑定原始滚动事件。&lt;/p&gt;
&lt;p&gt;这篇文章记录作品展示页的实现思路。文中的区间、资源策略和代码片段以当时的实现为准，不再使用早期的 &lt;code&gt;760vh / 680vh&lt;/code&gt; 时间轴或“滚一下就立刻改样式”的写法。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;目标不是播放，而是让读者控制节奏&lt;a href=&quot;#目标不是播放而是让读者控制节奏&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;作品展示区有几个约束，它们决定了这不是一组普通的 CSS 入场动画：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;读者慢慢滚动时，每一张作品卡片都要有足够的停留时间；停下来时，画面也应停住。&lt;/li&gt;
&lt;li&gt;快速滚动、触摸板惯性和不同刷新率不应让画面突然跳帧。&lt;/li&gt;
&lt;li&gt;转场要有连续的图层关系，不能让黑场、角色和下一幕像三次互不相干的淡入。&lt;/li&gt;
&lt;li&gt;首次进入展示区时，图片解码不能集中阻塞主线程。&lt;/li&gt;
&lt;li&gt;动画不是作品信息的唯一载体；键盘和辅助技术用户仍应能在普通作品列表中访问每个链接。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;换句话说，这一页的输入不是“经过了多少秒”，而是“读者在叙事中的位置”。页面滚动位置只是输入信号，真正被渲染的是一个受控的 &lt;code&gt;0 ~ 1&lt;/code&gt; 进度。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;scroll / resize&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;目标进度 targetShowcaseProgress&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;requestAnimationFrame 平滑追赶&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;渲染进度 renderedShowcaseProgress&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;phaseAt() 分镜区间 -&amp;gt; 卡片 / 快门 / 结尾场景&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;固定舞台：用滚动距离换取阅读时间&lt;a href=&quot;#固定舞台用滚动距离换取阅读时间&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;展示区使用“长容器 + sticky 舞台”的结构。长容器提供滚动距离，舞台始终固定在视口内，所有镜头都在这个舞台上完成。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;section&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;cinematic-showcase&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;showcase&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;showcase-pin&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;data-showcase&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;showcase-intro&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;...&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;showcase-panels&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;...&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;showcase-interlude&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;...&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;showcase-finale&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;...&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;section&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;当前 CSS 使用的是桌面端 &lt;code&gt;850vh&lt;/code&gt;、窄屏端 &lt;code&gt;760vh&lt;/code&gt;，而不是早期更短的数值：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.cinematic-showcase&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;position: &lt;/span&gt;&lt;span&gt;relative&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;height: &lt;/span&gt;&lt;span&gt;&lt;span&gt;850&lt;/span&gt;&lt;span&gt;vh&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;margin-top: &lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;rem&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.showcase-pin&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;position: &lt;/span&gt;&lt;span&gt;sticky&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;top: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;height: &lt;/span&gt;&lt;span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;svh&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;min-height: &lt;/span&gt;&lt;span&gt;&lt;span&gt;37.2&lt;/span&gt;&lt;span&gt;rem&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;overflow: &lt;/span&gt;&lt;span&gt;hidden&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;isolation: &lt;/span&gt;&lt;span&gt;isolate&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;@media&lt;/span&gt;&lt;span&gt; (max-width: &lt;/span&gt;&lt;span&gt;&lt;span&gt;600&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.cinematic-showcase&lt;/span&gt;&lt;span&gt; { height: &lt;/span&gt;&lt;span&gt;&lt;span&gt;760&lt;/span&gt;&lt;span&gt;vh&lt;/span&gt;&lt;/span&gt;&lt;span&gt;; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这里的 &lt;code&gt;850vh&lt;/code&gt; 不是动画持续时间，更像是时间轴的可用长度。真正可用的滚动距离不是容器高度本身，而是容器高度减去视口高度：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;measureShowcase&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;&lt;span&gt;showcaseSection&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;showcaseStart&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;scrollY&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;showcaseSection&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;getBoundingClientRect&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;top&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;showcaseDistance&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;showcaseSection&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;offsetHeight&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;innerHeight&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;getShowcaseProgress&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;clamp&lt;/span&gt;&lt;span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;scrollY&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;showcaseStart&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;showcaseDistance&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这样做有两个细节。&lt;/p&gt;&lt;p&gt;第一，&lt;code&gt;showcaseStart&lt;/code&gt; 在测量时写成相对文档的绝对位置，而不是每一帧都重新读取 &lt;code&gt;getBoundingClientRect()&lt;/code&gt;。滚动路径中只需计算一次减法和一次夹取，避免不必要的布局读取。&lt;/p&gt;&lt;p&gt;第二，舞台高度选用 &lt;code&gt;100svh&lt;/code&gt;。手机浏览器地址栏展开和收起时，&lt;code&gt;svh&lt;/code&gt; 比传统 &lt;code&gt;vh&lt;/code&gt; 更稳定；同时在 &lt;code&gt;resize&lt;/code&gt; 时重新测量，并立即同步渲染进度，避免视口变化后动画从旧位置缓慢追赶到新位置。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;resizeShowcase&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;measureShowcase&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;targetShowcaseProgress&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;getShowcaseProgress&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;renderedShowcaseProgress&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;targetShowcaseProgress&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;lastShowcaseProgress&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;applyShowcaseProgress&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;renderedShowcaseProgress&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;移动端并没有简单地把故事压缩得更快。当前实现缩短了总行程，并隐藏卡片中的长说明文字；保留编号和标题，让每个镜头仍有可理解的停留。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;不在 scroll 回调里直接渲染&lt;a href=&quot;#不在-scroll-回调里直接渲染&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;最容易写出的版本，是在每次 &lt;code&gt;scroll&lt;/code&gt; 事件中读取进度并修改所有元素的 &lt;code&gt;style&lt;/code&gt;。它在鼠标滚轮上似乎可行，但触摸板惯性、主线程短暂停顿和高刷新率设备会让同一段动画显得忽快忽慢。&lt;/p&gt;&lt;p&gt;当前实现把“用户想去哪里”和“当前画面画到哪里”分开：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SCROLL_SCRUB_DURATION&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;180&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SHOWCASE_SETTLE_EPSILON&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.00008&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;targetShowcaseProgress&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;renderedShowcaseProgress&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;showcaseFrame&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;lastShowcaseTimestamp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;requestShowcaseUpdate&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;targetShowcaseProgress&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;getShowcaseProgress&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;showcaseFrame&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;lastShowcaseTimestamp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;showcaseFrame&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;requestAnimationFrame&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;renderShowcase&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;滚动事件只更新目标值，并且以被动监听注册：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;scroll&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;requestShowcaseUpdate&lt;/span&gt;&lt;span&gt;, { &lt;/span&gt;&lt;/span&gt;&lt;span&gt;passive&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;resize&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;resizeShowcase&lt;/span&gt;&lt;span&gt;, { &lt;/span&gt;&lt;/span&gt;&lt;span&gt;passive&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; });&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;实际渲染发生在 &lt;code&gt;requestAnimationFrame&lt;/code&gt; 中。这里没有使用固定比例的 &lt;code&gt;progress += 0.1&lt;/code&gt;，而是使用基于时间差的指数平滑：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;renderShowcase&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;timestamp&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;elapsed&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;lastShowcaseTimestamp&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;min&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;64&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;timestamp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;lastShowcaseTimestamp&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;16.67&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;lastShowcaseTimestamp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timestamp&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;blend&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;exp&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt;elapsed&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SCROLL_SCRUB_DURATION&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;renderedShowcaseProgress&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;targetShowcaseProgress&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;renderedShowcaseProgress&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;blend&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;abs&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;targetShowcaseProgress&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;renderedShowcaseProgress&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;SHOWCASE_SETTLE_EPSILON&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;renderedShowcaseProgress&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;targetShowcaseProgress&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;applyShowcaseProgress&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;renderedShowcaseProgress&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;abs&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;targetShowcaseProgress&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;renderedShowcaseProgress&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;SHOWCASE_SETTLE_EPSILON&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;showcaseFrame&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;requestAnimationFrame&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;renderShowcase&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;showcaseFrame&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;lastShowcaseTimestamp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;blend = 1 - exp(-dt / duration)&lt;/code&gt; 的好处是刷新率无关：在 60Hz 和 120Hz 屏幕上，画面趋近目标的速度一致。&lt;code&gt;elapsed&lt;/code&gt; 被限制到 64ms，标签页从后台恢复时也不会因一个异常大的时间差直接跳到终点。进度足够接近后停止申请下一帧，减少静止状态下的无效工作。&lt;/p&gt;&lt;p&gt;渲染函数内部还有一层更细的保护：当新旧进度相差小于 &lt;code&gt;0.00001&lt;/code&gt; 时，不再重写样式。这一层在慢速滚动或停住后很有价值，因为页面的图层比普通卡片页多得多。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;phaseAt：把连续进度切成可维护的分镜&lt;a href=&quot;#phaseat把连续进度切成可维护的分镜&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;所有镜头都从同一个 &lt;code&gt;progress&lt;/code&gt; 出发，但每个镜头只关心属于自己的时间窗口。为此实现了一个“局部阶段函数”：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;clamp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;min&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ease&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;clamp&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;phaseAt&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;progress&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;end&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;ease&lt;/span&gt;&lt;span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;progress&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;.001&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;end&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;phaseAt(progress, start, end)&lt;/code&gt; 会先把任意区间归一化到 &lt;code&gt;0 ~ 1&lt;/code&gt;，再套一层 smoothstep。它有三个稳定的状态：开始前为 0，区间内平滑变化，结束后为 1。这样每段动画不需要知道页面总高度，也不用自己处理越界。&lt;/p&gt;&lt;p&gt;当前时间轴大致如下：&lt;/p&gt;












































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;分镜&lt;/th&gt;&lt;th&gt;进度区间&lt;/th&gt;&lt;th&gt;目的&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;引导文案淡出&lt;/td&gt;&lt;td&gt;0.00 - 约 0.21&lt;/td&gt;&lt;td&gt;让读者从引导进入作品卡片&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;五张卡片入场&lt;/td&gt;&lt;td&gt;第 &lt;em&gt;i&lt;/em&gt; 张为 0.04 + 0.055i 到 0.16 + 0.055i&lt;/td&gt;&lt;td&gt;逐张落位，最后一张不被前几张挤压&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;卡片文字入场&lt;/td&gt;&lt;td&gt;第 &lt;em&gt;i&lt;/em&gt; 张为 0.16 + 0.05i 到 0.25 + 0.05i&lt;/td&gt;&lt;td&gt;图片先建立画面，再交代标题&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;卡片退场&lt;/td&gt;&lt;td&gt;0.60 - 0.72&lt;/td&gt;&lt;td&gt;统一清空，不留下半透明边框&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;黑场与角色&lt;/td&gt;&lt;td&gt;0.63 - 0.97&lt;/td&gt;&lt;td&gt;把卡片阶段交给快门转场&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;上下快门合拢 / 展开&lt;/td&gt;&lt;td&gt;0.72 - 0.84 / 0.91 - 0.97&lt;/td&gt;&lt;td&gt;形成完整的一次闭合与打开&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;结尾场景&lt;/td&gt;&lt;td&gt;0.88 - 1.00&lt;/td&gt;&lt;td&gt;在转场遮挡下预热，再分层揭示&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;表格里的数字不是“魔法常量”，而是对叙事节奏的显式描述。它们集中在渲染函数里，调整某一幕时只需要移动相邻区间，不会牵连一堆独立的 &lt;code&gt;@keyframes&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;卡片为什么要同时算透明度和几何退场&lt;a href=&quot;#卡片为什么要同时算透明度和几何退场&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;五张作品卡片由数据数组生成，动画代码只使用索引。入场和退场使用同一组局部进度：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;inProgress&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;phase&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;.04&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.055&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;.16&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.055&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dissolve&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;phase&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;.60&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;.72&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;direction&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;startingY&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;inProgress&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;direction&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;150&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fanX&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dissolve&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;3.25&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fanY&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dissolve&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1.35&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;panel&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;opacity&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;inProgress&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dissolve&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;panel&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;transform&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;translate3d(&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fanX&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;vw, &quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;startingY&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fanY&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;%, 0) &quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;scale(&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dissolve&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.12&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;) &quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;rotate(&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;span&gt; ((&lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dissolve&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1.35&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;deg)&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;入场时，奇偶卡片从相反方向进入；退场时，它们微微散开、缩小并旋转。更重要的是，透明度使用 &lt;code&gt;inProgress * (1 - dissolve)&lt;/code&gt;，所以退场结束时一定回到 0。&lt;/p&gt;&lt;p&gt;这个约束是有原因的。早期版本曾保留少量半透明残影，卡片边框会在纯黑转场和窗景上变成一层浅灰横带。对于这种需要明确切镜的页面，残影不是氛围，而是上一幕没有离开干净的证据。&lt;/p&gt;&lt;p&gt;卡片文字单独安排了入场和退场，避免与图片一起仓促消失：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;copyIn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;phase&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;.16&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.05&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;.25&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.05&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;copyOut&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;phase&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;.58&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;.70&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;copy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;opacity&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;copyIn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;copyOut&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;copy&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;transform&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;translate3d(0, &quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;copyIn&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;18&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dissolve&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;26&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;px, 0)&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这让“图像抵达”和“文字可阅读”成为两个独立的节拍。设计上真正需要保证的不是卡片出现得多炫，而是最后一张卡片完全出现后，读者仍有机会理解它。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;快门转场不是淡出，而是一段可交接的场景&lt;a href=&quot;#快门转场不是淡出而是一段可交接的场景&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;卡片退场后，展示区进入黑场。这里的核心不是给画面叠一层黑色，而是明确每一层的职责：&lt;/p&gt;




























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;图层&lt;/th&gt;&lt;th&gt;作用&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;黑色背景&lt;/td&gt;&lt;td&gt;切断前一段丰富的卡片画面，给下一幕提供干净底色&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;上下两条横幅&lt;/td&gt;&lt;td&gt;从左右移动，完成快门的合拢和再次展开&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;前景角色&lt;/td&gt;&lt;td&gt;在黑场中连接卡片阶段与结尾场景&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;两个大字&lt;/td&gt;&lt;td&gt;只在快门闭合后的短暂读秒出现&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;结尾场景&lt;/td&gt;&lt;td&gt;先在黑场后方完成准备，再在遮挡结束后被看见&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;上下横幅不是只进不出。它们先从画面外移动到中间，在角色和文字退出时回到两侧：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stripsIn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;phase&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;.72&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;.84&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stripsOut&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;phase&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;.91&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;.97&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stripPosition&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stripsIn&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stripsOut&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;shutterStripLeft&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;transform&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;translate3d(&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; ((&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stripPosition&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;108&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;%, 0, 0)&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;shutterStripRight&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;transform&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;translate3d(&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; ((&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;stripPosition&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;108&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;%, 0, 0)&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;结尾场景则从 &lt;code&gt;0.88&lt;/code&gt; 就开始在快门后方进入：整体场景先渐显，视频和中景在 &lt;code&gt;0.90 - 0.97&lt;/code&gt; 进入，前景角色到 &lt;code&gt;0.94 - 1.00&lt;/code&gt; 才落位。这个重叠不是浪费，而是为了让读者先看到“场景已在”，再感受到前、中、后景的空间关系。&lt;/p&gt;&lt;p&gt;视频只在进度超过一个很小的阈值后尝试播放，并忽略被浏览器自动播放策略拒绝的结果：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;phase&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;.90&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;.97&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;finaleVideo&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;opacity&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;setVisibility&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;finaleVideo&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;v&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.02&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;finaleVideo&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;paused&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;finaleVideo&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;play&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;undefined&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;visibility&lt;/code&gt; 会在图层完全不可见时关闭其可见性；视频仍然带有 &lt;code&gt;muted&lt;/code&gt;、&lt;code&gt;loop&lt;/code&gt;、&lt;code&gt;playsinline&lt;/code&gt; 和 &lt;code&gt;preload=“auto”&lt;/code&gt;。即使视频不能自动播放，静态中景和前景仍会完整呈现，结尾不会变成空白。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;资源预热：不要把所有图片同时 decode&lt;a href=&quot;#资源预热不要把所有图片同时-decode&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;展示区一次会用到卡片、快门和结尾场景的多张图片。如果在首次进入页面时对所有图片立即调用 &lt;code&gt;decode()&lt;/code&gt;，图片解码可能和首段滚动竞争主线程，反而让第一幕出现卡顿。&lt;/p&gt;&lt;p&gt;当前策略按两个图片为一组预热，每完成一组就让出一个动画帧：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;warmShowcaseMedia&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;batch&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;of&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;showcaseImageBatches&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;all&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;batch&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;image&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;waitForImage&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;image&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;image&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;decode&lt;/span&gt;&lt;span&gt;?.().&lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;undefined&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Promise&lt;/span&gt;&lt;span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;resolve&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;requestAnimationFrame&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;resolve&lt;/span&gt;&lt;span&gt;());&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;waitForImage()&lt;/code&gt; 同时监听 &lt;code&gt;load&lt;/code&gt; 和 &lt;code&gt;error&lt;/code&gt;，单张资源失败不会把整条预热链卡住。这个策略不追求“最早把所有内容解码完”，而是控制一次被调度到主线程的工作量，让首页和第一段滚动仍保持响应。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;动画层不承担链接职责&lt;a href=&quot;#动画层不承担链接职责&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;展示区不承担项目链接职责：卡片列表和快门转场容器标记为 &lt;code&gt;aria-hidden&lt;/code&gt;，结尾场景中的图片使用空的 &lt;code&gt;alt&lt;/code&gt;。真正可点击、带标题和描述的项目内容在下面的普通作品网格中：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;showcase-panels&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;aria-hidden&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;true&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;...&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;section&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;works-section&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;works&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;article&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;work-card&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;work-card-link&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;...&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;...&amp;lt;/&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;article&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;section&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这避免了同一批作品被屏幕阅读器重复朗读，也让动效失效、被跳过或尚未加载时，内容仍有一条稳定的阅读路径。首屏的“向下看作品”入口指向 &lt;code&gt;#showcase&lt;/code&gt;，保证想体验叙事的读者进入动画开头；而普通网格则负责承载可访问的项目导航。&lt;/p&gt;&lt;p&gt;这里还有一个应继续完善的点：当前的 &lt;code&gt;prefers-reduced-motion&lt;/code&gt; 已经关闭了常规内容的 reveal 过渡，但还没有为整段分镜提供专门的静态展示模式。由于作品链接不依赖动画层，内容不会丢失；下一步更理想的做法是让该媒体查询直接展示首帧或静态作品列表，并跳过滚动平滑与视频播放。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;验证不是“看起来能动”&lt;a href=&quot;#验证不是看起来能动&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;每次调整分镜后，我会从三个方向检查：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;节奏：慢慢滚动时，第五张卡片和结尾文案是否都有可阅读的停留；快速滚动后画面是否平稳追上目标，而不是闪到终点。&lt;/li&gt;
&lt;li&gt;图层：卡片退场后是否确实为 0；黑场是否干净；快门再次打开时，结尾场景是否已经准备好而没有白屏或裸露的中间状态。&lt;/li&gt;
&lt;li&gt;环境：在展示区内改变窗口尺寸或旋转手机，进度是否保持；禁用或拒绝视频自动播放时，结尾是否仍可理解；键盘访问时，作品链接是否只在普通列表中出现一次。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;内容改动完成后，仓库内使用以下命令检查：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;check&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;type-check&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;build&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;结语&lt;a href=&quot;#结语&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这页最终留下的不是一套“滚动动画技巧”，而是一种更适合维护的拆分方式：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;CSS 管舞台、裁切和图层；&lt;/li&gt;
&lt;li&gt;滚动事件只更新目标；&lt;/li&gt;
&lt;li&gt;rAF 负责在帧间平滑渲染；&lt;/li&gt;
&lt;li&gt;分镜函数把连续进度切成可读的区间；&lt;/li&gt;
&lt;li&gt;资源预热和可访问内容各自有独立边界。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;当动画需要继续加镜头、换素材或调整节奏时，最重要的不是继续往页面里塞效果，而是先问清楚：这段变化处在哪个区间、它和前后镜头如何交接、内容是否仍有不依赖动画的路径。把这些问题写进结构之后，滚动才会真正成为叙事，而不是一串碰巧同时发生的视觉效果。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>写完这一批工具后，我重新打开了自己的博客</title><link>https://rainzt.cn/posts/after-the-tools-i-returned-to-my-blog/</link><guid isPermaLink="true">https://rainzt.cn/posts/after-the-tools-i-returned-to-my-blog/</guid><description>跨境电商自动化定制项目告一段落后，我重新回望电脑里堆满的工具文件夹，也重新想清楚了初代博客与第二代博客各自要承载什么。</description><pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最近一段时间，电脑终于安静了一点。&lt;/p&gt;
&lt;p&gt;不是风扇不转了，也不是消息提示彻底消失了，而是那些排着队等我处理的定制项目，大多已经交付。剩下的一些收尾、反馈和维护，也交给合作人员继续跟进。连续很久被需求、调试和交付日期推着往前走之后，日程表上总算出现了一点没有被预先占满的空白。&lt;/p&gt;
&lt;p&gt;我没有立刻开一个新项目，而是先整理电脑。&lt;/p&gt;
&lt;p&gt;桌面上有周报、表格转换、上架表格制作、库存清理、批量库存处理、SKU 销量汇总；往文件夹里再翻，还有订单筛选、活动 Excel 工具、图片搜索、错误 SKU 修改、自动识别、主图优化和上架状态监控。Shopee、TikTok Shop、美客多，不同平台的规则被拆进不同脚本里；同一个工具又会因为客户流程不同，留下好几个版本。&lt;/p&gt;
&lt;p&gt;这些文件夹没有统一命名，也谈不上整齐，但它们比周报更诚实地记录了我前段时间在做什么。&lt;/p&gt;
&lt;div&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;桌面上的 SKU、周报、表格与库存工具&quot; loading=&quot;lazy&quot; width=&quot;394&quot; height=&quot;205&quot; src=&quot;/_astro/desktop-tools-1.BHYpwXuP_Z1JT9Ki.webp&quot; srcset=&quot;/_astro/desktop-tools-1.BHYpwXuP_Z1JT9Ki.webp 394w&quot; /&gt;&lt;figcaption&gt;桌面上的 SKU、周报、表格与库存工具&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;围绕 TikTok、美客多和主图处理建立的项目文件夹&quot; loading=&quot;lazy&quot; width=&quot;162&quot; height=&quot;200&quot; src=&quot;/_astro/desktop-tools-2.UVDPNNx8_1iLX24.webp&quot; srcset=&quot;/_astro/desktop-tools-2.UVDPNNx8_1iLX24.webp 162w&quot; /&gt;&lt;figcaption&gt;围绕 TikTok、美客多和主图处理建立的项目文件夹&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/div&gt;
&lt;div&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;订单筛选、活动 Excel、自动上架等项目目录&quot; loading=&quot;lazy&quot; width=&quot;386&quot; height=&quot;250&quot; src=&quot;/_astro/project-folders-1.De9pbmqU_24Wz0Y.webp&quot; srcset=&quot;/_astro/project-folders-1.De9pbmqU_24Wz0Y.webp 386w&quot; /&gt;&lt;figcaption&gt;订单筛选、活动 Excel、自动上架等项目目录&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;上架监控、博客与各类辅助程序目录&quot; loading=&quot;lazy&quot; width=&quot;392&quot; height=&quot;401&quot; src=&quot;/_astro/project-folders-2.OdYEdK6k_Z1Vg5Mr.webp&quot; srcset=&quot;/_astro/project-folders-2.OdYEdK6k_Z1Vg5Mr.webp 392w&quot; /&gt;&lt;figcaption&gt;上架监控、博客与各类辅助程序目录&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;项目名称有的直白，有的临时，有的还带着“新”“最终版”的痕迹。它们拼在一起，就是这段时间最具体的工作记录。&lt;/em&gt;&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;文件夹背后，是一个个很具体的问题&lt;a href=&quot;#文件夹背后是一个个很具体的问题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;做自动化工具久了，很容易把工作概括成几个听起来很大的词：跨境电商、批量处理、流程自动化、AI 辅助。&lt;/p&gt;&lt;p&gt;可真正落到手上的需求，通常没有这么抽象。&lt;/p&gt;&lt;p&gt;它可能只是商家每天都要从平台导出一份 Excel，再按固定规则删除、筛选和拆分；可能是几千个 SKU 的库存状态对不上，需要从多个表格里重新汇总；也可能是同一批商品要适配不同平台的上架模板，字段名称、图片顺序、变体规则都不一样。还有一些工作并不难，却要不断重复：打开页面、复制内容、上传图片、检查状态、等待结果，再把异常项目重新处理一遍。&lt;/p&gt;&lt;p&gt;这些动作单独看都很小，连续做几个小时却足以消耗掉一个人的耐心。&lt;/p&gt;&lt;p&gt;我的工作，就是先把商家原本的操作完整走一遍，弄清楚哪些步骤不能变、哪些判断藏在经验里、哪些异常以前全靠人工兜底，再把它们一点点写进程序。需求沟通、流程拆解、脚本编写、界面调整、数据校验、打包交付，最后再根据真实使用反馈继续改。&lt;/p&gt;&lt;p&gt;有时候最费时间的并不是写代码，而是听懂一句“这里和以前一样处理就行”。所谓“以前一样”，背后可能藏着十几条没有写进文档的规则。只有把这些规则问出来、试出来，自动化才不是机械地替人点击，而是真的能接住工作。&lt;/p&gt;&lt;div&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;将多店铺 Shopee 订单集中处理的订单筛选工具&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;913&quot; src=&quot;/_astro/shopee-order-filter.DCyKafFp_Z13XUhr.webp&quot; srcset=&quot;/_astro/shopee-order-filter.DCyKafFp_Z2hUpxS.webp 640w, /_astro/shopee-order-filter.DCyKafFp_1JJVzk.webp 750w, /_astro/shopee-order-filter.DCyKafFp_2qjGcP.webp 828w, /_astro/shopee-order-filter.DCyKafFp_Z1wwr0j.webp 1080w, /_astro/shopee-order-filter.DCyKafFp_1QarVJ.webp 1280w, /_astro/shopee-order-filter.DCyKafFp_m1Bsg.webp 1668w, /_astro/shopee-order-filter.DCyKafFp_Z13XUhr.webp 1920w&quot; /&gt;&lt;figcaption&gt;将多店铺 Shopee 订单集中处理的订单筛选工具&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;围绕图案分类、检索与导出设计的智能管理系统&quot; loading=&quot;lazy&quot; width=&quot;1650&quot; height=&quot;878&quot; src=&quot;/_astro/pattern-library.VMJctkGg_Z13A07h.webp&quot; srcset=&quot;/_astro/pattern-library.VMJctkGg_49qQo.webp 640w, /_astro/pattern-library.VMJctkGg_21yjuk.webp 750w, /_astro/pattern-library.VMJctkGg_Z18gXBP.webp 828w, /_astro/pattern-library.VMJctkGg_ZCFsQ0.webp 1080w, /_astro/pattern-library.VMJctkGg_w9fuV.webp 1280w, /_astro/pattern-library.VMJctkGg_Z13A07h.webp 1650w&quot; /&gt;&lt;figcaption&gt;围绕图案分类、检索与导出设计的智能管理系统&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;一个项目处理订单数据，一个项目管理图像资产。界面和用途不同，出发点却一样：把反复发生的工作整理成清晰、稳定、可复用的流程。&lt;/em&gt;&lt;/p&gt;&lt;p&gt;这也是为什么电脑里的工具越来越多。它们并不是我事先规划好的一张产品路线图，而是现实需求一点点推出来的结果。一个客户遇到库存问题，就有了库存工具；另一个客户长期受困于上架流程，就继续往表格、图片、浏览器自动化和状态监控上延伸。&lt;/p&gt;&lt;p&gt;回头看，这些看似零散的文件夹，其实是一张很具体的需求地图。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;初代博客，曾经更像一间线上店铺&lt;a href=&quot;#初代博客曾经更像一间线上店铺&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;工具越做越多之后，我自然需要一个集中展示它们的地方。于是有了初代博客。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;初代博客首页&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;913&quot; src=&quot;/_astro/first-generation-blog.BhQoqQn2_LFWXM.webp&quot; srcset=&quot;/_astro/first-generation-blog.BhQoqQn2_Z2vAcxt.webp 640w, /_astro/first-generation-blog.BhQoqQn2_ZhdXGK.webp 750w, /_astro/first-generation-blog.BhQoqQn2_ZuG99q.webp 828w, /_astro/first-generation-blog.BhQoqQn2_Z1GQTF8.webp 1080w, /_astro/first-generation-blog.BhQoqQn2_1sEeWz.webp 1280w, /_astro/first-generation-blog.BhQoqQn2_Z290EYV.webp 1668w, /_astro/first-generation-blog.BhQoqQn2_LFWXM.webp 1920w&quot; /&gt;&lt;figcaption&gt;初代博客首页&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;那时搭建网站的目标非常明确：展示能力、介绍工具、承接需求。首页要让访客尽快知道我能做什么，项目页面要说明功能、参数和使用场景，文案也要回答潜在客户最关心的问题——这个程序能解决什么、适不适合自己的业务、要怎样联系开发者。&lt;/p&gt;&lt;p&gt;从这个目标看，初代博客并没有做错什么。它认真完成了当时的任务，也留下了我做服务器、前端和脚本开发的一段经历。&lt;/p&gt;&lt;p&gt;只是现在再回头看，我发现它更像一间布置过的线上店铺，而不是我真正想长期写下去的博客。&lt;/p&gt;&lt;p&gt;我习惯在文章里介绍“工具具备哪些功能”，却很少写“我为什么会想到这样做”；会认真展示运行效果，却不太记录调试到深夜时到底卡在了哪里；会把项目整理成一份完整、漂亮的说明，却略过了那些绕路、误判和临时改主意的瞬间。&lt;/p&gt;&lt;p&gt;网站里有我完成的东西，却不一定有完整的我。&lt;/p&gt;&lt;p&gt;这不是初代博客的问题，而是那个阶段的我确实把大部分注意力放在了业务上。那时的网站首先要服务接单，个人表达自然排在后面。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第二代博客，是一次有意的减法&lt;a href=&quot;#第二代博客是一次有意的减法&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;重新搭建 &lt;code&gt;rainzt.cn&lt;/code&gt; 时，我最想做的并不是增加多少功能，而是减掉一种过强的目的性。&lt;/p&gt;&lt;p&gt;第二代博客仍然会写自动化工具，也不会刻意回避商业项目。跨境电商自动化依旧是我会长期深耕的方向，真实项目里积累的经验，也恰恰是最值得整理的内容。不同的是，我不再要求每篇文章都承担展示产品和转化客户的任务。&lt;/p&gt;&lt;p&gt;我想写下批量库存工具为什么会从一张表格变成一套流程，自动上架系统如何一点点适配 Shopee 与 TikTok Shop，主图处理为什么不能只追求“批量生成”，还要考虑平台规则、视觉一致性和异常回退。我也想记录服务器运维、前端页面打磨和 AI 辅助开发中的判断：哪些方案最后保留下来，哪些看起来聪明却不适合真实使用。&lt;/p&gt;&lt;p&gt;这些内容可能没有一份产品说明那么干净。它们会有失败、有犹豫，也会有“后来才发现”的补充。但对我来说，恰恰是这些不够标准的部分，才构成了真正的开发经历。&lt;/p&gt;&lt;p&gt;除此之外，我也想给生活留一点位置。&lt;/p&gt;&lt;p&gt;不一定每次都要讲一项新技术，也不一定非要总结出一条可复用的方法。有时只是项目结束后的松弛，一次重新整理桌面的感触，或者某个晚上突然想清楚的一件小事。它们不能直接换来效率，却能让我在几年之后回头时，知道自己当时如何生活、如何理解手里的工作。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;让 AI 回到助手的位置&lt;a href=&quot;#让-ai-回到助手的位置&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;忙着赶项目时，我也把写文章变成过一条自动化流水线。&lt;/p&gt;&lt;p&gt;把 README、功能清单、代码逻辑和截图交给 AI，它很快就能整理出标题、章节和结尾。事实基本正确，结构也比我临时组织得更完整。项目刚交付、人已经很疲惫的时候，这种方式确实替我省下了许多时间。&lt;/p&gt;&lt;p&gt;但这种方便用久了，我也会感觉到一种落差：文章把项目说明白了，却没有把我留下来。&lt;/p&gt;&lt;p&gt;AI 能从资料里提炼出“系统实现了批量上架”，却不知道某个按钮为什么改过三次；它能总结订单筛选规则，却没有经历过客户一句含糊描述背后的来回确认；它可以把功能写得很专业，却很难替我判断，哪一次失败最值得记录，哪一个看似不起眼的细节真正改变了后来做项目的方法。&lt;/p&gt;&lt;p&gt;所以我并不打算拒绝 AI，也不会把“全部手写”当作一种必须遵守的仪式。我仍然会用它查漏、整理资料、校对表达，甚至在没有头绪时帮我搭一个起点。&lt;/p&gt;&lt;p&gt;我只是想慢慢把它从“替我完整交稿”的位置，放回“陪我整理和修改”的位置。重要的经历由我回忆，真正的判断由我确认，那些只属于当时心境的句子，也尽量留给自己慢慢写。&lt;/p&gt;&lt;p&gt;效率可以帮助我完成更多事情，但有些内容只有在放慢以后才会浮上来。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;两种创作，可以并行&lt;a href=&quot;#两种创作可以并行&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;项目告一段落，不代表商业工具开发也告一段落。&lt;/p&gt;&lt;p&gt;以后我仍然会接触新的平台规则、新的表格格式和新的运营流程，也会继续把合适的重复劳动做成程序。商用代码解决的是客户此刻真实存在的问题，它需要稳定、准确、按时交付，也需要在变化的平台环境里不断维护。&lt;/p&gt;&lt;p&gt;而博客可以承担另一件事：保留那些不适合写进交付文档的东西。&lt;/p&gt;&lt;p&gt;这里可以有技术方案，也可以有开发感受；可以复盘一套自动上架系统，也可以只记录某次调试后的想法。商业项目追求明确结果，私人写作允许暂时没有结论。前者让我不断面对真实需求，后者让我不至于只剩下一张完成项目的清单。&lt;/p&gt;&lt;p&gt;它们并不冲突。&lt;/p&gt;&lt;p&gt;代码把复杂操作封装起来，让别人少做一些重复劳动；文字把已经过去的经历重新展开，让我看见自己是怎样走到现在。一个向外解决问题，一个向内保存思考，都是创作，只是面对的对象不同。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;重新开始更新&lt;a href=&quot;#重新开始更新&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;整理电脑时，我没有删除那些密密麻麻的项目文件夹。&lt;/p&gt;&lt;p&gt;有些工具以后还会继续迭代，有些可能完成使命后就停在当前版本。再过几年，其中一部分程序也许已经不适配新的平台，甚至连当时依赖的页面都不存在了。但如果把开发过程、做过的判断和那段时间的感受写下来，它们就不会只剩下一个无法运行的旧文件。&lt;/p&gt;&lt;p&gt;这大概就是我重新深耕第二代博客的原因。&lt;/p&gt;&lt;p&gt;我并不想把它重新经营成一面更精致的商品橱窗，也不急着证明自己能保持多高的更新频率。我只想在忙碌告一段落之后，重新留出一块可以慢慢写字的地方。&lt;/p&gt;&lt;p&gt;往后，工具还会继续做，需求还会继续变化，电脑里的文件夹大概也不会真正变少。&lt;/p&gt;&lt;p&gt;但这一次，我希望它们增长的同时，博客里也能留下另一条时间线：不只记录我完成了什么，也记录我为什么开始、如何走过，以及一路上究竟想过些什么。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>当 AI 能替我写完项目博文，我为什么还想敲下这些字</title><link>https://rainzt.cn/posts/why-i-still-write-in-the-age-of-ai/</link><guid isPermaLink="true">https://rainzt.cn/posts/why-i-still-write-in-the-age-of-ai/</guid><description>当 AI 十分钟就能写完一篇结构清晰的项目介绍，亲手写作还有什么意义？聊聊效率、表达、个人经验，以及我对 AI 未来的一点期待和不安。</description><pubDate>Wed, 22 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;翻了翻站点统计，博客上线差不多半个月，三篇文章里有两篇是项目作品。&lt;/p&gt;
&lt;p&gt;说句实话，这两篇项目介绍，大半功劳不在我。&lt;/p&gt;
&lt;p&gt;把项目的 README、核心脚本逻辑、几张运行截图丢给 AI，再告诉它一句“写成一篇博客风格的项目介绍文”，不出十分钟，一篇结构清晰、术语准确、连阅读时长都算得刚刚好的文章就出来了。改改措辞，核对几个技术细节，补一张封面图，直接就能发布上线。&lt;/p&gt;
&lt;p&gt;省事，真的省事。&lt;/p&gt;
&lt;p&gt;做跨境电商自动上架系统那阵子，我每天泡在 BigSeller、Shopee 店铺规则和表格批量处理里。功能跑通的那一刻，只想往椅背上一瘫，完全不想再把已经折腾过一遍的东西整理成文章。换作以前，项目背景、实现逻辑、使用说明，至少还要花一两个晚上慢慢磨；现在把文档交给 AI，一篇近两千字的初稿很快就能摆在面前。&lt;/p&gt;
&lt;p&gt;后来做 AI 无限画布工作流也是一样。从 Firefly 节点调试到 GPT Image 接入，从画布布局到批量出图逻辑，整个过程踩了不少坑。但真要写文章的时候，我的第一反应还是打开 AI：“帮我把这套电商主图生成工作流写成博客。”&lt;/p&gt;
&lt;p&gt;效率拉满，但总觉得哪里不太对。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/wallpaper/wallpaper-42.webp&quot; alt=&quot;隔着窗看向远处，也像是在重新看自己的表达&quot; /&gt;&lt;figcaption&gt;隔着窗看向远处，也像是在重新看自己的表达&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;十分钟生成一篇“正确”的文章&lt;a href=&quot;#十分钟生成一篇正确的文章&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;如果只从结果看，AI 写项目介绍几乎挑不出什么大问题。&lt;/p&gt;&lt;p&gt;它知道应该先讲痛点，再讲架构；知道把一堆零散功能整理成几个模块；也知道在结尾补上一段愿景，让一个还在迭代中的项目显得完整而有方向。它能精准地写出：“这套系统打通了 BigSeller、Shopee 与 TikTok Shop，实现了批量上架自动化。”也能清晰地拆解：“无限画布工作流包含产品图导入、提示词编排、风格参考与批量生成。”&lt;/p&gt;&lt;p&gt;这些话都没错，甚至比我临时组织出来的表达更准确。&lt;/p&gt;&lt;p&gt;更麻烦的是，它不只写得快，有时还写得比我好。标题更像标题，小节更像小节，句子长短有节奏，连那些我脑子里只有模糊轮廓的想法，它也能替我补成一段听起来很成熟的论述。&lt;/p&gt;&lt;p&gt;从效率的角度，我实在找不到拒绝它的理由。&lt;/p&gt;&lt;p&gt;但“正确”慢慢变成了一种很奇怪的东西。文章什么都有：背景、功能、流程、优势、愿景，唯独少了一点只有我知道的东西。&lt;/p&gt;&lt;p&gt;它写不出我最开始做自动上架工具的起因，是运营时一天要上架几十款商品，重复操作到鼠标手犯了，才咬着牙想写个脚本解放自己。它也写不出调试接口时，一个参数没对齐卡了整整一下午，最后发现不是我理解错了，而是文档真的写错了。那种想生气又忍不住觉得荒唐的心情，不在 README 里，也不在代码注释里。&lt;/p&gt;&lt;p&gt;它能整理无限画布的模块，却不知道我第一次跑通全流程时，看着一整张画布上的主图方案依次生成，比自己出了爆款还开心。它更不知道我为什么放着现成的 AI 绘图工具不用，非要搭一套本地画布：因为市面上的工具很方便，但永远没法完全贴合跨境电商主图那些细碎、麻烦、又不能忽略的规则。&lt;/p&gt;&lt;p&gt;AI 能读取项目，却没有经历项目。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/wallpaper/wallpaper-27.webp&quot; alt=&quot;AI 可以迅速铺开一整张信息画布，但经历仍然来自画布之外&quot; /&gt;&lt;figcaption&gt;AI 可以迅速铺开一整张信息画布，但经历仍然来自画布之外&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;真正不对劲的，不是 AI 替我写了&lt;a href=&quot;#真正不对劲的不是-ai-替我写了&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;今天对着站点归档发愣的时候，我突然反应过来：我好像已经很久没有“手写”过一篇完整的文章了。&lt;/p&gt;&lt;p&gt;不是因为没东西可写。恰恰相反，项目越做越多，能写的内容也越来越多。只是每次准备记录时，我都会自然地选择最快的路径：整理资料，交给 AI，核对事实，发布。&lt;/p&gt;&lt;p&gt;这个流程没有错。问题在于，当我把所有表达都外包出去，博客也很容易从“我经历过什么”变成“我完成过什么”。前者是成长记录，后者更像项目陈列柜。&lt;/p&gt;&lt;p&gt;想起写&lt;a href=&quot;https://rainzt.cn/posts/build-rainzt-blog/&quot;&gt;《从零搭建 Rainzt.cn》&lt;/a&gt;的时候，那是几篇文章里唯一一篇从头写到尾的。从本地 Astro 项目初始化，到宝塔面板 Nginx 配置，再到 HTTPS 证书踩的坑，每一步都是自己走过的，每一行命令也都是亲手敲过的。&lt;/p&gt;&lt;p&gt;写的时候没觉得它有多特别。现在回头看，文章里那些不够规整的叙述、细碎的报错和临时补上的提醒，反而比一篇标准的项目介绍更有温度。因为它不只是告诉别人“怎么搭博客”，也保留了当时的我怎么理解问题、怎么走弯路、怎么把站点一点点推上线。&lt;/p&gt;&lt;p&gt;真正让我不舒服的，从来不是 AI 参与写作，而是我差点只留下结果，没有留下自己。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;我对 AI 的感受，是兴奋也是不安&lt;a href=&quot;#我对-ai-的感受是兴奋也是不安&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我其实很喜欢现在的 AI。&lt;/p&gt;&lt;p&gt;它把很多过去需要先学习很久才能开始的事情，变成了“先做起来，再边做边学”。不熟悉前端，也可以一边描述需求一边搭页面；不会设计完整工作流，也可以先让 AI 帮忙拆模块；面对陌生代码库，不再需要从第一行硬啃到最后一行，AI 可以先给出地图，再陪着定位问题。&lt;/p&gt;&lt;p&gt;这种能力很容易让人上瘾。想法和成品之间原本隔着一大片重复劳动，现在那段距离被迅速压短。以前一个念头可能只停在备忘录里，现在真的有机会在当天变成能运行的东西。&lt;/p&gt;&lt;p&gt;但不安也来自同一个地方。&lt;/p&gt;&lt;p&gt;当文字、代码、图片甚至视频都能快速生成，完成一件作品不再天然等于投入了多少时间。过去我们常常通过“他写了多少”“他做了多久”判断一件事的分量，未来这些线索会越来越不可靠。一个人可能没有写过其中的大部分代码，却真正决定了产品该解决什么问题；也可能生成了上万字内容，却没有一句是自己认真相信的。&lt;/p&gt;&lt;p&gt;AI 让产出变得便宜，也让判断变得昂贵。&lt;/p&gt;&lt;p&gt;我现在越来越在意的，不是“这个东西是不是亲手一行行做出来的”，而是另外几个问题：为什么要做？你选择保留什么、舍弃什么？遇到冲突时相信哪条原则？结果出了问题，谁愿意承担责任？&lt;/p&gt;&lt;p&gt;这些问题暂时还不能交给一个“生成”按钮。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/wallpaper/wallpaper-35.webp&quot; alt=&quot;人与技术真正有意思的地方，不是彼此替代，而是如何碰到一起&quot; /&gt;&lt;figcaption&gt;人与技术真正有意思的地方，不是彼此替代，而是如何碰到一起&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;未来的 AI，可能不会再像一个聊天框&lt;a href=&quot;#未来的-ai可能不会再像一个聊天框&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;现在我们习惯打开一个窗口，输入提示词，等待 AI 回答。可我觉得这种“我问一句，它答一句”的形态，只是很早期的界面。&lt;/p&gt;&lt;p&gt;接下来的 AI 会越来越像一层安静的工作环境。它知道我正在维护哪个项目，理解之前做过的决定，能读取文档、操作工具、检查运行结果，也能在任务持续几天甚至几周时保留上下文。它不只给建议，还会真的执行：修改代码、生成素材、整理数据、运行测试、发布版本，再把需要人判断的部分交回来。&lt;/p&gt;&lt;p&gt;到那时，“会不会写提示词”可能没现在想象得那么重要。真正重要的会变成：能不能把模糊的愿望说成清楚的目标，能不能给出边界和约束，能不能判断结果到底好不好，能不能在效率、成本、风险和体验之间做取舍。&lt;/p&gt;&lt;p&gt;这也是我做自动上架系统和无限画布时越来越明显的感受。工具真正成熟以后，人不应该一直盯着每个按钮和步骤；人更应该决定规则，处理例外，观察结果，再把新的经验沉淀回系统。AI 会吃掉大量机械执行，却不会自动替我们决定“什么值得被做”。&lt;/p&gt;&lt;p&gt;也许未来最稀缺的不是生产内容的能力，而是提出一个真问题的能力。&lt;/p&gt;&lt;p&gt;当所有人都可以快速生成一篇像样的文章、一张漂亮的图、一段能运行的代码时，真正拉开差距的会是经验、审美、取舍和长期积累。AI 可以组合已知答案，但一个人为什么在某天突然想做一个工具，为什么对某个细节执着，为什么愿意为一个看似很小的问题连续调试几天，这些东西仍然来自具体的生活。&lt;/p&gt;&lt;p&gt;AI 可以帮我描述今天，却不能替我活过今天。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;使用 AI，不等于放弃原创&lt;a href=&quot;#使用-ai不等于放弃原创&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我不太想把“亲手写”和“AI 写”分成非黑即白的两边。&lt;/p&gt;&lt;p&gt;原创也不一定意味着每个字都必须从键盘上纯手工长出来。摄影师不需要自己制造相机，程序员也不会因为使用开源库就失去作品署名。对我来说，更重要的是文章的经验从哪里来，观点是不是我真正认同的，最后呈现的内容由谁确认、谁负责。&lt;/p&gt;&lt;p&gt;如果 AI 写出了一段很漂亮的话，但我自己并不相信，只因为它听起来像正确答案就放进文章，那它很难算是我的表达。反过来，如果经历是我的，困惑是我的，判断和取舍也是我的，AI 帮我整理结构、调整节奏、删掉重复，我并不觉得这会让文章失去价值。&lt;/p&gt;&lt;p&gt;关键不是有没有用 AI，而是使用 AI 之后，我还在不在里面。&lt;/p&gt;&lt;p&gt;所以以后写项目介绍，我大概还是会继续让 AI 帮忙。技术结构、功能清单、流程说明，本来就适合交给它整理。它能让我少耗几晚时间，把精力留给真正需要解决的问题。&lt;/p&gt;&lt;p&gt;但我也想给自己留下一小块不追求效率的地方。写一点踩坑时的吐槽，记下功能跑通那一刻的心情，承认某些选择其实没有宏大的理由，只是我当时觉得这样更顺手。哪怕句子没那么漂亮、结构没那么标准，也至少能让我几年后再打开博客时，认得出那是自己留下的声音。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img src=&quot;/assets/images/wallpaper/wallpaper-39.webp&quot; alt=&quot;技术会继续向前，而我也想在路上保留一点自己的声音&quot; /&gt;&lt;figcaption&gt;技术会继续向前，而我也想在路上保留一点自己的声音&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;给那剩下的 10%&lt;a href=&quot;#给那剩下的-10&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;博客当然可以是一块项目展示板。它可以有 AI 生成的、规整清晰的技术说明，也可以快速记录每次上线和版本更新。工具本来就是用来解决问题的，我做自动化、搭 AI 工作流，本来就是因为我相信效率能把人从重复劳动里释放出来。&lt;/p&gt;&lt;p&gt;但博客也应该容得下一些没那么“有用”的文字。&lt;/p&gt;&lt;p&gt;可能是一次失败后的复盘，可能是做完项目的碎碎念，也可能只是像今天这样，忽然对着几篇文章产生的一点怀疑。它们不一定能带来搜索流量，也不一定能成为教程，却能组成一条更真实的时间线。&lt;/p&gt;&lt;p&gt;AI 能帮我完成 90% 的项目文案。剩下那 10%，可能不够专业，也很难批量生产，但它恰好记录了我为什么开始、为什么坚持、为什么在某个瞬间感到开心或疲惫。&lt;/p&gt;&lt;p&gt;这 10% 不需要和 AI 对抗。它只是提醒我，在享受效率的时候，别把自己的感受也一起自动化掉。&lt;/p&gt;&lt;p&gt;以后我大概还是会一边用 AI 快速做项目，一边偶尔慢下来写点这样的东西。因为博客说到底，不只是给别人看的作品列表，也是写给未来自己的成长记录。&lt;/p&gt;&lt;p&gt;其实写到一半还是觉得Ai写的方便，所以上面这些还是Ai写的（苦笑）&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>AI 无限画布工作流：为电商主图生成搭建一套可编排的创作中枢</title><link>https://rainzt.cn/posts/ai-infinite-canvas-workflow/</link><guid isPermaLink="true">https://rainzt.cn/posts/ai-infinite-canvas-workflow/</guid><description>一套面向电商主图批量生成的本地可视化工作台，把产品图、提示词、参考风格、文案规则、GPT Image 节点和结果复盘组织进无限画布，并为后续视频工作流留下扩展空间。</description><pubDate>Tue, 21 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;电商主图的生产，往往卡在一种看似琐碎、实际高频的往返里：找产品图、写场景描述、补品牌规则、调整视觉风格、反复生成，再把满意的结果归档。单次操作并不复杂，但一旦要做批量素材、多人协作或持续迭代，素材、提示词和结果就很容易散落在不同工具与文件夹里。&lt;/p&gt;
&lt;p&gt;这套 AI 无限画布工作流，想解决的正是这件事。它是一套面向电商主图批量生成的本地可视化工作台，把创作过程从一串零散操作，变成一张可以看见、编辑、复用和追溯的生产地图。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;为什么要做这套画布&lt;a href=&quot;#为什么要做这套画布&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;传统图片生成通常以对话或单个表单为入口，适合快速尝试，却不擅长表达完整的创作关系。产品图从哪里来，使用了哪版场景提示词，参考了哪张风格图，哪次生成最适合上架，往往需要靠人工记忆和文件命名维持。&lt;/p&gt;&lt;p&gt;无限画布提供了另一种组织方式：每一个输入、规则、模型调用和结果都是一个节点；节点之间的连线，就是素材流动和决策发生的路径。画布不是装饰，它让工作流本身成为可阅读、可讨论的内容。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;AI 无限画布总览&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;912&quot; src=&quot;/_astro/workflow-canvas.7Ol9Au9u_fx5od.webp&quot; srcset=&quot;/_astro/workflow-canvas.7Ol9Au9u_1KWnKX.webp 640w, /_astro/workflow-canvas.7Ol9Au9u_2dWDcC.webp 750w, /_astro/workflow-canvas.7Ol9Au9u_Z1kYFXk.webp 828w, /_astro/workflow-canvas.7Ol9Au9u_oSBns.webp 1080w, /_astro/workflow-canvas.7Ol9Au9u_Z151Ifl.webp 1280w, /_astro/workflow-canvas.7Ol9Au9u_Zl8S5r.webp 1668w, /_astro/workflow-canvas.7Ol9Au9u_fx5od.webp 1920w&quot; /&gt;&lt;figcaption&gt;AI 无限画布总览&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;现在先把图片主图跑稳&lt;a href=&quot;#现在先把图片主图跑稳&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;当前版本聚焦图片，目标不是做一个只会出图的入口，而是把电商主图生产中真正需要反复使用的部分固定下来。&lt;/p&gt;&lt;p&gt;从产品图开始，可以连接场景描述、参考风格、品牌文案与商品卖点，再把这些信息汇入视觉提示词节点和 GPT Image 生成节点。生成完成后，结果图会直接回到画布中，成为下一轮调整、对比或复用的输入。&lt;/p&gt;&lt;p&gt;这种结构很适合电商素材：同一件商品可以快速尝试不同场景和构图；同一套品牌规则也可以被多个商品项目复用。创作不再从空白提示词开始，而是从已经沉淀下来的工作流继续。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;主图工坊&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;912&quot; src=&quot;/_astro/main-studio.COGrilPr_1Ltzy7.webp&quot; srcset=&quot;/_astro/main-studio.COGrilPr_Z1HFxLR.webp 640w, /_astro/main-studio.COGrilPr_Z1xRXKV.webp 750w, /_astro/main-studio.COGrilPr_1OJPlg.webp 828w, /_astro/main-studio.COGrilPr_Z1j9S1P.webp 1080w, /_astro/main-studio.COGrilPr_Z9Ju6y.webp 1280w, /_astro/main-studio.COGrilPr_ZkYlUD.webp 1668w, /_astro/main-studio.COGrilPr_1Ltzy7.webp 1920w&quot; /&gt;&lt;figcaption&gt;主图工坊&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;节点化，让规则可以复用&lt;a href=&quot;#节点化让规则可以复用&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;画布中的节点不是单一的“输入框”。它们承载的是不同类型的生产信息：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;产品图片和商品信息，定义画面的主体。&lt;/li&gt;
&lt;li&gt;场景、风格和参考图，控制画面的氛围与视觉方向。&lt;/li&gt;
&lt;li&gt;文案规则和品牌约束，保证素材能够被实际使用。&lt;/li&gt;
&lt;li&gt;视觉提示词和图片生成节点，把前面的信息汇总为可执行的生成任务。&lt;/li&gt;
&lt;li&gt;预览、结果和执行状态节点，帮助快速判断一次任务是否值得保留。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;节点库把这些能力按用途整理，画布则允许它们按实际需求自由组合。对于一个成熟的主图方案，可以保存为模板；对于一次试验，也可以局部替换一个节点，而不影响整张画布的其他部分。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;节点库与工作流编排&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;912&quot; src=&quot;/_astro/node-library.7rH1q38R_ZnRstm.webp&quot; srcset=&quot;/_astro/node-library.7rH1q38R_2lAGjM.webp 640w, /_astro/node-library.7rH1q38R_dhfpV.webp 750w, /_astro/node-library.7rH1q38R_d8I9A.webp 828w, /_astro/node-library.7rH1q38R_Z1Hz92X.webp 1080w, /_astro/node-library.7rH1q38R_1wzKUW.webp 1280w, /_astro/node-library.7rH1q38R_Z1mNlGt.webp 1668w, /_astro/node-library.7rH1q38R_ZnRstm.webp 1920w&quot; /&gt;&lt;figcaption&gt;节点库与工作流编排&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;运行前校验，运行后复盘&lt;a href=&quot;#运行前校验运行后复盘&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;一套可以进入日常生产的工作流，不能只在“生成”时出现。画布会在执行前检查必要输入与节点连接，让缺少产品图、提示词或生成配置的问题尽量提前暴露。&lt;/p&gt;&lt;p&gt;执行过程中的状态、日志和结果也会留在项目内。这样一来，某张主图为什么有效、使用了什么组合、下一次该从哪里继续优化，都不需要重新猜测。项目切换、工作流保存和结果归档，最终服务的是一种更稳定的创作节奏。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;从图片走向视频&lt;a href=&quot;#从图片走向视频&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;图片是这套系统的第一站，也是最需要先跑稳的一站。主图生成已经具备了清晰的输入、规则、模型执行和结果回收链路，而这套链路天然可以继续向视频扩展。&lt;/p&gt;&lt;p&gt;未来的视频画布会继续沿用同一套可编排思路：把商品资产、脚本、分镜、镜头参考、关键帧、配音与视频提示词放到同一张工作流中；生成后的片段再进入预览、筛选、拼接与复盘。最终输出不只是单张电商主图，而是一条能够适配不同平台节奏的商品短视频。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;愿景：把素材生产变成可编排的系统&lt;a href=&quot;#愿景把素材生产变成可编排的系统&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;AI 的价值不应该只停留在“更快生成一张图”。更值得建设的是一套能持续积累的生产系统：每次尝试都会留下可复用的节点、规则和路径；每个成功案例都能变成下一次创作的起点。&lt;/p&gt;&lt;p&gt;这张无限画布会先帮助图片主图生产变得更清晰、更可控，随后逐步连接到视频内容。让产品素材从静态图片到动态短片，都能在同一个工作流里被组织、生成、评估和持续优化。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>访客第一次打开博客时，我想先认真说一句欢迎</title><link>https://rainzt.cn/posts/welcome-toast/</link><guid isPermaLink="true">https://rainzt.cn/posts/welcome-toast/</guid><description>记录首次访问欢迎提示的实现：分时问候、所在地获取、会话内只显示一次，以及接口失败时不打扰阅读的回退方案。</description><pubDate>Sun, 19 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;很多个人博客会在访客进入时弹出提示。有的展示浏览器和操作系统，有的显示 IP、天气和访问次数，也有的连续弹出好几条通知。&lt;/p&gt;
&lt;p&gt;我喜欢“有人来时说一句欢迎”这个想法，却不希望它变成开门后的第一道障碍。于是这次做的欢迎提示很轻：只在同一浏览会话第一次打开网站时出现，从右下角进入，停留几秒后自己离开。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;首次访问时出现的欢迎提示&quot; loading=&quot;lazy&quot; width=&quot;596&quot; height=&quot;138&quot; src=&quot;/_astro/effect.DiLZ_-0r_ZSvMDm.webp&quot; srcset=&quot;/_astro/effect.DiLZ_-0r_ZSvMDm.webp 596w&quot; /&gt;&lt;figcaption&gt;首次访问时出现的欢迎提示&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;欢迎应该出现一次，而不是每页一次&lt;a href=&quot;#欢迎应该出现一次而不是每页一次&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Firefly 在站内跳转时会替换页面内容。如果每次切换路由都重新执行欢迎逻辑，访客从首页打开文章、再去工具页，会连续看到同一条提示。&lt;/p&gt;&lt;p&gt;我使用 &lt;code&gt;sessionStorage&lt;/code&gt; 保存一个会话标记：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;rain_welcome_seen_v1&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;sessionStorage&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;getItem&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;sessionStorage&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;setItem&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;true&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;只要当前标签页没有关闭，它就不会再次出现。重新打开一个新会话时，欢迎提示才会回来。&lt;/p&gt;&lt;p&gt;我没有使用长期的 &lt;code&gt;localStorage&lt;/code&gt;，因为那会让看过一次的访客以后永远见不到；也没有使用 Cookie，因为这个状态只和页面体验有关，不需要发给服务器。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;先显示，再尝试获取所在地&lt;a href=&quot;#先显示再尝试获取所在地&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;提示出现时，会先根据本地时间生成一句问候：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;早晨显示“早上好”；&lt;/li&gt;
&lt;li&gt;下午显示“下午好”；&lt;/li&gt;
&lt;li&gt;深夜则把语气放轻。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;随后才在后台请求所在地。如果接口顺利返回，文案会补充为“来自某地的朋友，很高兴遇见你”；如果请求超时、被浏览器拦截或接口暂时不可用，就保留普通欢迎语。&lt;/p&gt;&lt;p&gt;这一步的顺序很重要。第三方接口不应该决定组件能不能出现，更不能拖慢博客正文。请求设置了较短超时，并且使用 &lt;code&gt;AbortController&lt;/code&gt; 主动终止。定位只是锦上添花，失败不是页面错误。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;动画要让人看见，也要允许离开&lt;a href=&quot;#动画要让人看见也要允许离开&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;弹窗使用位移、缩放和透明度完成进入动画，时间控制得比较短。右上角保留关闭按钮，不要求访客一定等自动计时结束。&lt;/p&gt;&lt;p&gt;在手机上，它会避开底部悬浮导航；如果系统开启“减少动态效果”，位移动画会被取消，只做轻微淡入淡出。&lt;/p&gt;&lt;p&gt;这些处理看起来比“显示一段文字”复杂，但它们共同决定了提示会不会打扰人：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;不遮挡主要按钮；&lt;/li&gt;
&lt;li&gt;不抢占页面焦点；&lt;/li&gt;
&lt;li&gt;可以手动关闭；&lt;/li&gt;
&lt;li&gt;几秒后自动消失；&lt;/li&gt;
&lt;li&gt;同一会话不重复；&lt;/li&gt;
&lt;li&gt;接口失败不报错。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;建立欢迎提示组件&lt;a href=&quot;#建立欢迎提示组件&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;组件放在：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;src/components/features/WelcomeToast.astro&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;它不是侧栏内容，而是覆盖在页面右下角，所以直接写成全局功能组件：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;welcome-toast&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;welcome-toast&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;aria-live&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;polite&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;aria-atomic&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;true&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;data-state&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;idle&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;welcome-toast-icon&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;✦&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;welcome-toast-kicker&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;WELCOME&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;strong&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;welcome-toast-title&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;欢迎来到朝朝听雨&amp;lt;/&lt;/span&gt;&lt;span&gt;strong&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;welcome-toast-message&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;很高兴在这里遇见你。&amp;lt;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;welcome-toast-close&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;aria-label&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;关闭欢迎提示&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;×&amp;lt;/&lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;welcome-toast&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;aria-live=&quot;polite&quot;&lt;/code&gt; 会让辅助技术在合适的时机读出提示，又不会强行打断正在阅读的内容。&lt;/p&gt;&lt;p&gt;显示与关闭只依赖 &lt;code&gt;data-state&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.welcome-toast&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;opacity: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pointer-events: &lt;/span&gt;&lt;span&gt;none&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;transform: &lt;/span&gt;&lt;span&gt;translateY&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;calc&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;rem&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;scale&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0.96&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;transition:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;opacity &lt;/span&gt;&lt;span&gt;&lt;span&gt;320&lt;/span&gt;&lt;span&gt;ms&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ease&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;transform &lt;/span&gt;&lt;span&gt;&lt;span&gt;420&lt;/span&gt;&lt;span&gt;ms&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cubic-bezier&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0.22&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0.36&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.welcome-toast&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;data-state&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;visible&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;opacity: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pointer-events: &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;transform: &lt;/span&gt;&lt;span&gt;translateY&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;scale&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;在窄屏下要给底部导航留出空间：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;@media&lt;/span&gt;&lt;span&gt; (max-width: &lt;/span&gt;&lt;span&gt;&lt;span&gt;640&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.welcome-toast&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;right: &lt;/span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;rem&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bottom: &lt;/span&gt;&lt;span&gt;calc&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;rem&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; env(safe-area-inset-bottom));&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;left: &lt;/span&gt;&lt;span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;rem&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;width: &lt;/span&gt;&lt;span&gt;auto&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;会话判断与定位回退&lt;a href=&quot;#会话判断与定位回退&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;初始化时先读 &lt;code&gt;sessionStorage&lt;/code&gt;，没有标记才延迟显示：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;connectedCallback&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;sessionStorage&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;getItem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;rain_welcome_seen_v1&quot;&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.welcome-toast-close&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;?.&lt;/span&gt;&lt;span&gt;addEventListener&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;click&quot;&lt;/span&gt;&lt;span&gt;, () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;close&lt;/span&gt;&lt;span&gt;(), { &lt;/span&gt;&lt;span&gt;once&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; })&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;showTimer&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;setTimeout&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;show&lt;/span&gt;&lt;span&gt;(), &lt;/span&gt;&lt;span&gt;850&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;show()&lt;/code&gt; 中先写入会话标记并显示普通欢迎语，再发起定位请求：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;sessionStorage&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;setItem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;rain_welcome_seen_v1&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;true&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;dataset&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;state&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;visible&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;response&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;await&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;https://v2.xxapi.cn/api/ip&quot;&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;signal&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;abortController&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;signal&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;接口返回结构可能变化，因此不要直接假设地址一定在某个字段。我的处理会依次尝试 &lt;code&gt;data.address&lt;/code&gt;、&lt;code&gt;data.location&lt;/code&gt;、&lt;code&gt;data.city&lt;/code&gt; 等候选值。任何异常都会进入 &lt;code&gt;catch&lt;/code&gt;，保留“很高兴在这里遇见你”的默认文本。&lt;/p&gt;&lt;p&gt;为了防止请求长时间占用，设置 3.5 秒超时：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;abortController&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AbortController&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;setTimeout&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;abortController&lt;/span&gt;&lt;span&gt;?.&lt;/span&gt;&lt;span&gt;abort&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;3500&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;关闭组件或离开页面时，也要终止请求并清理计时器。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;挂载到全局布局&lt;a href=&quot;#挂载到全局布局&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;在 &lt;code&gt;src/layouts/Layout.astro&lt;/code&gt; 顶部导入：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WelcomeToast&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;@components/features/WelcomeToast.astro&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;然后在 &lt;code&gt;body&lt;/code&gt; 末尾、其他全局效果附近挂载：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;SakuraEffect&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;WelcomeToast&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;FancyboxManager&lt;/span&gt;&lt;span&gt; /&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;测试“只出现一次”时，普通刷新不会再次显示，因为当前标签页的 &lt;code&gt;sessionStorage&lt;/code&gt; 仍然存在。可以新开一个标签页，或者在开发者工具的 Application 面板中删除 &lt;code&gt;rain_welcome_seen_v1&lt;/code&gt; 后再刷新。&lt;/p&gt;&lt;p&gt;还需要主动测试断网或接口失败的情况。一个合格的欢迎提示应该在没有定位数据时照常出现和关闭，而不是在控制台抛出未处理异常。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;个性化不等于收集更多信息&lt;a href=&quot;#个性化不等于收集更多信息&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;位置欢迎很容易继续扩展：显示 IP、网络运营商、浏览器、系统版本，甚至根据天气改变文案。&lt;/p&gt;&lt;p&gt;我没有继续加。访客来到博客，是为了看内容，不是为了被告知网站识别出了多少信息。现在只使用接口返回的概略地区来组织一句话，获取不到就立刻退回普通欢迎，并且不保存这些数据。&lt;/p&gt;&lt;p&gt;这也符合我对第二代博客的想法：可以有技术细节，也可以有一点个人气息，但不需要把每一种能力都展示出来。&lt;/p&gt;&lt;p&gt;欢迎提示最终只停留几秒。它不会成为网站里最重要的功能，却能在第一次见面时替我说一句：你好，欢迎来到朝朝听雨。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>把一年、一个月和一周放进三条进度条</title><link>https://rainzt.cn/posts/schedule-progress/</link><guid isPermaLink="true">https://rainzt.cn/posts/schedule-progress/</guid><description>给博客侧边栏加入时间进度与下一节点倒计时，并记录日期计算、闰年处理和无刷新切页中的计时器清理。</description><pubDate>Fri, 17 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;做定制项目的时候，时间通常以交付日期存在：还有几天要上线，今天必须完成哪一部分，下一轮反馈什么时候回来。&lt;/p&gt;
&lt;p&gt;项目告一段落以后，我想在博客里放一种没那么紧张的时间提醒。它不催我完成任务，只把一年、一个月和一周走到哪里，用三条进度条安静地显示出来。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;时间进度与倒计时组件&quot; loading=&quot;lazy&quot; width=&quot;453&quot; height=&quot;521&quot; src=&quot;/_astro/effect.CD_m62wy_2aUARH.webp&quot; srcset=&quot;/_astro/effect.CD_m62wy_2aUARH.webp 453w&quot; /&gt;&lt;figcaption&gt;时间进度与倒计时组件&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;三条进度，代表三种尺度&lt;a href=&quot;#三条进度代表三种尺度&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;组件放在右侧栏，依次显示：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;本年已经过去的比例，以及还剩多少天；&lt;/li&gt;
&lt;li&gt;本月已经过去的比例，以及还剩多少天；&lt;/li&gt;
&lt;li&gt;本周已经走到哪一天，以及周末前还剩多少天。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;下方再单独留一块区域，显示距离下一个节日还有多久。最初这里只保留元旦、劳动节和国庆节，结果到了 2026 年 7 月，组件直接把“下一站”指向了国庆节，漏掉了 9 月 25 日的中秋节。&lt;/p&gt;&lt;p&gt;现在的节日表分成三类：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;国内节日与纪念日，例如春节、清明、劳动节、国庆节；&lt;/li&gt;
&lt;li&gt;农历传统节日，例如元宵、端午、七夕、中秋、重阳和除夕；&lt;/li&gt;
&lt;li&gt;常见国际节日，例如情人节、复活节、母亲节、父亲节、万圣夜、感恩节和圣诞节。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这样做以后，“全面”不再等于堆一串当年日期。公历固定节日、按星期计算的节日和农历节日分别使用不同算法，第二年仍然能够继续工作。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;日期计算最怕看起来差不多&lt;a href=&quot;#日期计算最怕看起来差不多&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;本月进度最简单：获取今天是几号，再除以当月总天数。&lt;/p&gt;&lt;p&gt;本年进度需要考虑闰年：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;daysInYear&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;year&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;year&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;year&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;400&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;366&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;365&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;周进度则要处理 JavaScript 中星期日等于 &lt;code&gt;0&lt;/code&gt; 的情况。我把星期一到星期日统一映射成 &lt;code&gt;1&lt;/code&gt; 到 &lt;code&gt;7&lt;/code&gt;，这样进度条和“还剩几天”的文案更直观。&lt;/p&gt;&lt;p&gt;另一个容易忽略的问题是夏令时。直接用两个本地时间相减，在采用夏令时的地区可能遇到一天不是完整 24 小时的情况。计算天数时，我先把年月日转换到 UTC 的零点，再用毫秒差换算，避免时区偏移让倒计时多一天或少一天。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;不需要每秒重新计算&lt;a href=&quot;#不需要每秒重新计算&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这块组件展示的最小单位是“天”，没有必要像时钟一样每秒甚至每分钟更新。&lt;/p&gt;&lt;p&gt;页面加载时计算一次，然后只安排一个定时任务，等到下一个本地零点重新刷新。组件被移除时，定时器也会清理：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;connectedCallback&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;update&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;scheduleMidnightUpdate&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;disconnectedCallback&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;clearTimeout&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;midnightTimer&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这种写法对 Firefly 的无刷新页面切换更友好。它不会因为在首页和文章页之间来回跳转，就留下越来越多的后台任务。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;组件结构与进度条样式&lt;a href=&quot;#组件结构与进度条样式&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;组件文件为：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;src/components/widget/ScheduleProgress.astro&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;每一行由百分比、说明文字和进度轨道组成：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;progress-row&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;data-kind&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;year&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;progress-percent&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;--%&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;progress-detail&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;progress-copy&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;本年还剩 -- 天&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;progress-track&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;我没有直接使用原生 &lt;code&gt;&amp;lt;progress&amp;gt;&lt;/code&gt;，而是用普通元素控制宽度。这样渐变、发光和过渡动画更容易统一：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.progress-track&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;height: &lt;/span&gt;&lt;span&gt;&lt;span&gt;0.42&lt;/span&gt;&lt;span&gt;rem&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;overflow: &lt;/span&gt;&lt;span&gt;hidden&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;border-radius: &lt;/span&gt;&lt;span&gt;&lt;span&gt;999&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;background: &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--btn-regular-bg&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.progress-track&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;display: &lt;/span&gt;&lt;span&gt;block&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;width: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;height: &lt;/span&gt;&lt;span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;border-radius: &lt;/span&gt;&lt;span&gt;inherit&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;background: &lt;/span&gt;&lt;span&gt;linear-gradient&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;90&lt;/span&gt;&lt;span&gt;deg&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;#&lt;/span&gt;&lt;span&gt;8de6d1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--primary&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;transition: width &lt;/span&gt;&lt;span&gt;&lt;span&gt;650&lt;/span&gt;&lt;span&gt;ms&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cubic-bezier&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0.22&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0.36&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;脚本更新时只需要修改文字与 &lt;code&gt;width&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;setProgress&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;kind&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;percent&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;copy&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;row&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;`.progress-row[data-kind=&quot;&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;kind&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;]`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;row&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.progress-percent&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;textContent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;percent&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;toFixed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;%`&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;row&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.progress-copy&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;textContent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;copy&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;row&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.progress-track i&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;width&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;min&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;percent&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;%`&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;计算年、月、周进度&lt;a href=&quot;#计算年月周进度&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;核心更新函数可以按下面的顺序组织：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;year&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;getFullYear&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;month&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;getMonth&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;today&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;getDate&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;daysInMonth&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;year&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;month&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;getDate&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;weekday&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;getDay&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;||&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;7&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;startOfYear&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;year&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;startOfToday&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;year&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;month&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;today&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dayOfYear&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;floor&lt;/span&gt;&lt;span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;startOfToday&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;getTime&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;startOfYear&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;getTime&lt;/span&gt;&lt;span&gt;()) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;86400000&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;再分别传给 &lt;code&gt;setProgress&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setProgress&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;month&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;today&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;daysInMonth&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;`本月还剩 &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;daysInMonth&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;today&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 天`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setProgress&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;week&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;weekday&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;7&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;`本周还剩 &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;7&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;weekday&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 天`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;三类节日要用三种算法&lt;a href=&quot;#三类节日要用三种算法&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;固定公历日期最直接，月份仍然从 &lt;code&gt;0&lt;/code&gt; 开始：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fixedFestivals&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;元旦&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;month&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;day&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;kind&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;国内&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;情人节&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;month&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;day&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;14&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;kind&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;国际&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;国庆节&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;month&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;9&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;day&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;kind&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;国内&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;圣诞节&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;month&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;11&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;day&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;25&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;kind&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;国际&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;母亲节、父亲节和感恩节不是固定几号，而是“某月第几个星期几”。我写了一个通用函数：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;nthWeekdayOfMonth&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;year&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;month&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;weekday&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;nth&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;first&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;year&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;month&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;offset&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;weekday&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;first&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;getDay&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;7&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;7&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;year&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;month&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;offset&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;nth&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;7&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;mothersDay&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;nthWeekdayOfMonth&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;year&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;thanksgiving&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;nthWeekdayOfMonth&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;year&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;上面的 &lt;code&gt;0&lt;/code&gt; 代表星期日，&lt;code&gt;4&lt;/code&gt; 代表星期四。复活节的规则更特殊，组件使用 Gregorian computus 算法单独计算。&lt;/p&gt;&lt;p&gt;农历节日没有硬编码成 2026 年的公历日期，而是借助浏览器自带的中文农历日历：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;lunarFormatter&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Intl&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;DateTimeFormat&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;zh-CN-u-ca-chinese&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;month&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;numeric&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;day&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;numeric&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;getLunarMonthDay&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;date&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;parts&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;lunarFormatter&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;formatToParts&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;date&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;month&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Number&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;parts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;part&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;part&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;month&quot;&lt;/span&gt;&lt;span&gt;)?.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;day&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Number&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;parts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;find&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;part&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;part&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;day&quot;&lt;/span&gt;&lt;span&gt;)?.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;脚本从今天开始向后扫描 400 天，将每天转换成农历月日，再和节日表匹配。这样中秋始终匹配农历八月十五，端午始终匹配五月初五，不需要每年手动改日期。除夕不能写死为腊月三十，因为有些年份腊月只有二十九天；我的判断方式是检查“明天是否为正月初一”，如果是，今天就是除夕。&lt;/p&gt;&lt;p&gt;最后把三类候选日期合并、去重并排序，取第一个不早于今天的日期：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;&lt;span&gt;unique&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;values&lt;/span&gt;&lt;span&gt;()]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;filter&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;festival&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;festival&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;date&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;startOfToday&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;sort&lt;/span&gt;&lt;span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;date&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getTime&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;date&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getTime&lt;/span&gt;&lt;span&gt;())[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;2026 年 7 月 24 日之后，完整列表会先显示 8 月 19 日的七夕节，随后是 9 月 25 日的中秋节，不会再直接跳到国庆节。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;注册并放到右侧最下面&lt;a href=&quot;#注册并放到右侧最下面&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;与其他侧栏组件一样，需要完成三步：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;在 &lt;code&gt;WidgetComponentType&lt;/code&gt; 中加入 &lt;code&gt;&quot;scheduleProgress&quot;&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;SideBar.astro&lt;/code&gt; 导入组件并写入 &lt;code&gt;componentMap&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;rightComponents&lt;/code&gt; 最后加入配置。&lt;/li&gt;
&lt;/ol&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;scheduleProgress&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;enable&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;position&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;sticky&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;showOnPostPage&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;它被放在右侧配置最后，因此会出现在日历与其他启用组件之后。&lt;code&gt;position: &quot;sticky&quot;&lt;/code&gt; 让它属于跟随滚动区域；如果改成 &lt;code&gt;top&lt;/code&gt;，它会和时钟、统计等固定顶部组件排在一起。&lt;/p&gt;&lt;p&gt;测试时重点看月末、年末、星期日和闰年二月。日期组件最容易在边界条件出错，普通工作日显示正确并不能说明算法已经完整。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;进度条不是为了制造焦虑&lt;a href=&quot;#进度条不是为了制造焦虑&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;把时间做成百分比，很容易让人产生“今年怎么已经过去这么多”的紧迫感。所以我没有使用警告色，也没有加入“剩余生命”一类过重的文案。&lt;/p&gt;&lt;p&gt;三条进度都沿用博客主题色，数字保持小尺寸，下方倒计时只写“距离下一站”。它更像一个路标，而不是考核表。&lt;/p&gt;&lt;p&gt;有时我会觉得时间很快，电脑里的项目文件夹一批接一批，回过头才发现一个月已经接近尾声。把这些尺度放在侧边栏，至少能让我在打开博客时短暂地意识到：今天属于这一周，也属于这个月和这一年。&lt;/p&gt;&lt;p&gt;工具提醒我把工作做完，进度条则提醒我，时间本身也值得被看见。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>每天留一句话：我在侧边栏做了一个本地每日一言</title><link>https://rainzt.cn/posts/quote-of-the-day/</link><guid isPermaLink="true">https://rainzt.cn/posts/quote-of-the-day/</guid><description>不调用外部语录接口，也不需要数据库，用日期从十条本地名句中选出当天固定的一句。</description><pubDate>Tue, 14 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;重新开始写博客以后，我越来越在意“文字是不是自己的”。&lt;/p&gt;
&lt;p&gt;以前赶项目时，我习惯把资料交给 AI，让它快速整理出完整文章。效率很高，可当博客真正准备长期更新时，我又希望页面里能多留一点与文字有关的气息。每日一言正好给了我一个很小的位置：它不需要展开成一篇文章，也不负责讲清某项技术，只是在访客打开首页时，留下一句值得慢慢读的话。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;每日一言侧边栏效果&quot; loading=&quot;lazy&quot; width=&quot;446&quot; height=&quot;231&quot; src=&quot;/_astro/effect.Bm_SEfLm_Z1kw4jA.webp&quot; srcset=&quot;/_astro/effect.Bm_SEfLm_Z1kw4jA.webp 446w&quot; /&gt;&lt;figcaption&gt;每日一言侧边栏效果&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;为什么没有直接接语录 API&lt;a href=&quot;#为什么没有直接接语录-api&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;网上有很多现成的一言接口，请求一次就能得到一句随机内容。接入确实方便，但我最终没有用。&lt;/p&gt;&lt;p&gt;原因有三个。&lt;/p&gt;&lt;p&gt;第一，第三方接口的速度和稳定性不受我控制。侧边栏本来是静态页面的一部分，不值得为了十几个字增加一次网络依赖。&lt;/p&gt;&lt;p&gt;第二，接口返回什么内容无法预先检查，偶尔可能出现过长、重复或不符合博客气质的句子。自己维护内容，排版和语气都更可控。&lt;/p&gt;&lt;p&gt;第三，也是最重要的一点：我想让它逐渐变成自己筛选过的句子库，而不是另一个站点内容的转发器。&lt;/p&gt;&lt;p&gt;所以组件内放的是十条经过核对的经典名句，例如：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;千里之行，始于足下。&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;学而不思则罔，思而不学则殆。&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;纸上得来终觉浅，绝知此事要躬行。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;十条内容来自《道德经》《论语》《荀子》以及陆游、屈原的诗文。它们都保存在本地数组中，页面不需要为了十几个字再等待一次网络请求。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;今天固定，明天再换&lt;a href=&quot;#今天固定明天再换&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;“今日一言”不应该像轮播图一样十几秒换一次。我的规则很简单：语录库里共有十条，同一天无论刷新多少次都显示同一句，日期变化后再显示下一句。&lt;/p&gt;&lt;p&gt;实现时先把访客本地日期换算成一个稳定的天数，再对语录总数取余：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dayNumber&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;floor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;UTC&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;getFullYear&lt;/span&gt;&lt;span&gt;&lt;span&gt;(), &lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;getMonth&lt;/span&gt;&lt;span&gt;&lt;span&gt;(), &lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;getDate&lt;/span&gt;&lt;span&gt;()) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;86_400_000&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;quoteIndex&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dayNumber&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;quotes&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这里使用年、月、日，而不是当前的小时和分钟，所以刷新页面不会改变结果。天数每天只增加一，索引也会依次向后移动；十条全部出现一遍后，再从第一条重新开始。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;让它看起来像一句旁白&lt;a href=&quot;#让它看起来像一句旁白&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;视觉上我没有给它加复杂背景，只沿用侧边栏卡片：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;标题是“今日一言”；&lt;/li&gt;
&lt;li&gt;正文使用略微倾斜的字形；&lt;/li&gt;
&lt;li&gt;两侧引号用主题色降低透明度；&lt;/li&gt;
&lt;li&gt;作者放在右下角；&lt;/li&gt;
&lt;li&gt;鼠标经过时，正文才轻轻变成主题色。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;它的存在感比时间卡片低，也比统计组件更松。页面滚动到这里时，像在一组数字和工具之间短暂停了一下。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;把每日一言做成可注册的侧栏组件&lt;a href=&quot;#把每日一言做成可注册的侧栏组件&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;组件文件放在：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;src/components/widget/QuoteOfTheDay.astro&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;外层继续使用主题自带的 &lt;code&gt;WidgetLayout&lt;/code&gt;，这样圆角、标题、内边距和暗色模式不用重新造一套：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;---&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WidgetLayout&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;@/components/common/WidgetLayout.astro&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;className&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt; } &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Astro&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;props&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;---&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;WidgetLayout&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;quote-of-the-day&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;今日一言&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;className&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;daily-quote&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;blockquote&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;quote-mark&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;“&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;quote-text&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;慢一点，也是在向前走。&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;quote-mark&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;”&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;blockquote&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;cite&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;quote-author&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;—— Rain&amp;lt;/&lt;/span&gt;&lt;span&gt;cite&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;daily-quote&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;WidgetLayout&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;文字数组和日期选择逻辑放在组件脚本中。每一项都保存正文与署名，后续也可以把署名换成书名、项目名或记录日期：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;quotes&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;千里之行，始于足下。&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;《道德经》&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;学而不思则罔，思而不学则殆。&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;《论语》&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;纸上得来终觉浅，绝知此事要躬行。&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;陆游&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;DailyQuote&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;extends&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;HTMLElement&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;midnightTimer&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;connectedCallback&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;showTodayQuote&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;scheduleMidnightRefresh&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;disconnectedCallback&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;midnightTimer&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;clearTimeout&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;midnightTimer&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;showTodayQuote&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dayNumber&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;floor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;UTC&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;getFullYear&lt;/span&gt;&lt;span&gt;&lt;span&gt;(), &lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;getMonth&lt;/span&gt;&lt;span&gt;&lt;span&gt;(), &lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;getDate&lt;/span&gt;&lt;span&gt;()) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;86_400_000&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dayNumber&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;quotes&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;quotes&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.quote-text&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;textContent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;querySelector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;.quote-author&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;textContent&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;`—— &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;private&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scheduleMidnightRefresh&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;nextMidnight&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getFullYear&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getMonth&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;35&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getDate&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;36&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;37&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;38&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;midnightTimer&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;setTimeout&lt;/span&gt;&lt;span&gt;(() &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;39&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;showTodayQuote&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;40&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;scheduleMidnightRefresh&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;41&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}, &lt;/span&gt;&lt;span&gt;nextMidnight&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;getTime&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;getTime&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;42&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;43&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;44&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;45&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;customElements&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;define&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;daily-quote&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;DailyQuote&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;如果项目启用了无刷新切页，注册前需要先判断元素是否已经存在，避免重复定义报错：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;&lt;span&gt;customElements&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;daily-quote&quot;&lt;/span&gt;&lt;span&gt;)) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;customElements&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;define&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;daily-quote&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;DailyQuote&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;注册到左侧栏&lt;a href=&quot;#注册到左侧栏&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;先在 &lt;code&gt;src/types/sidebarConfig.ts&lt;/code&gt; 中加入类型：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;WidgetComponentType&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;profile&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;announcement&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;quoteOfTheDay&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;再在 &lt;code&gt;SideBar.astro&lt;/code&gt; 中导入并注册映射：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;QuoteOfTheDay&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;@/components/widget/QuoteOfTheDay.astro&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;componentMap&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;quoteOfTheDay&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;QuoteOfTheDay&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;我把它放在左侧资料与公告之后、音乐播放器之前：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;quoteOfTheDay&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;enable&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;position&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;top&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;showOnPostPage&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这里的顺序就是实际渲染顺序。想把它移到右边，不需要改组件，只要把这段配置从 &lt;code&gt;leftComponents&lt;/code&gt; 移到 &lt;code&gt;rightComponents&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;修改内容时要注意什么&lt;a href=&quot;#修改内容时要注意什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;如果句子里包含英文引号、反斜杠或换行，记得按照 TypeScript 字符串规则转义。句子过长时，侧栏会明显变高，因此我会尽量控制在一到两行。&lt;/p&gt;&lt;p&gt;日期索引会严格按顺序走完十条。想确认逻辑是否正确，可以在浏览器控制台中临时把日期改成连续几天，检查索引是否依次变化；同时反复刷新当天页面，确认正文始终一致。&lt;/p&gt;&lt;p&gt;我还额外设置了一个指向本地午夜的定时器。这样即使页面从晚上一直开到第二天，它也会在零点后主动更新，而不必依赖访客手动刷新。组件被移除时要清理这个定时器，避免无刷新切页后留下多余任务。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;文字库会跟着博客一起长大&lt;a href=&quot;#文字库会跟着博客一起长大&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这个组件现在保留十句话，不算很多，但每条都经过选择。&lt;/p&gt;&lt;p&gt;有些内容适合写成长文，有些只值得记成一行。以后做完新的自动化项目、踩过新的坑，或者某天只是突然想明白一件小事，我都可以把它补进去。几年之后再看，这个数组也许会变成一份非常简短的时间线。&lt;/p&gt;&lt;p&gt;每日一言真正吸引我的地方，不只是它会自动变化，而是它给零散文字留了一个固定入口。博客不只有完整、正式、可以被搜索到的文章，也可以有这些短暂停留，却仍然值得被记住的句子。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>连鼠标指针也换掉：给第二代博客补上一点自己的细节</title><link>https://rainzt.cn/posts/custom-cursor/</link><guid isPermaLink="true">https://rainzt.cn/posts/custom-cursor/</guid><description>一次很小的博客改造：用两枚本地 cur 文件区分默认与可点击状态，同时照顾输入框、触屏设备和加载失败时的回退。</description><pubDate>Sun, 12 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;做网站时，我通常会先处理最显眼的部分：横幅、文章卡片、侧边栏、配色和字体。鼠标指针几乎总被留到最后，因为它不影响内容，也不会决定页面能不能使用。&lt;/p&gt;
&lt;p&gt;可博客真正打开以后，鼠标又是桌面端最频繁出现的元素。它一直在页面上移动，经过导航、文章链接、播放器按钮和工具入口。默认箭头没有问题，只是和我已经重新整理过的第二代博客相比，显得过于“系统自带”。&lt;/p&gt;
&lt;p&gt;于是这次我给它换了两枚本地光标。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;自定义光标在页面中的效果&quot; loading=&quot;lazy&quot; width=&quot;197&quot; height=&quot;62&quot; src=&quot;/_astro/effect.C6YbBtXu_Z1ofjM3.webp&quot; srcset=&quot;/_astro/effect.C6YbBtXu_Z1ofjM3.webp 197w&quot; /&gt;&lt;figcaption&gt;自定义光标在页面中的效果&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;只做两种状态就够了&lt;a href=&quot;#只做两种状态就够了&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我没有做跟随鼠标的粒子、拖尾或点击烟花。博客页面已经有壁纸轮播、水波纹和樱花效果，再叠一层持续运行的鼠标动画，只会增加干扰。&lt;/p&gt;&lt;p&gt;最终只保留两种状态：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;普通浏览时使用默认光标；&lt;/li&gt;
&lt;li&gt;遇到链接、按钮和可点击控件时切换为指针光标。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;两枚 &lt;code&gt;.cur&lt;/code&gt; 文件放在 &lt;code&gt;public/mouse/&lt;/code&gt;，CSS 用变量统一管理：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;:root&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--cursor-default&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/mouse/default.cur&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;--cursor-pointer&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/mouse/pointer.cur&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;pointer&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这里的第二个值不是多余的。如果自定义文件加载失败，浏览器仍然能回退到系统的 &lt;code&gt;default&lt;/code&gt; 或 &lt;code&gt;pointer&lt;/code&gt;，页面不会因此失去正确的交互提示。&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;默认与可点击状态使用不同的光标文件&quot; loading=&quot;lazy&quot; width=&quot;117&quot; height=&quot;82&quot; src=&quot;/_astro/detail.Cia-sYP0_leujL.webp&quot; srcset=&quot;/_astro/detail.Cia-sYP0_leujL.webp 117w&quot; /&gt;&lt;figcaption&gt;默认与可点击状态使用不同的光标文件&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;可点击，不等于所有元素都用手型&lt;a href=&quot;#可点击不等于所有元素都用手型&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;最开始很容易写成：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cursor: &lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/mouse/default.cur&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这样虽然省事，却会把输入框和文本区域也覆盖掉。用户准备选中文字或输入内容时，光标仍然是一枚普通箭头，反而削弱了浏览器原本清楚的提示。&lt;/p&gt;&lt;p&gt;所以我只给明确的交互元素使用指针光标，包括链接、按钮、下拉框、&lt;code&gt;role=&quot;button&quot;&lt;/code&gt; 和站内已有的 &lt;code&gt;.cursor-pointer&lt;/code&gt;。输入框、文本域与可编辑区域继续使用系统文本光标。&lt;/p&gt;&lt;p&gt;这类细节很小，但它决定了自定义样式是在增强界面，还是只是在覆盖默认行为。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;只在真正有鼠标的设备上启用&lt;a href=&quot;#只在真正有鼠标的设备上启用&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;手机和平板没有悬停指针，没必要加载或应用桌面光标样式。我把规则放进了媒体查询：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;@media&lt;/span&gt;&lt;span&gt;&lt;span&gt; (hover: hover) &lt;/span&gt;&lt;span&gt;and&lt;/span&gt;&lt;span&gt; (pointer: fine) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;/* cursor rules */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;只有同时支持悬停、并且具有精确指针的设备才会启用。触屏端保持原样，也不会为了一个用不到的效果增加额外逻辑。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;完整接入步骤&lt;a href=&quot;#完整接入步骤&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;首先准备两枚 &lt;code&gt;.cur&lt;/code&gt; 文件，并按下面的结构放进 &lt;code&gt;public&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;public/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;└─ mouse/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;├─ default.cur&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;└─ pointer.cur&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;public&lt;/code&gt; 中的文件会原样复制到构建结果，因此页面访问路径分别是 &lt;code&gt;/mouse/default.cur&lt;/code&gt; 和 &lt;code&gt;/mouse/pointer.cur&lt;/code&gt;。如果文件名使用中文或空格，虽然也可能访问成功，但更容易在 CSS 转义和服务器迁移时出问题。&lt;/p&gt;&lt;p&gt;接着新建 &lt;code&gt;src/styles/mouse.css&lt;/code&gt;。下面是站内实际使用的完整规则：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;@media&lt;/span&gt;&lt;span&gt;&lt;span&gt; (hover: hover) &lt;/span&gt;&lt;span&gt;and&lt;/span&gt;&lt;span&gt; (pointer: fine) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;:root&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;--cursor-default&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/mouse/default.cur&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;--cursor-pointer&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;/mouse/pointer.cur&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;pointer&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cursor: &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--cursor-default&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;button&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;select&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;summary&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;role&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;button&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;role&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;link&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.cursor-pointer&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;button&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;submit&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;reset&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cursor: &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--cursor-pointer&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;textarea&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;contenteditable&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;true&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cursor: &lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;最后在全局布局 &lt;code&gt;src/layouts/Layout.astro&lt;/code&gt; 的 frontmatter 中导入：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;@/styles/main.css&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;@/styles/variables.styl&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;@/styles/markdown-extend.styl&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;@/styles/mouse.css&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这一步必须放在全局布局，而不是某个单独页面。否则首页可能已经换了光标，进入文章页后又恢复系统样式。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;怎样确认文件真的生效&lt;a href=&quot;#怎样确认文件真的生效&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;构建成功只代表 CSS 语法没错，不代表 &lt;code&gt;.cur&lt;/code&gt; 一定可以读取。可以直接在浏览器打开：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;http://127.0.0.1:4321/mouse/default.cur&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;http://127.0.0.1:4321/mouse/pointer.cur&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;再打开开发者工具，检查 &lt;code&gt;body&lt;/code&gt; 的计算样式中是否出现自定义 URL。鼠标移到链接上时，计算结果应切换到 &lt;code&gt;pointer.cur&lt;/code&gt;；移到正文段落时回到 &lt;code&gt;default.cur&lt;/code&gt;；进入输入框时仍然是文本光标。&lt;/p&gt;&lt;p&gt;如果浏览器完全没有变化，优先排查：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;文件实际下载成了 HTML 错误页，而不是 CUR 二进制文件；&lt;/li&gt;
&lt;li&gt;CSS 写的是相对路径，进入二级路由后找不到文件；&lt;/li&gt;
&lt;li&gt;光标尺寸或格式不被当前浏览器支持；&lt;/li&gt;
&lt;li&gt;设备被媒体查询识别为触屏设备。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;我最终使用绝对路径，并给每条规则都保留系统回退值。这样就算某个浏览器不支持自定义文件，交互语义也不会丢失。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;越小的改动，越应该克制&lt;a href=&quot;#越小的改动越应该克制&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;自定义光标的代码量很少，真正需要判断的是它应该影响哪些地方。&lt;/p&gt;&lt;p&gt;如果把它做得太抢眼，鼠标每移动一次都在提醒访客“这里换过皮肤”，阅读就会被打断。现在这版更像一个不主动解释的彩蛋：不仔细看可能不会立刻发现，可一旦注意到，会知道这个网站连最小的交互状态也经过了选择。&lt;/p&gt;&lt;p&gt;第二代博客对我来说不是一个急着堆满功能的展示站。它可以有个性，但个性不一定都要大声出现。有些只需要跟着鼠标，安静地待在每一次点击之前。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>让博客在不同时间说不同的话：侧边栏实时时钟改造记录</title><link>https://rainzt.cn/posts/time-greeting/</link><guid isPermaLink="true">https://rainzt.cn/posts/time-greeting/</guid><description>记录我给第二代博客加入分时问候实时时钟的过程，以及从暗色整图卡片重新调整为白色信息区与鸣潮随机配图的几次取舍。</description><pubDate>Sat, 11 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;最近重新打磨第二代博客时，我开始留意一些并不影响功能，却会影响“这个网站像不像自己”的小地方。&lt;/p&gt;
&lt;p&gt;侧边栏原本放着站点统计、日历和音乐播放器，它们各自都很实用，但少了一点会随时间发生变化的东西。于是我加了一块实时时钟：早晨说早上好，中午提醒吃饭和休息，到了晚上则让页面的语气慢下来。&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;时段问候组件的版式参考&quot; loading=&quot;lazy&quot; width=&quot;448&quot; height=&quot;402&quot; src=&quot;/_astro/effect.DOYj7sob_1vewwg.webp&quot; srcset=&quot;/_astro/effect.DOYj7sob_1vewwg.webp 448w&quot; /&gt;&lt;figcaption&gt;时段问候组件的版式参考&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;这项改动看上去只是多了几行时间和一句问候，真正做起来却经历了一次很明显的返工。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;第一版，做得太像一张海报&lt;a href=&quot;#第一版做得太像一张海报&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;最初我把整张卡片都铺成鸣潮壁纸，在图片上叠暗色渐变，再放一个很大的时间。单独看并不难看，甚至有一点游戏界面的感觉。&lt;/p&gt;&lt;p&gt;可它放进右侧栏以后，问题立刻出现了：信息层级太重，和旁边干净的白色卡片不在同一个体系里。用户第一眼看到的是背景图，而不是时间；卡片像一个缩小的横幅，而不是侧栏部件。&lt;/p&gt;&lt;p&gt;这次偏差也提醒了我，参考一个效果时不能只识别“它有哪些元素”，还要识别这些元素之间的关系。原版真正舒服的地方，是上半部分把问候、时间、星期和日期排得很克制，下半部分才留给图片。它不是在图片上写字，而是把信息和装饰分成了两个区域。&lt;/p&gt;&lt;p&gt;所以第二版我把结构彻底拆开：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;上半部分使用博客卡片原有的浅色背景；&lt;/li&gt;
&lt;li&gt;时间保持最大字号，问候语放在它上方；&lt;/li&gt;
&lt;li&gt;星期和日期缩小后靠在时间右侧；&lt;/li&gt;
&lt;li&gt;右上角只留一个跟随时段变化的小图标；&lt;/li&gt;
&lt;li&gt;底部固定为横向图片区域。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这样放回右侧栏后，它终于像这个博客原本就有的一部分。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;图片必须是我的鸣潮图库&lt;a href=&quot;#图片必须是我的鸣潮图库&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;版式稳定后，我又改了两次图片逻辑。&lt;/p&gt;&lt;p&gt;第一次为了加载速度，直接用了壁纸目录里的缩略图。卡片只有 280 像素宽，看起来似乎够用，但缩略图经过压缩后再裁切，人物细节明显发糊。最后还是换回同一批鸣潮壁纸的原图，让浏览器按卡片尺寸裁切显示。&lt;/p&gt;&lt;p&gt;图片池只读取站内已经整理好的鸣潮壁纸：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;images&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;wallpaper-01.webp&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;wallpaper-25.webp&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;// ...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;wallpaper-47.webp&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;每次刷新会重新随机选择一张。这样它不会请求第三方随机图接口，也不会突然出现和站点风格无关的风景照。页面横幅、相册和时钟卡片使用的是同一套视觉素材，整个网站的气质更统一。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;时间更新不能留下重复计时器&lt;a href=&quot;#时间更新不能留下重复计时器&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Firefly 有无刷新页面切换。如果直接在脚本里写一个全局 &lt;code&gt;setInterval&lt;/code&gt;，组件被替换后计时器可能仍然活着，来回切几次页面就会出现多个任务同时更新。&lt;/p&gt;&lt;p&gt;我把它做成了自定义元素，在组件挂载时启动，在移除时清理：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;connectedCallback&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;updateGreeting&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;alignToNextMinute&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;disconnectedCallback&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;clearInterval&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;minuteTimer&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;clearTimeout&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;alignmentTimer&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;第一次更新会对齐到下一分钟的边界，之后每分钟刷新一次。这样时间不会因为页面打开的秒数不同而慢慢偏移，也能适应站内的页面切换。&lt;/p&gt;&lt;p&gt;问候语按六个时段划分：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;深夜：提醒早点休息；&lt;/li&gt;
&lt;li&gt;早晨：迎接新的一天；&lt;/li&gt;
&lt;li&gt;上午：保持活力；&lt;/li&gt;
&lt;li&gt;中午：记得午休；&lt;/li&gt;
&lt;li&gt;下午：继续加油；&lt;/li&gt;
&lt;li&gt;晚上：适当放松。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;从零接入这个组件&lt;a href=&quot;#从零接入这个组件&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;如果要在同一套 Firefly 项目里复现，主要改动四个文件：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;src/components/widget/TimeGreeting.astro&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;src/components/layout/SideBar.astro&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;src/types/sidebarConfig.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;src/config/sidebarConfig.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;先建立 &lt;code&gt;TimeGreeting.astro&lt;/code&gt;。组件上半部分负责信息，下半部分负责图片：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;time-greeting-widget&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class:list&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;time-greeting-card card-base&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;className&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;time-greeting-info&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;time-greeting-message&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;正在获取时间...&amp;lt;/&lt;/span&gt;&lt;span&gt;p&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;time-greeting-meta&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;time-greeting-clock&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;--:--&amp;lt;/&lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;time-greeting-date&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;time-greeting-week&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;---&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;time-greeting-day&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;--&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;time-greeting-month&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;/--&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;time-greeting-image&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;time-greeting-widget&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;时段判断独立成函数，后续改文案时不需要碰 DOM 更新逻辑：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;getGreetingPeriod&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;hour&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;number&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;hour&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;late-night&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;夜深了，早点休息！&quot;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;hour&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;9&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;morning&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;早上好，新的一天！&quot;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;hour&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;12&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;forenoon&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;上午好，充满活力！&quot;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;hour&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;14&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;noon&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;中午好，记得午休！&quot;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;hour&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;18&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;afternoon&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;下午好，继续加油！&quot;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;evening&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;message&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;晚上好，放松一下！&quot;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;鸣潮图片池不必手写二十多个完整路径，可以用编号生成：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;images&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;01&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;&lt;span&gt;Array&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;({ &lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;23&lt;/span&gt;&lt;span&gt;&lt;span&gt; }, (&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;25&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;padStart&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;0&quot;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;`/assets/images/wallpaper/wallpaper-&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;.webp`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;imageUrl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;images&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;floor&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;random&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;images&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;先用 &lt;code&gt;new Image()&lt;/code&gt; 预加载，成功后再设置 CSS 变量，可以避免图片下载过程中卡片突然闪白：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;image&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Image&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;image&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;onload&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; () &lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setProperty&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;--time-greeting-image&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;`url(&quot;&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;imageUrl&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;)`&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;dataset&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;imageReady&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;true&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;image&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;src&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;imageUrl&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;CSS 中让图片区读取这个变量：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.time-greeting-image&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;height: &lt;/span&gt;&lt;span&gt;&lt;span&gt;9&lt;/span&gt;&lt;span&gt;rem&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;background-image: &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;--time-greeting-image&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;background-position: &lt;/span&gt;&lt;span&gt;center&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;background-size: &lt;/span&gt;&lt;span&gt;cover&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;opacity: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;transition: opacity &lt;/span&gt;&lt;span&gt;&lt;span&gt;650&lt;/span&gt;&lt;span&gt;ms&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ease&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;time-greeting-widget&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;data-image-ready&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;true&quot;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;.time-greeting-image&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;opacity: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;组件完成后，在 &lt;code&gt;SideBar.astro&lt;/code&gt; 导入并加入映射：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TimeGreeting&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;@/components/widget/TimeGreeting.astro&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;componentMap&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;// 其他组件&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;timeGreeting&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TimeGreeting&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;然后在 &lt;code&gt;sidebarConfig.ts&lt;/code&gt; 的右侧栏中启用：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;timeGreeting&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;enable&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;position&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;top&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;showOnPostPage&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;最后别忘了把 &lt;code&gt;&quot;timeGreeting&quot;&lt;/code&gt; 加进 &lt;code&gt;WidgetComponentType&lt;/code&gt; 联合类型，否则 TypeScript 会在构建时提示配置值不合法。&lt;/p&gt;&lt;p&gt;本地执行 &lt;code&gt;npm run build&lt;/code&gt; 后，至少检查三种情况：刷新页面时图片是否变化；停留到下一分钟时数字是否更新；从首页进入文章页后组件是否按配置隐藏。只看静态截图并不能证明定时器与无刷新切页都正常。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;小组件也需要服从整体&lt;a href=&quot;#小组件也需要服从整体&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这次改造最有价值的部分，并不是写出了一个时钟，而是把一版“看起来挺酷”的方案主动推倒。&lt;/p&gt;&lt;p&gt;个人博客很容易不断往里加效果。可每一个效果都在争夺注意力，如果只追求单独截图好看，最后页面反而会越来越吵。现在这块时钟保留了鸣潮图片、分时图标和实时变化，也仍然愿意退回到侧栏应有的位置。&lt;/p&gt;&lt;p&gt;它每天会说不同的话，每次刷新会换一张图，但不会压过文章本身。对我来说，这才是这类小组件最合适的状态。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>博客页脚定制方案</title><link>https://rainzt.cn/posts/footer-config/</link><guid isPermaLink="true">https://rainzt.cn/posts/footer-config/</guid><description>记录我给第二代博客重做页脚的过程：如何安排技术徽章、社交入口、运行时间、备案提示与版权信息，并把它做成一个可以继续维护的 Astro 组件。</description><pubDate>Fri, 10 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;以前我很少认真看页脚。&lt;/p&gt;
&lt;p&gt;搭建第一代博客时，我把它当成页面最后的一行版权文字：写上年份、站点名称，再放一个 RSS 链接，任务就算结束。后来文章越来越多，导航、侧栏、播放器和各种小组件陆续长出来，我才发现页脚其实是读者离开一篇文章之前，最后一次和站点发生关系的地方。&lt;/p&gt;
&lt;p&gt;它不适合塞满所有信息，却应该把最需要被记住的内容交代清楚：这个站点用了什么、作者还维护哪些入口、网站运行了多久、版权和备案信息应该去哪里确认。于是这次我没有继续往原来的 FooterConfig.html 里堆标签，而是把页脚重新拆成几个有明确职责的区域。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;先确定页脚的内容顺序&lt;a href=&quot;#先确定页脚的内容顺序&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我最后采用了下面这条从“氛围”到“信息”的顺序：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;一条轻量的装饰带，让正文和页脚之间有一个缓冲。&lt;/li&gt;
&lt;li&gt;技术徽章，说明站点由哪些工具组成。&lt;/li&gt;
&lt;li&gt;社交链接，只保留博客、GitHub、Bilibili 三个入口。&lt;/li&gt;
&lt;li&gt;运行时间，把“这个站点还活着”变成一个持续变化的数字。&lt;/li&gt;
&lt;li&gt;备案提示和版权信息，放在页面最底部，方便查找但不抢视线。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;这个顺序有一个很实际的好处：访客先看到站点气质，再看到可继续访问的入口，最后才是法律和版权信息。每一层都很短，手机上也不会变成一堵文字墙。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;Astro 组件应该负责什么&lt;a href=&quot;#astro-组件应该负责什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我把页脚放在 &lt;code&gt;src/components/layout/Footer.astro&lt;/code&gt;，让它只处理三类事情：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;在服务端输出稳定的静态内容，例如徽章、链接和版权年份；&lt;/li&gt;
&lt;li&gt;计算站点运行时间的初始值，避免页面加载时出现空白；&lt;/li&gt;
&lt;li&gt;在浏览器端每秒更新一次运行时间，其余内容不依赖 JavaScript。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;这种拆分比把全部 HTML 放到配置文件里更容易维护。以后要调整颜色和间距，只改组件的样式；要换社交链接，只改 &lt;code&gt;socials&lt;/code&gt; 数组；要修改起始日期，则改站点配置，不需要在多个文件里搜索日期字符串。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;用数据数组控制技术徽章&lt;a href=&quot;#用数据数组控制技术徽章&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;徽章不是为了炫技，所以我没有给每个徽章引入一套很重的图标。当前只显示技术名称，并用不同的低饱和色做区分：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;techStack&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Astro&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;tone&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;violet&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Tailwind CSS&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;tone&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;cyan&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Twikoo&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;tone&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;orange&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;EdgeOne&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;tone&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;blue&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Umami&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;tone&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;green&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;渲染时只需要遍历数组：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;stack-row&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;techStack&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;item&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;`stack-badge &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;/span&gt;&lt;span&gt;item&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;tone&lt;/span&gt;&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;span&gt;item&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;div&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这样做有两个好处。第一，增删技术栈不会复制粘贴一大段 HTML；第二，颜色属于 &lt;code&gt;tone&lt;/code&gt;，内容和视觉样式彼此独立。将来如果我把评论系统换掉，只需要替换一项数据。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;社交链接要设置“允许列表”&lt;a href=&quot;#社交链接要设置允许列表&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;之前的个人资料里还保留了微信和邮箱入口，但这次页脚只放三个链接：博客、GitHub、Bilibili。这里我用了一个明确的数组，而不是把所有 profile links 原样输出：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;socials&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;博客&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;icon&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;material-symbols:home-outline&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;https://rainzt.cn/&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;GitHub&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;icon&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;simple-icons:github&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;https://github.com/Jarvis0227&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Bilibili&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;icon&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;simple-icons:bilibili&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;https://space.bilibili.com/473321504&quot;&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;];&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这相当于给页脚加了一层“允许列表”。个人资料以后可以继续保存私密或备用联系方式，但它们不会因为配置被复用，就意外出现在公开页脚。外链统一加上 &lt;code&gt;target=&quot;_blank&quot;&lt;/code&gt; 和 &lt;code&gt;rel=&quot;noreferrer&quot;&lt;/code&gt;，避免读者离开博客后无法快速回来，也减少不必要的来源信息暴露。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;运行时间不是一张静态图片&lt;a href=&quot;#运行时间不是一张静态图片&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;站点运行时间的起点写在 &lt;code&gt;siteConfig.siteStartDate&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;siteStartDate&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;2026-07-08&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;组件先在构建时计算天、小时、分钟和秒，首屏直接得到完整文字；页面加载后再用一个很小的定时器更新数字：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;seconds&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;floor&lt;/span&gt;&lt;span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;startAt&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;days&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;floor&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;seconds&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;86400&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;hours&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;floor&lt;/span&gt;&lt;span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;seconds&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;86400&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3600&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;minutes&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Math&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;floor&lt;/span&gt;&lt;span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;seconds&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3600&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;60&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;secs&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;seconds&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;60&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这里特意保留了 &lt;code&gt;Math.max(0, ...)&lt;/code&gt;。如果服务器时间比起始日期还早，页面不会显示负数；如果以后修改起始日期，也不会让运行时间脚本直接失效。数字使用等宽数字，秒数跳动时文本宽度不会来回抖动。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;“萌备案”要和真实备案区分开&lt;a href=&quot;#萌备案要和真实备案区分开&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我在页脚里放了两条萌系占位文字：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;萌ICP备2026070801号&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;萌公网安备2026070801号&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;它们只是页面气氛的一部分，不是真实备案号。实际备案完成后，应该把文字和链接替换成工信部、公安备案平台提供的正式信息，不能把占位文字当作真实备案展示。这个区分也写进了我的维护笔记，避免未来换服务器或提交备案时忘记修改。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;CSS 重点是“收住”&lt;a href=&quot;#css-重点是收住&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;页脚最容易出现的问题不是不好看，而是太用力。我的处理方式有三点：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;用 &lt;code&gt;max-width&lt;/code&gt; 限制内容宽度，让信息集中在正文附近；&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;gap&lt;/code&gt; 和 &lt;code&gt;flex-wrap&lt;/code&gt; 排列徽章与链接，不写死每一行的长度；&lt;/li&gt;
&lt;li&gt;颜色使用 &lt;code&gt;color-mix&lt;/code&gt; 混合当前文字色，让浅色和深色主题自动保持一致。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;徽章采用圆角胶囊，但边框和背景都非常淡；鼠标悬停时只向上移动一像素，不再叠加发光、缩放或大面积渐变。页脚应该像一本书的版权页，能被看见，但不应该抢走文章结尾的注意力。&lt;/p&gt;&lt;p&gt;响应式部分只做必要的调整：小屏幕缩短两侧装饰线、缩小徽章字号、让所有行自动换行。没有给移动端单独复制一套模板，所以桌面和手机看到的是同一份信息，维护成本也更低。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;如何验证它真的工作&lt;a href=&quot;#如何验证它真的工作&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;完成代码后，我会按下面顺序检查：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;执行 &lt;code&gt;npm.cmd run build&lt;/code&gt;，确认 Astro 内容图片、图标和页面路由都能通过构建。&lt;/li&gt;
&lt;li&gt;打开文章页面滚动到底部，检查页脚是否完整出现。&lt;/li&gt;
&lt;li&gt;等待十秒，确认运行时间的秒数持续变化。&lt;/li&gt;
&lt;li&gt;逐个点击博客、GitHub、Bilibili，确认没有多余的微信或邮箱入口。&lt;/li&gt;
&lt;li&gt;调整浏览器宽度，检查徽章和备案行是否自然换行。&lt;/li&gt;
&lt;li&gt;切换深色模式，检查文字对比度和边框是否仍然清楚。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt=&quot;实际页面中的完整页脚效果&quot; loading=&quot;lazy&quot; width=&quot;1126&quot; height=&quot;378&quot; src=&quot;/_astro/footer-effect.BGavE1je_lHuMH.webp&quot; srcset=&quot;/_astro/footer-effect.BGavE1je_Z1eXeuF.webp 640w, /_astro/footer-effect.BGavE1je_zJmFH.webp 750w, /_astro/footer-effect.BGavE1je_eEmzD.webp 828w, /_astro/footer-effect.BGavE1je_PYAKj.webp 1080w, /_astro/footer-effect.BGavE1je_lHuMH.webp 1126w&quot; /&gt;&lt;figcaption&gt;实际页面中的完整页脚效果&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;最后这张图不是参考站点的截图，而是这次修改后我自己的页面截图。把它放进文章里，一方面是给以后回顾留下一个版本快照，另一方面也能在页脚样式再次调整后，快速对比哪些细节发生了变化。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;留给下一次改版的空间&lt;a href=&quot;#留给下一次改版的空间&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这次我没有把页脚做成一个“功能展示区”，也没有加入访问量、天气或一长串友情链接。它现在只承担站点身份、联系入口和基础信息三件事。以后如果博客真的需要新增内容，我会优先考虑两种方式：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;把内容加进现有的数据数组，让它继续保持可维护的结构；&lt;/li&gt;
&lt;li&gt;新增一个独立的、可关闭的区域，而不是把所有东西挤进同一行。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;对我来说，第二代博客最重要的变化，就是不再把每个角落都当成产品橱窗。页脚也是如此：它可以有一点可爱、有一点技术感，但最后还是要安静地把一段路送到终点。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>从零搭建 Rainzt.cn：我的个人博客上线流程</title><link>https://rainzt.cn/posts/build-rainzt-blog/</link><guid isPermaLink="true">https://rainzt.cn/posts/build-rainzt-blog/</guid><description>记录这个个人博客从本地运行、内容配置、生产构建到宝塔 Nginx 部署和 HTTPS 上线的完整流程。</description><pubDate>Wed, 08 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;section&gt;&lt;h2&gt;为什么要搭这个博客&lt;a href=&quot;#为什么要搭这个博客&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;我想要一个真正属于自己的内容空间。它不只是用来发文章，也用来整理项目、记录折腾过程、放一些自己喜欢的页面和小工具。&lt;/p&gt;&lt;p&gt;最后，我把这套基于 Astro 与 Firefly 深度改造的站点命名为 Aemeath 主题。它保留了静态生成速度快、页面效果完整、文章用 Markdown 管理这些优点，部署后只需要一份 &lt;code&gt;dist&lt;/code&gt; 静态文件就能跑起来。对个人博客来说，这种结构足够轻，也足够自由，同时也终于有了更贴近自己审美的名字。&lt;/p&gt;&lt;p&gt;这个站点最终部署在 &lt;code&gt;rainzt.cn&lt;/code&gt;，服务器侧使用宝塔面板和 Nginx，HTTPS 证书也已经配置好。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;项目准备&lt;a href=&quot;#项目准备&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;开始之前，先确认本地已经准备好 Node.js 和 pnpm。这个博客使用的是 Astro 项目结构，内容、配置和静态资源都放在源码目录中维护，生产环境只需要部署构建后的静态文件。&lt;/p&gt;&lt;p&gt;项目主要结构大概是这样：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;src/content/posts/     文章目录&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;src/content/spec/      关于、友链、留言等特殊页面&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;src/config/            站点配置&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;public/                静态资源&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;dist/                  构建后的生产文件&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;package.json           项目脚本&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;wrangler.jsonc         Cloudflare Workers 静态资源配置&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;vercel.json            Vercel 输出目录配置&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;其中最常用的是 &lt;code&gt;src/content/posts/&lt;/code&gt;。以后新文章基本都放在这里。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;本地运行&lt;a href=&quot;#本地运行&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;项目使用 pnpm 管理依赖。进入项目根目录后，先安装依赖：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;开发环境启动：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dev&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;本地开发服务会跑在类似下面的地址：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;http://127.0.0.1:4333/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;开发环境和生产环境有一个重要区别：开发环境会显示草稿文章，方便预览和调试；生产构建会自动过滤 &lt;code&gt;draft: true&lt;/code&gt; 的文章。所以本地看到的文章数量可能比线上多，这是正常现象。&lt;/p&gt;&lt;p&gt;这个逻辑在 &lt;code&gt;src/utils/content-utils.ts&lt;/code&gt; 里：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;meta&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;PROD&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;draft&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;!==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;也就是说，生产环境只展示非草稿文章。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;配置站点信息&lt;a href=&quot;#配置站点信息&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;站点的大部分基础信息都在 &lt;code&gt;src/config/siteConfig.ts&lt;/code&gt; 里配置，例如：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;站点标题&lt;/li&gt;
&lt;li&gt;副标题&lt;/li&gt;
&lt;li&gt;站点地址&lt;/li&gt;
&lt;li&gt;导航栏标题和 Logo&lt;/li&gt;
&lt;li&gt;页面开关&lt;/li&gt;
&lt;li&gt;分页数量&lt;/li&gt;
&lt;li&gt;壁纸、字体、主题色等显示配置&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;导航菜单在 &lt;code&gt;src/config/navBarConfig.ts&lt;/code&gt; 里维护。比如首页、文章、工具、更新日志、友链、留言、我的、关于等菜单，都是从这里生成的。&lt;/p&gt;&lt;p&gt;如果某个菜单带有子菜单，它会渲染成下拉框。比如“文章”下面有归档、分类、标签；“我的”下面有相册、追番、番组计划。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;写文章&lt;a href=&quot;#写文章&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;文章使用 Markdown 或 MDX。最简单的一篇文章格式如下：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;---&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;文章标题&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;published&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2026-07-08&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;文章简介&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;image&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;/assets/images/wallpaper/wallpaper-20.webp&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;tags&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;博客搭建&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;Astro&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;category&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;博客指南&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;draft&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;---&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;这里开始写正文。&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;published&lt;/code&gt; 是发布时间。&lt;br /&gt;
&lt;code&gt;draft: false&lt;/code&gt; 表示会在生产环境发布。&lt;br /&gt;
&lt;code&gt;draft: true&lt;/code&gt; 表示只在本地开发时显示。&lt;/p&gt;&lt;p&gt;如果文章需要配图，可以像现在这样创建一个文章目录：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;src/content/posts/build-rainzt-blog/index.md&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;src/content/posts/build-rainzt-blog/cover.png&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;src/content/posts/build-rainzt-blog/step-1.png&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;正文里可以直接引用同目录图片：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;![&lt;/span&gt;&lt;span&gt;部署截图&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;./step-1.png&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;生产构建&lt;a href=&quot;#生产构建&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;确认文章和配置没问题后，执行生产构建：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;build&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这个命令不是只跑 Astro，它会连续做几件事：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;node&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scripts/generate-icons.js&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tsx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scripts/generate-lqips.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;astro&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;build&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;npx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;tsx&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;scripts/subset-fonts.ts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pagefind&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--site&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dist&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;分别对应：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;生成项目用到的图标常量&lt;/li&gt;
&lt;li&gt;为图片生成低质量占位图数据&lt;/li&gt;
&lt;li&gt;生成 Astro 静态站点&lt;/li&gt;
&lt;li&gt;处理字体子集&lt;/li&gt;
&lt;li&gt;生成 Pagefind 搜索索引&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;构建完成后，生产文件会出现在：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;dist/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;部署到服务器的就是这个目录里的内容。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;本地预览生产版本&lt;a href=&quot;#本地预览生产版本&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;开发环境正常不代表生产构建一定正常，所以部署前最好跑一次预览：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;preview&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;也可以指定端口：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pnpm&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;preview&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--host&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;127.0.0.1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--port&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;4334&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这个预览服务读取的是 &lt;code&gt;dist&lt;/code&gt;，更接近线上效果。如果这里正常，线上不正常，问题通常就在部署目录、静态资源缓存或服务器配置上。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;服务器环境&lt;a href=&quot;#服务器环境&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;线上使用的是宝塔面板加 Nginx。当前站点配置里，&lt;code&gt;rainzt.cn&lt;/code&gt; 和 &lt;code&gt;www.rainzt.cn&lt;/code&gt; 都指向同一个网站根目录。&lt;/p&gt;&lt;p&gt;Nginx 配置里核心部分是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;root &lt;/span&gt;&lt;span&gt;/path/to/site;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;index &lt;/span&gt;&lt;span&gt;index.html;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;location&lt;/span&gt;&lt;span&gt; / {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;   &lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;try_files &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;uri&lt;/span&gt;&lt;span&gt; $&lt;/span&gt;&lt;span&gt;uri&lt;/span&gt;&lt;span&gt;/ $&lt;/span&gt;&lt;span&gt;uri&lt;/span&gt;&lt;span&gt;.html &lt;/span&gt;&lt;span&gt;=404&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;HTTPS 证书由宝塔管理。部署静态文件时，只需要操作网站根目录，不要动证书配置。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;部署流程&lt;a href=&quot;#部署流程&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;部署时的思路很简单：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;本地执行 &lt;code&gt;pnpm build&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;把 &lt;code&gt;dist&lt;/code&gt; 打包上传到服务器&lt;/li&gt;
&lt;li&gt;备份旧站点目录&lt;/li&gt;
&lt;li&gt;清空网站根目录里的旧静态文件&lt;/li&gt;
&lt;li&gt;解压新的 &lt;code&gt;dist&lt;/code&gt; 到网站根目录&lt;/li&gt;
&lt;li&gt;检查 Nginx 配置并 reload&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;清空旧文件很重要。Astro 生成的 &lt;code&gt;_astro&lt;/code&gt; 资源带 hash，如果只覆盖不删除，旧 CSS、旧 JS 可能残留。浏览器或 Nginx 又会长期缓存这些带 hash 的资源，最后就容易出现“本地正常，线上某些交互异常”的情况。&lt;/p&gt;&lt;p&gt;部署前最好保留一份旧版本备份。这样就算新版本有问题，也可以快速回滚。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;线上验证&lt;a href=&quot;#线上验证&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;部署完成后，需要检查几个点：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;https://rainzt.cn/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;https://rainzt.cn/api/allPostMeta.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;https://rainzt.cn/_astro/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;首页能打开，说明 Nginx root 和静态文件没问题。&lt;br /&gt;
&lt;code&gt;api/allPostMeta.json&lt;/code&gt; 正常，说明文章元数据构建出来了。&lt;br /&gt;
&lt;code&gt;_astro&lt;/code&gt; 里的 CSS 和 JS 能访问，说明资源路径没问题。&lt;/p&gt;&lt;p&gt;如果刚部署完页面样式或下拉框还是旧状态，可以先强制刷新：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Ctrl + F5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;如果仍然不对，就要检查线上 HTML 引用的资源文件名是不是最新构建生成的文件。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;Git 管理&lt;a href=&quot;#git-管理&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;项目已经初始化 Git。常用流程：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;status&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;git&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;commit&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-m&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Add new post&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;注意 &lt;code&gt;dist/&lt;/code&gt;、&lt;code&gt;node_modules/&lt;/code&gt;、日志文件等不需要提交。源码、配置、文章、静态资源才是应该进入 Git 的内容。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;后续更新文章&lt;a href=&quot;#后续更新文章&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;以后发新文章可以按这个流程：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;在 &lt;code&gt;src/content/posts/&lt;/code&gt; 新建文章&lt;/li&gt;
&lt;li&gt;设置好 &lt;code&gt;published&lt;/code&gt;、&lt;code&gt;tags&lt;/code&gt;、&lt;code&gt;category&lt;/code&gt;、&lt;code&gt;draft&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;本地 &lt;code&gt;pnpm dev&lt;/code&gt; 预览&lt;/li&gt;
&lt;li&gt;确认无误后 &lt;code&gt;pnpm build&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;上传新的 &lt;code&gt;dist&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;访问线上确认&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;如果只是草稿，就保持：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;draft&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;等要发布时再改成：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;draft&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;小结&lt;a href=&quot;#小结&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这个博客的核心链路可以概括成一句话：&lt;/p&gt;&lt;p&gt;用 Markdown 写内容，用 Astro 生成静态站点，用宝塔 Nginx 托管 &lt;code&gt;dist&lt;/code&gt;，用 HTTPS 把它稳定地挂到 &lt;code&gt;rainzt.cn&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;它不是最复杂的部署方式，但对个人博客来说很舒服：本地写作足够快，线上访问足够轻，出问题时也容易定位。后面无论是加文章、加工具页、改导航、换壁纸，基本都能围绕这套流程继续迭代。&lt;/p&gt;&lt;/section&gt;</content:encoded></item></channel></rss>