项目:PEXELS 壁纸应用 | 版本:v1.1 | 生成日期:2026-10-06 | 依据:源码分析
本项目的工程底子相当健康:分层纪律严格(《01-系统架构》第 2 节)、注释档案完整(《09-测试与质量保障》第 4 节收编了约 40 条历史 bug)、无大段死逻辑。现存债务集中在四个层面:安全债(1 项 P0)、死代码与文档漂移(6 项,清理成本极低)、产品留白(6 项,多数是"设计过的取舍"而非疏忽)、工程化缺失(测试/lint/CI 三缺)。下表给出全量编号与分级,第 2~5 节逐条展开(位置/证据/影响/建议四要素),第 6 节给出排期建议。
| 编号 | 债务 | 级别 | 类型 |
|---|---|---|---|
| D-01 | 🔴 API Key 明文落盘并已进入构建产物 | P0(立即) | 安全 |
| D-02 | src/types/vendor.d.ts 的 md5 声明(依赖已移除) | P2 | 死代码 |
| D-03 | src/env.d.ts 与 env 文件中的 VITE_BASE_API/VITE_APP_SECRET 残留 | P2 | 死代码 |
| D-04 | 数据模型中的"影子字段"(videoPictures/avatar/liked/cover 等) | P3 | 死代码(有意保留) |
| D-05 | .playwright-mcp 空目录等杂项 | P3 | 卫生 |
| D-06 | docs/dependency-upgrade-plan.md 部分结论过时(SFC lang=ts 统计) | P2 | 文档漂移 |
| D-07 | 搜索无结果无空态 | P1 | 产品留白 |
| D-08 | 合集元信息翻页匹配(配额贵 + 可能查不到) | P1 | 产品/架构 |
| D-09 | 移动端 hover 交互弱(popover、hover 下载) | P2 | 产品留白 |
| D-10 | 「我的合集」功能性留白(无登录态) | P2 | 产品留白 |
| D-11 | 直达页无错误占位(加载失败纯白屏) | P2 | 产品留白 |
| D-12 | 协议先行参数未暴露 UI(orientation/color 等) | P3 | 产品留白 |
| D-13 | 合集详情页静态 import 弹层(gsap 提前加载) | P3 | 架构 |
| D-14 | 测试 / ESLint / CI 三缺 | P1 | 工程化 |
| D-15 | file-saver 停更维护 | P3 | 依赖 |
D-01 API Key 明文落盘并已进入构建产物。位置与证据:PEXELS/.env.development 与 PEXELS/.env.production 存有同一个 56 位真实 Key;当前 dist/assets/pexels-*.js 实测含该 Key 明文;git 提交 c5f41bd 自述 env 文件曾入库、Key 曾进历史(现已清理,本仓库无远端 ⚠️ 待确认是否曾推送)。影响:任何拿到工作区副本或部署产物的第三方都能提取 Key 盗用配额,触发 429 后全站体验崩坏(《12-FAQ与故障排查》FAQ-3),并有账号级风控风险 ⚠️ 待确认(以 Pexels 条款为准)。建议:按《08-安全与权限设计》第 2.3 节的四步处置(轮换 → 代理 → 清工作区 → 历史审计),这是全项目唯一"现在就该做"的事项,其余债务都可以排期。
D-02 md5 类型声明。位置:src/types/vendor.d.ts 全文仅剩 declare module 'md5';证据:package.json 与 pnpm-lock.yaml 中已无 md5 依赖(随账号体系移除,《00-项目总览》第 1 节);影响:纯死文件,且文件头自述"若安装了 @types/* 可移除"的条件永不满足,误导读者以为签名链路仍在;建议:删除整个文件(注意《09-测试与质量保障》第 2.2 节——它是两个全局声明文件之一,删除后确认 vue-tsc 无引用报错)。
D-03 环境变量残留。位置:src/env.d.ts 的 ImportMetaEnv 中 VITE_BASE_API(标注为必有)与 VITE_APP_SECRET 两个声明;.env.development(值 /api)与 .env.production(值 /prod-api)中的对应赋值;证据:全源码 grep VITE_BASE_API|VITE_APP_SECRET 仅命中 env.d.ts 声明本身,0 消费方(.env.example 注释亦明示"已无任何读取方,故删除"——模板删了,类型声明与实际 env 忘了删);影响:三重误导——新维护者可能以为存在自有后端、VITE_BASE_API 在 env.d.ts 里还标成了必有字段(实际 undefined);建议:删除 env.d.ts 两个声明与两个 env 文件中的赋值,pnpm type-check 验证。
D-04 数据模型影子字段。位置与证据:src/utils/pexels.ts 的 NormalizedVideo.videoPictures(视频预览帧序列,归一化后全站 0 消费,注释自称"为视频抽帧预览预留");WallpaperItem.avatar 恒空串(Pexels 照片接口不返回头像,组件 v-if 兜底隐藏);liked 仅透传无功能;NormalizedCollection.cover? 协议层永不赋值(由调度器在卡片局部状态回填);src/store/index.ts 的 useCategoryData 异步空壳(保留调用形状)。影响:这些都是有注释、有意图的预留,不是事故性死代码;风险在于数量会随时间增长且没有清点机制;建议:保留(删除会破坏注释记录的兼容意图),但在 D-14 建立测试后,为每个影子字段写一条"仍然未消费"的断言注释,让预留变成显式契约。
D-05 杂项卫生。位置:仓库根目录 .playwright-mcp/ 空目录(历史自动化痕迹);.codebuddy/(已在 .gitignore,属本地工具数据);影响:无运行时影响,仅仓库整洁度;建议:随手清理空目录。
D-06 文档漂移。位置:docs/dependency-upgrade-plan.md 尾部"type-check 的实际覆盖面"一节声称"全部 39 个 SFC 都是无 lang=ts 的 script setup";证据:实测当前 29 个 SFC 中 28 个已有 lang=ts,仅 src/views/videos/components/list/index.vue 例外(《09-测试与质量保障》第 2.2 节);影响:按旧结论会低估类型门禁覆盖面、高估补 ts 工作量;建议:两步走——先给最后 1 个 SFC 补 lang="ts"(半小时内完成,盲区归零),再修订该文档(或在该节加"已被 09 篇修正"的指引)。此条同时是文档集其它篇目需要跟进的模板:源码演进后,引用它的文档要连带检查。
D-07 搜索无结果空态。位置:src/views/main/components/list/index.vue 与 src/views/videos/components/list/index.vue 的模板(只有 infinite-list 与弹层,无空列表分支);证据:搜索冷门词时页面空白,底部仅显示「已经没有更多数据了!」(判定逻辑 list.length === 0 直接进入完成态,《05-核心流程》第 1 节);影响:用户无法区分"没有结果"与"加载坏了",这是当前最影响真实使用感知的一条;建议:两个列表容器加 v-if="!isLoading && 列表.length===0" 的空态(文案 + 回到热门按钮),改动局部、无状态机风险,约半天工作量。
D-08 合集元信息翻页匹配。位置:src/views/collection/index.vue 的 loadCollectionMeta(最多 3 页×80 特色 + 3 页×80 我的,逐页 find id);证据:源码注释明示"Pexels 没有按 id 查合集元信息的端点",查不到时显示「合集 ID:xxx(未在特色/我的合集中找到,已展示其全部内容)」;影响:两头受损——配额贵(最坏 6 次请求×80 条只为一个标题,metaCache 缓解但首次仍贵)、匹配率不满(第 4 页之后的特色合集必然查不到);建议:短期接受现状(兜底文案已算体面);中期若启用代理(D-01 处置的副产品),把 featured 列表做成代理层长缓存资源,前端按需全量翻页即不再心疼配额;长期等 Pexels 补端点 ⚠️ 待确认(无公开计划)。
D-09 移动端 hover 弱。位置:src/libs/popover/index.vue(hover 展开)、src/components/media-hover-actions/index.vue(v-if="!isMobileTerminal" 整体仅 PC)、src/views/main/components/navigation/index.vue(汉堡弹层是移动端唯一完整分类入口);证据:《05-核心流程》第 4 节记录"触屏点击会先触发 mouseenter 语义,主题菜单可用但交互偏弱";影响:移动端下载/复制入口只剩弹层内一处(功能可达但入口少一层)、主题切换点击体验粗糙;建议:主题菜单换 m-popup 或长按手势二选一;hover 下载维持现状(移动端弹层已有同功能,强行双入口反增噪)。
D-10 我的合集留白。位置:src/api/pexels.ts 的 getMyCollections(任何失败返回空数组)+ src/views/collections/index.vue 的提示文案;证据:该端点依赖 Pexels 账号登录态,前端直连架构下没有获取登录态的路径(《08-安全与权限设计》第 7 节);影响:Tab 永远为空,仅提示"需要在 Pexels 登录"——功能上是个诚实但无解的占位;建议:短期保留(移除 Tab 反而损害可发现性);若引入服务端代理,可由代理侧实现 OAuth 换取用户态后代理该端点,前端零改动。
D-11 直达页无错误占位。位置:src/views/pins/index.vue、src/views/video/index.vue(catch 后仅 message('error'),viewerIndex 保持 -1);证据:请求失败时页面纯白底无任何占位;影响:分享链接失效或 429 时打开者面对白屏 + 一条 3 秒即逝的提示;建议:加与列表页一致的错误占位(图标 + 文案 + 重试按钮),两个页面共用一个小组件即可。
D-12 协议先行参数未暴露。位置:Orientation/ColorFilter(src/utils/pexels.ts)、视频 size/locale/min_* 参数;证据:协议层支持、全站无 UI(见《03-接口设计》2.1 节"协议先行、界面未跟上");影响:无负面影响,属储备能力;建议:按《11-扩展开发指南》SOP-C 的四步暴露即可,不必专项排期。
D-13 合集详情页静态 import 弹层。位置:src/views/collection/index.vue 第 76 行直接 import mediaViewer(对比首页/视频页的 defineAsyncComponent);证据:dist 实测 collection chunk 静态依赖 media-viewer chunk 连带 vendor-animation(gsap 69.6KB 提前加载);影响:/collections/:id 首屏多拉约 77KB(未压缩);建议:一行改回 defineAsyncComponent(《10-性能设计与优化》第 6 节第 1 条)。
D-14 测试 / ESLint / CI 三缺。位置:package.json(scripts 仅 4 条、无测试与 lint 依赖)、仓库无 .github;证据与影响全景见《09-测试与质量保障》第 1 节;建议:按该篇第 5 节的五级优先执行——最小集是"Vitest 覆盖瀑布流 utils + 降级矩阵 + 封面调度器 + viewer 归一化(四个纯逻辑区)+ GitHub Actions 跑 type-check",两三个工作日即可把注释档案中最危险的旧坑变成机器契约。这里补充一条关于为什么把它列为 P1 而不是 P2 的论证:本项目的历史 bug 分布有明显规律(时序竞态、清理遗漏、单位换算三类占了大头),这三类恰好都是类型系统管不到、人工 code review 低效、而单测廉价的领域;且项目的演进计划(SOP-D/E 的多数据源、代理层建设)都会触碰同一段核心逻辑——没有契约测试的扩展等于在流沙上加盖楼。
D-15 file-saver 停更。位置:package.json(^2.0.5);证据:docs/dependency-upgrade-plan.md"待评估"清单原文(2020 年后无维护但功能稳定);影响:无已知漏洞 ⚠️ 待确认(以审计工具为准),主要是长期升级心理负担;建议:维持现状,等 D-14 就绪后用"原生 a[download] + 跨域失败回退 file-saver"的封装替换并加测试。
架构债的盘点里还有一类**"隐性契约"**需要单独点出,它们不是独立条目,但拆除任何一项都会连带伤及他处:其一,滚动容器约定(页面自己的 h-full overflow-auto 容器同时被瀑布流的 findScrollParent、useScrollRestore、m-infinite-list 的观察根三方依赖,《02-页面设计》第 6 节);其二,弹层与列表同序约定(viewerItems 恒等于列表的归一化映射,弹层下标即列表下标);其三,history.state 依赖(media-viewer 关闭分支读取 vue-router 写入的 state.back,属于对 vue-router 内部行为的弱契约,升级 vue-router 大版本时需回归验证)。这些契约的共同特征是"没有任何机制强制,只靠约定与文档"——D-14 的测试落地后,优先给这三条写守护用例,比给它们写重构方案更划算。
把上述债务与产品机会排成三个阶段,原则是:先封风险、再补契约、后扩能力——每一阶段结束都应保持"文档集与源码一致"这个本项目的既有优势。
第一阶段(风险清零,1 周内,均为小改动):D-01 四步处置(轮换 Key + 启用 VITE_PEXELS_PROXY_BASE + 清工作区与 dist + 确认历史);D-13 一行改异步弹层;D-02/D-03/D-05 死代码清理合并为一次提交;D-06 补最后一个 lang=ts 并修订文档。阶段验收:发布校验清单(《06-部署运行》4.2)全绿、grep 产物无 Key。
第二阶段(契约固化,2~3 周):D-14 按风险序落地测试与 CI(瀑布流 → 降级矩阵 → 调度器 → 纯函数,接入 GitHub Actions);随后用测试护航做 D-07 搜索空态与 D-11 直达页错误占位(两个高感知低风险的产品修复);D-06 类文档漂移做一次全量核对(本篇 D 清单即核对底稿)。阶段验收:核心逻辑测试覆盖、两条产品留白关闭、pnpm type-check 与测试在 CI 常绿。
第三阶段(能力扩展,按需启动):三条互不阻塞的线——① 体验线:D-09 移动端交互升级、D-12 暴露筛选 UI(SOP-C);② 架构线:若公网多人使用,代理层加限流与短 TTL 缓存(同时根治 D-08 的配额痛点、打开 D-10 的可行路径);③ 生态线:若需第二媒体源,走《11-扩展开发指南》SOP-D/E,先归一化后 UI。三条线的公共前置只有一条:第二阶段的最小测试集已就位,否则扩展期回归全靠手工。
最后一个提醒:本清单中所有"建议"都刻意保持了与源码注释记录的决策不冲突——例如 D-04 的影子字段是注释明示的预留、D-10 的空数组是注释论证过的语义。动它们之前先读注释,这是本项目从第 00 篇开始贯穿到第 15 篇的底线方法论。