项目:PEXELS 壁纸应用 | 版本:v1.2 | 生成日期:2026-10-06 | 依据:源码分析
这是文档集(00~20,共 21 篇)的最后一篇,承担三个收尾职能:术语表——把全集中反复出现的项目专有名词收进一处,给出定义、首次深入讲解的篇目与源码落点,消除"每篇都要重新解释一遍"的重复;反向索引——按源码文件查文档,回答"我要改 这个文件,该先读哪几篇",它是《00-项目总览》第 6 节"按角色读法"的文件维度补集;任务阅读路径——按四个高频任务(加页面、适配新 API、排查 429、上线检查)给出"读哪些篇的哪些节、按什么顺序、改哪些文件"的操作序列,它是《11-扩展开发指南》的阅读侧投影。本篇不引入任何新知识,所有条目都是既有内容的汇编与指路,条目末尾的《XX》引用就是权威出处。
使用方法按场景区分:新人接手时,先用第 1 节扫一遍术语建立词汇表,再走《00-项目总览》第 5 节的推荐顺序通读;日常开发时,把第 2 节当"改动前置检查"——动任何文件前先看它关联的篇目,特别是标注"机制级"的引用;处理线上问题时,第 3 节的排查路径与《12-FAQ与故障排查》的决策树配合使用,前者给阅读顺序,后者给判定逻辑。
瀑布流(m-waterfall)。指 src/libs/waterfall/index.vue 实现的"绝对定位多列布局":每张卡片被放置到"当前最矮的列"顶部,列高随放置累加,最终形成参差错落的多列墙。区别于 CSS columns 方案(内容按列纵向填充、顺序不可预知),这里的布局算法自己计算每个 item 的 left/top,因此"第 N 项在哪、多高"完全可预知——这是虚拟裁剪得以实现的数学前提。组件是全站最复杂的单件(544 行 + utils.ts 29 行),三个列表页各自传入列数梯度(首页四档 2→5 列,其余三档 2→4 列)。机制详解见《07-模块详解》6.2,性能视角见《10-性能设计与优化》第 2 节,逐段走读见《18-代码走读实录》第 2 节,选型考古见《16-决策记录ADR》ADR-005。
虚拟裁剪(虚拟滚动)。只把"视口上下各 400px 缓冲(VISIBLE_BUFFER = 400)内的 item"渲染进 DOM 的技术,其余 item 只存在于数据数组与定位结果 itemStyles 中。三个不变量值得背下来:DOM 数量只与视口面积相关、与数据总量无关(理论上限 8000 条);未定位的 item 始终保留渲染(否则永远测不到高度,布局自锁);渲染集合与上一帧完全相同时不触发响应式更新(滚动大多数帧零渲染)。与它咬合的两个搭档:v-lazy 的 loadedCache(DOM 销毁重建时不重复请求图片)与 useScrollRestore(keep-alive 页面的滚动恢复)。术语在全集中的口径一致:"虚拟裁剪"强调动作(updateVisible 的区间筛选),"虚拟滚动"强调能力整体,同义。出处:《07》6.2、《10》第 2 节、《18》2.4。
熔断(429 熔断)。src/utils/pexels-collection-cache.ts 的限流自保机制:当封面请求命中 429(配额耗尽),调度器置 rateLimited = true 并记录 rateLimitUntil = now + 5分钟;冷却期内所有封面请求直接回调空串(卡片显示占位色块)、排队项被整体释放;冷却到期后由 isRateLimited() 的惰性判断自动复位(无定时器)。配套的"每冷却期只提示一次"由 rateLimitNotified 标志保证。注意口径:熔断只覆盖合集封面这一条静默失败路径;列表类请求遇到 429 是"抛错 + 可重试",不走熔断(错误分级见降级门面条目)。出处:《07》5.2、《03》第 4 节、《12》FAQ-3/FAQ-12、《18》3.3~3.4。
降级门面。src/api/pexels.ts 的整体角色:全部 8 个接口函数的"降级决策"收敛在这一个文件,页面层永远面对 { list, total } 这类规整形状,不感知数据真假。两条铁律构成它的行为核心:其一,401/403/429 必须上抛不降级(否则熔断与配额提示成为死分支);其二,按"数据可信度要求"分档——列表可降级、指向性详情(getPexelsFromId/getVideoById/getCollectionMedia)有 Key 时失败必须抛出、getMyCollections 一律空数组。完整行为矩阵见《15》第 3 节,决策考古见《16》ADR-002,逐段走读见《18》第 1 节。口语里的"降级"若不加限定词,通常指"Picsum 占位降级"这一分支。
媒体查看器(media-viewer)。src/components/media-viewer/index.vue(398 行),图片与视频共用的详情弹层:gsap 从卡片中心点放大进场、左右切换(按钮/方向键/触屏滑动)、清晰度胶囊、下载与"原页"外链、地址栏同步。它有两种运行形态——embedded=true(列表内弹层,pushState 写地址、popstate 关闭)与 embedded=false(直达页复用,地址已是详情路由、关闭时 router.back 或 push exitPath)。弹层的数据入口永远是 ViewerItem[](viewerItems 与列表同序,切换零网络请求)。机制状态机见《05》第 2 节,走读清单见《07》10.1,地址同步的取舍见《16》ADR-006。
双端分支(isMobileTerminal)。src/utils/helpers.ts 导出的响应式布尔值,全站双端差异的唯一开关:移动端 UA 正则 ∨(有触摸能力 ∧ 视口宽 <1024),三条件叠加规避 iPadOS 13+ UA 伪装 Mac 的盲区。分支发生在组件层而非路由层(单路由表双端复用,ADR-008),典型应用:tabbar 的整体渲染裁剪、hover 下载浮层仅 PC、导航组件两端两套模板。已知边界:触屏笔记本与 iPad 横屏会走 PC 分支。出处:《00》3.1、《07》第 9 节、《12》FAQ-10。
| 术语 | 一句话定义 | 权威篇目 / 源码落点 |
|---|---|---|
| 协议层 | src/utils/pexels.ts:PexelsClient、类型、归一化、降级生成器,不懂业务 | 《03》第 1~2 节 |
| 门面层 | src/api/pexels.ts:参数钳制、降级决策、错误分级、total 截断 | 《07》第 4 节 |
| 调度层 | src/utils/pexels-collection-cache.ts:封面缓存 + 排队 + 熔断 | 《07》第 5 节 |
| 归一化 | snake_case 原始结构 → camelCase Normalized* 模型,字段缺失兜底空串/0 | 《04》3.2 |
| 适配器 | toWallpaperItem / toViewerItem:归一化模型 → 卡片/弹层专用形状 | 《04》3.2、《01》决策四 |
| 鸭子测试 | viewer.ts 判别视频的方式:唯一带非空 videoFiles 数组的即视频 | 《04》3.2 |
| 请求序号(requestSequence) | 列表页自增整数,响应验号不匹配即整包丢弃;卸载时 +1 终结在途写回 | 《04》第 4 节 |
| appliedQuery 快照 | 列表页记录"已生效查询",去重 + 失活挂起期间暂存变更 | 《05》第 1 节 |
| keep-alive 名单 | CACHE_NAMES = ['home','videos','collections'],三个一级页常驻内存 | 《02》第 1 节 |
| 弹层路由化 | media-viewer 用 pushState/replaceState 把详情地址写入历史栈 | 《01》决策五 |
| 直达页 | /pins/:id、/videos/:id 的独立页面,embedded=false 复用弹层 | 《02》3.3/3.6 |
| 封面调度 | 合集封面 N+1 的治理集合:按需入队、3 并发 250ms、缓存、取消函数 | 《07》5.2 |
| 影子字段 | 归一化后无消费方但有意保留的字段(videoPictures/avatar/liked/cover) | 《13》D-04 |
| 配额三问 | 加新请求前的自检:能不能晚点发、能不能不发、能不能只发一次 | 《11》第 0 节 |
| total 截断 | Math.min(total, 8000),配合 >= 完成判定保证无限滚动收敛 | 《07》4.2 |
| 占位底色 | `avgColor | |
| 懒加载(v-lazy) | IntersectionObserver 指令 + 1×1 透明 GIF 占位 + loadedCache 内存缓存 | 《07》第 7 节 |
| 滚动容器约定 | 页面自备 h-full overflow-auto 容器,被瀑布流/滚动恢复/触底观察三方依赖 | 《02》第 6 节 |
| CSS-first | Tailwind 4 无 config.js,@theme/@utility/@custom-variant 全在 styles/index.css | 《07》第 12 节 |
| px 逃生通道 | 任意值 w-[15px] 不被 px-to-viewport 转换(Tailwind 产物不匹配 include) | 《14》4.2 |
| 语义化分包 | manualChunks 将依赖分入 vendor-vue/utils/animation/media,余落 vendor-misc | 《01》第 5 节 |
| SPA 回退 | 服务器把未命中路径回退到 index.html,缺它则详情路由刷新 404 | 《06》4.2 |
| dev 内置代理 | /api/pexels → api.pexels.com/v1,规避 localhost 被 Cloudflare 拦截 | 《14》4.3 |
| 演示模式 | 无 Key 无代理时 client=null,全站占位数据、零网络请求的运行形态 | 《05》第 5 节 |
按"改动这个文件前必读"的原则编制。每文件给出主篇目(机制级讲解所在)与辅篇目(关联约束、历史坑、行为契约)。全集 21 篇的编号:00 总览、01 架构、02 页面、03 接口设计、04 数据模型、05 核心流程、06 部署运行、07 模块详解、08 安全、09 测试、10 性能、11 扩展、12 FAQ、13 技术债、14 配置、15 接口全量、16 ADR、17 依赖、18 走读、19 示例集、20 本篇。
| 源码文件 | 主篇目 | 辅篇目 |
|---|---|---|
package.json / pnpm-lock.yaml | 17(逐依赖解析) | 00(栈速览)、14(scripts)、06(环境) |
vite.config.ts | 14(逐段详解) | 01 第 5 节、06 第 4 节、10 第 5 节、16 ADR-011 |
tsconfig.json / tsconfig.node.json | 14 第 5 节 | 09 第 2.1 节(门禁效力) |
.env.example / .env.* | 14 第 2 节 | 08 第 2~3 节(Key 风险)、06 第 3 节 |
.npmrc / .prettierrc / .gitignore | 14 第 6 节 | 06 第 1 节、09 第 3 节 |
index.html | 06 第 4.2 节 | 08 第 4/6 节(CSP 与统计脚本)、10 第 6 节 |
src/main.ts | 07 第 2 节 | 05 第 4 节(启动初始化链) |
src/App.vue | 07 第 2 节 | — |
src/router/index.ts | 02 第 1 节 | 01、16 ADR-008、06(SPA 回退) |
src/store/index.ts | 04 第 1 节、07 第 8 节 | 02 第 5 节、16 ADR-009 |
src/constants/index.ts | 04 第 1 节 | 11 SOP-A、16 ADR-003(id 重复旧坑) |
src/api/pexels.ts | 07 第 4 节、18 第 1 节 | 03 第 3~4 节、15 第 3 节、16 ADR-001/002/003 |
src/utils/pexels.ts | 03 第 1~2 节、07 第 3 节 | 15 第 2/4 节、19 全篇(响应示例)、16 ADR-001 |
src/utils/pexels-collection-cache.ts | 07 第 5 节、18 第 3 节 | 03 第 4 节、10 第 4 节、16 ADR-004 |
src/utils/viewer.ts | 04 第 3.2 节、07 第 9 节 | 01 决策四、16 ADR-012 |
src/utils/helpers.ts | 07 第 9 节 | 02 第 6 节(滚动恢复)、16 ADR-008 |
src/utils/theme.ts | 07 第 9 节 | 05 第 4 节、12 FAQ-8 |
src/libs/index.ts | 02 第 4 节 | 01 决策八、11 SOP-F |
src/libs/waterfall/(index.vue + utils.ts) | 07 第 6.2 节、18 第 2 节 | 10 第 2 节、09 第 5 节(补测)、16 ADR-005 |
src/libs/infinite-list/index.vue | 07 第 6.1 节 | 05 第 1 节(首屏未铺满补发)、12 FAQ-4 |
src/libs/popup/index.vue | 02 第 6 节 | 09 档案㉙(双钩子解锁) |
src/libs/popover/index.vue / segmented/ / button/ / message/ / icon-map.ts | 07 第 6.3 节 | 02 第 6 节、09 档案㉖~㉘ |
src/directives/index.ts / modules/lazy.ts | 07 第 7 节 | 10 第 3 节、04 第 5 节(loadedCache) |
src/components/media-viewer/index.vue | 05 第 2 节、07 第 10.1 节 | 12 FAQ-6/7、16 ADR-006、18 走读引用 |
src/components/media-hover-actions/index.vue | 07 第 10.2 节 | 13 D-09 |
src/views/layout/index.vue + components/header/ + tabbar/ + main-tabs.ts | 02 第 1~2 节 | 16 ADR-007(CACHE_NAMES)、07 第 11 节 |
src/views/main/index.vue + components/navigation/ | 02 第 3.1 节 | 05 第 1 节、11 SOP-A |
src/views/main/components/list/index.vue + item.vue | 07 第 11 节、05 第 1 节 | 04 第 4 节(状态机)、13 D-07(空态留白) |
src/views/videos/index.vue + components/ | 02 第 3.2 节 | 05 第 3 节、09 第 2.2 节(唯一无 lang=ts 文件) |
src/views/video/index.vue / views/pins/index.vue | 02 第 3.3/3.6 节 | 12 FAQ-6、13 D-11(错误占位留白) |
src/views/collections/index.vue + collection-item.vue | 02 第 3.4 节、05 第 5b 节 | 10 第 4 节(每页 12 条)、07 第 5.2 节 |
src/views/collection/index.vue + media-item.vue | 02 第 3.5 节、07 第 11 节 | 13 D-08/D-13、18 第 1 节(requestSequence 实例) |
src/views/error/404/index.vue | 02 第 1 节 | 06(catchAll) |
src/env.d.ts / src/types/vendor.d.ts | 07 第 2 节、04 第 7 节 | 13 D-02/D-03(待清理)、08 S-04 |
src/styles/index.css | 07 第 12 节 | 14 第 4 节、00 第 5 节(设计约定) |
docs/dependency-upgrade-plan.md | 17(逐条对照) | 13 D-06(漂移修正)、16(证据源) |
.codebuddy/plans/pexels-api-integration_*.md | 16(改造考古) | 13 D-12(未暴露 UI 的反证) |
索引的使用有一条递归规则:主篇目自身的"引用链"也要跟读——例如改 waterfall/index.vue 前读 07 第 6.2 节,该节又引用 09 档案⑭~㉑ 与 02 第 6 节,这些一级引用通常才是改动真正会触碰的约束(滚动容器约定、清理纪律)。反向找文件时若两个篇目都标"主",以任务性质选:理解机制读 07/18,改行为前验证契约读 15/19。
四条路径都标注了"预计精读量"(以节计),并遵循同一条元规则:先读约定与契约,再读机制,最后动手——《11-扩展开发指南》第 0 节的三条公共约定是所有任务的前置。
读《11》SOP-B(两条走法:轻量静态页 vs 复用列表骨架)→《02》第 1 节(路由表与 keep-alive 机制)→ 若进 CACHE_NAMES:读《16》ADR-007 与《02》第 6 节(三个配套:name 一致、滚动恢复、失活挂起)→ 若含列表:读《04》第 4 节(列表状态机四不变量)与《18》1.3(requestSequence 用法)→ 动手文件:src/router/index.ts、新页面目录、(可选)src/views/layout/components/main-tabs.ts。收尾走《11》第 7 节统一收尾(type-check、构建告警、手工验收、注释归档)。
读《11》SOP-D(来源并存或整体替换的四步)→《16》ADR-002 与《03》3.2 节(降级矩阵必须原样继承:新家也要 401/403/429 上抛)→《04》3.2 节(三个归一化模型是返回形状的硬契约)→《15》第 3 节(门面行为矩阵,改完逐格验收)→ 若涉及视频清晰度:《04》3.2 的 pickVideoFile 规则 → 动手文件:src/utils/(新协议文件或改造 pexels.ts)、src/api/pexels.ts(通道选择)、src/utils/pexels.ts 的归一化函数。验证三件事:无 Key 降级形态、401/429 行为一致、pnpm type-check 全绿(归一化字段遗漏会被 strict 拦截)。
先读《12》FAQ-3(现象判定:列表报错 vs 封面静默停更是两条不同的路)→《03》第 4 节(三层防线全图)→ 若封面熔断:读《07》5.2(5 分钟冷却的自动复位机制,确认是"等"还是"查")→《18》3.3~3.4(fetchOne 与 drain 的失败路径,判断是否改坏了熔断触发)→ 若 429 出现得远早于配额预期:读《15》第 6 节(配额预算表,逐场景对账)与《08》第 7 节(多访客共享 Key 的结构性风险)。处置的长期方案只有一个方向:代理层限流与短 TTL 缓存(《08》2.3 的处置步骤 +《06》第 6 节)。期间两个不要:不要在前端加重试循环(协议层无重试是设计,15 篇第 1 节),不要把 429 吞进降级(ADR-002 的死分支论证)。
按执行顺序读《06》第 2 节(构建与 preview 的边界:preview 不执行代理逻辑)→《06》4.2 +《14》第 7 节(部署三要素:SPA 回退、根路径、直连需真实域名;缓存头组合:HTML 短缓存 + assets immutable)→《08》第 2.3 节(Key 处置:轮换 → 代理 → 清工作区,公网部署的前置项)→《08》第 9 节与《06》4.2 发布校验清单(两张清单合并执行:grep 产物无 Key 明文、无 .map 文件、五类 vendor chunk 齐全、统计脚本去留已决策)→《12》FAQ-11(三个部署期高发故障的对照表)→ 上线后按《06》第 5 节的 7 条手工验收路径过一遍核心流程。若面向公网多人使用,加读《08》第 7 节与《10》第 6 节(代理层限流 + vendor-misc 优化)。
除上述四条,三个低频任务的最短读法:加新分类只读《11》SOP-A(5 分钟级,id 唯一且为有效搜索词);修样式/适配问题读《14》4.2(px-to-viewport 参数)+《00》第 5 节(设计约定红线)+《12》FAQ-9;写第一批测试读《09》第 5 节(五级优先排序)+《13》D-14(为什么是 P1)+《18》(被测代码的逐段说明)。
至此 21 篇形成三层结构:认知层(0002、16)回答"这是什么、为什么这样",机制层(0305、07、10、1415、1719)回答"它怎么工作、契约是什么",操作层(06、0809、1113、20)回答"怎么跑起来、怎么改、怎么查、坏了怎么办"。本篇的术语表与索引是三层之间的总线:任何一篇读到的陌生名词都能在 1 节找到出处,任何一处源码改动都能在 2 节找到前置阅读,任何一项任务都能在 3 节找到起跑线。维护这份文档集的纪律也只有一条,且在《13》第 6 节已经立过——源码演进后,被引用的篇目要连带检查;本篇第 2 节的索引表就是"连带检查"的核对底稿:改了哪个文件,就沿它的行把关联篇目过一遍,文档与代码的一致性才能像这个项目过去 21 篇一样,始终是可依赖的。