403 前端页面构建
使用 Cursor + Codex 组合拳,快速构建企业级前端界面
学习目标
完成本课程后,你将能够:
一、前端开发工具组合
Cursor + Codex 协作模式
| 场景 | 使用工具 | 说明 |
|---|---|---|
| 页面/组件生成 | Codex | 对话式生成完整代码 |
| 局部编辑调试 | Cursor | Tab 补全 + 内联编辑 |
| 样式调整 | Cursor | 实时预览修改 |
| 重构优化 | Codex | `/refactor` 批量重构 |
| 调试排错 | Codex | `/debug` 错误分析 |
开发流程
1. Codex 生成页面骨架和组件代码 → 2. Cursor 精调 UI 细节
3. Codex 审核代码质量 → 4. Cursor 修复 lint 问题
5. Codex 生成测试 → 6. 浏览器验证二、任务看板页面
用 Codex 生成主看板
请为任务管理平台生成看板页面,使用 Next.js 14 App Router + Tailwind CSS:
- 四列看板:待办、进行中、审核中、已完成
- 每列显示任务卡片,支持拖拽排序
- 任务卡片显示标题、优先级标签、指派人头像、截止日期
- 顶部工具栏:搜索框、筛选器、新建任务按钮
- 响应式布局,移动端折叠为单列滚动页面骨架
// frontend/src/app/dashboard/page.tsx
'use client';
import { useState } from 'react';
import { BoardColumn } from '@/components/board/BoardColumn';
import { TaskCard } from '@/components/task/TaskCard';
import { SearchBar } from '@/components/board/SearchBar';
import { FilterBar } from '@/components/board/FilterBar';
import { CreateTaskModal } from '@/components/task/CreateTaskModal';
import { useBoard } from '@/hooks/useBoard';
export default function DashboardPage() {
const {
columns,
filters,
setFilters,
handleDragEnd,
isLoading,
error,
} = useBoard();
if (error) return <ErrorState message={error} />;
return (
<div className="h-screen flex flex-col bg-gray-50">
{/* 顶部工具栏 */}
<header className="bg-white border-b px-6 py-4">
<div className="flex items-center justify-between mb-4">
<h1 className="text-2xl font-bold">任务看板</h1>
<CreateTaskButton />
</div>
<div className="flex gap-4">
<SearchBar onSearch={setFilters} />
<FilterBar filters={filters} onChange={setFilters} />
</div>
</header>
{/* 看板主体 */}
<main className="flex-1 overflow-x-auto p-6">
{isLoading ? (
<BoardSkeleton />
) : (
<div className="flex gap-4 min-h-full">
{columns.map((column) => (
<BoardColumn
key={column.id}
column={column}
onDragEnd={handleDragEnd}
/>
))}
</div>
)}
</main>
</div>
);
}三、组件化开发
任务卡片组件
// frontend/src/components/task/TaskCard.tsx
'use client';
import { useDraggable } from '@dnd-kit/core';
import { Badge } from '@/components/ui/Badge';
import { Avatar } from '@/components/ui/Avatar';
import { formatDate, isOverdue } from '@/lib/utils';
interface TaskCardProps {
task: Task;
isDragging?: boolean;
}
export function TaskCard({ task, isDragging }: TaskCardProps) {
const { attributes, listeners, setNodeRef, transform } = useDraggable({
id: task.id,
});
const style = transform ? {
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
} : undefined;
const priorityColors: Record<Priority, string> = {
URGENT: 'bg-red-100 text-red-700',
HIGH: 'bg-orange-100 text-orange-700',
MEDIUM: 'bg-blue-100 text-blue-700',
LOW: 'bg-gray-100 text-gray-700',
};
return (
<div
ref={setNodeRef}
style={style}
{...listeners}
{...attributes}
className={`
bg-white rounded-lg p-4 shadow-sm border cursor-grab
hover:shadow-md transition-shadow
${isDragging ? 'opacity-50 shadow-lg' : ''}
`}
>
{/* 优先级 + 标签 */}
<div className="flex items-center gap-2 mb-2">
<Badge className={priorityColors[task.priority]}>
{task.priority}
</Badge>
{task.tags?.map((tag) => (
<Badge key={tag.id} style={{ backgroundColor: tag.color }}>
{tag.name}
</Badge>
))}
</div>
{/* 标题 */}
<h3 className="font-medium text-gray-900 mb-2 line-clamp-2">
{task.title}
</h3>
{/* 底部信息 */}
<div className="flex items-center justify-between mt-4">
<div className="flex items-center gap-2">
{task.assignee && (
<Avatar
src={task.assignee.avatar}
name={task.assignee.name}
size="sm"
/>
)}
</div>
{task.dueDate && (
<span className={`text-xs ${isOverdue(task.dueDate) ? 'text-red-500' : 'text-gray-500'}`}>
{formatDate(task.dueDate)}
</span>
)}
</div>
</div>
);
}UI 基础组件库
用 Codex 快速生成常用 UI 组件:
请为项目生成一套基础 UI 组件:
- Button(变体:primary/secondary/ghost/danger)
- Input / Textarea(带错误状态)
- Modal / Dialog(带遮罩动画)
- Badge / Tag
- Avatar(支持图片和首字母回退)
- Skeleton(加载骨架屏)
- Toast(通知提示)
使用 Tailwind CSS + Radix UI,确保无障碍访问四、状态管理
数据获取 Hook
// frontend/src/hooks/useBoard.ts
import useSWR from 'swr';
import { useState, useCallback } from 'react';
const fetcher = (url: string) => fetch(url).then(r => r.json());
export function useBoard() {
const [filters, setFilters] = useState<BoardFilters>({
search: '',
priority: '',
assigneeId: '',
});
const queryString = new URLSearchParams(
Object.entries(filters).filter(([_, v]) => v)
).toString();
const { data, error, isLoading, mutate } = useSWR(
`/api/tasks/board?${queryString}`,
fetcher,
{ refreshInterval: 30000 } // 30秒轮询
);
const handleDragEnd = useCallback(async (result: DragEndEvent) => {
const { active, over } = result;
if (!over) return;
// 乐观更新
await mutate(optimisticUpdate(active.id, over.id), false);
}, [mutate]);
return {
columns: data?.columns ?? [],
filters,
setFilters,
handleDragEnd,
isLoading,
error,
mutate,
};
}状态管理策略
| 数据类型 | 方案 | 工具 |
|---|---|---|
| 服务端数据 | 请求缓存 | SWR / React Query |
| 全局 UI 状态 | Context + Reducer | React Context |
| 表单状态 | 受控组件 | React Hook Form + Zod |
| URL 参数 | 路由查询参数 | next/navigation |
五、响应式设计
Tailwind 响应式断点
// 使用 Tailwind 响应式前缀
<div className="
grid
grid-cols-1 // 移动端:单列
md:grid-cols-2 // 平板:两列
lg:grid-cols-4 // 桌面:四列
gap-4
p-4 md:p-6 lg:p-8
">响应式 Hook
// frontend/src/hooks/useMediaQuery.ts
import { useEffect, useState } from 'react';
export function useMediaQuery(query: string): boolean {
const [matches, setMatches] = useState(false);
useEffect(() => {
const media = window.matchMedia(query);
setMatches(media.matches);
const listener = (e: MediaQueryListEvent) => setMatches(e.matches);
media.addEventListener('change', listener);
return () => media.removeEventListener('change', listener);
}, [query]);
return matches;
}
// 使用
const isMobile = useMediaQuery('(max-width: 768px)');六、实战练习
练习 1:生成表单页面
请为任务创建/编辑生成一个完整表单页面,包含:
- 标题、描述、优先级、截止日期
- 所属项目和指派人的下拉选择
- 标签多选
- React Hook Form + Zod 验证
- 加载和错误状态处理练习 2:生成数据表格
请生成一个项目列表页面,包含:
- 服务端分页的数据表格
- 搜索、排序、筛选
- 行内编辑
- 批量操作
- 导出功能练习 3:错误处理
请为所有 API 调用添加统一的错误处理:
- 网络错误重试(最多 3 次)
- 401 自动跳转登录
- 错误 Toast 提示
- 全局 ErrorBoundary七、本课总结
**核心要点:**
- Cursor 负责精调,Codex 负责生成和重构,两者互补
- 组件按功能拆分,保持单一职责
- SWR / React Query 管理服务端状态,避免手动管理 loading/error
- Tailwind 响应式前缀 + useMediaQuery Hook 覆盖多端适配
- 乐观更新提升体验,数据一致性靠 mutate 回调保障
**下一步:** 模块 404 — 数据库设计与优化