diff --git a/.claude/skills/d8d-dev/visual-diff-mode-pencil.md b/.claude/skills/d8d-dev/visual-diff-mode-pencil.md
index 01972bd..27f8c45 100644
--- a/.claude/skills/d8d-dev/visual-diff-mode-pencil.md
+++ b/.claude/skills/d8d-dev/visual-diff-mode-pencil.md
@@ -56,7 +56,7 @@ Phase PVD-1: 逐页对比循环
对每个映射页面:
├── A. Pencil MCP 导出设计帧截图
├── B. Playwright 截取实际页面
- ├── C. AI 视觉对比
+ ├── C. 结构校验(.pen节点树)+ AI 视觉对比
├── D. 差异分级 + 强制执行计数
└── E. PAGE GATE-CHECK 门禁检查
@@ -150,7 +150,7 @@ visual_diff_pencil_mapping:
```
原理:代码中存在的页面一定要对比,不能因为自动映射失败就漏掉。
1. 读取 app/mini-program/src/pages.json
- 2. 提取所有 pages 和 subpackages 中的页面路径
+ 2. 提取 all pages 和 subpackages 中的页面路径
3. 对每个页面路径:
- 提取页面名 = 路径倒数第二段("pages/withdraw/index" → "withdraw")
- 检查当前映射表中是否已有该页面
@@ -175,6 +175,15 @@ mkdir -p tests/e2e/screenshots/visual-diff-pencil
- 便于独立追踪每个页面的验证进度
- 避免单个 agent 上下文过大导致污染或遗漏
+### 分批并行策略
+
+**Anthropic API 配额限制**: 单次 5 小时窗口有请求次数限额,一次性启动全部 agent 容易触发 `429 AccountQuotaExceeded`。
+
+**推荐执行策略**:
+- 每次启动 **5 个页面** 验证
+- 等待全部完成后再启动下一批
+- 如果遇到 429 配额超限,**提示用户更换 API key 或等待配额重置** 后继续(重置时间在错误提示中有说明)
+
### 0.8 启动逐个任务验证
对**每个任务**启动一个**独立的 agent**,按照 Phase PVD-1 流程执行验证。独立 agent 避免上下文污染,执行更精准。
@@ -184,15 +193,15 @@ mkdir -p tests/e2e/screenshots/visual-diff-pencil
## Phase PVD-1: 逐页门禁对比循环
⛔ **核心原则:逐页执行,每页必须通过门禁才能继续下一页。禁止全量截图后批量对比。**
-**最佳实践:每个页面启动一个独立 agent,避免上下文污染,执行更精准。**
+**最佳实践:每个页面启动一个独立 agent,分批并行执行,避免上下文污染和 Playwright MCP 配额超限,执行更精准。**
-对映射表中每个 `status: active` 的页面,依次执行以下完整循环(每个页面由独立 agent 处理):
+对映射表中每个 `status: active` 的页面,**分批启动**独立 agent 依次执行以下完整循环(每个页面由独立 agent 处理):
```
PVD-1 PER-PAGE LOOP:
A. 导出设计截图
B. 截取实际页面
- C. AI 视觉对比
+ C. 结构校验 + AI 视觉对比
D. 差异分级
E. PAGE GATE-CHECK ⛔
├── 🟢 通过 → G. 记录 → NEXT PAGE
@@ -221,22 +230,199 @@ PVD-1 PER-PAGE LOOP:
```
1. browser_resize → 375x812(移动端)或 1440x900(管理后台)
-2. browser_navigate → {实际 URL}{route}
-3. browser_wait_for → time=3
-4. browser_take_screenshot(fullPage=false)
+2. 处理认证(如需要)→ 见下文
+3. browser_navigate → {实际 URL}{route}
+4. browser_wait_for → time=3
+5. browser_take_screenshot(fullPage=false)
→ tests/e2e/screenshots/visual-diff-pencil/{name}-actual.png
```
-### 1.C AI 视觉对比
+### 认证处理(需要登录的页面)
-使用 Read 工具分别查看设计截图和实际截图,AI 综合分析视觉差异:
-- 布局结构(网格/弹性/定位、对齐方式)
-- 组件排列(导航栏、卡片、按钮、图标)
-- 颜色(Neon Pulse 规范:#000000 背景、#ffb3b5 粉色、#e8b3ff 紫色、#00dce5 青色)
-- 间距(内边距、外边距、元素间距)
-- 字体(大小、粗细、颜色、对齐方式)
-- 图标(是否存在、形状是否正确、是否 emoji 替代)
-- 设计规范(是否违反 Neon Pulse 原则:如存在不必要的 1px 边框)
+大多数功能页面需要登录才能访问,未登录会自动跳转到登录页。正确流程:
+
+1. **生成测试 token**(需要组织者权限的页面必须):
+ 项目已提供预先生成 token 的脚本,直接运行:
+ ```bash
+ node tests/generate-test-token.cjs
+ ```
+ 输出一个已认证组织者身份的 JWT token,复制备用。
+
+ > 脚本会使用 `api/.env` 中的 `JWT_SECRET` 生成签名,确保与后端验证一致。
+
+2. 如果已有测试 token,在 `browser_navigate` **之前** 注入 token 到 `localStorage`:
+ ```javascript
+ await page.evaluate((token, userInfo) => {
+ localStorage.setItem('token', token);
+ localStorage.setItem('user', JSON.stringify(userInfo));
+ }, token, userInfo);
+ ```
+ 其中 `userInfo` 是:
+ ```javascript
+ {
+ id: "test-organizer-user-id",
+ nickname: "Test Organizer",
+ role: "organizer",
+ organizer: { id: "test-organizer-id", status: "APPROVED" }
+ }
+ ```
+
+3. 再导航到目标 URL
+4. 如果需要组织者权限,确保测试账号已通过局头认证(状态 `APPROVED`)
+
+### 常见问题排查
+
+| 问题现象 | 可能原因 | 解决方案 |
+|------|------|---------|
+| 截图黑屏 / 内容空白 | 缺少必要 URL 参数(如 `eventId`),页面无法获取数据 | 从 API 获取有效测试 ID,访问时带上查询参数 `?id=xxx` |
+| 始终显示登录页(即使应该已登录) | 缺少 token 或 token 无效 | 重新登录获取有效 token 注入 `localStorage` |
+| API 返回 401 即使有 token | **服务端环境变量 `JWT_SECRET` 与本地不匹配** | 修改 `api/.env` 后执行 `pm2 restart api` 重启加载 |
+| 页面白屏 / JavaScript 报错 | API 返回 `null` 时代码直接访问属性 | 添加可选链操作符 `?.` 做防御性编程 |
+
+### 1.C 结构校验 + AI 视觉对比
+
+**分两步:先做结构校验,再做视觉对比**
+
+```
+步骤:
+ 1. 从 Pencil MCP batch_get 获取当前设计帧的完整节点树
+ 2. **递归提取所有层级命名节点**(任意层级只要 name 属性存在的节点)→ 得到"必须存在的组件列表"
+ **递归算法**:
+ function collectNamedNodes(node, path, resultList):
+ if node.name != null && node.name != "":
+ resultList.push({path: path + node.name, node: node})
+ for each child in node.children:
+ collectNamedNodes(child, path + node.name + ".", result);
+
+ **强制要求**: **任何层级**只要节点有 name 属性,都必须提取并检查。**不允许只检查顶层不检查深层**。
+
+ 结果包含所有层级:topBar, topBar.topLeft, topBar.topRight, tabSection, tabSection.tabLeft, barCard1, barCard1.ratingBadge, barCard1.activitySnippet 等等(深度嵌套没问题,全部提取)。
+ 示例: topBar.topLeft / tabSection.tabLeft / availableCard / amountInput / ...
+ 3. **检查全局配置关联**: 如果设计帧包含 name=bottomNav/BottomNav 且是 ref 组件
+ - 读取 app/mini-program/src/pages.json → 获取当前 tabBar 配置
+ - 对比设计中 bottomNav.children.length vs 实际 tabBar.list.length
+ - 对比每个 item 文字描述是否匹配(如果可匹配)
+ - 数量不匹配 → critical_count += 1(底部导航结构错误是严重差异)
+ 4. 读取实际页面源码(app/mini-program/src/pages/{page}/index.vue),双向检查每个必需组件:
+ - **必需组件检查**: 按路径节点整个区块缺失 → 直接判定为 🔴 严重差异 → critical_count += 1
+ **强制规则**: 无论层级多深,**只要设计节点有 name 就必须存在**,缺失必须计数
+ - **多余组件检查**: 实际页面存在设计稿中没有的顶层区块 → 🟡 中等差异 → moderate_count += 1
+ - **组件类型一致性检查**: 设计是 `icon_font` 但实现用纯文本字符 → 🟡 中等差异 → moderate_count += 1
+ - **文字样式一致性检查**: **任意设计节点 type=text 且有 name** → 检查 fill 颜色 + fontSize + fontWeight 与实际CSS不符 → 🟡 中等差异 → moderate_count += 1
+ - **布局对齐检查**: 设计节点声明 `justifyContent: space_between` → 检查实际CSS是否正确设置 `display: flex; justify-content: space-between;` → 如果右侧元素未靠右对齐 → 🟡 中等差异 → moderate_count += 1
+ 5. 结构校验通过后,使用 Read 工具分别查看设计截图和实际截图,AI 综合分析视觉差异:
+ - 布局结构(网格/弹性/定位、对齐方式)
+ - 组件排列(导航栏、卡片、按钮、图标)
+ - 颜色(Neon Pulse 规范:#000000 背景、#ffb3b5 粉色、#e8b3ff 紫色、#00dce5 青色)
+ - 间距(内边距、外边距、元素间距)
+ - 字体(大小、粗细、颜色、对齐方式)
+ - 图标(是否存在、形状是否正确、是否 emoji 替代)
+ - 设计规范(是否违反 Neon Pulse 原则:如存在不必要的 1px 边框)
+```
+
+### 可复用组件检查(通用规则)
+
+设计稿 (.pen) 中标记 `reusable: true` 的组件为**全局可复用组件**,必须抽取为 Vue 全局组件在 `app/mini-program/src/components/` 目录,所有页面使用 import 导入,禁止在每个页面重复手写完整实现。
+
+**检查规则**:
+ 1. 先通过 Pencil MCP `batch_get` 扫描 `.pen` 中所有标记 `reusable: true` 的节点 → 获取全局可复用组件列表
+ 2. 对每个需要使用该组件的页面:
+ - 如果页面**完整手写实现了可复用组件代码**(未 import 使用全局组件)→ 🟡 中等差异 `moderate_count += 1`
+ - 如果使用了全局组件但样式(背景色/尺寸)不符合设计规范 → 🟡 中等差异 `moderate_count += 1`
+ - 正确使用了全局组件且样式符合规范 → 不计数
+
+**当前已识别可复用组件**:
+ - `NavigationBarPrimary` (一级页面通用导航栏) → 要求:背景 `#000000` 不透明
+ - `NavigationBarWithBack` (二级页面带返回导航栏) → 要求:背景 `#000000cc` 半透明 + `backdrop-filter: blur(20px)`
+
+**修复要求**:发现重复手写实现必须抽取为全局组件,并更新所有页面使用。
+
+### 图标检查特别规则(@egoist/tailwindcss-icons)
+
+**设计要求**:设计节点 `type=icon_font` 必须用 tailwindcss-icons 实现,禁止使用 emoji 或纯文本占位。
+
+**正确 HTML 结构:**
+```vue
+
+
+
+
+
+
+
+```
+
+**正确 CSS 规则:**
+```css
+/* 必须添加确保SVG继承颜色 */
+.action-icon > view svg {
+ width: 100%;
+ height: 100%;
+ fill: currentColor;
+}
+```
+
+**图标命名规则:**
+
+项目 tailwind.config.js 已启用三个图标集合:
+```js
+collections: getIconCollections(["mdi", "lucide", "material-symbols"]);
+```
+
+| 图标库 | 类名前缀 | 适用场景 |
+|---------|----------|----------|
+| Material Symbols | `i-material-symbols-*` | 直接使用设计中的原始名称,不需要转换 |
+| Material Design Icons | `i-mdi-*` | 常用 material 图标 |
+| Lucide | `i-lucide-*` | 设计中 Material Symbols 不存在时使用 |
+
+**Material Symbols → Lucide 名称映射表**(当设计使用 Material Symbols 但需要用 Lucide 替代时参考):
+
+| Material 名称 | Lucide 对应名称 | 示例场景 |
+|---------------|-----------------|------------|
+| `local_fire_department` | `flame` | 热门推荐/火焰 |
+| `near_me` | `navigation` | 附近/定位 |
+| `military_tech` | `trophy` | 排行/奖杯 |
+| `wine_bar` | `wine` | 酒吧/酒杯 |
+| `location_on` | `map-pin` | 位置 |
+| `star` | `star` | 评分/星星 |
+| `chevron_right` | `chevron-right` | 向右箭头 |
+
+**差异定级规则:**
+- 结构错误(图标类在容器上)→ 🟡 中等差异 `moderate_count += 1`
+- 缺少 `fill: currentColor` → 🟡 中等差异 `moderate_count += 1`
+- 图标名称不匹配(按上表)→ 🟡 中等差异 `moderate_count += 1`
+- **使用 emoji 替代图标** → 🔴 严重差异 `critical_count += 1` **(微信小程序不兼容emoji,强制修复)**
+
+### 布局对齐检查
+
+对于设计节点使用 `justifyContent: space_between` 的水平排列容器:
+- 检查实际CSS是否正确设置 `display: flex; justify-content: space-between; align-items: flex-start;`
+- 如果左侧元素挤在一起,右侧元素没有靠右对齐 → 🟡 中等差异 `moderate_count += 1`
+
+对于设计节点声明的对齐方式:
+- 实际实现对齐方式不符 → 🟡 中等差异 `moderate_count += 1`
+
+### 防御性编程提醒
+
+当 API 数据可能返回 `null` 时(未登录/加载失败),确保代码使用**可选链操作符 `?.`**访问属性避免渲染崩溃:
+
+```vue
+
+{{ earnings?.totalAmount }}
+
+
+{{ earnings.totalAmount }}
+```
+
+如果发现源码缺少可选链,在 AUTO-FIX 阶段应当添加。
+
+**优势**:
+- 利用设计文件本身的结构信息,比纯视觉分析更可靠
+- **递归检查所有层级** → 确保不会漏检顶层存在但内部子区块缺失(如 topBar 存在但 topLeft 缺失)
+- **自动检查全局 tabBar 配置** → 设计稿显示的 bottomNav 结构必须与实际配置一致
+- **所有文字节点都检查** → 正文/meta/操作计数等颜色不符不会漏检
+- 确保不会漏检"整个区块缺失"(如支付结果页订单信息卡片整个不见了)
+- 帮助 AI 聚焦到每个具体组件,差异分析更准确
### 1.D 差异分级
@@ -253,8 +439,8 @@ PVD-1 PER-PAGE LOOP:
| 情况 | 级别 | 是否计数 |
|------|------|----------|
-| 布局结构不同、关键组件缺失 | 🔴 严重 | `critical_count += 1` |
-| 颜色/间距偏差、图标不一致、文本对齐方式不符、圆角尺寸差异、多余顶层区块、违反Neon Pulse设计规范、组件类型不匹配、文字标签颜色/字体大小不符 | 🟡 中等 | `moderate_count += 1` |
+| 布局结构不同、**任意层级**关键组件缺失、bottomNav 配置与设计不匹配 (Tab数量/文字) | 🔴 严重 | `critical_count += 1` |
+| 颜色/间距偏差、图标不一致、文本对齐方式不符、圆角尺寸差异、多余顶层区块、违反Neon Pulse设计规范、组件类型不匹配、**任意命名文字节点颜色/字体大小不符** | 🟡 中等 | `moderate_count += 1` |
| 微小像素差异 (1-2px 偏移) | 🟢 轻微 | `minor_count += 1` |
**门禁规则再强调**:
@@ -294,6 +480,10 @@ FIX LOOP (最多 3 轮):
F2. 修改代码(CSS/组件/布局/颜色)
- 使用 Edit 工具修改对应文件
- 参照设计稿的 Neon Pulse 设计规范
+ - **图标问题修复优先顺序**:
+ 1. 修正结构 → 将图标类从容器移出,嵌套独立 ``
+ 2. 修正名称 → 按 Material → Lucide 映射表修改正确名称
+ 3. 添加CSS → 确保 `svg { fill: currentColor; }` 让颜色正确继承
F3. 重启服务(如修改了后端或需要刷新构建)
- Bash: pm2 restart mini-h5(移动端)或 pm2 restart admin(管理后台)
F4. 重截实际页面
@@ -332,6 +522,22 @@ visual_diff_pencil:
---
+## 复查阶段(必须)
+
+> **第一轮 PVD-1 验证完成成后,必须启动一轮专门的复查阶段**
+
+原因:
+- 初始验证时,很多需要登录/参数的页面因未登录/缺少参数导致截图不正确,但代码对齐检查已通过
+- 必须逐个页面确认**截图内容实际正确**
+- 只有**所有页面截图都正确**,整个验证任务才算完成
+
+操作:
+1. 为每个 `status: active` 页面创建独立的**复查 Task**
+2. 逐个检查:截图文件存在 → 内容显示正确目标页面 → 布局与设计对齐
+3. 如果不正确:修复问题(注入token/添加URL参数/修复代码bug)→ 重新截图
+
+---
+
## Phase PVD-2: 汇总报告
### 2.1 生成报告