diff --git a/.claude/skills/d8d-dev/auto-fix-engine.md b/.claude/skills/d8d-dev/auto-fix-engine.md
new file mode 100644
index 0000000..6717c6f
--- /dev/null
+++ b/.claude/skills/d8d-dev/auto-fix-engine.md
@@ -0,0 +1,611 @@
+# 统一修复引擎(Auto-Fix Engine)
+
+> **⛔ 所有模式共享**:完整模式、回归验证模式、旅程验证模式均引用此文件。任何模式不得另起炉灶重新定义修复机制。
+
+---
+
+## 1. 步骤完成判定标准(⛔ 每个 step 执行后强制判定)
+
+**PASS**: `browser_snapshot` 显示的内容 **完全包含** 旅程/验证定义中该步骤"用户看到 X"的预期状态。
+- 例: 预期"看到已登录状态(头像、昵称)" → snapshot 中有实际昵称文字(非占位符、非"加载中...") → PASS
+
+**FAIL**: 以下任一 = FAIL
+1. snapshot 内容与预期不符(如预期"已登录"但看到"加载中...")
+2. `browser_click` / `browser_type` 无法找到目标元素(元素不存在或不可交互)
+3. `browser_wait_for` 超时(页面未按预期响应)
+4. snapshot 显示错误状态(错误提示、空白页、404、500)
+5. 数据断言不通过(操作前后数据状态无变化或变化不符预期)
+
+**⛔ 禁止判定为 PASS 的情况**:
+- `"加载中..."` / `"请稍候"` / 空白页
+- 元素存在但内容为空 / 占位符
+- `"暂未开放"` / `"开发中"` / `"功能开发中"` 提示
+- 不做 snapshot 直接假设操作成功
+- 用 `browser_evaluate` 绕过不可交互的元素后声称"操作完成"
+
+---
+
+## 2. FAIL 分类标准(⛔ 所有模式共享)
+
+| # | FAIL 特征 | 分类 | 修复类型 | 示例 |
+|---|-----------|------|---------|------|
+| 1 | API 返回 404 且路由文件不存在 | 未实现 | AUTO-IMPLEMENT | 路由缺失 |
+| 2 | API 返回 500 / 错误状态码 | Bug | AUTO-FIX | 空 body 返回 500 |
+| 3 | 前端组件/按钮未渲染 | 未实现 | AUTO-IMPLEMENT | 登录入口按钮缺失 |
+| 4 | 页面跳转到错误位置 | Bug | AUTO-FIX | 导航逻辑错误 |
+| 5 | 页面停留在非功能态("加载中"/空白/错误态/有内容但零交互元素) | Bug | AUTO-FIX | hydration 失败 / 渲染 Bug / fetchProfile 空 catch / 响应结构不匹配 |
+| 6 | 可交互元素无法通过 Playwright 操作 | Bug | AUTO-FIX | uni-input 编译后 browser_type 失败 |
+| 7 | 功能提示"暂未开放"/"开发中"/"暂未实现" | 未实现 | AUTO-IMPLEMENT | 手机号登录只有占位符 |
+| 8 | 测试数据过期/状态不对 | 数据问题 | AUTO-DATA-FIX | 收益冻结期不可提现 |
+| 9 | 已有数据阻止测试(重复评价等) | 数据问题 | AUTO-DATA-FIX | 删除旧评价或换测试用户 |
+| 10 | 正常业务规则正确拒绝无效操作 | 可接受 | 仅记录 | 最低提现金额限制 |
+| 11 | 旅程定义或源码中发现 Mock 登录且手机号登录未实现 | 未实现 | AUTO-IMPLEMENT | Mock 登录不是真实用户流程,必须实现手机号+验证码登录 |
+| 12 | 操作被前端表单校验拦截(Toast/提示阻止提交)| 环境限制/校验 | ENV-ADAPT | 按 Toast 内容分流:缺少必填→补充填写;文件上传→browser_file_upload;环境限制→代码添加 dev 替代 |
+| 13 | 表单校验失败但页面无任何提示(无 Toast/无错误文字)| Bug(UX 缺陷)| AUTO-FIX | 用户不知道为什么操作无效 → 添加 Toast 或内联错误提示 |
+| 14 | 即时使用数据(验证码等)仅通过 Toast 显示,无 console.log 或自动填充,下一步骤无法获取 | Bug(数据丢失风险)| AUTO-FIX | sendCode() 用 Toast 显示验证码但无 console.log,流程卡死 |
+| 15 | UI 直接输出英文枚举值(如 APPROVED/REJECTED/PENDING/PAID/CANCELLED 等)而非本地化中文标签 | Bug(UI 本地化缺失)| AUTO-FIX | 任何前端页面(管理后台/小程序/H5)状态字段显示英文枚举而非中文 |
+| 16 | 约束声明与代码行为不一致(Constraint Mismatch): 旅程描述/UI 文案说"可选/可跳过/选填"但代码实际必填,或 UI 标记"选填"但接口必填 | 约束不一致 | JOURNEY-FIX / AUTO-FIX | 旅程说"可跳过上传证件"但代码 `if (!idCardFront)` Toast "请上传所有证件照片" |
+| 17 | 前端代码 CSS/style 含 `url("data:image/svg+xml")` 或 `url('data:image/svg+xml')`(SVG data URI 充当图标/背景/装饰) | Bug(平台不兼容) | AUTO-IMPLEMENT | 微信小程序不支持 SVG data URI。⛔ 必须按 platform-uni-app.md §1 完整安装 Iconify + tailwindcss-icons + weapp-tailwindcss,将所有 SVG data URI 替换为 Iconify 类名。**禁止用另一种 SVG data URI 替代原来的** |
+| 18 | 页面同时有系统导航栏(pages.json navigationBarTitleText 非 custom)和自定义 fixed header,导致双层标题栏 | Bug(双重导航栏) | AUTO-FIX | 截图中顶部出现两层标题(系统导航+自定义头部),参见 ./reference/platform-uni-app.md §3 |
+| 19 | Tab 页面下拉刷新方式不一致(scroll-view refresher / onPullDownRefresh / 无 混用),或使用列表级 scroll-view refresher | Bug(UX 不一致) | AUTO-FIX | 首页用 scroll-view refresher,其他 Tab 无刷新,参见 ./reference/platform-uni-app.md §7 |
+| 20 | API route handler 无 try-catch 包裹,导致未处理异常直接返回原始 500 且无日志 | Bug(健壮性) | AUTO-FIX | Admin 提现审批 PUT 返回 500 但状态仍更新(异常被吞) |
+| 21 | uni-app 页面使用 `onMounted` 获取数据,但该页面是 `uni.navigateBack()` 的返回目标,导致回退时数据不刷新 | Bug(数据陈旧) | AUTO-FIX | 提现后返回收益页余额未刷新(onMounted 不再触发) |
+| **99** | **以上都不匹配的任何 FAIL** | **默认: Bug** | **AUTO-FIX** | **兜底规则,确保无遗漏** |
+| **V1** | 页面主题/配色与原型不一致(深色→浅色、主色调偏差) | 视觉差异 | VISUAL-FIX | 原型深色主题但实际浅色;按钮颜色不一致 |
+| **V2** | 页面布局结构与原型不同(顶部导航↔侧边栏、网格↔列表、卡片↔表格) | 视觉差异 | VISUAL-FIX | 原型顶部水平导航但实际左侧边栏;原型卡片网格但实际列表 |
+| **V3** | 品牌标识与原型不同(Logo 图标、标题文字、副标题) | 视觉差异 | VISUAL-FIX | 原型闪电图标+"VividSpark"但实际 "V" 字母+"Welcome back" |
+| **V4** | 原型存在的 UI 组件在实际页面中缺失(Remember me、FAQ、过滤器、标签页等) | 视觉差异 | VISUAL-FIX | 原型有 FAQ 区域但实际缺失;原型有 Remember me 但实际无 |
+| **V5** | 按钮/输入框/卡片样式与原型不一致(padding、圆角、边框、字号等) | 视觉差异 | VISUAL-FIX | 原型圆角 16px 但实际 8px;原型按钮 padding 16/32 但实际 12/24 |
+
+### "可接受"严格限定(⛔ 仅以下 2 种,其余一律必须修复)
+
+1. **系统按设计正确拒绝了无效操作**(如未选协议点登录 → "请先同意用户协议"),且不影响后续步骤
+2. **业务规则正确阻止了当前数据状态下的操作**(如余额不足不可提现),且该规则不应为测试目的修改
+
+**除以上 2 种外,任何 FAIL 不得标记为"可接受"。功能缺失、代码错误、交互故障均必须修复。**
+
+### ⛔ "已知问题不豁免"规则(⛔ 强制 — 跨轮次生效)
+
+**核心原则**: "已知" ≠ "已修复"。任何在之前的验证轮次中被发现但未实际修复的问题,在后续轮次再次检测到时,**必须**立即触发修复,不得以任何理由跳过。
+
+**判定流程**:
+```
+检测到 FAIL → 查分类表确定修复类型
+ ↓
+读取 pipeline-state.yaml 前一次验证结果
+ ↓
+该问题是否在上一轮已被记录(pending_fixes / verification_detail 中存在)?
+ ├── 是(跨轮次未修复)→ ⛔ 提升优先级,立即执行修复(不得再次"仅记录")
+ └── 否(新问题)→ 按正常分类触发修复
+```
+
+**⛔ 禁止行为**:
+- ❌ 以"已知/遗留/历史/V3遗留"为由跳过修复
+- ❌ 只记录到 `pending_fixes` 而不执行实际修复动作
+- ❌ 将修复推迟到"旅程完成后"或"下次验证"
+- ❌ 标记"已记录"作为修复动作("已记录"不是修复)
+- ❌ 以"非当前 EPIC/旅程范围"为由跳过 pending_fixes 中的问题(范围外 ≠ 豁免)
+
+**跨轮次问题升级规则**:
+- 第 1 轮检测到 → 正常修复流程
+- 第 2 轮仍未修复 → **强制修复**(不得延迟,不得降级为 WARN)
+- 第 3+ 轮仍未修复 → **阻断当前旅程**(标记 FAIL + 输出修复失败报告)
+
+### 关键判断流程
+
+- API 404 → `grep` 检查路由文件是否存在 → 存在 = Bug(#2) → 不存在 = 未实现(#1)
+- 页面显示"暂未开放"/"开发中"/"暂未实现" → 一律未实现(#7) → AUTO-IMPLEMENT
+- 元素可见但不可操作(`browser_type` 失败) → Bug(#6) → ⛔ **先诊断**(`browser_evaluate` 检查 DOM/CSS) → 按诊断结果分流修复(禁止直接用 `browser_run_code` 绕过)
+- 页面卡在"加载中..."/空白 → Bug(#5) → AUTO-FIX(调查根因:空 catch、响应结构不匹配、状态管理错误)
+- 页面有静态内容但零交互元素(有文字但无 button/input/a/form)→ `browser_evaluate` 执行 FAIL #5 诊断 → 按诊断结果分流(见 FAIL #5 诊断子步骤)
+- 操作后页面无变化(无跳转/无反馈)→ browser_snapshot 检查 Toast → 按 Toast 内容分流(#12) → ENV-ADAPT
+ - 有 Toast/错误提示 → 读取内容 → 按 #12 分流(补充填写 / browser_file_upload / ENV-ADAPT)
+ - ⛔ 无任何提示 → Bug(UX 缺陷:校验失败但用户看不到原因)→ AUTO-FIX(添加 Toast/错误提示)
+- 即时使用数据(验证码)仅通过 Toast 显示且源码无 console.log → Bug(#14) → AUTO-FIX(添加 console.log + 自动填充)
+- UI 显示英文枚举值(APPROVED/REJECTED/PENDING/PAID/CANCELLED 等)→ Bug(#15) → AUTO-FIX(添加中文映射函数,适用于所有前端:管理后台 statusLabel / 小程序 computed 映射)
+- 前端代码含 `data:image/svg+xml`(SVG data URI)→ `grep -rn "data:image/svg+xml" app/mini-program/src/` 扫描所有 `.vue` 文件 → 发现 → 平台不兼容(#17) → AUTO-IMPLEMENT(⛔ 必须按 platform-uni-app.md §1 完整安装 Iconify + 替换所有 SVG data URI,**禁止用另一种 SVG data URI 替代原来的**)
+- Tab 页面下拉刷新方式不一致(scroll-view refresher / onPullDownRefresh / 无 混用)→ Bug(#19) → AUTO-FIX(参见 ./reference/platform-uni-app.md §7 统一为 onPullDownRefresh)
+- 无法归类的 FAIL → 默认 Bug(#99) → AUTO-FIX
+- API 返回 500 + `Read` 源码发现 handler 无 try-catch → Bug(#20) → AUTO-FIX(添加 try-catch 包裹 + console.error + JSON 500 响应)
+- `uni.navigateBack()` 返回后 `browser_snapshot` 数据未刷新 + 源码用 `onMounted` 非 `onShow` → Bug(#21) → AUTO-FIX(替换 `onMounted` 为 `onShow`)
+
+---
+
+## 3. 真实用户完成规则(⛔ 旅程验证模式强制)
+
+**核心原则**: 旅程中的每个用户步骤,必须是一个**仅使用鼠标/键盘/触摸屏的真实用户**能够完成的方式。
+
+**具体规则**:
+- `browser_type` 在可见 input 上失败 → Bug(#6) → AUTO-FIX
+- `browser_click` 在可见按钮上失败 → Bug(#6) → AUTO-FIX
+- 禁止用 `browser_evaluate` / `browser_run_code` 绕过不可交互的元素
+- "需要 evaluate 才能操作" 本身就是 Bug → AUTO-FIX
+
+**`browser_run_code` 仅允许场景**:
+- 检测布局溢出等非用户交互操作
+- 读取 localStorage / cookie 等数据状态验证
+- 复杂的拖拽操作(`browser_drag` 不可用时)
+
+**⛔ 如果 `browser_type` 在可见 input 上失败**:
+1. 标记该步骤 FAIL
+2. 分类: Bug(#6 前端交互问题)
+3. ⛔ **诊断**: `browser_evaluate` 检查 DOM 结构和计算样式(见下方 FAIL #6 诊断子步骤)
+4. 根据诊断结果选择修复策略(CSS Bug / 可见性 Bug / 事件 Bug / 框架编译 Bug)
+5. 触发 AUTO-FIX: 修复具体根因
+6. 修复后重试 `browser_type`
+7. 仍失败 → 标记 journey-step-fail,继续下一步骤
+
+**⛔ FAIL #5 诊断子步骤(页面有内容但零交互元素时强制执行)**:
+
+**触发条件**: `browser_snapshot` 显示页面有静态内容(标题/文字),但没有 button、input、a、form 等可交互元素。
+
+1. `browser_evaluate` 检查页面状态:
+ ```javascript
+ () => {
+ const interactive = document.querySelectorAll('button, input, a, form, [role="button"], [role="link"]');
+ const hasContent = document.body.innerText.trim().length > 0;
+ // 检测框架挂载状态
+ const hasNextRoot = !!document.getElementById('__next');
+ const hasReactRoot = !!document.querySelector('[data-reactroot]');
+ const hasVueApp = !!document.querySelector('[data-v-app]') || !!document.querySelector('#app')?.__vue_app__;
+ const frameworkMounted = hasNextRoot || hasReactRoot || hasVueApp;
+ // 检测 JS 是否加载
+ const scriptTags = document.querySelectorAll('script[src]').length;
+ return {
+ interactiveCount: interactive.length,
+ hasContent,
+ frameworkMounted,
+ hasNextRoot, hasReactRoot, hasVueApp,
+ scriptTags,
+ bodyHTMLLength: document.body.innerHTML.length,
+ bodyText: document.body.innerText.substring(0, 300)
+ };
+ }
+ ```
+
+2. 根据诊断结果分流:
+ - `interactiveCount === 0 && frameworkMounted === true` → **框架已挂载,当前页面源码就是无交互的展示页**
+ → `grep/Read` 检查当前页面源码:源码无交互组件 → **功能缺失** → AUTO-IMPLEMENT(添加 redirect/导航逻辑)
+ → 源码有交互组件但未渲染 → **渲染 Bug(#5)** → AUTO-FIX
+ - `interactiveCount === 0 && frameworkMounted === false && scriptTags > 0` → **JS 加载但框架未挂载** → **Hydration 失败 Bug(#5)** → AUTO-FIX(检查控制台错误、检查构建产物)
+ - `interactiveCount === 0 && !hasContent` → **空白页** → Bug(#5) → AUTO-FIX(检查加载逻辑)
+
+3. 修复后重测:
+ - AUTO-IMPLEMENT 添加 redirect 后 → `browser_navigate` 重试 → 应自动跳转到功能页 → `browser_snapshot` 确认有交互元素
+ - AUTO-FIX 修复 hydration/渲染后 → `browser_navigate` 重试 → `browser_snapshot` 确认交互元素已渲染
+
+**⛔ FAIL #6 诊断子步骤(`browser_type`/`browser_click` 失败时强制执行)**:
+
+> uni-app/小程序编译特性诊断(如 uni-input height:0px)详见 **./reference/platform-uni-app.md 第 3.1 节**。
+
+1. `browser_evaluate` 检查目标元素的 DOM 结构和计算样式:
+ ```javascript
+ (el) => {
+ const tag = el.tagName?.toLowerCase();
+ const computed = getComputedStyle(el);
+ const info = {
+ tag, height: computed.height, width: computed.width,
+ opacity: computed.opacity, display: computed.display,
+ innerInputs: []
+ };
+ el.querySelectorAll('input').forEach(i => {
+ const c = getComputedStyle(i);
+ info.innerInputs.push({
+ height: c.height, width: c.width,
+ opacity: c.opacity, pointerEvents: c.pointerEvents
+ });
+ });
+ return info;
+ }
+ ```
+
+2. 根据诊断结果分流:
+ - `innerInputs[0].height === '0px'` → **框架编译 CSS Bug** → AUTO-FIX: 给 input 或包裹容器添加显式 `height`
+ - `opacity === '0'` 或 `pointerEvents === 'none'` → **可见性 Bug** → AUTO-FIX: 修复 CSS 可见性
+ - 无 `innerInputs`(非框架编译)→ **原生交互 Bug** → AUTO-FIX: 修复事件绑定或 DOM 结构
+ - 诊断结果正常但 `browser_type` 仍失败 → **Playwright 适配** → 允许 `browser_run_code` + `force: true`(仅限此场景,且须记录适配原因)
+
+---
+
+## 4. 修复流程
+
+### AUTO-FIX(代码 Bug,max 2 次)
+
+**适用**: API 错误、逻辑错误、配置错误、渲染异常、交互故障、空 catch 块、响应结构不匹配
+
+**流程**:
+1. 读取失败原因,定位源文件和行号(`grep` / `Read`)
+2. ⛔ 如果是 FAIL #6(交互失败)→ 先执行诊断子步骤(见上方),按诊断结果选择修复策略
+3. `Edit` 最小改动修复
+4. `Bash` 重启受影响服务(如有必要)
+5. 重测失败步骤 → PASS 继续 / FAIL 重试
+
+**示例**:
+- 退款 API 无 body 返回 500 → `Edit` 添加 body 解析容错
+- `fetchProfile` 空 catch 吞噬错误 → `Edit` 添加错误日志 + null 处理
+- API 响应结构与前端期望不匹配 → `Edit` 修正数据提取逻辑
+- input 元素不可交互 → `browser_evaluate` 诊断 → 按根因修复(见 FAIL #6 诊断子步骤)
+- uni-app input `height: 0px` → `Edit` 给 `.input-field` 添加显式 `height: 44px`
+- 管理后台首页缺少 redirect → 源码检查发现 page.tsx 是纯展示 → AUTO-IMPLEMENT: 添加 `redirect("/login")`
+- React hydration 失败 → `browser_evaluate` 确认框架未挂载 → AUTO-FIX: 检查构建产物和 `use client` 指令
+- 任何前端页面状态字段显示英文枚举 → `Edit` 添加中文映射函数(管理后台: statusLabel / 小程序: computed 属性)→ 替换直接输出为映射函数调用
+
+### AUTO-IMPLEMENT(功能缺失,max 2 次)
+
+**适用**: 路由缺失、组件未渲染、功能占位符("暂未开放")、后端接口未实现
+
+**流程**:
+1. 读取 `phase-03-implement.md` 对应方案(如存在)
+2. `Write` 新文件 / `Edit` 补充实现
+3. `Bash` 安装依赖(如需)+ 重启服务
+4. 重测失败步骤 → PASS 继续 / FAIL 重试
+
+**示例**:
+- 个人页无登录入口 → `Edit` 添加登录按钮
+- 手机号登录"暂未开放" → `AUTO-IMPLEMENT` 实现完整手机号+验证码登录流程
+- 后端路由缺失 → `Write` 新 `route.ts` + `handler`
+- 图标方案未实施 → 按方案安装 Tailwind + Iconify
+
+**Emoji → Iconify 替换方案(检测到 emoji 充当功能图标时使用 ⛔ 强制)**:
+
+⛔ **完整安装配置、使用方式、禁止方案列表见 ./reference/platform-uni-app.md 第 1 节。禁止自行发明替代方案。**
+
+适用条件: icon/btn 元素内含 emoji 字符(如 🔍🔔💬🪪💳📷🏠📱)
+
+执行步骤:
+1. **Read fully and follow: ./reference/platform-uni-app.md 第 1 节**(⛔ 不可跳过 — 含 Admin/Mini-program 两条路径完整安装步骤)
+2. **替换源码**: 将所有 emoji 字符替换为 Iconify 类名(如 🔍 → `class="i-lucide-search"`)
+3. **Bash** 安装依赖 + 重新构建
+4. **重测** 验证图标渲染正常
+
+⛔ **禁止的替代方案**(完整列表见 ./reference/platform-uni-app.md 第 1 节"禁止的替代方案")
+
+**手机号+验证码登录实现方案(#11 触发时使用)**:
+
+当旅程验证发现 Mock 登录(非真实用户流程)且手机号登录未实现时,按以下方案 AUTO-IMPLEMENT:
+
+1. **后端: POST /api/auth/send-code**
+ - 接收 `{phone}`
+ - `NODE_ENV=development`: 生成 6 位随机验证码,存 Redis `sms:${phone}` TTL 300s,**直接返回** `{code: "123456"}` — 这是标准开发实践,不是 Mock
+ - `NODE_ENV=production`: 调用短信服务商 API 发送验证码,返回 `{success: true}`
+2. **后端: POST /api/auth/login** — 增强已有接口
+ - 接收 `{phone, code}`
+ - 从 Redis 取出验证码比对
+ - 匹配 → 查找/创建用户 → 返回 JWT(复用已有登录逻辑)
+3. **前端: 启用 login/index.vue 已有的手机号表单**(HTML 已存在,仅需实现 JS)
+ - `sendCode()`: 调用 `POST /api/auth/send-code`,开发环境将返回的 code 自动填入输入框
+ - `handlePhoneLogin()`: 调用 `POST /api/auth/login {phone, code}`,成功后跳转首页
+4. **清理**: 删除 Mock 登录按钮(`v-if="isDev" class="mock-btn"`)和 `handleMockLogin()` 函数
+
+### AUTO-FIX #14: 即时使用数据持久化(max 1 次)
+
+**适用**: FAIL #14 — 即时使用数据(验证码等)仅通过 Toast 显示,无 console.log,下一步骤无法获取
+
+**为什么这是 Bug**: 下一步骤需要的数据(如验证码)仅通过 Toast 传递,3秒后消失。Playwright 无法回溯,流程卡死。
+注意:仅适用于**下一步骤立即需要**的数据。订单号、支付结果等后续页面自然显示的不在此列。
+
+**流程**:
+1. 读取失败页面源码,定位 `uni.showToast` 传递即时使用数据的位置
+2. 确认数据变量名(如 `res.code`)
+3. 在 `uni.showToast` 调用**前**添加 `console.log('[DEV] 验证码:', res.code)`
+4. 对验证码场景:添加自动填充 `if (res.code) { code.value = res.code }`
+5. Toast 保留作为辅助反馈,不删除
+6. 重测失败步骤 → PASS 继续 / FAIL 标记 journey-step-fail
+
+**修复模板**:
+```javascript
+// 修复前:
+const msg = res.code ? `验证码已发送:${res.code}(开发环境)` : "验证码已发送";
+uni.showToast({ title: msg, icon: "none" });
+
+// 修复后:
+console.log('[DEV] 验证码:', res.code);
+if (res.code) { code.value = res.code; }
+uni.showToast({ title: "验证码已发送", icon: "success" });
+```
+
+### AUTO-FIX #18: 双重导航栏修复(max 1 次)
+
+**适用**: FAIL #18 — 页面在 pages.json 有 `navigationBarTitleText`(非 custom)且页面源码有 `position:fixed` 的自定义 header
+
+**为什么这是 Bug**: 系统导航栏与自定义头部同时显示,用户看到两层标题(如"首页"+"NEON PULSE"),浪费屏幕空间且视觉混乱。uni-app 小程序项目应统一使用 `navigationStyle: "custom"`。
+
+**流程**:
+1. `Read` pages.json → 找到该页面的 style 配置
+2. `Edit` pages.json → 将该页面 style 改为 `{ "navigationStyle": "custom" }`(移除或保留 navigationBarTitleText 均可,custom 模式下不显示)
+3. `Read` 页面源码 → 找到自定义 header 的 CSS(通常为 `position: fixed; top: 0` 的元素)
+4. `Edit` 页面源码 CSS:
+ - header 添加 `padding-top: env(safe-area-inset-top, 20px)`
+ - header height 改为 `calc(原高度 + env(safe-area-inset-top, 20px))`
+ - content margin-top 与新 header height 一致(含 safe-area)
+5. 重测 → 截图确认只有一层标题
+
+**修复示例**:
+```css
+/* 修复前(首页 header 与系统导航栏重叠): */
+.header { position: fixed; top: 0; left: 0; right: 0; height: 64px; }
+.content { margin-top: 64px; }
+
+/* 修复后(统一使用自定义导航栏): */
+.header {
+ position: fixed; top: 0; left: 0; right: 0;
+ height: calc(64px + env(safe-area-inset-top, 20px));
+ padding-top: env(safe-area-inset-top, 20px);
+}
+.content {
+ margin-top: calc(64px + env(safe-area-inset-top, 20px));
+}
+```
+
+```jsonc
+// pages.json 修复:
+// 修复前:
+{ "path": "pages/home/index", "style": { "navigationBarTitleText": "首页" } }
+// 修复后:
+{ "path": "pages/home/index", "style": { "navigationStyle": "custom" } }
+```
+
+### AUTO-FIX #19: 下拉刷新方式统一(max 1 次)
+
+**适用**: FAIL #19 — Tab 页面下拉刷新方式不一致(scroll-view refresher / onPullDownRefresh / 无 混用)
+
+**⛔ 详细修复步骤见 ./reference/platform-uni-app.md §7**
+
+**流程**:
+1. `Edit` pages.json → globalStyle 添加 `"enablePullDownRefresh": true`
+2. 对 LIST_LEVEL 页面(scroll-view refresher):
+ - 移除 scroll-view 的 `refresher-enabled` / `:refresher-triggered` / `@refresherrefresh`
+ - 移除 `refreshing` ref 和 `onRefresh` 函数
+ - 添加 `onPullDownRefresh` 生命周期(from `@dcloudio/uni-app`)
+ - 数据加载 `finally` 中调用 `uni.stopPullDownRefresh()`
+3. 对 NONE 页面:添加 `onPullDownRefresh` + 对应数据刷新逻辑 + `uni.stopPullDownRefresh()`
+4. 重测 → 所有 Tab 页面整页下拉刷新效果一致
+
+### AUTO-FIX #20: API route try-catch 包裹(max 1 次)
+
+**适用**: API endpoint 返回 500,`Read` 源码发现 handler 函数无 try-catch 包裹
+
+**为什么这是 Bug**: 未处理异常直接返回原始 500 且无日志,无法诊断根因。常见的二次操作(如审批后创建通知)失败会吞噬前面的成功状态。
+
+**检测方法**: API 返回 500 时,`Read` 对应 route.ts 源码,检查 `export async function POST/PUT/DELETE/GET` 内是否有 try-catch
+
+**修复流程**:
+1. `Read` 失败的 route.ts 文件
+2. 确认 handler 无 try-catch 包裹
+3. 在 requireAdmin/鉴权检查之后,将剩余逻辑包裹在 try-catch 中
+4. catch 块:`console.error("Xxx error:", error)` + `NextResponse.json({error: "处理失败"}, {status: 500})`
+5. 参考模式:同项目中已有 try-catch 的 route(如 admin auth login)
+
+**修复模板**:
+```typescript
+// 修复前:
+export async function PUT(req, { params }) {
+ const result = requireAdmin(req);
+ if ("error" in result) return result.error;
+ const body = await req.json(); // ← 无 try-catch,异常直接变 500
+ // ... 业务逻辑 ...
+}
+
+// 修复后:
+export async function PUT(req, { params }) {
+ const result = requireAdmin(req);
+ if ("error" in result) return result.error;
+ try {
+ const body = await req.json();
+ // ... 业务逻辑不变 ...
+ } catch (error) {
+ console.error("Xxx error:", error);
+ return NextResponse.json({ error: "处理失败" }, { status: 500 });
+ }
+}
+```
+
+### AUTO-FIX #21: uni-app 页面 onMounted→onShow 替换(max 1 次)
+
+**适用**: 旅程中 `uni.navigateBack()` 返回目标页面后,页面数据未刷新
+
+**为什么这是 Bug**: `onMounted` 仅在组件首次创建时触发。当用户从子页面 `navigateBack()` 返回时,父页面组件仍在导航栈中未被销毁,`onMounted` 不会再次触发。uni-app 的 `onShow` 在每次页面显示时都会触发,是正确的数据刷新时机。
+
+**触发条件**(三条件全部满足):
+- 条件 A: 旅程中使用 `uni.navigateBack()` 返回到该页面
+- 条件 B: 返回后 `browser_snapshot` 显示数据未更新(如余额、列表数量未变化)
+- 条件 C: `Read` 页面源码确认数据获取在 `onMounted` 而非 `onShow` 中
+
+**修复流程**:
+1. `Read` 页面 `.vue` 文件
+2. 修改 import:将 `import { ref, onMounted } from "vue"` 中 `onMounted` 移除(若 vue 导入不再需要 onMounted)
+3. 添加 `import { onShow } from "@dcloudio/uni-app"`(新 import 行)
+4. 将 `onMounted(() => { fetchXxx() })` 替换为 `onShow(() => { fetchXxx() })`
+5. 参考模式:项目中已有正确使用 `onShow` 的页面(如 profile/index.vue)
+
+**修复模板**:
+```typescript
+// 修复前:
+import { ref, onMounted } from "vue";
+// ...
+onMounted(() => { fetchEarnings(); });
+
+// 修复后:
+import { ref } from "vue";
+import { onShow } from "@dcloudio/uni-app";
+// ...
+onShow(() => { fetchEarnings(); });
+```
+
+### AUTO-FIX/JOURNEY-FIX #16: 约束一致性修复(max 1 次)
+
+**适用**: FAIL #16 — 旅程描述/UI 文案说"可选"但代码实际必填,或 UI 标记"选填"但接口必填
+
+**检测触发条件**(双条件同时满足):
+- 条件 1:声明方说"可选"(旅程描述含"可跳过/可选/选填/skip/optional/使用占位图" 或 页面文本含"(选填)/(可选)")
+- 条件 2:代码实际"必填"(Toast 含"请上传/请填写/请选择/不能为空" 或 源码有 `if (!field)` 验证)
+
+**为什么这是问题**: 用户被"可选"承诺误导,但操作时被强制校验拦截。真实用户体验受损。
+
+**流程**:
+1. `Read` 对应页面源码 → 定位必填验证逻辑(`if (!field)` / `required` / `validate`)
+2. 判断正确设计意图:
+ - PRD/UX 规范说可选 → AUTO-FIX 代码(移除必填验证或改为可选提交)
+ - PRD/UX 规范说必填 → JOURNEY-FIX 旅程定义(更新描述为必填)+ AUTO-FIX UI 文案(移除"选填"标记)
+ - 无明确 PRD 依据 → JOURNEY-FIX(旅程定义改为反映代码行为)+ 记录 `constraint_mismatch` 到 pending_fixes
+3. 修复后重测该步骤
+
+**代码修复示例**(设计意图=可选时):
+```javascript
+// 修复前(强制必填):
+if (!form.idCardFront || !form.idCardBack || !form.idCardHolding) {
+ uni.showToast({ title: "请上传所有证件照片", icon: "none" });
+ return;
+}
+
+// 修复后(改为可选):
+// 移除强制校验,允许不上传照片直接提交
+```
+
+**旅程修复示例**(设计意图=必填时):
+```markdown
+// 修复前:
+9. 用户第二步上传身份证照片(可跳过或使用占位图)→ 点击"下一步"
+
+// 修复后:
+9. 用户第二步上传身份证照片(必填:身份证正面、反面、手持照片)→ 点击"下一步"
+```
+
+### AUTO-DATA-FIX(数据状态,max 1 次)
+
+**适用**: 测试数据过期、冻结期收益不可用、关联数据缺失
+
+**流程**:
+1. `Bash` 直接操作数据库或 API 修复数据状态
+2. 重测失败步骤
+
+**示例**:
+- 收益冻结期不可提现 → `Bash`: `psql UPDATE earnings SET status='AVAILABLE'`
+- 组局状态不正确 → API: `PUT /api/admin/events/[id]` 修正状态
+- 测试用户数据缺失 → API: `POST` 创建缺失的关联记录
+
+### ENV-ADAPT(环境限制,max 1 次)
+
+**适用**: 操作被前端表单校验拦截(#12),Toast 提示了具体原因
+
+**流程**:
+1. `browser_snapshot` 读取页面 Toast/错误提示内容
+2. 根据 Toast 内容分流:
+ - "请输入/请填写/不能为空" → 找到对应字段 → `browser_type` 补充填写 → 重试操作
+ - "请上传/请选择图片/请选择文件" → `browser_click` 触发文件选择 → `browser_file_upload` 上传测试文件 → 重试操作
+ - "暂未开放/开发中" → 分类为 AUTO-IMPLEMENT → 走 auto-implement 流程
+3. 重试仍失败 → 标记 journey-step-fail + Toast 内容
+
+**⛔ 重要:表单校验失败但页面无任何提示 → Bug(#13) → AUTO-FIX(添加 Toast/错误提示)**
+
+**示例**:
+- 局头申请要求上传证件照 → Toast "请上传所有证件照片" → `browser_file_upload` 上传测试图片 → 重试提交
+- 注册表单缺少必填项 → Toast "请输入手机号" → `browser_type` 填写手机号 → 重试提交
+- 表单提交无反应且无 Toast → Bug(#13) → AUTO-FIX: 添加 Toast 提示
+
+### 修复失败处理
+
+- 重试达到上限仍 FAIL → 标记该步骤 `journey-step-fail`
+- 记录: `{journey, step, fail_reason, fix_type, fix_attempted, fix_result}`
+- 继续执行下一个步骤(不阻断整条旅程)
+- 旅程完成后统计: `steps_passed / steps_total` → 决定整体 PASS/FAIL
+
+---
+
+## 5. GATE-CHECK(⛔ 修复完成后、记录阶段之前执行)
+
+```
+GATE-CHECK:
+├── 所有交互使用真实用户操作(无 browser_run_code 绕过 input/button) ← 真实用户完成规则
+├── browser_take_screenshot 已执行 ← 证据检查
+├── ⛔ analyze_image 已执行(所有截图都有对应 AI 视觉分析结果) ← 视觉验证检查
+│ 验证:pipeline-state 中 analyze_image_executed === true
+│ 未执行 → 不得标记完成,必须回退执行视觉验证(见 ./visual-verification.md)
+├── browser_network_requests 已执行 ← 网络检查
+├── failed_steps 数量 = 0 ← 断言检查
+├── 即时使用数据有 console.log 或自动填充(无 #14 违规) ← 数据可追溯检查
+├── auto_fix 对所有 FAIL 已触发 ← 修复检查
+├── ⛔ verification_detail 中每个 FAIL 项都有对应的 auto_fix 记录 ← FAIL-修复交叉验证
+│ 逐一比对 verification_detail 与 auto_fix 记录:
+│ english_enum_check.found = FOUND → auto_fix 必须包含 #15 修复记录
+│ icons_check.emoji_as_icons = FOUND → auto_fix 必须包含 Iconify 替换修复记录
+│ icons_check.tabbar_icons_visible = FAIL → auto_fix 必须包含 TabBar 修复记录
+│ 缺少对应记录 → 不得标记完成,必须回退执行修复
+└── ⛔ 视觉验证 FAIL 的修复在内联关卡已执行(非推迟到步骤 4) ← 内联修复检查
+
+结果判定:
+├── 全部 PASS → 继续记录阶段,status: pass
+├── FAIL + 修复成功 → 继续记录阶段,status: pass(含修复记录)
+└── FAIL + 修复失败/未触发 → status: fail(⛔ 不得记录为 pass)
+```
+
+### ⛔ 严重违规行为
+
+- 检测到 FAIL 但没有执行分类判定
+- 分类判定为 Bug/未实现但没有触发修复
+- 修复失败但没有记录 `auto_fix_result`
+- `status: pass` 但 `failed_steps > 0` 且未全部修复成功
+- 用 `browser_evaluate` 绕过不可交互的 input/button 后标记步骤 PASS
+- 把"加载中..."/"暂未开放"判定为 PASS
+- 任何 FAIL 标记为"可接受"但不符合上述严格限定的 2 种情况
+- **用 SVG data URI / CSS background-image 替代 Iconify 图标方案**(微信小程序不支持 SVG data URI,必须用 @egoist/tailwindcss-icons + Iconify)
+
+---
+
+## 5.5 步骤级状态更新(⛔ AUTO-FIX/AUTO-IMPLEMENT 完成后强制执行)
+
+**修复完成后,必须同步更新步骤级跟踪文件,确保修复证据完整可追溯。**
+
+```
+AUTO-FIX/AUTO-IMPLEMENT 执行后:
+ 1. 更新 verification-checklist.yaml:
+ - 找到当前 step → status 改为 "fixed"(修复后待重测)或 "pass"(重测通过后)
+ - 如果重测通过 → status 改为 "pass"
+
+ 2. 更新 pipeline-state.yaml step_results:
+ - 在当前步骤的 auto_fixes 数组中追加修复记录:
+ ```yaml
+ auto_fixes:
+ - fix_id: "FIX-{NNN}"
+ issue_id: "ISSUE-{NNN}" # 关联触发修复的问题
+ fix_type: "AUTO-FIX|AUTO-IMPLEMENT|AUTO-DATA-FIX"
+ description: "{修复描述}"
+ files_changed: [ "{文件路径}" ]
+ retry_count: {N}
+ result: "pass|fail"
+ fixed_at: "{ISO timestamp}"
+ screenshot_after_fix: "{修复后重测截图路径}"
+ ```
+
+ 3. 更新步骤级 GATE:
+ - 重新检查 G5: all_issues_resolved
+ - 修复通过 → step_gate 改为 "pass"
+ - 修复失败且重试耗尽 → step_gate 改为 "fail",步骤标记 failed
+
+ 4. 如重测需要重新截图和 analyze_image:
+ - browser_take_screenshot → 保存为新截图(原截图保留)
+ - analyze_image → 更新 analyze_image_result
+ - 更新 step_results 的 screenshot_path 和 analyze_image_result
+```
+
+**⛔ 禁止行为**:
+- ❌ 修复后不更新 verification-checklist.yaml 的 step status
+- ❌ 修复后不追加 auto_fix 记录到 step_results
+- ❌ 修复后不重新检查步骤级 GATE
+- ❌ 修复失败但 step_gate 仍标记为 "pass"
+
+---
+
+## 6. 各模式引用方式
+
+以下文件必须引用本引擎,删除各自的重复定义:
+
+| 文件 | 引用位置 |
+|------|---------|
+| `journey-mode.md` | 执行模板步骤 4(修复阶段)→ `Read fully and follow: ./auto-fix-engine.md` |
+| `validation-mode.md` | AUTO-FIX/AUTO-IMPLEMENT 分支 → `Read fully and follow: ./auto-fix-engine.md` |
+| `phase-04-test.md` | Phase 4.5 AUTO-FIX 循环 → 引用第 4 节修复流程 |
+| `phase-05-review.md` | 质量关卡 FAIL 处理 → 引用第 2 节分类标准 |
+| `visual-diff-mode.md` | Phase VD-3 逐页修复 → VISUAL-FIX (V1-V5) 分类引用 |
+| `e2e-testing.md` | 步骤 9.5 判定规则 → 引用第 2 节分类标准 |
+| `iron-laws.md` | 检测即阻断规则 → 引用第 2 节分类标准 + 第 5 节 GATE-CHECK |
diff --git a/.claude/skills/d8d-dev/e2e-testing.md b/.claude/skills/d8d-dev/e2e-testing.md
new file mode 100644
index 0000000..c0c170c
--- /dev/null
+++ b/.claude/skills/d8d-dev/e2e-testing.md
@@ -0,0 +1,329 @@
+# E2E 测试
+
+> 参见 ./auto-fix-engine.md 了解统一修复引擎(FAIL分类标准 + 修复流程 + GATE-CHECK)。
+
+## E2E 测试覆盖矩阵
+
+| 目标 | 启动方式 | Playwright MCP 测试方法 |
+|------|---------|----------------------|
+| 管理后台(Next.js) | `pm2 start ecosystem.config.js` → admin 进程 | browser_navigate → snapshot → click/type → verify |
+| 小程序用户端(uni-app) | `pm2 start ecosystem.config.js` → mini-h5 进程 | browser_navigate → snapshot → click/type → verify |
+| API 路由 | `pm2 start ecosystem.config.js` → api 进程 | browser_navigate → evaluate(JSON 验证) |
+
+### Playwright MCP 测试序列模式
+
+**此序列为完整模式,所有步骤都是强制的**(除非页面确实没有表单/按钮)。
+
+```
+0. browser_resize → 设置正确视口(管理后台 1440x900 / 小程序 375x812)
+1. browser_navigate → 目标 URL
+2. browser_snapshot → 验证页面/组件加载(确认非 404/空白)
+3. browser_type → 填写表单(页面有表单则必须填写,不能跳过)
+4. browser_click → 提交/操作按钮(页面有按钮则必须点击,不能跳过)
+5. browser_wait_for → 等待响应/数据加载(timeout: 5s)
+6. browser_snapshot → 验证操作结果(数据已创建/更新/删除)
+7. browser_take_screenshot → 保存截图到 tests/e2e/screenshots/story-x-y.png(强制)
+8. analyze_image → AI 视觉验证截图(强制,见下方"增强 analyze_image 提示词")
+8.5. browser_evaluate → 布局溢出检测(小程序 Story 强制,见下方"步骤 8.5")
+9. browser_network_requests → API 调用目标检测(步骤 8 之后,前端 Story 强制)
+```
+
+**强制规则**:
+- 步骤 0(resize)不能省略,即使"上一个 Story 刚 resize 过"
+- 步骤 3-5(交互)在页面有表单/按钮时**绝对不能省略**
+- 步骤 7(截图)是强制证据,不是可选装饰
+- 步骤 8(视觉验证)**不调用等于验证未完成**(⛔ 详细规则和标准提示词见 ./visual-verification.md)
+- 步骤 9(API 目标检测)**前端 Story 必须执行**,检测页面 JS 发起的 API 请求是否指向 localhost 而非外网域名
+
+### API 调用目标检测(步骤 9 详解)
+
+**目的**:检测前端页面的 JavaScript 发出的 API 请求是否指向正确的服务器地址。如果前端代码 fallback 到 `http://localhost:3001`,在 Playwright 测试环境下可以成功(因为 Playwright 运行在 localhost),但真实外网用户会失败。
+
+**检测方法**:
+
+```
+在步骤 8(analyze_image)之后执行:
+browser_network_requests:
+ filter: "/api/"
+ requestHeaders: false
+ requestBody: false
+ static: false
+```
+
+**判定规则**:
+
+1. 读取返回的网络请求列表中所有请求的 URL
+2. 检查是否有任何 URL 包含 `localhost` 或 `127.0.0.1`
+3. 结合 `pipeline-context.md` 中的 `URL_MODE` 判断:
+ - `URL_MODE` 为 "外网域名" 且发现 localhost API 调用 → **FAIL**
+ - `URL_MODE` 为 "localhost(fallback)" 且发现 localhost API 调用 → **PASS**(符合预期)
+
+**结果记录**:
+```yaml
+verification_detail:
+ api_target_verified: true/false
+ api_target_violations: ["http://localhost:3001/api/xxx"] # 仅失败时
+```
+
+**失败处理**:
+1. 检查 `.env.local` 是否存在:`cat app/mini-program/.env.local` / `cat app/admin/.env.local`
+2. 检查文件内容是否包含正确的外网域名
+3. 如果文件正确但仍失败 → 重启服务 `pm2 restart admin && pm2 restart mini-h5`,清缓存后重试
+
+**注意**:
+- 此检测必须在页面有 API 交互后执行(先完成 browser_click 等操作触发 API 调用)
+- 如果页面刚打开还没有 API 请求,需要先触发一个会产生 API 调用的操作
+- 此检测是对 phase-00-setup.md 步骤 4.7 的运行时验证
+
+### 步骤 9.5: 源码三重检查(所有前端 Story 强制)
+
+**目的**:检测前端源码中的三类问题 — localhost fallback、图标方案未合规、emoji 充当图标。
+**适用范围**: app/ 下所有前端代码(admin、mini-program 等),不仅限于 admin。
+
+**检测方法**(对所有涉及前端页面的 Story 强制执行):
+
+```bash
+# 检查 1: localhost fallback 值
+LOCALHOST_FALLBACK=$(grep -rn "localhost:[0-9]" app/*/src/ --include="*.ts" --include="*.tsx" --include="*.vue" | grep -v node_modules)
+LOCALHOST_COUNT=$(echo "$LOCALHOST_FALLBACK" | grep -c ":" || echo 0)
+echo "localhost_fallback=$LOCALHOST_COUNT"
+[ "$LOCALHOST_COUNT" -gt 0 ] && echo "LOCALHOST_FILES:" && echo "$LOCALHOST_FALLBACK"
+
+# 检查 2: 内联 API 声明(非集中式模块)
+INLINE_FILES=$(grep -rn "const.*API_BASE\s*=" app/*/src/ --include="*.ts" --include="*.tsx" --include="*.vue" | grep -v node_modules | grep -v "lib/api\|config/api\|lib/config")
+INLINE_COUNT=$(echo "$INLINE_FILES" | grep -c ":" || echo 0)
+echo "inline_api=$INLINE_COUNT"
+[ "$INLINE_COUNT" -gt 0 ] && echo "INLINE_FILES:" && echo "$INLINE_FILES"
+
+# 检查 3: 图标方案合规 + Emoji 充当图标
+# 完整检测命令见 ./reference/platform-uni-app.md 第 6 节
+```
+
+**判定规则**:
+
+| 条件 | 分类 | 结果 | 动作 |
+|------|------|------|------|
+| `LOCALHOST_COUNT > 0` | Bug | **FAIL** | AUTO-FIX: 替换为外网域名 |
+| `INLINE_COUNT > 0` 且集中模块存在 | Bug | **FAIL** | AUTO-FIX: 替换为 import |
+| `INLINE_COUNT > 0` 且无集中模块 | 未实现 | **FAIL** | AUTO-IMPLEMENT: 创建集中模块 |
+| `tabbar_iconpath_empty=true` | 未实现 | **FAIL** | AUTO-IMPLEMENT: 实现自定义 TabBar |
+| `EMOJI_COUNT > 0` | 未实现 | **FAIL** | AUTO-IMPLEMENT: Iconify 方案替换 |
+| 全部为 0 | — | **PASS** | — |
+
+**AUTO-FIX(Bug 类型)**:
+1. localhost fallback → 从 pipeline-context.md 读取外网域名,Edit 替换
+2. 内联 API(集中模块存在)→ Edit 替换为 import 引用
+3. 重启前端服务 → 重新执行步骤 9.5 验证
+
+**AUTO-IMPLEMENT(未实现类型)**:
+按照 `./reference/platform-uni-app.md` 第 1 节(图标方案)和第 2 节(TabBar 方案)执行完整实现:
+1. 安装依赖(Tailwind + Iconify + weapp-tailwindcss)
+2. 创建配置文件(tailwind.config.js, vite.config.ts)
+3. 创建组件(自定义 TabBar)
+4. 修改引用(替换 emoji、更新 pages.json)
+5. 重启服务 → 重新执行步骤 9.5 验证
+
+> 完整的 AUTO-FIX/IMPLEMENT/DATA-FIX 分类标准和修复流程见 ./auto-fix-engine.md
+
+### 增强 analyze_image 提示词(步骤 8 详解)
+
+**目的**:当前 analyze_image 只检查项目设计系统颜色,需增加多个维度,每个维度必须明确 PASS/FAIL。
+
+**提示词模板**:
+
+```
+分析此截图,按以下维度逐一给出 PASS/FAIL 判定:
+
+1. **设计规范**(从项目 _bmad-output/04-ui-ux-design.md 提取):
+ - {执行时:Read _bmad-output/04-ui-ux-design.md → 提取设计系统规则 → 填入}
+ - 回退:文件不存在 → grep 项目 CSS :root 变量
+ → PASS/FAIL + 具体问题
+
+2. **图标/图片完整性**:
+ - TabBar 图标是否全部可见(非空白/缺失)
+ - 是否有 broken image(加载失败的图片占位符)
+ - 是否有 emoji 充当功能图标(如 🏠🏠📱 等)
+ - 可缺失的图片(如头像)是否有 CSS 渐变色占位符
+ - TabBar 图标:如果有底部导航栏,检查图标是实际图片/SVG 还是纯文本标签?
+ 如果只有文字(如"首页/广场/消息/我的")而没有对应的图标 → FAIL
+ 检查方法:图标区域是否有
或