feat(d8d-dev): merge 14+ round journey verification enhancements
Merged from bmad-dev-pipeline (bar-party project) back to upstream template: New files: - reference/platform-uni-app.md: uni-app platform knowledge (icons, tabbar, nav, layout) - visual-verification.md: independent visual verification checkpoint spec Major enhancements: - AUTO-FIX #15-#21: english enum→CN mapping, constraint mismatch, SVG data URI→Iconify, dual navbar fix, pull-to-refresh unification, API try-catch, onMounted→onShow - R7 constraint consistency rule (journey-authenticity-rules.md) - Zero-interaction element detection (FAIL #5 diagnostic) - Per-step visual verification checkpoint (not just final screenshot) - "Known issues no exemption" iron law - Design system generalization (Neon Pulse → dynamic from 04-ui-ux-design.md) - Icon/layout reference extraction to platform-uni-app.md - Categorized commit phase (6 categories) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -33,7 +33,7 @@
|
||||
| 2 | API 返回 500 / 错误状态码 | Bug | AUTO-FIX | 空 body 返回 500 |
|
||||
| 3 | 前端组件/按钮未渲染 | 未实现 | AUTO-IMPLEMENT | 登录入口按钮缺失 |
|
||||
| 4 | 页面跳转到错误位置 | Bug | AUTO-FIX | 导航逻辑错误 |
|
||||
| 5 | 页面停留在"加载中"/空白/错误态 | Bug | AUTO-FIX | fetchProfile 空 catch + 响应结构不匹配 |
|
||||
| 5 | 页面停留在非功能态("加载中"/空白/错误态/有内容但零交互元素) | Bug | AUTO-FIX | hydration 失败 / 渲染 Bug / fetchProfile 空 catch / 响应结构不匹配 |
|
||||
| 6 | 可交互元素无法通过 Playwright 操作 | Bug | AUTO-FIX | uni-input 编译后 browser_type 失败 |
|
||||
| 7 | 功能提示"暂未开放"/"开发中"/"暂未实现" | 未实现 | AUTO-IMPLEMENT | 手机号登录只有占位符 |
|
||||
| 8 | 测试数据过期/状态不对 | 数据问题 | AUTO-DATA-FIX | 收益冻结期不可提现 |
|
||||
@@ -43,6 +43,13 @@
|
||||
| 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** | **兜底规则,确保无遗漏** |
|
||||
|
||||
### "可接受"严格限定(⛔ 仅以下 2 种,其余一律必须修复)
|
||||
@@ -52,17 +59,50 @@
|
||||
|
||||
**除以上 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`)
|
||||
|
||||
---
|
||||
|
||||
@@ -90,8 +130,49 @@
|
||||
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) => {
|
||||
@@ -140,6 +221,9 @@
|
||||
- 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 次)
|
||||
|
||||
@@ -157,6 +241,20 @@
|
||||
- 后端路由缺失 → `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:
|
||||
@@ -201,6 +299,173 @@ 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 次)
|
||||
|
||||
**适用**: 测试数据过期、冻结期收益不可用、关联数据缺失
|
||||
@@ -248,10 +513,20 @@ uni.showToast({ title: "验证码已发送", icon: "success" });
|
||||
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 已触发 ← 修复检查
|
||||
├── 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
|
||||
@@ -268,6 +543,7 @@ GATE-CHECK:
|
||||
- 用 `browser_evaluate` 绕过不可交互的 input/button 后标记步骤 PASS
|
||||
- 把"加载中..."/"暂未开放"判定为 PASS
|
||||
- 任何 FAIL 标记为"可接受"但不符合上述严格限定的 2 种情况
|
||||
- **用 SVG data URI / CSS background-image 替代 Iconify 图标方案**(微信小程序不支持 SVG data URI,必须用 @egoist/tailwindcss-icons + Iconify)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
- 步骤 0(resize)不能省略,即使"上一个 Story 刚 resize 过"
|
||||
- 步骤 3-5(交互)在页面有表单/按钮时**绝对不能省略**
|
||||
- 步骤 7(截图)是强制证据,不是可选装饰
|
||||
- 步骤 8(视觉验证)**不调用等于验证未完成**
|
||||
- 步骤 8(视觉验证)**不调用等于验证未完成**(⛔ 详细规则和标准提示词见 ./visual-verification.md)
|
||||
- 步骤 9(API 目标检测)**前端 Story 必须执行**,检测页面 JS 发起的 API 请求是否指向 localhost 而非外网域名
|
||||
|
||||
### API 调用目标检测(步骤 9 详解)
|
||||
@@ -95,16 +95,8 @@ 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: 图标方案合规(小程序/移动端)
|
||||
if find app/ -name "pages.json" -not -path "*/node_modules/*" -print0 | xargs -0 grep -l '"iconPath": ""' 2>/dev/null | head -1; then
|
||||
echo "tabbar_iconpath_empty=true"
|
||||
fi
|
||||
|
||||
# 检查 4: Emoji 充当图标
|
||||
EMOJI_ICONS=$(grep -rn 'class="[^"]*icon[^"]*"[^>]*>[🔍🔔🏠📱💬👤⚙️📝➕❤️⭐📢✅❌]' app/*/src/ --include="*.vue" --include="*.tsx" | grep -v node_modules)
|
||||
EMOJI_COUNT=$(echo "$EMOJI_ICONS" | grep -c ":" || echo 0)
|
||||
echo "emoji_as_icons=$EMOJI_COUNT"
|
||||
[ "$EMOJI_COUNT" -gt 0 ] && echo "EMOJI_FILES:" && echo "$EMOJI_ICONS"
|
||||
# 检查 3: 图标方案合规 + Emoji 充当图标
|
||||
# 完整检测命令见 ./reference/platform-uni-app.md 第 6 节
|
||||
```
|
||||
|
||||
**判定规则**:
|
||||
@@ -124,7 +116,7 @@ echo "emoji_as_icons=$EMOJI_COUNT"
|
||||
3. 重启前端服务 → 重新执行步骤 9.5 验证
|
||||
|
||||
**AUTO-IMPLEMENT(未实现类型)**:
|
||||
按照 `phase-03-implement.md` 第 5 节的方案执行完整实现:
|
||||
按照 `./reference/platform-uni-app.md` 第 1 节(图标方案)和第 2 节(TabBar 方案)执行完整实现:
|
||||
1. 安装依赖(Tailwind + Iconify + weapp-tailwindcss)
|
||||
2. 创建配置文件(tailwind.config.js, vite.config.ts)
|
||||
3. 创建组件(自定义 TabBar)
|
||||
@@ -135,18 +127,16 @@ echo "emoji_as_icons=$EMOJI_COUNT"
|
||||
|
||||
### 增强 analyze_image 提示词(步骤 8 详解)
|
||||
|
||||
**目的**:当前 analyze_image 只检查 Neon Pulse 设计颜色,需增加多个维度,每个维度必须明确 PASS/FAIL。
|
||||
**目的**:当前 analyze_image 只检查项目设计系统颜色,需增加多个维度,每个维度必须明确 PASS/FAIL。
|
||||
|
||||
**提示词模板**:
|
||||
|
||||
```
|
||||
分析此截图,按以下维度逐一给出 PASS/FAIL 判定:
|
||||
|
||||
1. **设计规范(Neon Pulse)**:
|
||||
- 背景:深色 #000 或接近黑色
|
||||
- 按钮:渐变色(粉→紫→青 135deg)
|
||||
- 卡片:玻璃态效果(半透明 + blur)
|
||||
- 无 1px 实线边框(用背景色区分区域)
|
||||
1. **设计规范**(从项目 _bmad-output/04-ui-ux-design.md 提取):
|
||||
- {执行时:Read _bmad-output/04-ui-ux-design.md → 提取设计系统规则 → 填入}
|
||||
- 回退:文件不存在 → grep 项目 CSS :root 变量
|
||||
→ PASS/FAIL + 具体问题
|
||||
|
||||
2. **图标/图片完整性**:
|
||||
@@ -165,10 +155,14 @@ echo "emoji_as_icons=$EMOJI_COUNT"
|
||||
- 列表项是否有数据(非空列表且无"暂无数据"提示)
|
||||
→ PASS/FAIL + 具体问题
|
||||
|
||||
4. **布局合理性**:
|
||||
- 内容是否在视口范围内(无严重溢出/裁剪)
|
||||
- 文字是否可读(无截断/重叠)
|
||||
- 底部 TabBar 是否完整可见(小程序页面)
|
||||
4. **布局质量**(⛔ 逐一检查以下每一项,不得跳过):
|
||||
a. 溢出检测:内容在视口范围内,无水平溢出或裁剪
|
||||
b. 文字可读:文字不被截断,不显示省略号,对比度足够
|
||||
c. 重叠检测:元素无重叠(标题栏与自定义头部不碰撞,导航栏不遮盖内容)
|
||||
d. 间距合理性:无异常大间距(如导航栏与内容之间的过大空白),无紧贴元素(相邻交互元素应有明确间距)
|
||||
e. 元素变形检测:按钮/输入框/卡片未被挤压变形(宽高比异常),文字未被截断
|
||||
f. 对齐检测:同行元素水平对齐,同列元素垂直对齐
|
||||
g. TabBar:底部 TabBar 完整可见(非 TabBar 页面此项 PASS)
|
||||
→ PASS/FAIL + 具体问题
|
||||
|
||||
总结:列出所有 FAIL 项,如全部 PASS 则输出"全部 PASS"。
|
||||
@@ -177,10 +171,10 @@ echo "emoji_as_icons=$EMOJI_COUNT"
|
||||
**结果记录**:
|
||||
```yaml
|
||||
verification_detail:
|
||||
design_check: { neon_pulse: PASS/FAIL, details: "..." }
|
||||
design_check: { design_compliance: PASS/FAIL, design_spec_source: "04-ui-ux-design.md", details: "..." }
|
||||
icons_check: { tabbar_icons_visible: PASS/FAIL, broken_images: PASS/FAIL, emoji_as_icons: PASS/FAIL }
|
||||
functional_check: { elements_rendered: PASS/FAIL, text_readable: PASS/FAIL }
|
||||
layout_check: { viewport_fit: PASS/FAIL, tabbar_visible: PASS/FAIL }
|
||||
layout_check: { overflow: PASS/FAIL, spacing_anomaly: NONE/FOUND, element_deformation: NONE/FOUND, alignment: PASS/FAIL, tabbar_visible: PASS/FAIL }
|
||||
```
|
||||
|
||||
### 步骤 8.5: 布局溢出检测(小程序强制)
|
||||
@@ -188,34 +182,10 @@ verification_detail:
|
||||
**目的**:小程序页面在真实移动设备上经常出现宽度/高度溢出视口、底部 TabBar 被遮挡的问题。此步骤通过 JavaScript 检测这类布局问题。
|
||||
|
||||
**检测方法**:
|
||||
> 完整的检测 JS 代码和 TabBar CSS 选择器见 **./reference/platform-uni-app.md 第 5.2 节**。
|
||||
> 防溢出规则见 **./reference/platform-uni-app.md 第 5.1 节**。
|
||||
|
||||
在步骤 8(analyze_image)之后、步骤 9(network_requests)之前执行:
|
||||
|
||||
```
|
||||
browser_evaluate:
|
||||
function: () => {
|
||||
const html = document.documentElement;
|
||||
const body = document.body;
|
||||
const tabBar = document.querySelector('.uni-tabbar, .tabbar, [class*="tabbar"], [class*="tab-bar"]');
|
||||
|
||||
return {
|
||||
// 水平溢出检测
|
||||
horizontal_overflow: html.scrollWidth > window.innerWidth,
|
||||
document_width: html.scrollWidth,
|
||||
viewport_width: window.innerWidth,
|
||||
overflow_diff: html.scrollWidth - window.innerWidth,
|
||||
|
||||
// 底部 TabBar 可见性检测(仅小程序页面)
|
||||
tabbar_found: tabBar !== null,
|
||||
tabbar_visible: tabBar ? (tabBar.getBoundingClientRect().bottom <= window.innerHeight) : null,
|
||||
tabbar_rect: tabBar ? tabBar.getBoundingClientRect() : null,
|
||||
|
||||
// 页面总高度(用于判断内容是否超出视口太多)
|
||||
page_height: html.scrollHeight,
|
||||
viewport_height: window.innerHeight,
|
||||
};
|
||||
}
|
||||
```
|
||||
在步骤 8(analyze_image)之后、步骤 9(network_requests)之前执行。
|
||||
|
||||
**判定规则**:
|
||||
|
||||
@@ -244,12 +214,6 @@ verification_detail:
|
||||
tabbar_visible: true/false/null # null = 页面无 TabBar
|
||||
```
|
||||
|
||||
**失败处理**:
|
||||
1. 检查页面根元素是否有超过 `100vw` 的宽度设置
|
||||
2. 检查固定定位元素的 `bottom` 值是否考虑了 TabBar 高度
|
||||
3. 检查可滚动内容高度是否减去了 TabBar + Header 高度
|
||||
4. 修复后重新执行步骤 8.5 验证
|
||||
|
||||
### 用户旅程测试(Epic 级集成验证)
|
||||
|
||||
**⛔ 工具选择强制规则**:执行旅程测试时,每个用户步骤必须遵循上述 Playwright MCP 测试序列(步骤 0-9)。**禁止使用 curl 替代 browser_click / browser_type / browser_snapshot / browser_navigate**。curl 仅用于数据状态的前后对比验证(补充,非替代用户交互)。详见 `journey-mode.md` 的"工具选择守卫"部分。
|
||||
@@ -261,7 +225,7 @@ verification_detail:
|
||||
**用户旅程 ≠ 单元测试。必须模拟真实用户从自然入口开始的行为路径。**
|
||||
|
||||
**规则 1:起始页必须是自然入口**
|
||||
- 小程序:起始页 = 用户打开网址看到的第一个页面(pages.json 第一个 page,通常是首页)
|
||||
- 小程序:起始页 = 用户打开网址看到的第一个页面(参见 ./reference/platform-uni-app.md §4 入口页面)
|
||||
- 管理后台:起始页 = 根 URL `/`(通常重定向到登录或 Dashboard)
|
||||
- ❌ `browser_navigate("/pages/login/index")` — 用户不会直接输入登录页 URL
|
||||
- ✅ `browser_navigate("http://mini-xxx.dev.d8d.fun/")` → 用户看到首页 → 自然触发登录流程
|
||||
|
||||
@@ -118,7 +118,7 @@ Epic N+1 完成
|
||||
browser_resize → browser_navigate → browser_snapshot
|
||||
→ browser_type → browser_click → browser_wait_for → browser_snapshot
|
||||
→ browser_take_screenshot → analyze_image
|
||||
→ "表单提交成功、数据已更新、视觉符合 Neon Pulse" → 下一个
|
||||
→ "表单提交成功、数据已更新、视觉符合项目设计系统" → 下一个
|
||||
```
|
||||
|
||||
### 按Story类型的验证要求矩阵
|
||||
@@ -146,8 +146,8 @@ Epic N+1 完成
|
||||
|------|---------|---------|---------|
|
||||
| **渲染验证** | browser_snapshot | 组件树包含预期元素 | 只做了这一步就跳过 |
|
||||
| **交互验证** | browser_type + browser_click | 表单可填写、按钮可点击、操作有结果响应 | 管理后台不填表单、小程序不测试流程 |
|
||||
| **视觉验证** | analyze_image(对截图) | Neon Pulse + 图标/图片加载完整 + 无 emoji 占位符 + 无 broken image | 完全不调用 analyze_image |
|
||||
| **截图存档** | browser_take_screenshot | 保存到 tests/e2e/screenshots/story-x-y.png | 只保存了极少数截图 |
|
||||
| **视觉验证** | analyze_image(对每步截图) | 项目设计系统(04-ui-ux-design.md) + 图标/图片加载完整 + 无 emoji 占位符 + 无 broken image | 只在最后做一次,中间步骤视觉问题被遗漏 |
|
||||
| **截图存档** | browser_take_screenshot(每步) | 每步保存到 tests/e2e/screenshots/(含步骤编号) | 只在最后保存一张截图 |
|
||||
| **API 目标验证** | browser_network_requests | 前端 JS 发起的 API 请求不指向 localhost(外网域名模式下) | 完全不检测,导致外网用户无法使用 |
|
||||
| **布局溢出验证** | browser_evaluate(JS 检测) | scrollWidth <= viewportWidth、TabBar 可见(小程序页面) | 完全不检测布局溢出 |
|
||||
|
||||
@@ -202,15 +202,10 @@ Epic N+1 完成
|
||||
- ❌ 不点击按钮就说"交互功能正常"
|
||||
- ❌ 不调用 analyze_image 就说"视觉验证通过"
|
||||
- ❌ 不保存截图就继续下一个 Story
|
||||
- ❌ 只在最后阶段截图和 analyze_image,中间步骤无视觉证据
|
||||
- ❌ 不执行 browser_network_requests 检测就说"API 调用正确"
|
||||
- ❌ 前端页面 JS 调用 localhost:3001 但仍标记 Story 为 done
|
||||
- ❌ 混用视口(管理后台用 375 宽度或小程序用 1440 宽度)
|
||||
- ❌ 旅程验证中用户操作步骤使用 curl 替代 Playwright(点击、导航、表单填写、页面验证必须用 Playwright 工具)
|
||||
- ❌ 整条旅程只用 curl API 调用而未使用任何 Playwright 浏览器工具
|
||||
- ❌ 旅程 FAIL 被记录但未触发分类判定(Bug/未实现/数据问题)
|
||||
- ❌ 旅程 FAIL 分类为 Bug 或未实现但未触发 AUTO-FIX/AUTO-IMPLEMENT
|
||||
- ❌ 旅程 status: pass 但存在 failed_steps > 0 且未全部修复成功
|
||||
- ❌ 一次 Playwright 会话验证多个 Story 的多个页面
|
||||
|
||||
### 检测即阻断规则(⛔ 铁律四补充)
|
||||
|
||||
@@ -219,13 +214,14 @@ Epic N+1 完成
|
||||
| 检测项 | FAIL 条件 | 分类 | 修复动作 |
|
||||
|--------|----------|------|---------|
|
||||
| 布局溢出 | `horizontal_overflow === true` | Bug | AUTO-FIX: CSS 修复 |
|
||||
| TabBar 图标缺失 | 只有文本无图标 | 未实现 | AUTO-IMPLEMENT: 实现自定义 TabBar |
|
||||
| TabBar 图标缺失 | 只有文本无图标 | 未实现 | AUTO-IMPLEMENT: 实现自定义 TabBar(参见 ./reference/platform-uni-app.md §2) |
|
||||
| API 内联声明 | 任何前端代码有独立 `const API_BASE =` | Bug/未实现 | 集中模块存在→AUTO-FIX,否则→AUTO-IMPLEMENT |
|
||||
| API 目标错误 | 外网模式下发现 localhost | Bug | AUTO-FIX: 替换 fallback 值 |
|
||||
| 源码 localhost fallback | grep 发现 `localhost:[port]` | Bug | AUTO-FIX: 替换为外网域名 |
|
||||
| TabBar iconPath 为空 | `pages.json` iconPath 是空字符串 | 未实现 | AUTO-IMPLEMENT: 自定义 TabBar + Iconify |
|
||||
| Emoji 充当功能图标 | icon/btn 元素内含 emoji 字符 | 未实现 | AUTO-IMPLEMENT: Iconify 方案替换 |
|
||||
| Tailwind/Iconify 未配置 | mini-program 无 tailwindcss-icons | 未实现 | AUTO-IMPLEMENT: 完整安装配置 |
|
||||
| TabBar iconPath 为空 | `pages.json` iconPath 是空字符串 | 未实现 | AUTO-IMPLEMENT: 自定义 TabBar + Iconify(参见 ./reference/platform-uni-app.md §1+§2) |
|
||||
| Emoji 充当功能图标 | icon/btn 元素内含 emoji 字符 | 未实现 | AUTO-IMPLEMENT: **⛔ 必须按 ./reference/platform-uni-app.md §1 完整安装 Iconify + tailwindcss-icons + weapp-tailwindcss(小程序),禁止 SVG data URI 等不支持的替代方案** |
|
||||
| SVG data URI 充当图标/装饰 | `grep -rn "data:image/svg+xml" app/mini-program/src/` 在 CSS/style 中发现 `url("data:image/svg+xml...")` 或 `url('data:image/svg+xml...')` | Bug(平台不兼容) | AUTO-IMPLEMENT: **⛔ 必须按 ./reference/platform-uni-app.md §1 完整安装 Iconify + tailwindcss-icons + weapp-tailwindcss/vite,将所有 SVG data URI 替换为 Iconify 类名,⛔ 禁止用另一种 SVG data URI 替代原来的** |
|
||||
| Tailwind/Iconify 未配置 | mini-program 无 tailwindcss-icons | 未实现 | AUTO-IMPLEMENT: 完整安装配置(参见 ./reference/platform-uni-app.md §1) |
|
||||
| Phase 衔接跳步 | Phase 0V 未完成就进入 Story 验证 | 阻断 | 不得开始 Story 验证直到 Phase 0V COMPLETE GATE 通过 |
|
||||
| 三写不一致 | pipeline-state 和 pipeline-context 的 done 计数不匹配 | 阻断 | 不得标记 Story 完成,必须定位漏写的文件 |
|
||||
| 用户旅程未执行 | `pipeline-state` user_journeys 为空 | 阻断 | 不得标记 complete,必须回退执行 |
|
||||
@@ -234,6 +230,13 @@ Epic N+1 完成
|
||||
| 旅程 FAIL 未触发修复 | FAIL 被记录但未执行 AUTO-FIX/AUTO-IMPLEMENT/AUTO-DATA-FIX 分类判定和修复流程 | 阻断 | 必须执行分类判定并触发对应修复流程 |
|
||||
| 即时使用数据仅 Toast 传递 | 验证码等下一步骤需要的数据仅通过 Toast 显示且源码无 console.log 且无自动填充 | Bug | AUTO-FIX(#14): 添加 console.log + 自动填充 |
|
||||
| 框架编译 input 不可交互 | `browser_type` 失败后 `browser_evaluate` 诊断发现内部 input `height: 0px`(如 uni-app `uni-input` 在 flex 容器中无显式 height) | Bug | AUTO-FIX(#6): 修复 CSS 添加显式 height/width |
|
||||
| 页面有内容但零交互元素 | `browser_evaluate` 确认 `interactiveCount===0` 且 `hasContent=true`(用户打开页面看到标题但无任何 button/input/a/form 可操作) | Bug(#5) 或 未实现(#3) | 诊断分流:框架已挂载+源码无交互→AUTO-IMPLEMENT(添加 redirect/导航);框架未挂载→AUTO-FIX(hydration) |
|
||||
| UI 硬编码英文枚举值 | `browser_evaluate` 检测页面可见文本含 APPROVED/REJECTED/PENDING/PAID/CANCELLED/REFUNDED/DRAFT/ONGOING/COMPLETED 等英文大写枚举(适用于所有前端页面,不限于管理后台) | Bug(#15) | AUTO-FIX: 添加中文映射函数(管理后台 statusLabel / 小程序 computed) |
|
||||
| 约束声明与代码行为不一致 | 旅程描述含"可选/可跳过/选填"或页面 UI 含"(选填)/(可选)"标记,但代码实际有必填验证(Toast 含"请上传/请填写/不能为空"或源码 `if (!field)` 拦截) | 约束不一致(#16) | JOURNEY-FIX: 修复旅程定义 / AUTO-FIX: 修复代码或 UI 文案,使声明与行为一致 |
|
||||
| analyze_image 视觉验证未执行 | pipeline-state 无 `analyze_image_called: true` 或 verification_detail.design_check 缺失 | 阻断 | 必须回退执行 analyze_image 后才能标记完成(见 ./visual-verification.md) |
|
||||
| 双重导航栏 | 页面在 pages.json 中有 `navigationBarTitleText`(非 custom)且页面源码含 `position:fixed` 的自定义 header 元素,导致系统导航栏与自定义头部同时显示(截图中可见两层标题) | Bug(#18) | AUTO-FIX: pages.json 该页面添加 `navigationStyle: "custom"` + 页面 header CSS 添加 safe-area padding(参见 ./reference/platform-uni-app.md §3) |
|
||||
| API route 无 try-catch | API 返回 500 + `Read` 源码发现 handler 函数无 try-catch 包裹(异常直接返回原始 500 且无日志) | Bug(#20) | AUTO-FIX: 添加 try-catch 包裹 + console.error + JSON 500 响应(参见 ./auto-fix-engine.md #20) |
|
||||
| 页面 navigateBack 后数据陈旧 | `uni.navigateBack()` 返回目标页后 `browser_snapshot` 数据未更新 + 源码用 `onMounted` 非 `onShow` | Bug(#21) | AUTO-FIX: 替换 `onMounted` 为 `onShow`(from `@dcloudio/uni-app`,参见 ./auto-fix-engine.md #21) |
|
||||
|
||||
> 完整的 FAIL 分类标准(含兜底规则 #99)和修复流程见 ./auto-fix-engine.md 第 2-4 节
|
||||
|
||||
@@ -243,5 +246,17 @@ Epic N+1 完成
|
||||
- ❌ 跳过用户旅程测试直接标记 Epic 完成
|
||||
- ❌ 发现 localhost fallback 但标记"已通过 env 覆盖"然后 done
|
||||
- ❌ 发现 emoji 当图标但写"视觉可接受"然后 done
|
||||
- ❌ **将视觉问题标记为任何非检测即阻断表中定义的分类**(模式性禁止)
|
||||
- 禁止的分类包括但不限于:"已知遗留项"、"技术债"、"后续优化"、"暂时保留"、"非本次引入"
|
||||
- 唯一允许的分类:检测即阻断表中的分类(Bug/未实现/阻断)
|
||||
- ❌ **将修复从 AUTO-FIX/AUTO-IMPLEMENT 降级为"仅记录"或"WARN"**
|
||||
- ❌ **推迟视觉验证修复到"旅程完成后"或"步骤 4 修复阶段"**(必须在关卡内立即执行)
|
||||
- ❌ 发现 pages.json iconPath 为空但标记"TabBar 可见"然后 done
|
||||
- ❌ 页面有静态标题但无交互元素(用户无法操作)时标记"渲染通过"
|
||||
- ❌ 遇到零交互元素但不诊断原因直接判定为 hydration Bug(可能是缺少 redirect)
|
||||
- ❌ 任何前端页面直接显示英文枚举值(如 APPROVED/REJECTED/PENDING)但标记"显示正常"
|
||||
- ❌ 旅程说"可跳过/可选/选填"但代码必填验证拦截后仅静默补填继续(不标记 Constraint Mismatch)
|
||||
- ❌ 页面 UI 含"(选填)/(可选)"标记但后端/前端实际必填(不标记 UX Mismatch)
|
||||
- ❌ API 返回 500 但未检查源码 try-catch 就标记"环境问题"或"已知问题"
|
||||
- ❌ 页面 navigateBack 后数据未刷新但标记"已通过 API 验证"(必须检查 onShow)
|
||||
|
||||
|
||||
@@ -268,10 +268,98 @@ uni.showToast({ title: "验证码已发送", icon: "success" })
|
||||
|
||||
---
|
||||
|
||||
## 规则 R7:约束声明必须与代码实际行为一致
|
||||
|
||||
> **⛔ 通用规则,检测旅程描述和页面 UI 文案中的约束声明与代码实际验证逻辑的不一致。**
|
||||
|
||||
### 为什么需要 R7
|
||||
|
||||
当旅程定义或页面 UI 告诉用户"某步骤/字段可选",但代码实际强制必填时:
|
||||
- 旅程验证会"静默补填继续",不标记为问题
|
||||
- 真实用户会被意外的校验错误困惑("明明说可选的?")
|
||||
- 这是比功能缺失更隐蔽的问题:**用户被误导了**
|
||||
|
||||
### R7-A:旅程步骤约束 vs 源码验证逻辑
|
||||
|
||||
```
|
||||
检测方法:
|
||||
1. 扫描旅程"用户视角步骤"中的约束关键词:
|
||||
- 可选/可跳过/选填/optional/skip/跳过/使用占位图
|
||||
- 必填/必须/required/不可为空
|
||||
2. 对含约束描述的步骤,读取对应页面源码的提交/下一步函数:
|
||||
- 搜索 if (!field) / required / validate
|
||||
- 搜索 Toast/提示中的"请上传/请填写/请选择/不能为空"
|
||||
3. 比对旅程约束 vs 代码实际验证
|
||||
```
|
||||
|
||||
### R7-B:页面 UI 文案 vs 代码/接口实际约束
|
||||
|
||||
```
|
||||
检测方法:
|
||||
1. browser_snapshot / analyze_image 后,扫描页面文本中的约束标识:
|
||||
(选填)/(可选)/(必填)/ * /required
|
||||
2. 对含约束标记的字段,读取对应源码:
|
||||
- 前端 JS:表单提交函数中该字段的验证逻辑
|
||||
- 后端 API:接口对该字段是否 required
|
||||
3. 比对 UI 文案 vs 前后端实际约束
|
||||
|
||||
自动触发时机:
|
||||
- 旅程验证执行时 browser_snapshot 显示含约束标识的字段标签
|
||||
- analyze_image 检测到页面含"选填/可选"文本
|
||||
```
|
||||
|
||||
### 判定表
|
||||
|
||||
| 声明方 | 声明内容 | 代码实际 | 判定 | 分类 |
|
||||
|-------|---------|---------|------|------|
|
||||
| 旅程描述 | 可选/可跳过 | 必填验证存在 | ❌ 不一致 | Constraint Mismatch |
|
||||
| 旅程描述 | 必填/必须 | 无验证 | ⚠️ 遗漏验证 | Bug / Journey Update |
|
||||
| 页面 UI | (选填) | 前端/后端必填 | ❌ Bug | UX Mismatch |
|
||||
| 页面 UI | *(必填标记) | 无验证 | ⚠️ 遗漏验证 | Bug |
|
||||
| 旅程描述 | 未提及约束 | 有验证 | ⚠️ 旅程遗漏 | Journey Update |
|
||||
|
||||
### 修复策略
|
||||
|
||||
```
|
||||
检测到 Constraint Mismatch / UX Mismatch:
|
||||
↓
|
||||
判断正确设计意图:
|
||||
├── PRD/UX 说可选 → 旅程/UI 正确 → AUTO-FIX 代码(移除必填或改为可选)
|
||||
├── PRD/UX 说必填 → 代码正确 → AUTO-FIX 旅程定义或 UI 文案
|
||||
└── 无法判断 → 标记 WARN + 记录到 pending_fixes
|
||||
同时修复旅程定义使其准确反映代码行为
|
||||
执行时按代码实际行为执行
|
||||
```
|
||||
|
||||
### 执行时约束检测(⛔ 强制)
|
||||
|
||||
旅程验证执行过程中,每个用户步骤首次操作被拦截时:
|
||||
|
||||
```
|
||||
操作被 Toast 拦截(含"请上传/请填写/请选择/不能为空")
|
||||
↓
|
||||
检查触发条件(双条件):
|
||||
条件 A: 旅程描述含"可跳过/可选/选填/skip/optional"
|
||||
条件 B: browser_snapshot 页面含"(选填)/(可选)"文本
|
||||
↓
|
||||
A 或 B 任一满足 + Toast 拦截 → Constraint Mismatch (FAIL #16)
|
||||
→ 不得静默补填继续,必须标记并记录
|
||||
→ 读取源码确认 → 按设计意图修复
|
||||
A 和 B 均不满足 → 普通 FAIL #12 (ENV-ADAPT) → 按提示补填继续
|
||||
```
|
||||
|
||||
### 通过标准
|
||||
|
||||
- 旅程中含约束关键词的步骤,与源码验证逻辑一致
|
||||
- 页面上含"选填/可选/必填"标记的字段,与前后端验证一致
|
||||
- 不一致项必须有修复动作,禁止仅记录不修复
|
||||
|
||||
---
|
||||
|
||||
## 自动校验执行流程(定义和验证通用)
|
||||
|
||||
```
|
||||
步骤 1:逐规则校验(R1 → R2 → R3 → R4 → R5)
|
||||
步骤 1:逐规则校验(R1 → R2 → R3 → R4 → R5 → R7)
|
||||
↓
|
||||
步骤 2:汇总结果
|
||||
├── 全部 PASS → 返回校验通过
|
||||
|
||||
@@ -31,7 +31,7 @@ Story 验证全部通过 ≠ 用户能正常使用系统。旅程模式弥补这
|
||||
1. 读取 PRD + Epic/Story 定义 + sprint-status.yaml
|
||||
↓
|
||||
2. 读取入口配置:
|
||||
- 小程序: app/mini-program/src/pages.json(第一个 page = 自然入口)
|
||||
- 小程序: 参见 ./reference/platform-uni-app.md §4(入口页面 = pages.json 第一个 page)
|
||||
- 管理后台: app/admin/src/app/ 路由结构(根 URL = 自然入口)
|
||||
↓
|
||||
3. 为每个 Epic 生成/重写 epic-N-journeys.md
|
||||
@@ -64,7 +64,7 @@ Story 验证全部通过 ≠ 用户能正常使用系统。旅程模式弥补这
|
||||
详细检测方法和自动修复策略见 **./journey-authenticity-rules.md**(统一规则集 R1-R5)。
|
||||
|
||||
**规则 1:起始页必须是自然入口**
|
||||
- 小程序:起始页 = 用户打开网址后看到的第一个页面(pages.json 的第一个 page)
|
||||
- 小程序:起始页 = 用户打开网址后看到的第一个页面(参见 ./reference/platform-uni-app.md §4 入口页面)
|
||||
- 管理后台:起始页 = 根 URL `/`(通常重定向到登录或 Dashboard)
|
||||
- ❌ `browser_navigate("/pages/login/index")` — 用户不会直接输入登录页 URL
|
||||
- ✅ `browser_navigate("http://mini-xxx.dev.d8d.fun/")` — 用户打开网址看到首页
|
||||
@@ -153,7 +153,9 @@ Story 验证全部通过 ≠ 用户能正常使用系统。旅程模式弥补这
|
||||
↓
|
||||
1.5 执行前校验与自动修复(⛔ 强制)
|
||||
**Read fully and follow: ./journey-authenticity-rules.md**
|
||||
- 按统一规则 R1-R5 校验所有旅程文件
|
||||
- 按统一规则 R1-R5 + R7 校验所有旅程文件
|
||||
- R7 约束一致性校验:对含"可选/可跳过/选填"关键词的步骤,grep 对应源码验证逻辑
|
||||
- 不一致 → 自动修复旅程定义(使其准确反映代码)→ 重新校验
|
||||
- 违规 → 自动修复旅程文件 → 重新校验(max 3x)
|
||||
- 环境适配:读取 pipeline-context.md 确定当前环境,按 R3 适配
|
||||
↓
|
||||
@@ -332,26 +334,9 @@ Story 验证全部通过 ≠ 用户能正常使用系统。旅程模式弥补这
|
||||
|
||||
### ⛔ 真实用户完成规则(Real User Completion Rule)
|
||||
|
||||
**核心原则**: 旅程中的每个用户步骤,必须是一个**仅使用鼠标/键盘/触摸屏的真实用户**能够完成的方式。如果真实用户无法完成,那就是 Bug 或未实现功能,必须修复。
|
||||
|
||||
**具体规则**:
|
||||
- `browser_type` 在可见 input 上失败 → Bug → AUTO-FIX(修复 input 组件的可操作性)
|
||||
- `browser_click` 在可见按钮上失败 → Bug → AUTO-FIX(修复按钮事件绑定或渲染)
|
||||
- 禁止用 `browser_evaluate` / `browser_run_code` 绕过不可交互的元素
|
||||
- "需要 evaluate 才能操作" 本身就是 Bug
|
||||
|
||||
**`browser_run_code` 仅允许场景**:
|
||||
- 检测布局溢出等非用户交互操作
|
||||
- 读取 localStorage / cookie 等数据状态验证
|
||||
|
||||
**如果 `browser_type` 在可见 input 上失败**:
|
||||
1. 标记该步骤 FAIL
|
||||
2. 分类: Bug(#6 前端 input 组件交互问题)
|
||||
3. ⛔ **诊断**: `browser_evaluate` 检查 DOM 结构和计算样式(见 auto-fix-engine.md FAIL #6 诊断子步骤)
|
||||
4. 根据诊断结果选择修复策略(CSS Bug / 可见性 Bug / 事件 Bug / 框架编译 Bug)
|
||||
5. 触发 AUTO-FIX: 修复具体根因
|
||||
6. 修复后重试 `browser_type`
|
||||
7. 仍失败 → 标记 journey-step-fail,继续下一步骤
|
||||
**Read fully and follow: ./auto-fix-engine.md 第 3 节"真实用户完成规则"**
|
||||
- 核心原则:browser_type/click 失败 = Bug → AUTO-FIX,禁止 evaluate/run_code 绕过
|
||||
- browser_type 失败后必须执行 FAIL #6 诊断子步骤(含完整修复流程)
|
||||
|
||||
### 执行模板(每条旅程 — 规定式,不得偏离)
|
||||
|
||||
@@ -367,26 +352,91 @@ Story 验证全部通过 ≠ 用户能正常使用系统。旅程模式弥补这
|
||||
2. 执行阶段(按用户视角步骤逐一执行 — ⛔ 只能用 Playwright):
|
||||
- browser_navigate → 起始页(自然入口)(⛔ 禁止用 curl 替代)
|
||||
- browser_snapshot → 验证起始页面(⛔ 禁止用 curl GET 替代)
|
||||
- 循环每个用户步骤:
|
||||
- ⛔ **零交互检查**: 如果 snapshot 显示页面有文字但无任何 button/input/a/form:
|
||||
- 标记步骤 FAIL(用户无法从此页面继续操作)
|
||||
- `browser_evaluate` 执行 FAIL #5 诊断(检查 interactiveCount / frameworkMounted — 见 auto-fix-engine.md)
|
||||
- 按诊断结果分流 → AUTO-FIX 或 AUTO-IMPLEMENT
|
||||
- ⛔ **起始页视觉验证**: browser_take_screenshot(journey-epic{N}-{M}-step0.png)+ analyze_image
|
||||
- 循环每个用户步骤(步骤编号 S = 1, 2, 3...):
|
||||
a. browser_snapshot → 找到目标元素
|
||||
b. browser_click / browser_type / browser_run_code → 执行操作(⛔ 禁止用 curl 替代)
|
||||
c. browser_wait_for → 等待响应/跳转
|
||||
d. browser_snapshot → 验证操作结果(⛔ 禁止用 curl GET 替代)
|
||||
e. 如涉及数据变化 → browser_evaluate 或 curl 验证数据(✅ 允许 curl 补充)
|
||||
e. ⛔ **视觉验证关卡**(⛔ 强制 — Read fully and follow: ./visual-verification.md)
|
||||
此关卡为独立步骤,不是子步骤。每个用户步骤完成后必须通过此关卡才能继续。
|
||||
1. browser_take_screenshot → 保存到 tests/e2e/screenshots/journey-epic{N}-{M}-step{S}.png
|
||||
2. analyze_image → 使用 visual-verification.md 中的标准提示词
|
||||
3. 结果记录到 pipeline-state.yaml 的 verification_detail(含 analyze_image_executed: true)
|
||||
4. 视觉验证 FAIL → 按分流规则处理(见 auto-fix-engine.md + iron-laws.md 检测即阻断表)
|
||||
4.5. ⛔ **源码 SVG data URI 扫描**(视觉验证关卡内 — 每次 snapshot 后强制执行):
|
||||
- Bash: `grep -rn "data:image/svg+xml" app/mini-program/src/`(扫描小程序源码目录)
|
||||
- 发现匹配 → svg_data_uri_check.found = FOUND → 记录文件列表
|
||||
- → 按检测即阻断表 "SVG data URI 充当图标/装饰" 条目处理
|
||||
- → AUTO-IMPLEMENT: 完整安装 Iconify + 替换所有 SVG data URI(参见 ./reference/platform-uni-app.md §1)
|
||||
5. ⛔ **视觉验证修复必须内联执行**(不得推迟到步骤 4):
|
||||
- 视觉 FAIL 后,必须在当前关卡内**立即**执行 AUTO-FIX/AUTO-IMPLEMENT
|
||||
- 修复后重测:重新 browser_take_screenshot + analyze_image 确认问题已修复
|
||||
- 修复失败 → 标记 journey-step-fail → 继续下一步骤
|
||||
- 步骤 4(修复阶段)仅处理功能断言 FAIL,不处理视觉验证 FAIL
|
||||
f. 如涉及数据变化 → browser_evaluate 或 curl 验证数据(✅ 允许 curl 补充)
|
||||
g. ⛔ **约束一致性检查**(每个用户步骤首次执行时 — R7 执行时强制检测):
|
||||
触发条件(操作被拦截 + 声明方说"可选"):
|
||||
- 操作后 Toast 出现且含"请上传/请填写/请选择/不能为空/不能为空"
|
||||
- 且满足以下任一:
|
||||
a. 旅程定义中该步骤描述含"可跳过/可选/选填/optional/skip/跳过/使用占位图"
|
||||
b. browser_snapshot 页面文本含"(选填)/(可选)"标记
|
||||
满足 → Constraint Mismatch (FAIL #16):
|
||||
- ❌ 不得静默补填继续
|
||||
- 记录到 pipeline-state.yaml 的 constraint_mismatches
|
||||
- 读取源码确认验证逻辑 → 按设计意图修复代码或旅程定义
|
||||
- 修复后重测该步骤
|
||||
不满足(仅 Toast 拦截但无"可选"声明)→ 普通 FAIL #12 → 按提示补填继续
|
||||
|
||||
3. 证据阶段(⛔ 强制,不得跳过):
|
||||
- browser_take_screenshot → 保存截图到 tests/e2e/screenshots/journey-epicN-M.png
|
||||
- browser_take_screenshot → 保存最终截图到 tests/e2e/screenshots/journey-epic{N}-{M}-final.png
|
||||
- browser_network_requests → 检查无 localhost API 调用
|
||||
|
||||
4. 修复阶段(⛔ 步骤 2-3 有任何 FAIL 时必须执行,不得跳过):
|
||||
4. 修复阶段(⛔ 以下任一条件满足时必须执行,不得跳过):
|
||||
|
||||
**Read fully and follow: ./auto-fix-engine.md**
|
||||
(包含: PASS/FAIL 判定标准 → FAIL 分类表 → 真实用户完成规则 → AUTO-FIX/IMPLEMENT/DATA-FIX 流程 → GATE-CHECK)
|
||||
|
||||
**触发条件(⛔ 明确化 — 以下任一即触发)**:
|
||||
- `steps_failed > 0`(功能断言失败)
|
||||
- `constraint_mismatches` 非空(R7 约束不一致检测到 — 即使 steps_passed == steps_executed)
|
||||
- 跨轮次累计的视觉问题 ≥ 1(emoji_as_icons / english_enum / broken_images 等 — 检测到即修复)
|
||||
- 前一次验证的 `pending_fixes` 中有未修复项(⛔ 即使是其他 EPIC 范围的问题,也必须在本轮修复 — 参见 auto-fix-engine.md "已知问题不豁免"规则)
|
||||
- `verification_detail` 中任一检查项为 FOUND 且非设计系统主观视觉评价(WARN)
|
||||
|
||||
**⛔ 禁止的"记录但不修复"模式**:
|
||||
- ❌ 将问题添加到 `pending_fixes` 但不执行实际修复动作
|
||||
- ❌ 标记"已记录"/"已知"作为修复结果("已记录"不是修复)
|
||||
- ❌ 以"不影响本次旅程流程"为由跳过修复
|
||||
- ❌ 以"V3遗留/历史问题"为由降级为 WARN
|
||||
- ❌ 以"非当前 EPIC/旅程范围"为由跳过 pending_fixes 中的问题(范围外 ≠ 豁免)
|
||||
|
||||
5. 记录阶段:
|
||||
- 更新 pipeline-state.yaml(含截图路径、coverage_frs、auto_fix 记录)
|
||||
- 更新 pipeline-state.yaml 的 user_journeys
|
||||
- 输出: "旅程 {name}: {PASS/FAIL} — {摘要}"
|
||||
|
||||
6. 分类提交推送阶段(⛔ 每条旅程完成后强制执行):
|
||||
- git status → 确认变更范围
|
||||
- 按以下分类顺序逐类提交(无变更则跳过):
|
||||
|
||||
| 顺序 | 类型 | 文件范围 | Commit Message 格式 |
|
||||
|------|------|---------|-------------------|
|
||||
| 1️⃣ | 基础设施/配置 | `package.json`、`vite.config.*`、`tailwind.config.*`、`postcss.config.*`、`tsconfig.json` | `chore: {描述}` |
|
||||
| 2️⃣ | 后端代码 | `api/src/` | `feat(api): {描述}` |
|
||||
| 3️⃣ | 小程序代码 | `app/mini-program/src/` | `feat(mini): {描述}` |
|
||||
| 4️⃣ | 管理后台代码 | `app/admin/src/` | `feat(admin): {描述}` |
|
||||
| 5️⃣ | 测试产物 | `tests/e2e/screenshots/`、`tests/e2e/fixtures/` | `test: journey screenshots` |
|
||||
| 6️⃣ | 流水线状态 | `_bmad-output/implementation-artifacts/pipeline-state.yaml`、`_bmad-output/implementation-artifacts/pipeline-report.md` | `ci(pipeline): update journey verification status` |
|
||||
|
||||
- 提交前 `git diff --stat` 确认变更范围
|
||||
- 排除 `.env`、`node_modules/`、`.next/`、`dist/`
|
||||
- 所有分类提交完成后: `git push origin main`
|
||||
- 推送失败 → 记录到 error_log,不中断
|
||||
```
|
||||
|
||||
### 结果记录格式
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
- pnpm workspace 初始化命令
|
||||
- uni-app 项目脚手架(`npx degit dcloudio/uni-preset-vue#vite-ts`)
|
||||
- Next.js 项目脚手架(`npx create-next-app@latest`)
|
||||
- Neon Pulse 设计令牌 CSS 变量
|
||||
- 项目设计令牌 CSS 变量(按 04-ui-ux-design.md 规范)
|
||||
- 初始管理员种子数据
|
||||
|
||||
3. **更新管道状态**
|
||||
|
||||
@@ -56,71 +56,13 @@
|
||||
grep -rn "const.*API_BASE.*=" app/mini-program/src/pages/ | grep -v "import" || echo "PASS: 无独立 API 常量"
|
||||
```
|
||||
|
||||
5. **图标方案:@egoist/tailwindcss-icons + Iconify(按项目类型分路径)**
|
||||
5. **图标方案**:**Read fully and follow: ./reference/platform-uni-app.md 第 1 节(⛔ 强制)**
|
||||
- 含 Admin (Next.js) 和 Mini-program (uni-app) 两条路径的完整安装配置
|
||||
- 含使用方式、TabBar 图标方案、禁止方案列表
|
||||
- **所有前端项目必须使用 Iconify 图标方案,禁止 emoji 充当功能图标**
|
||||
|
||||
**所有前端项目必须使用 Iconify 图标方案,禁止 emoji 充当功能图标。**
|
||||
|
||||
**A. Admin (Next.js) — 已有 Tailwind CSS v3**
|
||||
- 安装:`pnpm add -D @egoist/tailwindcss-icons @iconify-json/mdi @iconify-json/lucide`
|
||||
- 配置:在 `tailwind.config.ts` 的 `plugins` 数组中加入:
|
||||
```ts
|
||||
import { iconsPlugin, getIconCollections } from "@egoist/tailwindcss-icons"
|
||||
// plugins 中添加:
|
||||
iconsPlugin({ collections: getIconCollections(["mdi", "lucide"]) })
|
||||
```
|
||||
|
||||
**B. Mini-program (uni-app) — 无 Tailwind CSS,需完整安装**
|
||||
- 安装依赖:
|
||||
```bash
|
||||
pnpm add -D tailwindcss autoprefixer postcss
|
||||
pnpm add -D weapp-tailwindcss/vite
|
||||
pnpm add -D @egoist/tailwindcss-icons @iconify-json/mdi @iconify-json/lucide
|
||||
```
|
||||
- `vite.config.ts` 配置(⚠️ uvwt 必须在 uni() 之后):
|
||||
```ts
|
||||
import { UnifiedViteWeappTailwindcssPlugin as uvwt } from 'weapp-tailwindcss/vite'
|
||||
export default defineConfig({
|
||||
plugins: [uni(), uvwt()],
|
||||
css: {
|
||||
postcss: {
|
||||
plugins: [require('tailwindcss'), require('autoprefixer')],
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
- 创建 `tailwind.config.js`:
|
||||
```js
|
||||
const { iconsPlugin, getIconCollections } = require("@egoist/tailwindcss-icons")
|
||||
module.exports = {
|
||||
content: ['./src/**/*.{html,js,ts,jsx,tsx,vue}', '!./src/uni_modules/**/*'],
|
||||
plugins: [
|
||||
iconsPlugin({ collections: getIconCollections(["mdi", "lucide"]) }),
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
**通用规则**:
|
||||
- 使用方式:`<view class="i-mdi-home text-3xl text-red-600"></view>` 或 `<span class="i-lucide-search"></span>`
|
||||
- **TabBar 图标**:不使用 pages.json 的 iconPath(需维护 PNG 文件),改用自定义 TabBar 组件 + Iconify 类名
|
||||
- **禁止 emoji 充当功能图标**(不同平台显示不一致,如 🏠📱💬)
|
||||
- **可缺失图片**(头像等)使用 CSS 渐变色占位符(如 `background: linear-gradient(135deg, #ff5167, #7508a5)`),不引用不存在的文件路径
|
||||
- **图标预览**:https://icones.js.org 或 https://icon-sets.iconify.design/
|
||||
|
||||
6. **布局防溢出规则(⛔ 强制,小程序页面尤其重要)**
|
||||
|
||||
**问题背景**:小程序页面在真实移动设备上经常出现宽度/高度溢出视口、底部 TabBar 被遮挡的问题。
|
||||
|
||||
**规则**:
|
||||
- 页面根元素宽度不得超过 `100vw`(或 `100%`),禁止固定像素宽度
|
||||
- TabBar 页面可滚动内容高度 = `100vh - Header高度 - TabBar高度`(约 50px)
|
||||
- 固定定位(`position: fixed`)元素的 `bottom` 值必须考虑 TabBar 高度
|
||||
- 图片/卡片组件必须 `max-width: 100%` + `overflow: hidden`
|
||||
- 不允许出现水平滚动条(除非是刻意设计的横滑组件)
|
||||
|
||||
**检测命令**(E2E 测试中由步骤 8.5 自动执行):
|
||||
```js
|
||||
document.documentElement.scrollWidth > window.innerWidth // 水平溢出
|
||||
```
|
||||
6. **布局防溢出规则**:**参见 ./reference/platform-uni-app.md 第 5 节(⛔ 强制,小程序页面尤其重要)**
|
||||
- 含防溢出规则、TabBar 遮挡处理、布局溢出检测 JS 代码
|
||||
|
||||
#### 3B: Epic 0 特殊实现
|
||||
|
||||
@@ -136,7 +78,7 @@
|
||||
5. Prisma schema + 首次迁移(连接容器内置 PG)
|
||||
6. .env 配置(指向容器内置服务)
|
||||
7. 基础配置文件 (TypeScript, Tailwind, ESLint)
|
||||
8. Neon Pulse 设计令牌全局 CSS
|
||||
8. 项目设计令牌全局 CSS(按 04-ui-ux-design.md 规范)
|
||||
```
|
||||
|
||||
2. **容器环境连接信息**
|
||||
|
||||
399
.claude/skills/d8d-dev/reference/platform-uni-app.md
Normal file
399
.claude/skills/d8d-dev/reference/platform-uni-app.md
Normal file
@@ -0,0 +1,399 @@
|
||||
# Uni-app 微信小程序平台参考
|
||||
|
||||
> **⛔ 主流程文件统一引用此文件**:所有 uni-app/微信小程序的平台特定内容集中于此。
|
||||
> 主流程文件(auto-fix-engine / iron-laws / journey-mode / visual-verification / validation-mode / e2e-testing / phase-03-implement)中不再内联平台特定方案,改为引用本文件对应章节。
|
||||
|
||||
---
|
||||
|
||||
## 1. 图标方案(⛔ 强制)
|
||||
|
||||
**所有前端项目必须使用 Iconify 图标方案,禁止 emoji 充当功能图标。**
|
||||
|
||||
### A. Admin (Next.js) — 已有 Tailwind CSS v3
|
||||
|
||||
- 安装:`pnpm add -D @egoist/tailwindcss-icons @iconify-json/mdi @iconify-json/lucide`
|
||||
- 配置:在 `tailwind.config.ts` 的 `plugins` 数组中加入:
|
||||
```ts
|
||||
import { iconsPlugin, getIconCollections } from "@egoist/tailwindcss-icons"
|
||||
// plugins 中添加:
|
||||
iconsPlugin({ collections: getIconCollections(["mdi", "lucide"]) })
|
||||
```
|
||||
|
||||
### B. Mini-program (uni-app) — 无 Tailwind CSS,需完整安装
|
||||
|
||||
- 安装依赖:
|
||||
```bash
|
||||
pnpm add -D tailwindcss autoprefixer postcss
|
||||
pnpm add -D weapp-tailwindcss/vite
|
||||
pnpm add -D @egoist/tailwindcss-icons @iconify-json/mdi @iconify-json/lucide
|
||||
```
|
||||
- `vite.config.ts` 配置(⚠️ uvwt 必须在 uni() 之后):
|
||||
```ts
|
||||
import { UnifiedViteWeappTailwindcssPlugin as uvwt } from 'weapp-tailwindcss/vite'
|
||||
export default defineConfig({
|
||||
plugins: [uni(), uvwt()],
|
||||
css: {
|
||||
postcss: {
|
||||
plugins: [require('tailwindcss'), require('autoprefixer')],
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
- 创建 `tailwind.config.js`:
|
||||
```js
|
||||
const { iconsPlugin, getIconCollections } = require("@egoist/tailwindcss-icons")
|
||||
module.exports = {
|
||||
content: ['./src/**/*.{html,js,ts,jsx,tsx,vue}', '!./src/uni_modules/**/*'],
|
||||
plugins: [
|
||||
iconsPlugin({ collections: getIconCollections(["mdi", "lucide"]) }),
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### 通用规则
|
||||
|
||||
- 使用方式:`<view class="i-mdi-home text-3xl text-red-600"></view>` 或 `<span class="i-lucide-search"></span>`
|
||||
- **TabBar 图标**:不使用 pages.json 的 iconPath(需维护 PNG 文件),改用自定义 TabBar 组件 + Iconify 类名
|
||||
- **禁止 emoji 充当功能图标**(不同平台显示不一致,如 🏠📱💬)
|
||||
- **可缺失图片**(头像等)使用 CSS 渐变色占位符(如 `background: linear-gradient(135deg, #ff5167, #7508a5)`),不引用不存在的文件路径
|
||||
- **图标预览**:https://icones.js.org 或 https://icon-sets.iconify.design/
|
||||
|
||||
### ⛔ 禁止的替代方案(微信小程序不支持)
|
||||
|
||||
- ❌ SVG data URI / CSS background-image(微信小程序不支持内联 SVG)
|
||||
- ❌ Emoji 字符(不同平台渲染不一致)
|
||||
- ❌ Unicode 符号替换(不同平台显示不一致)
|
||||
- ❌ img 标签引用 SVG 文件(不符合 Iconify 规范)
|
||||
- ❌ 直接删除不替换任何图标
|
||||
|
||||
---
|
||||
|
||||
## 2. TabBar 方案
|
||||
|
||||
### 问题
|
||||
|
||||
- `pages.json` 的 `iconPath` 需要维护 PNG 文件,不同分辨率需要多套图片
|
||||
- 小程序 TabBar 在真实设备上经常被内容遮挡
|
||||
|
||||
### 方案
|
||||
|
||||
- 不使用 `pages.json` 的 `iconPath`,改用自定义 TabBar 组件 + Iconify 类名
|
||||
- TabBar 页面可滚动内容高度 = `100vh - Header高度 - TabBar高度`(约 50px)
|
||||
- 固定定位(`position: fixed`)元素的 `bottom` 值必须考虑 TabBar 高度
|
||||
|
||||
### 检测方法
|
||||
|
||||
```bash
|
||||
# pages.json iconPath 为空
|
||||
grep '"iconPath": ""' app/mini-program/src/pages.json
|
||||
```
|
||||
|
||||
### TabBar CSS 选择器(E2E 测试用)
|
||||
|
||||
```css
|
||||
.uni-tabbar, .tabbar, [class*="tabbar"], [class*="tab-bar"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 导航栏方案(⛔ 强制)
|
||||
|
||||
### 原则
|
||||
|
||||
- **uni-app 小程序项目统一使用 `navigationStyle: "custom"`**,禁止混合使用系统导航栏和自定义头部
|
||||
- 原因:系统导航栏样式不可控(无法实现 Neon Pulse 深色/玻璃态/霓虹渐变),且与自定义 header 同时显示会导致**双重导航栏 Bug**
|
||||
|
||||
### 检测方法(⛔ 每次视觉验证关卡必须执行)
|
||||
|
||||
```bash
|
||||
# 1. 提取 pages.json 中每个页面的导航栏配置
|
||||
cat app/mini-program/src/pages.json | python3 -c "
|
||||
import sys, json
|
||||
cfg = json.load(sys.stdin)
|
||||
pages = cfg.get('pages', [])
|
||||
global_style = cfg.get('globalStyle', {})
|
||||
for p in pages:
|
||||
path = p.get('path', '')
|
||||
style = p.get('style', {})
|
||||
nav_title = style.get('navigationBarTitleText', global_style.get('navigationBarTitleText', ''))
|
||||
nav_custom = style.get('navigationStyle', '') == 'custom'
|
||||
if nav_title and not nav_custom:
|
||||
print(f'DUAL_NAV_RISK: {path} title={nav_title} custom={nav_custom}')
|
||||
"
|
||||
|
||||
# 2. 对有风险的页面检查是否有自定义 fixed header
|
||||
# grep -l "position.*fixed\|class=\"header" app/mini-program/src/pages/{page-path}/*.vue
|
||||
# 发现匹配 → 双重导航栏 confirmed
|
||||
```
|
||||
|
||||
### 判定标准
|
||||
|
||||
| 条件 | 判定 |
|
||||
|------|------|
|
||||
| 页面有 `navigationBarTitleText`(非 custom)+ 页面源码有 `position:fixed` 的自定义 header | ❌ **双重导航栏 Bug** → AUTO-FIX |
|
||||
| 页面有 `navigationBarTitleText`(非 custom)+ 无自定义 header | ✅ 正常(使用系统导航栏) |
|
||||
| 页面 `navigationStyle: "custom"` + 有自定义 header | ✅ 正确(纯自定义) |
|
||||
| 页面 `navigationStyle: "custom"` + 无自定义 header | ⚠️ 无导航栏(可能设计意图) |
|
||||
|
||||
### AUTO-FIX 修复策略
|
||||
|
||||
1. `Edit` pages.json → 该页面 style 改为 `{ "navigationStyle": "custom" }`
|
||||
2. `Edit` 页面源码 → 自定义 header 添加 `padding-top: env(safe-area-inset-top, 20px)` 和 `height` 调整
|
||||
3. `Edit` 内容区 → `margin-top` 与新 header 高度一致(含 safe-area)
|
||||
4. 重测截图确认只有一层标题
|
||||
|
||||
---
|
||||
|
||||
## 4. 编译特性与已知问题
|
||||
|
||||
### 3.1 uni-input 编译后 height:0px
|
||||
|
||||
**问题**:uni-app `<input>` 编译为 `<uni-input>` 包裹 `<input>`,在 flex 容器中内部 input 可能 `height: 0px`,导致 `browser_type` 失败。
|
||||
|
||||
**诊断代码**:
|
||||
```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;
|
||||
}
|
||||
```
|
||||
|
||||
**分流**:
|
||||
- `innerInputs[0].height === '0px'` → **框架编译 CSS Bug** → AUTO-FIX: 给 input 或包裹容器添加显式 `height: 44px`
|
||||
- `opacity === '0'` 或 `pointerEvents === 'none'` → **可见性 Bug** → AUTO-FIX
|
||||
- 诊断结果正常但 `browser_type` 仍失败 → **Playwright 适配** → 允许 `browser_run_code` + `force: true`(仅限此场景,须记录适配原因)
|
||||
|
||||
### 3.2 uni-app API 约定
|
||||
|
||||
- `uni.showToast` — 提示消息(注意:即时使用数据不应仅通过 Toast 传递)
|
||||
- `uni.chooseImage` — 图片选择(H5 模式下触发 file chooser)
|
||||
- `uni.request` — 网络请求
|
||||
- `uni.navigateTo` / `uni.switchTab` — 页面导航
|
||||
|
||||
### 3.3 即时使用数据与 uni.showToast
|
||||
|
||||
如果 `uni.showToast` 传递了下一步骤立即需要的数据(如验证码),必须同时添加:
|
||||
```javascript
|
||||
console.log('[DEV] 验证码:', res.code);
|
||||
if (res.code) { code.value = res.code; }
|
||||
uni.showToast({ title: "验证码已发送", icon: "success" });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 测试配置
|
||||
|
||||
### 视口尺寸
|
||||
|
||||
| 平台 | 视口 |
|
||||
|------|------|
|
||||
| 小程序 (uni-app H5) | 375 × 812 |
|
||||
| 管理后台 (Next.js) | 1440 × 900 |
|
||||
|
||||
### 入口页面
|
||||
|
||||
| 平台 | 入口 |
|
||||
|------|------|
|
||||
| 小程序 | `pages.json` 第一个 page(通常为首页) |
|
||||
| 管理后台 | 根 URL `/`(通常重定向到登录或 Dashboard) |
|
||||
|
||||
### 入口 URL 格式
|
||||
|
||||
- 小程序:`http://mini-{project}.dev.d8d.fun/`(首页,非直接 navigate 到 login)
|
||||
- 管理后台:`http://admin-{project}.dev.d8d.fun/`
|
||||
|
||||
---
|
||||
|
||||
## 5. 布局规则
|
||||
|
||||
### 5.1 防溢出规则(⛔ 强制,小程序页面尤其重要)
|
||||
|
||||
**问题背景**:小程序页面在真实移动设备上经常出现宽度/高度溢出视口、底部 TabBar 被遮挡的问题。
|
||||
|
||||
**规则**:
|
||||
- 页面根元素宽度不得超过 `100vw`(或 `100%`),禁止固定像素宽度
|
||||
- TabBar 页面可滚动内容高度 = `100vh - Header高度 - TabBar高度`(约 50px)
|
||||
- 固定定位(`position: fixed`)元素的 `bottom` 值必须考虑 TabBar 高度
|
||||
- 图片/卡片组件必须 `max-width: 100%` + `overflow: hidden`
|
||||
- 不允许出现水平滚动条(除非是刻意设计的横滑组件)
|
||||
|
||||
### 5.2 布局溢出检测(E2E 步骤 8.5)
|
||||
|
||||
```javascript
|
||||
function checkLayoutOverflow() {
|
||||
const html = document.documentElement;
|
||||
const tabBar = document.querySelector('.uni-tabbar, .tabbar, [class*="tabbar"], [class*="tab-bar"]');
|
||||
return {
|
||||
horizontal_overflow: html.scrollWidth > window.innerWidth,
|
||||
document_width: html.scrollWidth,
|
||||
viewport_width: window.innerWidth,
|
||||
overflow_diff: html.scrollWidth - window.innerWidth,
|
||||
tabbar_found: tabBar !== null,
|
||||
tabbar_visible: tabBar ? (tabBar.getBoundingClientRect().bottom <= window.innerHeight) : null,
|
||||
tabbar_rect: tabBar ? tabBar.getBoundingClientRect() : null,
|
||||
page_height: html.scrollHeight,
|
||||
viewport_height: window.innerHeight,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 源码检测命令
|
||||
|
||||
### 图标方案合规检查
|
||||
|
||||
```bash
|
||||
# A. pages.json tabBar iconPath 是否为空
|
||||
if find app/ -name "pages.json" -not -path "*/node_modules/*" -print0 | xargs -0 grep -l '"iconPath": ""' 2>/dev/null | head -1; then
|
||||
echo "tabbar_iconpath_empty=true"
|
||||
fi
|
||||
|
||||
# B. static 目录存在性
|
||||
STATIC_EXISTS=false
|
||||
for dir in app/*/static app/*/src/static; do [ -d "$dir" ] && STATIC_EXISTS=true; done
|
||||
echo "static_dir_exists=$STATIC_EXISTS"
|
||||
|
||||
# C. Tailwind + Iconify 配置(如果 mini-program 存在)
|
||||
if [ -d "app/mini-program" ]; then
|
||||
HAS_TW=$(grep -rl "tailwindcss" app/mini-program/ --include="*.js" --include="*.ts" --include="*.json" | grep -v node_modules | wc -l)
|
||||
HAS_ICONIFY=$(grep -rl "iconify\|tailwindcss-icons" app/mini-program/ --include="*.js" --include="*.ts" --include="*.json" | grep -v node_modules | wc -l)
|
||||
echo "mini_tw=$HAS_TW mini_iconify=$HAS_ICONIFY"
|
||||
fi
|
||||
```
|
||||
|
||||
### Emoji 充当图标检查
|
||||
|
||||
```bash
|
||||
# 搜索模板中 icon/btn 相关元素内含 emoji 的情况
|
||||
EMOJI_ICONS=$(grep -rn 'class="[^"]*icon[^"]*"[^>]*>[🔍🔔🏠📱💬👤⚙️📝➕❤️⭐📢✅❌]' app/*/src/ --include="*.vue" --include="*.tsx" | grep -v node_modules)
|
||||
EMOJI_COUNT=$(echo "$EMOJI_ICONS" | grep -c ":" || echo 0)
|
||||
echo "emoji_as_icons=$EMOJI_COUNT"
|
||||
[ "$EMOJI_COUNT" -gt 0 ] && echo "EMOJI_FILES:" && echo "$EMOJI_ICONS"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 下拉刷新方案(⛔ 强制)
|
||||
|
||||
### 问题
|
||||
|
||||
uni-app 中有两种下拉刷新实现:
|
||||
- **页面级**:`pages.json` 配置 `enablePullDownRefresh: true` + 组件内 `onPullDownRefresh()` 生命周期 → **整个页面下拉**,体验一致
|
||||
- **列表级**:`scroll-view` 的 `refresher-enabled` + `@refresherrefresh` → **仅列表区域下拉**,header 固定不动,体验割裂
|
||||
|
||||
Tab 页面间下拉刷新方式不一致(有的用列表级、有的没有),用户体验混乱。应统一使用**页面级 `onPullDownRefresh`**。
|
||||
|
||||
### 检测方法(⛔ 每次视觉验证关卡必须执行)
|
||||
|
||||
```bash
|
||||
# 步骤 1: 从 pages.json 提取 tabBar 页面路径
|
||||
cat app/mini-program/src/pages.json | python3 -c "
|
||||
import sys, json
|
||||
cfg = json.load(sys.stdin)
|
||||
tab_list = cfg.get('tabBar', {}).get('list', [])
|
||||
for t in tab_list:
|
||||
print(t.get('pagePath', ''))
|
||||
"
|
||||
|
||||
# 步骤 2: 对每个 tabBar 页面检查刷新方式
|
||||
# LIST_LEVEL: scroll-view refresher
|
||||
grep -l "refresher-enabled" app/mini-program/src/pages/{home,square,messages,profile}/index.vue 2>/dev/null
|
||||
|
||||
# PAGE_LEVEL: onPullDownRefresh
|
||||
grep -l "onPullDownRefresh" app/mini-program/src/pages/{home,square,messages,profile}/index.vue 2>/dev/null
|
||||
|
||||
# NONE: 无任何刷新
|
||||
# 不在以上两个结果中的 tabBar 页面
|
||||
```
|
||||
|
||||
### 判定标准
|
||||
|
||||
| 条件 | 标记 |
|
||||
|------|------|
|
||||
| 使用 `scroll-view refresher-enabled` | LIST_LEVEL |
|
||||
| 使用 `onPullDownRefresh`(from `@dcloudio/uni-app`) | PAGE_LEVEL |
|
||||
| 无任何下拉刷新实现 | NONE |
|
||||
|
||||
| 组合情况 | 判定 |
|
||||
|----------|------|
|
||||
| 所有 Tab 页面均为 PAGE_LEVEL | ✅ CONSISTENT |
|
||||
| 所有 Tab 页面均为 NONE(内容简单的项目) | ✅ CONSISTENT |
|
||||
| 混用 LIST_LEVEL / PAGE_LEVEL / NONE | ❌ **INCONSISTENT → AUTO-FIX #19** |
|
||||
| 存在 LIST_LEVEL(即使只有一处) | ❌ **INCONSISTENT → AUTO-FIX #19**(应全部改为 PAGE_LEVEL) |
|
||||
|
||||
### AUTO-FIX #19 修复策略
|
||||
|
||||
**目标**:所有内容 Tab 页面统一使用页面级 `onPullDownRefresh`。
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. **pages.json** → `globalStyle` 添加 `"enablePullDownRefresh": true`
|
||||
|
||||
2. **对 LIST_LEVEL 页面**(scroll-view refresher):
|
||||
- 移除 `scroll-view` 上的 `refresher-enabled` / `:refresher-triggered` / `@refresherrefresh` 属性
|
||||
- 移除 `const refreshing = ref(false)` 和 `function onRefresh()`
|
||||
- 添加 `import { onPullDownRefresh } from "@dcloudio/uni-app"`
|
||||
- 添加 `onPullDownRefresh(() => { page.value = 1; fetchEvents(); })`
|
||||
- 在数据加载函数的 `finally` 块中添加 `uni.stopPullDownRefresh()`
|
||||
|
||||
3. **对 NONE 页面**(无刷新的内容 Tab 页面):
|
||||
- 添加 `import { onPullDownRefresh } from "@dcloudio/uni-app"`
|
||||
- 添加 `onPullDownRefresh(() => { /* 对应数据刷新逻辑 */ })`
|
||||
- 数据加载完成后调用 `uni.stopPullDownRefresh()`
|
||||
|
||||
4. 重测 → 所有 Tab 页面整页下拉刷新效果一致
|
||||
|
||||
### 修复模板
|
||||
|
||||
**LIST_LEVEL → PAGE_LEVEL 改造(首页示例)**:
|
||||
|
||||
```vue
|
||||
<!-- 改前 scroll-view: -->
|
||||
<scroll-view class="content" scroll-y @scrolltolower="loadMore"
|
||||
refresher-enabled :refresher-triggered="refreshing" @refresherrefresh="onRefresh">
|
||||
|
||||
<!-- 改后 scroll-view(移除 refresher 属性): -->
|
||||
<scroll-view class="content" scroll-y @scrolltolower="loadMore">
|
||||
```
|
||||
|
||||
```typescript
|
||||
// 改前 script:
|
||||
const refreshing = ref(false);
|
||||
function onRefresh() {
|
||||
refreshing.value = true;
|
||||
page.value = 1;
|
||||
fetchEvents();
|
||||
}
|
||||
|
||||
// 改后 script:
|
||||
import { onPullDownRefresh } from "@dcloudio/uni-app";
|
||||
// 移除 refreshing ref 和 onRefresh 函数
|
||||
onPullDownRefresh(() => {
|
||||
page.value = 1;
|
||||
fetchEvents();
|
||||
});
|
||||
// fetchEvents 的 finally 中: uni.stopPullDownRefresh();
|
||||
```
|
||||
|
||||
```typescript
|
||||
// fetchEvents finally 块添加:
|
||||
} finally {
|
||||
loading.value = false;
|
||||
loadingMore.value = false;
|
||||
uni.stopPullDownRefresh(); // ← 新增
|
||||
}
|
||||
```
|
||||
@@ -177,7 +177,7 @@ CREATE-SPEC → IMPLEMENT → TEST → REVIEW → MARK-DONE
|
||||
6. browser_snapshot → 验证操作结果(数据已创建/更新/删除)
|
||||
7. browser_take_screenshot → 保存截图到 tests/e2e/screenshots/story-x-y-admin.png(强制)
|
||||
8. analyze_image(对截图文件路径调用)→ AI 视觉验证:
|
||||
- Neon Pulse 设计规范:深色背景(#000)、霓虹色按钮、玻璃态卡片、无1px边框
|
||||
- 项目设计系统(从 _bmad-output/04-ui-ux-design.md 提取)
|
||||
- 功能完整性:表单字段齐全、按钮可操作、数据正确渲染
|
||||
- 布局合理性:响应式排列、无重叠溢出、内容可读
|
||||
如果 analyze_image 发现问题 → 记录到验证报告,标记为需修复
|
||||
@@ -194,7 +194,7 @@ CREATE-SPEC → IMPLEMENT → TEST → REVIEW → MARK-DONE
|
||||
6. browser_snapshot → 验证操作结果
|
||||
7. browser_take_screenshot → 保存截图到 tests/e2e/screenshots/story-x-y-mini.png(强制)
|
||||
8. analyze_image(对截图文件路径调用)→ AI 视觉验证:
|
||||
- Neon Pulse 设计规范一致性
|
||||
- 项目设计系统一致性(从 04-ui-ux-design.md 提取)
|
||||
- 移动端布局合理性(375 宽度下无溢出)
|
||||
- 交互元素可操作(按钮尺寸、间距合理)
|
||||
如果 analyze_image 发现问题 → 记录到验证报告,标记为需修复
|
||||
@@ -248,7 +248,7 @@ CREATE-SPEC → IMPLEMENT → TEST → REVIEW → MARK-DONE
|
||||
#### 教训 3: analyze_image 不是可选项
|
||||
- **发生了什么**:35 个 Story 的验证中完全没有使用 analyze_image
|
||||
- **根本原因**:文档中 analyze_image 没有强调为强制步骤
|
||||
- **修复要求**:每个前端 Story 必须调用 analyze_image 验证 Neon Pulse 设计规范
|
||||
- **修复要求**:每个前端 Story 必须调用 analyze_image 验证项目设计系统规范
|
||||
|
||||
#### 教训 4: 截图是验证的证据,不是装饰
|
||||
- **发生了什么**:35 个 Story 只有 2 个截图文件
|
||||
|
||||
@@ -103,5 +103,5 @@ AI 自动执行 d8d-dev
|
||||
- [ ] pipeline-report.md 已生成
|
||||
- [ ] 数据库迁移可重现(`npx prisma migrate reset` 成功)
|
||||
- [ ] 所有开发服务器无错误启动
|
||||
- [ ] Neon Pulse 设计令牌在两端一致应用
|
||||
- [ ] 项目设计令牌在两端一致应用(按 04-ui-ux-design.md)
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ Phase 0V: 批量创建 Task + 读取 Story 规格文档
|
||||
│ ├── browser_type + browser_click(交互操作) │
|
||||
│ ├── browser_wait_for + browser_snapshot(验证) │
|
||||
│ ├── browser_take_screenshot(保存证据) │
|
||||
│ └── analyze_image(AI 视觉验证 Neon Pulse) │
|
||||
│ └── analyze_image(AI 视觉验证项目设计系统) │
|
||||
│ 4.5. GATE-CHECK(⛔ 必须全部通过才能继续) │
|
||||
│ 检查项: │
|
||||
│ - layout_check.horizontal_overflow === false │
|
||||
@@ -262,36 +262,14 @@ echo "localhost_fallback=$LOCALHOST_COUNT inline_api=$INLINE_COUNT"
|
||||
- 全部为 0 → **PASS**
|
||||
|
||||
**检查 2: 图标方案合规(小程序/移动端 Story 强制)**
|
||||
```bash
|
||||
# A. pages.json tabBar iconPath 是否为空
|
||||
if find app/ -name "pages.json" -not -path "*/node_modules/*" -print0 | xargs -0 grep -l '"iconPath": ""' 2>/dev/null | head -1; then
|
||||
echo "tabbar_iconpath_empty=true"
|
||||
fi
|
||||
# B. static 目录存在性
|
||||
STATIC_EXISTS=false
|
||||
for dir in app/*/static app/*/src/static; do [ -d "$dir" ] && STATIC_EXISTS=true; done
|
||||
echo "static_dir_exists=$STATIC_EXISTS"
|
||||
# C. Tailwind + Iconify 配置(如果 mini-program 存在)
|
||||
if [ -d "app/mini-program" ]; then
|
||||
HAS_TW=$(grep -rl "tailwindcss" app/mini-program/ --include="*.js" --include="*.ts" --include="*.json" | grep -v node_modules | wc -l)
|
||||
HAS_ICONIFY=$(grep -rl "iconify\|tailwindcss-icons" app/mini-program/ --include="*.js" --include="*.ts" --include="*.json" | grep -v node_modules | wc -l)
|
||||
echo "mini_tw=$HAS_TW mini_iconify=$HAS_ICONIFY"
|
||||
fi
|
||||
```
|
||||
> 完整检测命令和判定规则见 **./reference/platform-uni-app.md 第 6 节**。
|
||||
|
||||
**判定**:
|
||||
- `tabbar_iconpath_empty=true` → **FAIL (未实现)** → AUTO-IMPLEMENT
|
||||
- `STATIC_EXISTS=false` 且有 tabBar → **FAIL (未实现)** → AUTO-IMPLEMENT
|
||||
- mini-program 缺 Tailwind/Iconify → **WARN (未实现)** → AUTO-IMPLEMENT
|
||||
|
||||
**检查 3: Emoji 充当功能图标(所有前端强制)**
|
||||
```bash
|
||||
# 搜索模板中 icon/btn 相关元素内含 emoji 的情况
|
||||
EMOJI_ICONS=$(grep -rn 'class="[^"]*icon[^"]*"[^>]*>[🔍🔔🏠📱💬👤⚙️📝➕❤️⭐📢✅❌]' app/*/src/ --include="*.vue" --include="*.tsx" | grep -v node_modules)
|
||||
EMOJI_COUNT=$(echo "$EMOJI_ICONS" | grep -c ":" || echo 0)
|
||||
echo "emoji_as_icons=$EMOJI_COUNT"
|
||||
[ "$EMOJI_COUNT" -gt 0 ] && echo "EMOJI_FILES:" && echo "$EMOJI_ICONS"
|
||||
```
|
||||
> 完整检测命令和判定规则见 **./reference/platform-uni-app.md 第 6 节**。
|
||||
|
||||
**判定**:
|
||||
- `EMOJI_COUNT > 0` → **FAIL (未实现)** → AUTO-IMPLEMENT
|
||||
@@ -325,6 +303,44 @@ echo "placeholder_functions=$PLACEHOLDER_COUNT"
|
||||
- `PLACEHOLDER_COUNT > 0` → **FAIL (未实现)** → AUTO-IMPLEMENT: 实现该功能
|
||||
- `PLACEHOLDER_COUNT === 0` → **PASS**
|
||||
|
||||
**检查 5: 零交互元素检测(Story 涉及前端页面时浏览器端检查)**
|
||||
|
||||
在 E2E 验证步骤(步骤 4)中,`browser_navigate` 到目标页面后,`browser_snapshot` 如显示有内容但无交互元素(button/input/a/form),执行:
|
||||
|
||||
```javascript
|
||||
// browser_evaluate 检查
|
||||
() => {
|
||||
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]');
|
||||
return { interactiveCount: interactive.length, hasContent, frameworkMounted: hasNextRoot || hasReactRoot || hasVueApp };
|
||||
}
|
||||
```
|
||||
|
||||
**判定**:
|
||||
- `interactiveCount === 0` 且 `hasContent === true` → **FAIL** → 触发 FAIL #5 诊断子步骤(见 auto-fix-engine.md)
|
||||
- `interactiveCount > 0` → **PASS** → 继续正常 E2E 流程
|
||||
|
||||
**检查 6: UI 硬编码英文枚举值(所有前端 Story 强制)**
|
||||
|
||||
在 E2E 验证步骤(步骤 4)中,`browser_navigate` 到任何前端页面后,执行:
|
||||
|
||||
```javascript
|
||||
// browser_evaluate 检查页面可见文本是否包含英文枚举值
|
||||
() => {
|
||||
const text = document.body.innerText;
|
||||
const enumPatterns = /\b(APPROVED|REJECTED|PENDING|PAID|CANCELLED|REFUNDED|DRAFT|ONGOING|COMPLETED|AVAILABLE|FROZEN|WITHDRAWN)\b/g;
|
||||
const matches = text.match(enumPatterns);
|
||||
return { hasEnglishEnum: matches !== null, matches: matches || [] };
|
||||
}
|
||||
```
|
||||
|
||||
**判定**:
|
||||
- `hasEnglishEnum === true` → **FAIL (#15)** → AUTO-FIX(添加中文映射函数)
|
||||
- `hasEnglishEnum === false` → **PASS**
|
||||
|
||||
#### 步骤 4: E2E 交互验证(Story 涉及前端页面时)
|
||||
- **必须使用完整 8 步 Playwright 测试序列**(见下方"Playwright MCP 测试序列模式")
|
||||
- 按 Story 规格文档的 V2 Validation Tasks 逐项验证
|
||||
@@ -332,18 +348,23 @@ echo "placeholder_functions=$PLACEHOLDER_COUNT"
|
||||
- 管理后台:必须填写表单、点击按钮、验证提交结果
|
||||
- 小程序:必须模拟用户交互流程(登录/浏览/报名等)
|
||||
- 切换前端目标时必须 `browser_resize` 到正确视口
|
||||
- **⛔ 视觉验证关卡**(⛔ 强制 — Read fully and follow: ./visual-verification.md):
|
||||
- 每个关键操作后必须通过视觉验证关卡(独立步骤,不可跳过)
|
||||
- 关卡 = browser_take_screenshot + analyze_image(使用 visual-verification.md 标准提示词)
|
||||
- 关卡未通过 → 按分流规则处理,不得进入下一个操作
|
||||
|
||||
#### 步骤 5: 截图保存(Story 涉及前端页面时)
|
||||
- **强制保存**,不是可选
|
||||
- 路径:`tests/e2e/screenshots/story-x-y.png`
|
||||
#### 步骤 5: 最终截图保存(Story 涉及前端页面时)
|
||||
- 保存最终状态截图到 `tests/e2e/screenshots/story-x-y.png`
|
||||
- 管理后台和小程序分别截图时使用不同后缀:
|
||||
- `story-x-y-admin.png`(管理后台页面)
|
||||
- `story-x-y-mini.png`(小程序页面)
|
||||
- 此步骤保留作为最终证据归档(中间步骤截图已在步骤 4 中完成)
|
||||
|
||||
#### 步骤 6: AI 视觉验证(Story 涉及前端页面时)
|
||||
- **强制调用 `analyze_image` 验证截图**
|
||||
- 验证维度:Neon Pulse 设计规范 / 功能完整性 / 布局合理性
|
||||
- **不调用 analyze_image 等于未完成该 Story 的验证**
|
||||
- **⛔ 详细规则和标准提示词见 ./visual-verification.md**
|
||||
- 对最终截图强制调用 `analyze_image`
|
||||
- **不调用 analyze_image 等于未完成该 Story 的验证**(GATE-CHECK 会验证 `analyze_image_called: true`)
|
||||
- 注:中间步骤的视觉验证已在步骤 4 逐步完成(视觉验证关卡),此步骤是最终确认
|
||||
|
||||
#### 步骤 6.5: GATE-CHECK(⛔ 所有前端 Story 必须通过才能继续)
|
||||
|
||||
|
||||
314
.claude/skills/d8d-dev/visual-verification.md
Normal file
314
.claude/skills/d8d-dev/visual-verification.md
Normal file
@@ -0,0 +1,314 @@
|
||||
# 视觉验证共享模块(Visual Verification Module)
|
||||
|
||||
> **⛔ 所有模式共享**:旅程验证模式、回归验证模式、E2E 测试、Phase 4 测试均引用此文件。
|
||||
>
|
||||
> **被引用位置**:
|
||||
> - `journey-mode.md` 执行模板步骤 2e
|
||||
> - `validation-mode.md` Story 验证流程
|
||||
> - `e2e-testing.md` 步骤 8
|
||||
> - `phase-04-test.md` 测试流程
|
||||
|
||||
---
|
||||
|
||||
## 核心原则
|
||||
|
||||
**截图 ≠ 视觉验证**。`browser_take_screenshot` 只保存像素证据,`analyze_image` 才是检查设计合规性的动作。两者必须配对执行。
|
||||
|
||||
**任何模式在完成前端页面验证时,必须对每个截图执行 `analyze_image` AI 视觉分析。**
|
||||
|
||||
---
|
||||
|
||||
## 三层防护机制
|
||||
|
||||
### 第一层:关卡机制(结构保障)
|
||||
|
||||
视觉验证是**独立关卡**,不是循环内的子步骤。每个用户步骤/验证步骤完成后,必须经过视觉验证关卡才能继续。
|
||||
|
||||
```
|
||||
每个步骤的执行流程:
|
||||
a. browser_snapshot → 找到目标元素
|
||||
b. browser_click / browser_type → 执行操作
|
||||
c. browser_wait_for → 等待响应
|
||||
d. browser_snapshot → 验证操作结果
|
||||
↓
|
||||
⛔ 视觉验证关卡(独立,不可跳过):
|
||||
1. browser_take_screenshot → 保存截图
|
||||
2. analyze_image → AI 视觉分析(使用下方标准提示词)
|
||||
3. 分析结果记录到 verification_detail
|
||||
4. 发现问题 → 按分流规则处理
|
||||
5. 关卡未通过 → 不得进入下一个用户步骤
|
||||
```
|
||||
|
||||
### 第二层:GATE-CHECK 验证(闭环保障)
|
||||
|
||||
`auto-fix-engine.md` GATE-CHECK 增加:
|
||||
|
||||
```
|
||||
├── ⛔ analyze_image 已执行(所有截图都有对应视觉分析结果)
|
||||
│ 验证方法:pipeline-state.yaml 中 analyze_image_executed === true
|
||||
│ 未执行 → 不得标记完成,必须回退执行视觉验证
|
||||
```
|
||||
|
||||
### 第三层:执行后自检清单(审计保障)
|
||||
|
||||
每个 Story/旅程验证完成后强制执行:
|
||||
|
||||
```
|
||||
执行后自检清单(⛔ 强制):
|
||||
1. ✅ browser_take_screenshot 保存了截图文件(路径含步骤编号)
|
||||
2. ✅ analyze_image 对截图执行了视觉分析(结果已记录)
|
||||
3. ✅ verification_detail.design_check 存在且值为 PASS 或 FAIL(不能为 null)
|
||||
4. ✅ verification_detail.icons_check.emoji_as_icons 已检查
|
||||
5. ✅ 发现 FAIL 后执行了分类判定和修复流程
|
||||
|
||||
任一为"否" → Story 不得标记为 verified/done。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## analyze_image 标准提示词
|
||||
|
||||
每个截图的 AI 视觉分析使用以下提示词:
|
||||
|
||||
```
|
||||
详细分析此移动端/桌面端页面截图,检查以下维度:
|
||||
|
||||
1. **设计规范**(从项目 _bmad-output/04-ui-ux-design.md 提取):
|
||||
- {执行时:Read _bmad-output/04-ui-ux-design.md → 提取 🎨 设计系统节的色彩/卡片/边框/渐变/阴影规则 → 填入具体值}
|
||||
- 回退:文件不存在 → grep 项目 CSS :root 变量 → 仍无 → 仅检查下方 2-4 维度
|
||||
|
||||
2. **图标/图片完整性**:
|
||||
- TabBar 图标可见且有实际图标(非纯文本)
|
||||
- 功能图标使用图标库(非 emoji)
|
||||
- 无 broken image(加载失败的图片占位符)
|
||||
- 无 emoji 充当功能图标
|
||||
|
||||
3. **英文枚举检测**:
|
||||
- 页面可见文本中无英文大写枚举值(如 APPROVED/REJECTED/PENDING/PAID 等)
|
||||
|
||||
3.5. **SVG data URI 源码扫描**(⛔ 强制 — 每次视觉验证关卡必须执行):
|
||||
- `grep -rn "data:image/svg+xml" app/mini-program/src/`(或当前小程序源码目录)
|
||||
- 发现任何匹配 → 标记 svg_data_uri_check.found = FOUND → 记录文件列表
|
||||
- → 按检测即阻断表 "SVG data URI 充当图标/装饰" 条目处理(AUTO-INSTALL Iconify)
|
||||
|
||||
3.6. **双重导航栏源码扫描**(⛔ 强制 — 每次视觉验证关卡必须执行):
|
||||
- 步骤 1: 解析 pages.json,提取每个页面的 `navigationBarTitleText` 和 `navigationStyle`
|
||||
:
|
||||
- 步骤 2: 对每个 DUAL_NAV_RISK 页面,检查源码是否有 `position:fixed` 的自定义 header:
|
||||
](step 2)
|
||||
- 发现匹配 → 标记 dual_nav_check.found = FOUND → 记录文件列表
|
||||
- → 按检测即阻断表 "双重导航栏" 条目处理(AUTO-FIX #18,参见 ./reference/platform-uni-app.md §3)
|
||||
|
||||
)
|
||||
|
||||
-
|
||||
4. **布局质量**(⛔ 逐一检查以下每一项,不得跳过): ```bash
|
||||
cat app/mini-program/src/pages.json | python3 -c "
|
||||
import sys, json
|
||||
cfg = json.load(sys.stdin)
|
||||
pages = cfg.get('pages', [])
|
||||
global_style = cfg.get('globalStyle', {})
|
||||
for p in pages:
|
||||
path = p.get('path', '')
|
||||
style = p.get('style', {})
|
||||
nav_title = style.get('navigationBarTitleText', global_style.get('navigationBarTitleText', ''))
|
||||
nav_custom = style.get('navigationStyle', '') == 'custom'
|
||||
if nav_title and not nav_custom:
|
||||
print(f'DUAL_NAV_RISK: {path} title={nav_title} custom={nav_custom}')
|
||||
"
|
||||
```
|
||||
- 步骤 2: 对每个 DUAL_NAV_RISK 页面,检查源码是否有 `position:fixed` 的自定义 header:
|
||||
```bash
|
||||
grep -l "position.*fixed" app/mini-program/src/pages/{page-path}/*.vue
|
||||
# 或
|
||||
grep -l "class=\"header" app/mini-program/src/pages/{page-path}/*.vue
|
||||
```
|
||||
- 发现匹配 → 标记 dual_nav_check.found = FOUND → 记录文件列表
|
||||
- → 按检测即阻断表 "双重导航栏" 条目处理(AUTO-FIX #18,参见 ./reference/platform-uni-app.md §3)
|
||||
|
||||
⛔ **源码扫描优先规则**(适用于所有源码扫描检测项:dual_nav、svg_data_uri、emoji 等):
|
||||
- 当源码扫描检测到 FOUND → **必须标记为 FOUND**,按对应修复流程处理
|
||||
- analyze_image 只能**确认**问题,不能**否认**源码扫描结果
|
||||
- 当源码扫描说有问题但 analyze_image 说没问题 → **以源码扫描为准**,执行修复
|
||||
- 理由:源码扫描是确定性检测(grep 文件内容),analyze_image 是概率性检测(AI 视觉分析可能误判)
|
||||
- ❌ 禁止以 analyze_image 结果覆盖源码扫描的 FOUND 结论
|
||||
|
||||
3.7. **下拉刷新一致性源码扫描**(⛔ 强制 — 每次视觉验证关卡必须执行,仅 uni-app 项目):
|
||||
- 步骤 1: 从 pages.json 提取 tabBar 页面路径列表
|
||||
- 步骤 2: 对每个 tabBar 页面源码检查刷新方式:
|
||||
- `refresher-enabled` → LIST_LEVEL
|
||||
- `onPullDownRefresh` → PAGE_LEVEL
|
||||
- 无上述代码 → NONE
|
||||
- 步骤 3: tabBar 页面间刷新方式不一致(混用 LIST_LEVEL/PAGE_LEVEL/NONE,或存在 LIST_LEVEL)
|
||||
→ 标记 refresh_check.found = INCONSISTENT → 记录各页面刷新方式
|
||||
- → 按检测即阻断表 "下拉刷新方式不一致" 条目处理(AUTO-FIX #19,参见 ./reference/platform-uni-app.md §7)
|
||||
|
||||
4. **布局质量**(⛔ 逐一检查以下每一项,不得跳过):
|
||||
a. 溢出检测:无水平溢出,内容不超出视口
|
||||
b. 文字可读:对比度足够,文字不被截断
|
||||
c. 重叠检测:元素无重叠(标题栏与自定义头部不碰撞,导航栏不遮盖内容)
|
||||
d. 间距合理性:无异常大间距(如导航栏与内容之间的过大空白),无紧贴元素(相邻交互元素应有明确间距,不能挤在一起)
|
||||
e. 元素变形检测:按钮/输入框/卡片未被挤压变形(宽高比异常),文字未被截断显示省略号
|
||||
f. 对齐检测:同行元素水平对齐,同列元素垂直对齐
|
||||
g. TabBar:可见且完整(非 TabBar 页面此项 PASS)
|
||||
h. 导航冗余检测(⛔ 强制):页面顶部不应同时存在两层标题栏。
|
||||
- 判断标准:如果页面最顶部有标准系统导航栏(显示页面标题如"首页""我的""广场"等),
|
||||
紧接着下方又有另一行品牌名/logo/标题(如"NEON PULSE""极音派对"),标记为 FAIL(双重导航栏)。
|
||||
- 只允许一种:要么系统导航栏(有 navigationBarTitleText),要么自定义头部(品牌名/logo)。
|
||||
- ❌ 示例 FAIL:顶部显示"首页"(系统) + 下方紧接"NEON PULSE"(自定义) = 双重导航栏
|
||||
- ✅ 示例 PASS:只有"NEON PULSE"(自定义,系统栏已设custom) 或只有"首页"(系统栏,无自定义header)
|
||||
|
||||
列出所有发现的问题,按严重程度排序。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 视觉检查结果记录格式
|
||||
|
||||
每步视觉验证结果必须记录到 `pipeline-state.yaml`:
|
||||
|
||||
```yaml
|
||||
verification_detail:
|
||||
analyze_image_executed: true # ⛔ GATE-CHECK 验证此字段
|
||||
design_check:
|
||||
design_spec_source: "04-ui-ux-design.md" # 或 "css-fallback" 或 "none"
|
||||
design_compliance: PASS/FAIL/WARN
|
||||
details: "具体描述"
|
||||
icons_check:
|
||||
tabbar_icons_visible: PASS/FAIL
|
||||
emoji_as_icons: NONE/FOUND # NONE=无emoji, FOUND=发现emoji
|
||||
broken_images: NONE/FOUND
|
||||
details: "具体描述"
|
||||
english_enum_check:
|
||||
found: NONE/FOUND
|
||||
details: "具体枚举值列表"
|
||||
svg_data_uri_check:
|
||||
source_scan_executed: true # ⛔ 源码扫描必须执行
|
||||
found: NONE/FOUND
|
||||
files: [] # FOUND 时列出文件路径
|
||||
details: "具体描述"
|
||||
dual_nav_check:
|
||||
source_scan_executed: true # ⛔ 源码扫描必须执行
|
||||
found: NONE/FOUND
|
||||
files: [] # FOUND 时列出文件路径
|
||||
details: "具体描述"
|
||||
refresh_check: # ⛔ uni-app 项目必须执行
|
||||
source_scan_executed: true
|
||||
found: CONSISTENT/INCONSISTENT
|
||||
pages: # 各 tabBar 页面刷新方式
|
||||
home: PAGE_LEVEL/LIST_LEVEL/NONE
|
||||
square: PAGE_LEVEL/LIST_LEVEL/NONE
|
||||
messages: PAGE_LEVEL/LIST_LEVEL/NONE
|
||||
profile: PAGE_LEVEL/LIST_LEVEL/NONE
|
||||
details: "具体描述"
|
||||
layout_check:
|
||||
horizontal_overflow: false # a. 溢出检测
|
||||
text_readable: true # b. 文字可读
|
||||
element_overlap: false # c. 重叠检测
|
||||
spacing_anomaly: NONE/FOUND # d. 间距合理性(异常大间距/紧贴元素)
|
||||
element_deformation: NONE/FOUND # e. 元素变形(按钮/输入框被挤压)
|
||||
alignment_issues: NONE/FOUND # f. 对齐问题
|
||||
details: "具体描述"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 视觉检查问题分流规则
|
||||
|
||||
**⛔ 强制声明**:除"设计系统主观视觉评价"(改进建议/WARN)外,下表所有问题的修复动作均为 **强制**。
|
||||
不得以任何理由跳过、推迟或重新分类。以下表达均属于**无效分类**,禁止使用:
|
||||
"已知遗留项"、"技术债"、"后续优化"、"暂时保留"、"视觉可接受"、"非本次旅程引入"、"记录但推迟修复"。
|
||||
任何未在下表中定义的分类均为无效分类,必须按原分类执行修复。
|
||||
|
||||
`analyze_image` 发现问题后的分流路径:
|
||||
|
||||
| 发现的问题 | 分类 | 修复动作 | 铁律依据 |
|
||||
|-----------|------|---------|---------|
|
||||
| TabBar 图标缺失(只有文本无图标) | 未实现 | AUTO-IMPLEMENT: 自定义 TabBar + Iconify(参见 ./reference/platform-uni-app.md §1+§2) | iron-laws L223, L227 |
|
||||
| Emoji 充当功能图标 | 未实现 | AUTO-IMPLEMENT: **⛔ 必须按 ./reference/platform-uni-app.md §1 完整安装 Iconify + tailwindcss-icons(小程序需 weapp-tailwindcss/vite),禁止 SVG data URI** | iron-laws L228 |
|
||||
| Tailwind/Iconify 未配置 | 未实现 | AUTO-IMPLEMENT: 完整安装配置(参见 ./reference/platform-uni-app.md §1) | iron-laws L229 |
|
||||
| SVG data URI 充当图标/装饰 | Bug(平台不兼容 #17) | AUTO-IMPLEMENT: **⛔ 必须按 ./reference/platform-uni-app.md §1 完整安装 Iconify + tailwindcss-icons + weapp-tailwindcss/vite** | iron-laws L223 |
|
||||
| 英文枚举值显示 | Bug(#15) | AUTO-FIX: 添加中文映射函数 | iron-laws L239 |
|
||||
| Broken image | Bug | AUTO-FIX: 修复图片路径/占位符 | — |
|
||||
| 布局溢出 | Bug | AUTO-FIX: CSS 修复 | iron-laws L222 |
|
||||
| 元素变形/挤压(按钮/输入框/卡片宽高比异常、被挤压、文字截断显示省略号) | Bug | AUTO-FIX: 修复 CSS flex/width/padding/gap | — |
|
||||
| 间距异常(导航栏与内容之间过大空白、相邻交互元素紧贴无间距、异常大空白区域) | Bug | AUTO-FIX: 修复 margin/padding/gap | — |
|
||||
| 设计系统客观指标不合规(背景色/主色调/卡片样式/边框规则/按钮渐变等可从源码验证项) | Bug | AUTO-FIX: 修复 CSS/class,参照 _bmad-output/04-ui-ux-design.md 设计规范 | — |
|
||||
| 设计系统主观视觉评价不合规(发光效果强度/动画质感等无法从源码客观验证的维度) | 改进建议 | WARN | — |
|
||||
|
||||
> **注意**:视觉检查发现的问题分流后,走 `auto-fix-engine.md` 标准修复流程(AUTO-FIX/AUTO-IMPLEMENT),不在此文件定义修复细节。
|
||||
|
||||
### 跨步骤累计检测规则(⛔ 强制)
|
||||
|
||||
同一类型的问题在多个步骤被 `analyze_image` 检测到时,必须累计计数:
|
||||
|
||||
```
|
||||
累计检测流程:
|
||||
每个 analyze_image 执行后:
|
||||
- 检查本次发现的视觉问题类型
|
||||
- 与前序步骤的 verification_detail 合并累计
|
||||
- 同一类型累计 FOUND ≥ 1 → 必须在当前关卡立即触发修复,不得推迟(视觉问题确定性高,不需多次确认)
|
||||
- 如果修复在步骤 N 完成,步骤 N+1 起不再累计该类型(已修复)
|
||||
```
|
||||
|
||||
禁止行为:
|
||||
- ❌ 将同一类型问题在每个步骤独立记录为 FOUND 但从不触发修复
|
||||
- ❌ 发现第 1 次标记"记录",发现第 2 次仍标记"记录",发现第 N 次仍不修复
|
||||
|
||||
### 跨验证轮次累计检测规则(⛔ 强制 — 修复"每轮 count=1 永远不触发")
|
||||
|
||||
**问题**: 同一类型问题在 V1 检测到 count=1,V2 检测到 count=1,V3 检测到 count=1… 每轮都不够阈值 ≥ 2,永远不触发修复。
|
||||
|
||||
**解决**: 累计计数必须跨验证轮次(V1→V2→V3→V4…)累加。
|
||||
|
||||
```
|
||||
跨轮次累计流程:
|
||||
验证开始前:
|
||||
1. 读取 pipeline-state.yaml 中前一次验证的 verification_detail
|
||||
2. 提取每个视觉问题类型的检测状态:
|
||||
- emoji_as_icons: FOUND → 跨轮次累计基础 +1
|
||||
- english_enum_check: FOUND → 跨轮次累计基础 +1
|
||||
- broken_images: FOUND → 跨轮次累计基础 +1
|
||||
- tabbar_icons_visible: FAIL → 跨轮次累计基础 +1
|
||||
3. 将跨轮次基础值设为本次验证的累计起点
|
||||
(注意:基础值来自"前一次仍为 FOUND 的项")
|
||||
|
||||
每步 analyze_image 执行后:
|
||||
- 本次 FOUND + 跨轮次基础值 = 实际累计值
|
||||
- 实际累计值 ≥ 1 → ⛔ 立即触发修复(视觉问题确定性高,不需多次确认)
|
||||
- 修复完成后,后续步骤不再累计该类型
|
||||
```
|
||||
|
||||
**判定示例**:
|
||||
```
|
||||
V3: emoji_as_icons=FOUND → 记录到 pending_fixes(未修复)
|
||||
V4 开始前: 读取 V3 → emoji_as_icons 跨轮次基础=1
|
||||
V4 第一步 analyze_image: emoji FOUND → 1+1=2 → ⛔ 立即触发 AUTO-IMPLEMENT
|
||||
|
||||
V3: english_enum_check=FOUND → 记录到 pending_fixes(未修复)
|
||||
V4 开始前: 读取 V3 → english_enum 跨轮次基础=1
|
||||
V4 第一步 analyze_image: english FOUND → 1+1=2 → ⛔ 立即触发 AUTO-FIX
|
||||
|
||||
V5: 局头认证页 👤 emoji FOUND → 跨轮次基础=0(V5标记FIXED但实际仅部分替换)→ 0+1=1 → ⛔ 立即触发 AUTO-IMPLEMENT
|
||||
```
|
||||
|
||||
**⛔ 禁止行为**:
|
||||
- ❌ 只看本次验证的 count,忽略前一次验证的 FOUND 记录
|
||||
- ❌ 以"V3遗留/已知"为由重置累计计数为 0
|
||||
- ❌ 跨轮次基础值 + 本次 FOUND ≥ 1 但仍标记"记录"不触发修复
|
||||
|
||||
---
|
||||
|
||||
## 禁止行为(⛔)
|
||||
|
||||
- ❌ 截图但不执行 analyze_image 就标记"视觉验证通过"
|
||||
- ❌ analyze_image 报告 FAIL 但 Story 仍标记 done
|
||||
- ❌ 发现 emoji 当图标但写"视觉可接受"然后 done
|
||||
- ❌ 发现 TabBar iconPath 为空但标记"TabBar 可见"然后 done
|
||||
- ❌ 只在最后做一次 analyze_image,中间步骤跳过
|
||||
- ❌ analyze_image 使用过于宽松的提示词(如"看起来正常吗?")
|
||||
- ❌ **将视觉问题标记为任何非分流规则表中定义的分类**(模式性禁止 — 覆盖所有语义变体)
|
||||
- 禁止使用的分类包括但不限于:"已知遗留项"、"技术债"、"后续优化"、"暂时保留"、"非本次引入"
|
||||
- 唯一允许的分类:分流规则表中的 7 种(未实现×3、Bug×3、改进建议×1)
|
||||
- ❌ **将修复动作从 AUTO-FIX/AUTO-IMPLEMENT 降级为"仅记录"或"WARN"**(除设计系统主观视觉评价外)
|
||||
- ❌ **推迟视觉验证修复到"旅程完成后"或"步骤 4 修复阶段"**(必须在关卡内立即执行)
|
||||
Reference in New Issue
Block a user