# Profeto 前端问题审计报告 > 审计范围:`frontend/` 全部 53 个 `.ts/.tsx/.css` 文件(8,378 行) > 审计基线:`a7e8b75`(含本次组件合并后的工作区改动) > 审计方式:全量只读遍历 + 磁盘实证复核(不使用摘要推断) --- ## 修复状态(第三轮更新) 第三轮收掉了「建议的处理顺序」里剩余的三项工程化内容: | 项 | 处理 | 验证 | |---|---|---| | 第三批-9 自定义规则 | 新增 `Tabs` 基元(Matches/Standings 两份漂移的联赛导航收口为一处);`Button` 基元增加 `href` 锚点分支;ESLint 新增 `no-restricted-syntax` 规则——**error 级**禁止在 `className` 中手拼 `btn`/`field`/`tab` 系列基元类名 | 探针正反双向验证:3 类违规写法全部命中,`searchParams.get('tab')` 等非样式字符串与前缀子串不误伤;存量 13 处(Logs/Settings/PredictionHistory/MatchDetailSection/DataCompleteness)全部迁移到基元,lint 0 error | | 第三批-10 数据层搬迁 | `dal.ts`/`api.ts`/`types.ts`(1047 行)`git mv` 至 `src/api/`,与上轮的 `public.ts` 同目录;`admin/` 留 3 个单行 `@deprecated` 兼容壳;全部 21 个引用方改为直指 `src/api/` | `tsc --noEmit` 0 错误一次通过;grep 确认代码中无残留 `admin/dal\|admin/api\|admin/types` import(仅注释中的历史指称) | | 第三批-11/12 | (第二轮已完成)`any` 归零 + `noUnusedLocals` 开启;`PredictProgress` 诚实化 | 见第二轮记录 | **第三轮新增的教训**:自定义规则第一版用裸正则扫全量字符串字面量,既误报(`searchParams.get('tab')`)又在收紧后静默失效(探针 0 命中)——**任何「0 违规」都必须用已知违规样本反向验证**。修复后的规则用 `JSXAttribute[name.name="className"]` AST 选择器把匹配范围钉死在 className 属性值内,并用探针文件双向验证后才放行。 ### 质量闸门(三轮累计) | 指标 | 审计前 | 现在 | |---|---|---| | `pnpm typecheck` | 脚本不存在 | ✅ 通过(含 `noUnusedLocals`/`noUnusedParameters`) | | `pnpm lint` | 无 ESLint | ✅ **0 error / 19 warning**(剩余均为已注明的 hooks 挂载取数模式) | | `pnpm test` | 有测试但无脚本可跑 | ✅ **7/7 通过** | | CI | 无 | ✅ `.gitlab-ci.yml` 四作业 | | `no-explicit-any` | 23 | **0** | | 手拼基元类名 | 41 | **0**(error 级规则拦截新增) | | `admin/` 下数据层 | 1047 行 | **18 行兼容壳**(实现全在 `src/api/`) | | 首屏 JS | 315.14 kB | **~237.6 kB(−24.6%)** | --- ## 修复状态(第二轮更新) 本报告列出的 **17 项问题已全部处理**。各项的落地方式与验证证据见下表。 ### 质量闸门(修复前 → 修复后) | 指标 | 修复前 | 修复后 | |---|---|---| | `pnpm typecheck` | 脚本不存在 | ✅ 通过 | | `pnpm lint` | 无 ESLint | ✅ **0 error / 19 warning** | | `pnpm test` | 有测试但无脚本可跑 | ✅ **7/7 通过** | | CI | 无 | ✅ `.gitlab-ci.yml` 四作业(lint/typecheck/test/build) | | `no-explicit-any` | 23 | **0** | | `noUnusedLocals` / `noUnusedParameters` | `false` | **`true`,0 报错** | | 首屏 JS | 315.14 kB | **~237.6 kB(−24.6%)** | | `index` chunk | 110.20 kB | **31.80 kB** | ### 逐项落地 | 编号 | 问题 | 处理 | |---|---|---| | P0-1 | 无 lint / 测试 / CI | 新增 `eslint.config.mjs`、`scripts/verify-tokens.sh`、`.gitlab-ci.yml`;`package.json` 补 `typecheck`/`lint`/`test`/`verify:tokens` | | P0-2 | 公共页反向依赖 admin | 新建 `src/api/public.ts` 承载 `fetchStandings`/`fetchMatchDetail`/`fetchMatchContext`,`dal.ts` 改为再导出;`Matches.tsx`/`Standings.tsx` 改指 `api/public` | | P0-3 | 伪造数据当真指标 | `avg_latency_ms: 2400` → `null`(UI 显示「—」+ 「未接入」);`DashboardStats` 三个无端点字段由空数组改 `null`;`fetchSystemConfig` 等死接口删除 | | P0-4 | 假进度条 | `PredictProgress` 重写为**不确定态**进度条 + 已等待秒数;移除全部伪造的 `pct` 计算 | | P1-1 | 23 处 `any` | 归零。新增 `PredictionMatchRef`、`IngestTriggerResult`、`PredictionJobRef`、`LLMPingResult`、`SettleResult`、`HealthProbe` 等类型;打开 `noUnusedLocals`/`noUnusedParameters` | | P1-2 | 无障碍缺口 | `aria-live` 0→4、`htmlFor` 1→17、`aria-describedby` 0→1、`aria-invalid` 0→1、`role=` 8→18;`Alert` 按语义输出 `role="alert"/"status"` | | P1-3 | `App.tsx` 布局重复 | 抽出 `components/SiteLayout.tsx` | | P1-4 | 零代码分割 | 路由级 `lazy` + `Suspense` + `manualChunks`;admin 页面各自独立 chunk | | P1-5 | SEO 元信息缺失 | `index.html` 补 title/description/theme-color/robots/4 个图标/OG/Twitter Card;新增 `public/` 图标资源 | | P1-6 | 死代码 | 删除 `loadMatchDetailBundle`;抽出 `admin/AdminIcon.tsx` 替代 90 行内联 switch(`nav.ts` 的 `icon` 同步收窄为联合类型) | | P2-1 | `Login.tsx` 裸色值 | 改用 `bg-paper-50 shadow-print` | | P2-2 | `index.css` 硬编码色 | 引入 `:root` RGB 三元组变量;**并加 `verify-tokens.sh` 纳入构建**,防止 `` 回归 | | P2-3 | 热路径无记忆化 | `Matches.tsx` 的 `visibleMatches`/`dateGroups` 加 `useMemo` | | P2-4 | 滚动监听三处重复 | 抽出 `lib/useScroll.ts`(`useWindowScrollY` / `useCanScrollRight`) | | P2-5 | 内联原生控件 | `Matches.tsx` 两处裸 `