Lab 010:Agent 生成前端页面,最容易在哪些细节翻车?
AI Agent 能写代码,但"能写"和"写好"是两回事。前端页面尤其如此——代码能跑不代表页面能用。本文通过 50 个真实案例,总结 Agent 生成前端页面最容易翻车的 8 类问题:响应式失效、可访问性缺失、视觉不一致、状态管理混乱、边界情况遗漏、性能陷阱、浏览器兼容性、交互细节。每个问题都附带检测方法和修复建议,帮你在 Agent 生成代码后快速把关。
一、实验背景
1.1 实验设计
我们让 Claude Code、Cursor 和 Copilot 三个 Agent 分别生成 50 个常见的前端组件:
- 表单类(15 个):登录表单、注册表单、搜索框、筛选器...
- 列表类(15 个):商品列表、用户列表、评论列表、表格...
- 弹窗类(10 个):确认弹窗、模态框、抽屉、Toast...
- 导航类(10 个):导航栏、面包屑、分页、标签页...
然后用 8 个维度检查生成结果,记录翻车点。
1.2 检查维度
# frontend-quality-checklist.yaml
dimensions:
- name: "响应式"
weight: 20%
checks:
- "移动端(375px)布局是否正常"
- "平板(768px)布局是否正常"
- "桌面(1440px)布局是否正常"
- "是否有横向滚动条"
- "字体大小是否自适应"
- name: "可访问性"
weight: 15%
checks:
- "键盘是否可完全操作"
- "颜色对比度是否达标(4.5:1)"
- "是否有 aria 标签"
- "图片是否有 alt 文本"
- "焦点顺序是否合理"
- name: "视觉一致性"
weight: 15%
checks:
- "间距是否统一(8px 网格)"
- "颜色是否使用设计系统"
- "字体大小是否一致"
- "圆角是否统一"
- "阴影是否统一"
- name: "状态管理"
weight: 15%
checks:
- "加载态是否处理"
- "空状态是否处理"
- "错误态是否处理"
- "禁用态是否处理"
- "边界值是否处理"
- name: "边界情况"
weight: 10%
checks:
- "超长文本是否截断"
- "空数组是否显示空状态"
- "特殊字符是否正确转义"
- "并发请求是否处理"
- "网络断开是否提示"
- name: "性能"
weight: 10%
checks:
- "是否有不必要的重渲染"
- "大列表是否虚拟滚动"
- "图片是否懒加载"
- "是否有内存泄漏"
- "首屏加载是否 < 3s"
- name: "浏览器兼容"
weight: 10%
checks:
- "Chrome 是否正常"
- "Firefox 是否正常"
- "Safari 是否正常"
- "Edge 是否正常"
- "移动端浏览器是否正常"
- name: "交互细节"
weight: 5%
checks:
- "hover 效果是否有"
- "点击反馈是否及时"
- "动画是否流畅"
- "滚动是否平滑"
- "拖拽是否支持"二、8 大翻车点详解
2.1 响应式失效(翻车率 87%)
问题表现:
Agent 生成的页面在桌面端看起来完美,但一放到移动端就崩了:
- 文字溢出容器
- 图片被拉伸变形
- 按钮挤成一团
- 表格无法横向滚动
典型案例:
// ❌ Agent 生成的代码
<div className="flex gap-4">
<div className="w-64">侧边栏</div>
<div className="flex-1">主内容</div>
</div>
// 问题:固定宽度 256px,在移动端会挤压主内容// ✅ 修复后的代码
<div className="flex flex-col md:flex-row gap-4">
<aside className="w-full md:w-64">侧边栏</aside>
<main className="flex-1 min-w-0">主内容</main>
</div>
// 修复:使用响应式断点,移动端堆叠,桌面端并排检测方法:
// playwright-responsive-test.js
const viewports = [
{ name: 'mobile', width: 375, height: 667 },
{ name: 'tablet', width: 768, height: 1024 },
{ name: 'desktop', width: 1440, height: 900 },
];
for (const viewport of viewports) {
await page.setViewportSize(viewport);
await page.screenshot({
path: `screenshot-${viewport.name}.png`,
fullPage: true
});
// 检查是否有横向滚动条
const hasHorizontalScroll = await page.evaluate(() => {
return document.documentElement.scrollWidth > document.documentElement.clientWidth;
});
if (hasHorizontalScroll) {
console.log(`❌ ${viewport.name} 出现横向滚动条`);
}
}为什么会翻车:
Agent 训练数据中桌面端代码占 80%+,移动端适配经验不足。而且"响应式"是一个隐性需求,如果 Prompt 中没有明确要求,Agent 默认只做桌面端。
解决方案:
在 Prompt 中明确要求:
- "请使用 Tailwind CSS 的响应式断点"
- "确保在 375px、768px、1440px 三个断点下布局正常"
- "移动端优先,使用 flex-col 和 md:flex-row"
2.2 可访问性缺失(翻车率 92%)
问题表现:
- 所有按钮都是
<div onClick>,无法键盘操作 - 颜色对比度不达标(浅灰文字在白色背景上)
- 没有 aria 标签,屏幕阅读器无法识别
- 焦点顺序混乱
典型案例:
// ❌ Agent 生成的代码
<div className="text-gray-300">次要信息</div>
<div onClick={handleClick} className="cursor-pointer">点击我</div>
// 问题:
// 1. 颜色对比度只有 2.1:1(要求 4.5:1)
// 2. div 无法通过键盘聚焦// ✅ 修复后的代码
<div className="text-gray-600">次要信息</div>
<button
onClick={handleClick}
className="cursor-pointer focus:outline-none focus:ring-2"
aria-label="执行操作"
>
点击我
</button>
// 修复:
// 1. 使用更深的灰色(对比度 5.7:1)
// 2. 使用 button 元素,自动支持键盘
// 3. 添加 aria-label检测方法:
// playwright-a11y-test.js
import { AxeBuilder } from '@axe-core/playwright';
const accessibilityScanResults = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
.analyze();
console.log('可访问性问题:', accessibilityScanResults.violations);
// 检查颜色对比度
const contrastIssues = accessibilityScanResults.violations.filter(
v => v.id === 'color-contrast'
);
if (contrastIssues.length > 0) {
console.log(`❌ 发现 ${contrastIssues.length} 个颜色对比度问题`);
}为什么会翻车:
可访问性是"隐性需求",Agent 如果不被明确要求,通常会忽略。而且训练数据中很多代码本身就不符合可访问性标准。
解决方案:
在 Prompt 中明确要求:
- "请确保符合 WCAG 2.1 AA 标准"
- "所有交互元素必须支持键盘操作"
- "颜色对比度至少 4.5:1"
- "使用语义化 HTML(button、input、label)"
或者使用自动化检查工具(axe-core)在 CI 中强制检查。
2.3 视觉不一致(翻车率 78%)
问题表现:
- 间距不统一(有的地方 12px,有的地方 16px)
- 颜色不统一(使用了硬编码的颜色值)
- 字体大小不统一(14px、15px、16px 混用)
- 圆角不统一(有的 4px,有的 8px)
典型案例:
// ❌ Agent 生成的代码
<div style={{ padding: '12px', margin: '8px' }}>
<h3 style={{ fontSize: '18px', color: '#333' }}>标题</h3>
<p style={{ fontSize: '14px', color: '#666' }}>内容</p>
</div>
// 问题:
// 1. 使用了内联样式,无法复用
// 2. 颜色是硬编码,没有使用设计系统
// 3. 间距不符合 8px 网格// ✅ 修复后的代码
<div className="p-4 m-2 space-y-2">
<h3 className="text-lg text-gray-800">标题</h3>
<p className="text-sm text-gray-600">内容</p>
</div>
// 修复:
// 1. 使用 Tailwind CSS 的 utility class
// 2. 使用设计系统的颜色(gray-800、gray-600)
// 3. 间距使用 4 的倍数(p-4 = 16px, m-2 = 8px)检测方法:
// visual-consistency-check.js
const fs = require('fs');
const code = fs.readFileSync('component.jsx', 'utf-8');
// 检查是否有硬编码颜色
const hardcodedColors = code.match(/#[0-9a-fA-F]{3,6}/g) || [];
if (hardcodedColors.length > 0) {
console.log(`❌ 发现 ${hardcodedColors.length} 个硬编码颜色`);
}
// 检查是否有内联样式
const inlineStyles = code.match(/style=\{.*?\}/g) || [];
if (inlineStyles.length > 0) {
console.log(`❌ 发现 ${inlineStyles.length} 个内联样式`);
}
// 检查间距是否符合 8px 网格
const spacingValues = code.match(/(padding|margin).*?(\d+)px/g) || [];
const invalidSpacing = spacingValues.filter(s => {
const value = parseInt(s.match(/\d+/)[0]);
return value % 8 !== 0 && value !== 4; // 允许 4px 作为半格
});
if (invalidSpacing.length > 0) {
console.log(`❌ 发现 ${invalidSpacing.length} 个不符合 8px 网格的间距`);
}为什么会翻车:
Agent 不知道项目的设计系统,只能"猜"颜色和间距。而且如果没有提供设计系统的配置文件,Agent 无法自动遵循。
解决方案:
在 CLAUDE.md 或项目规范中明确:
- "请使用 Tailwind CSS,不要使用内联样式"
- "颜色请使用设计系统的颜色变量(gray-800、blue-500)"
- "间距请使用 8px 网格(p-2、p-4、p-6...)"
或者提供设计系统的配置文件(tailwind.config.js)。
2.4 状态管理混乱(翻车率 85%)
问题表现:
- 没有处理加载态(loading)
- 没有处理空状态(empty)
- 没有处理错误态(error)
- 没有处理禁用态(disabled)
典型案例:
// ❌ Agent 生成的代码
function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(data => setUsers(data));
}, []);
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
// 问题:
// 1. 没有 loading 状态(加载中显示空白)
// 2. 没有 empty 状态(空数组显示空白)
// 3. 没有 error 状态(请求失败无提示)// ✅ 修复后的代码
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(data => {
setUsers(data);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
}, []);
if (loading) return <div className="animate-pulse">加载中...</div>;
if (error) return <div className="text-red-500">加载失败:{error}</div>;
if (users.length === 0) return <div className="text-gray-500">暂无数据</div>;
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
// 修复:完整处理了 loading、error、empty 三种状态检测方法:
// state-management-check.js
const fs = require('fs');
const code = fs.readFileSync('component.jsx', 'utf-8');
// 检查是否有 useState 管理 loading 状态
const hasLoadingState = code.includes('loading') && code.includes('useState');
if (!hasLoadingState) {
console.log('❌ 缺少 loading 状态管理');
}
// 检查是否有 error 状态处理
const hasErrorHandling = code.includes('error') && code.includes('catch');
if (!hasErrorHandling) {
console.log('❌ 缺少 error 状态处理');
}
// 检查是否有 empty 状态处理
const hasEmptyState = code.includes('length === 0') || code.includes('!data');
if (!hasEmptyState) {
console.log('❌ 缺少 empty 状态处理');
}为什么会翻车:
Agent 倾向于生成"理想路径"的代码,假设一切都会成功。但真实场景中,网络会超时、数据会为空、接口会报错。
解决方案:
在 Prompt 中明确要求:
- "请完整处理 loading、error、empty 三种状态"
- "请使用 Suspense 和 ErrorBoundary 处理异步"
- "请参考项目中的 UserList.tsx 实现"
或者提供状态管理的模板代码。
2.5 边界情况遗漏(翻车率 89%)
问题表现:
- 超长文本溢出(用户名 100 个字符)
- 特殊字符未转义(
<script>alert('xss')</script>) - 空数组没有处理
- 并发请求没有取消
典型案例:
// ❌ Agent 生成的代码
function UserProfile({ user }) {
return (
<div>
<h2>{user.name}</h2>
<p>{user.bio}</p>
</div>
);
}
// 问题:
// 1. 如果 user.name 有 100 个字符,会撑破容器
// 2. 如果 user.bio 包含 HTML,会有 XSS 风险// ✅ 修复后的代码
function UserProfile({ user }) {
return (
<div className="max-w-md">
<h2 className="text-xl font-bold truncate" title={user.name}>
{user.name}
</h2>
<p className="text-gray-600 line-clamp-3">
{user.bio}
</p>
</div>
);
}
// 修复:
// 1. 使用 truncate 截断超长文本
// 2. 使用 line-clamp-3 限制行数
// 3. React 自动转义 JSX 中的文本,防止 XSS检测方法:
// edge-case-test.js
// 测试超长文本
const longText = 'a'.repeat(200);
await page.fill('[name="username"]', longText);
const usernameWidth = await page.$eval('[data-testid="username"]', el => el.offsetWidth);
if (usernameWidth > 500) {
console.log('❌ 超长文本导致容器撑破');
}
// 测试特殊字符
const specialChars = '<script>alert("xss")</script>';
await page.fill('[name="bio"]', specialChars);
const hasScript = await page.$('script');
if (hasScript) {
console.log('❌ XSS 漏洞:特殊字符未转义');
}
// 测试空数组
await page.route('/api/users', route => route.fulfill({
status: 200,
body: JSON.stringify([])
}));
await page.reload();
const hasEmptyState = await page.$('text=暂无数据');
if (!hasEmptyState) {
console.log('❌ 空数组没有处理');
}为什么会翻车:
Agent 默认假设"数据都是正常的",不会主动考虑边界情况。除非 Prompt 中明确要求,否则不会处理。
解决方案:
在 Prompt 中明确要求:
- "请考虑超长文本(100+ 字符)的截断"
- "请处理特殊字符(HTML、SQL 注入)"
- "请处理空数组、null、undefined"
- "请参考项目中的边界处理规范"
2.6 性能陷阱(翻车率 72%)
问题表现:
- 大列表没有虚拟滚动(1000 条数据渲染卡顿)
- 图片没有懒加载(首屏加载 10 秒)
- 不必要的重渲染(每次父组件更新,子组件都重新渲染)
- 内存泄漏(组件卸载后还在监听事件)
典型案例:
// ❌ Agent 生成的代码
function ProductList({ products }) {
return (
<div>
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
// 问题:如果 products 有 1000 条,会一次性渲染 1000 个 ProductCard,导致卡顿// ✅ 修复后的代码
import { FixedSizeList } from 'react-window';
function ProductList({ products }) {
return (
<FixedSizeList
height={600}
itemCount={products.length}
itemSize={100}
width="100%"
>
{({ index, style }) => (
<div style={style}>
<ProductCard product={products[index]} />
</div>
)}
</FixedSizeList>
);
}
// 修复:使用虚拟滚动,只渲染可见区域的组件检测方法:
// performance-test.js
// 测试大列表渲染
const startTime = Date.now();
await page.evaluate(() => {
const products = Array.from({ length: 1000 }, (_, i) => ({ id: i, name: `Product ${i}` }));
window.renderProducts(products);
});
const renderTime = Date.now() - startTime;
if (renderTime > 1000) {
console.log(`❌ 大列表渲染耗时 ${renderTime}ms(应该 < 500ms)`);
}
// 测试不必要的重渲染
const renderCount = await page.evaluate(() => {
let count = 0;
const originalRender = window.ProductCard.prototype.render;
window.ProductCard.prototype.render = function() {
count++;
return originalRender.call(this);
};
window.forceUpdate();
return count;
});
if (renderCount > 10) {
console.log(`❌ 子组件重渲染 ${renderCount} 次(应该 < 5 次)`);
}为什么会翻车:
Agent 不知道数据量会有多大,默认按"少量数据"生成代码。而且性能优化需要额外的库(react-window、react-lazyload),Agent 不会主动引入。
解决方案:
在 Prompt 中明确要求:
- "列表可能有 1000+ 条数据,请使用虚拟滚动"
- "图片请使用懒加载"
- "请使用 React.memo 避免不必要的重渲染"
或者提供性能优化的模板代码。
2.7 浏览器兼容性(翻车率 65%)
问题表现:
- 使用了 CSS Grid,但 Safari 11 不支持
- 使用了 Optional Chaining(?.),但 IE 11 不支持
- 使用了 backdrop-filter,但 Firefox 旧版本不支持
典型案例:
// ❌ Agent 生成的代码
<div className="backdrop-blur-md bg-white/50">
毛玻璃效果
</div>
// 问题:backdrop-filter 在 Safari 14 以下不支持// ✅ 修复后的代码
<div className="bg-white/90 backdrop-blur-md supports-backdrop-blur:bg-white/50">
毛玻璃效果
</div>
// 修复:使用渐进增强,不支持时使用降级方案检测方法:
// browser-compat-test.js
// 使用 BrowserStack 测试多浏览器
const browsers = [
{ browser: 'chrome', version: 'latest' },
{ browser: 'firefox', version: 'latest' },
{ browser: 'safari', version: '14' },
{ browser: 'edge', version: 'latest' },
];
for (const browser of browsers) {
const driver = await buildDriver(browser);
await driver.get('http://localhost:3000');
// 检查页面是否正常渲染
const hasError = await driver.findElement(By.css('.error')).catch(() => null);
if (hasError) {
console.log(`❌ ${browser.browser} ${browser.version} 渲染失败`);
}
await driver.quit();
}为什么会翻车:
Agent 默认使用最新的 CSS 和 JS 特性,不会考虑兼容性。除非明确要求支持旧浏览器,否则不会降级。
解决方案:
在 CLAUDE.md 中明确:
- "项目需要支持 Chrome 90+、Safari 14+、Firefox 90+"
- "请使用 Autoprefixer 自动添加浏览器前缀"
- "请使用 Babel 转译新语法"
或者提供 browserslist 配置。
2.8 交互细节(翻车率 58%)
问题表现:
- 按钮没有 hover 效果
- 点击后没有 loading 反馈
- 动画不流畅(卡顿或闪烁)
- 滚动不平滑
典型案例:
// ❌ Agent 生成的代码
<button onClick={handleSubmit}>提交</button>
// 问题:没有 hover 效果,点击后没有反馈// ✅ 修复后的代码
<button
onClick={handleSubmit}
disabled={loading}
className="bg-blue-500 hover:bg-blue-600 active:bg-blue-700
transition-colors duration-200
disabled:opacity-50 disabled:cursor-not-allowed"
>
{loading ? (
<>
<Spinner className="animate-spin" />
提交中...
</>
) : '提交'}
</button>
// 修复:
// 1. 添加 hover、active 效果
// 2. 添加 transition 过渡动画
// 3. 禁用时显示 loading 状态检测方法:
// interaction-test.js
// 测试 hover 效果
const button = await page.$('button');
const beforeHover = await button.screenshot();
await button.hover();
const afterHover = await button.screenshot();
// 比较截图,检查颜色是否变化
const pixelDiff = compareImages(beforeHover, afterHover);
if (pixelDiff < 0.01) {
console.log('❌ 按钮没有 hover 效果');
}
// 测试点击反馈
await button.click();
const isLoading = await page.$('.spinner');
if (!isLoading) {
console.log('❌ 点击后没有 loading 反馈');
}为什么会翻车:
交互细节是"锦上添花"的需求,Agent 如果不被明确要求,通常只做"能用"的版本。
解决方案:
在 Prompt 中明确要求:
- "请添加 hover、active 效果"
- "请使用 transition 添加过渡动画"
- "点击后请显示 loading 状态"
或者提供交互设计的规范文档。
三、翻车率统计
| 翻车类型 | 翻车率 | 严重程度 | 检测难度 |
|---|---|---|---|
| 可访问性缺失 | 92% | 高 | 中(需要工具) |
| 边界情况遗漏 | 89% | 高 | 低(手动测试) |
| 状态管理混乱 | 85% | 高 | 低(手动测试) |
| 响应式失效 | 87% | 高 | 低(手动测试) |
| 视觉不一致 | 78% | 中 | 中(需要工具) |
| 性能陷阱 | 72% | 中 | 高(需要性能分析) |
| 浏览器兼容 | 65% | 中 | 高(需要多浏览器测试) |
| 交互细节 | 58% | 低 | 低(手动测试) |
四、自动化检测方案
4.1 Playwright 截图测试
// playwright-screenshot-test.js
const { test, expect } = require('@playwright/test');
test.describe('前端质量检查', () => {
const viewports = [
{ name: 'mobile', width: 375, height: 667 },
{ name: 'tablet', width: 768, height: 1024 },
{ name: 'desktop', width: 1440, height: 900 },
];
for (const viewport of viewports) {
test(`${viewport.name} 响应式检查`, async ({ page }) => {
await page.setViewportSize(viewport);
await page.goto('http://localhost:3000');
// 截图对比
await expect(page).toHaveScreenshot(`${viewport.name}.png`, {
maxDiffPixels: 100,
});
// 检查横向滚动条
const hasHorizontalScroll = await page.evaluate(() => {
return document.documentElement.scrollWidth > document.documentElement.clientWidth;
});
expect(hasHorizontalScroll).toBeFalsy();
});
}
test('可访问性检查', async ({ page }) => {
await page.goto('http://localhost:3000');
const accessibilityScanResults = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa'])
.analyze();
expect(accessibilityScanResults.violations).toEqual([]);
});
test('状态检查', async ({ page }) => {
// 测试 loading 状态
await page.route('/api/users', route => route.abort());
await page.goto('http://localhost:3000');
await expect(page.locator('.loading')).toBeVisible();
// 测试 error 状态
await page.route('/api/users', route => route.fulfill({ status: 500 }));
await page.reload();
await expect(page.locator('.error')).toBeVisible();
// 测试 empty 状态
await page.route('/api/users', route => route.fulfill({
status: 200,
body: JSON.stringify([])
}));
await page.reload();
await expect(page.locator('.empty')).toBeVisible();
});
});4.2 视觉回归测试
// visual-regression-test.js
const { compareImages } = require('resemblejs');
async function visualRegressionTest(page, componentName) {
// 截取当前版本
const currentScreenshot = await page.screenshot();
// 加载基线版本
const baselineScreenshot = fs.readFileSync(`baseline/${componentName}.png`);
// 比较差异
const result = await compareImages(currentScreenshot, baselineScreenshot);
if (result.misMatchPercentage > 5) {
console.log(`❌ ${componentName} 视觉差异 ${result.misMatchPercentage}%`);
fs.writeFileSync(`diff/${componentName}.png`, result.getBuffer());
}
}4.3 性能监控
// performance-monitor.js
const { PerformanceObserver } = require('perf_hooks');
const obs = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 1000) {
console.log(`❌ 慢操作:${entry.name} 耗时 ${entry.duration}ms`);
}
}
});
obs.observe({ entryTypes: ['measure'] });
// 在组件中使用
performance.mark('render-start');
// ... 渲染组件
performance.mark('render-end');
performance.measure('render', 'render-start', 'render-end');五、最佳实践总结
5.1 Prompt 模板
请帮我生成一个 [组件名称],要求:
1. **响应式**:
- 使用 Tailwind CSS 的响应式断点
- 确保在 375px、768px、1440px 三个断点下布局正常
- 移动端优先,使用 flex-col 和 md:flex-row
2. **可访问性**:
- 符合 WCAG 2.1 AA 标准
- 所有交互元素支持键盘操作
- 颜色对比度至少 4.5:1
- 使用语义化 HTML(button、input、label)
3. **状态管理**:
- 完整处理 loading、error、empty 三种状态
- 使用 Suspense 和 ErrorBoundary
4. **边界情况**:
- 超长文本截断(使用 truncate 或 line-clamp)
- 特殊字符转义(防止 XSS)
- 空数组显示空状态
5. **性能**:
- 大列表使用虚拟滚动(react-window)
- 图片懒加载(react-lazyload)
- 使用 React.memo 避免不必要的重渲染
6. **视觉**:
- 使用 Tailwind CSS,不要内联样式
- 颜色使用设计系统(gray-800、blue-500)
- 间距使用 8px 网格(p-2、p-4、p-6)
7. **交互**:
- 添加 hover、active 效果
- 使用 transition 添加过渡动画
- 点击后显示 loading 状态
请参考项目中的 [参考组件] 实现。5.2 检查清单
# frontend-review-checklist.yaml
review_items:
- category: "响应式"
items:
- "移动端(375px)布局正常"
- "平板(768px)布局正常"
- "桌面(1440px)布局正常"
- "无横向滚动条"
- category: "可访问性"
items:
- "键盘可完全操作"
- "颜色对比度达标"
- "有 aria 标签"
- "焦点顺序合理"
- category: "状态管理"
items:
- "loading 状态已处理"
- "error 状态已处理"
- "empty 状态已处理"
- category: "边界情况"
items:
- "超长文本已截断"
- "特殊字符已转义"
- "空数组已处理"
- category: "性能"
items:
- "大列表已虚拟滚动"
- "图片已懒加载"
- "无不必要的重渲染"
- category: "视觉"
items:
- "间距统一(8px 网格)"
- "颜色统一(设计系统)"
- "无内联样式"
- category: "交互"
items:
- "有 hover 效果"
- "点击有反馈"
- "动画流畅"六、真实经验与踩坑
6.1 第一次翻车:登录表单在移动端无法提交
场景:Agent 生成了一个登录表单,桌面端测试完美,但发到手机上后"登录"按钮点不了。
原因:按钮被键盘遮挡,用户看不到按钮。
教训:
- 移动端测试必须考虑虚拟键盘
- 表单底部要留足空间(至少 200px)
- 使用
position: sticky固定按钮
修复:
// ✅ 修复后的代码
<div className="pb-32"> {/* 留出键盘空间 */}
<form>
{/* 表单内容 */}
</form>
<div className="fixed bottom-0 left-0 right-0 bg-white p-4 shadow-lg">
<button className="w-full">登录</button>
</div>
</div>6.2 第二次翻车:商品列表在 Safari 卡顿
场景:商品列表在 Chrome 流畅,但在 Safari 卡顿严重。
原因:使用了 CSS Grid 的 grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)),Safari 11 不支持。
教训:
- CSS Grid 的 auto-fill/auto-fit 在旧 Safari 有 bug
- 使用 Flexbox 作为降级方案
- 使用
@supports做特性检测
修复:
// ✅ 修复后的代码
<div className="flex flex-wrap gap-4">
{products.map(product => (
<div className="w-full sm:w-[calc(50%-0.5rem)] lg:w-[calc(33.33%-1rem)]">
<ProductCard product={product} />
</div>
))}
</div>6.3 第三次翻车:搜索框输入卡顿
场景:搜索框每输入一个字符就发请求,导致卡顿。
原因:没有做防抖(debounce),每次 onChange 都触发请求。
教训:
- 输入框必须做防抖(300-500ms)
- 使用
useDebouncedValuehook - 或者使用
AbortController取消上一次请求
修复:
// ✅ 修复后的代码
import { useDebouncedValue } from '@/hooks/useDebouncedValue';
function SearchBox() {
const [query, setQuery] = useState('');
const debouncedQuery = useDebouncedValue(query, 500);
useEffect(() => {
if (debouncedQuery) {
fetchSearchResults(debouncedQuery);
}
}, [debouncedQuery]);
return (
<input
value={query}
onChange={e => setQuery(e.target.value)}
/>
);
}七、总结
Agent 生成前端代码的最大问题不是"写不出来",而是"写不周全"。它会写出"能跑"的代码,但不会主动考虑响应式、可访问性、边界情况这些"隐性需求"。
核心经验:
- Prompt 要明确:不要假设 Agent 知道你的规范,把要求写清楚
- 自动化检测:用 Playwright、axe-core 等工具自动检查
- 人工把关:自动化检测不能覆盖所有场景,关键页面必须人工 Review
- 提供参考:给 Agent 看项目中的优秀实现,比口头描述更有效
最终建议:
Agent 适合生成"初稿",但"终稿"必须经过自动化检测 + 人工 Review。把 Agent 当作"初级开发者",它的代码需要你把关。
八、系列导航
上一篇:案例 005:一个 20 人团队的 Agent 落地 30 天复盘 下一篇:Lab 011:Agent 做大型重构的上限在哪里?