1. 設定前端項目
// Prompt cho Agent Mode:
Create a Next.js 15 frontend project with:
- TypeScript, App Router
- TailwindCSS v4 with custom design tokens
- Shadcn/ui components
- Axios for API calls
- React Hook Form + Zod validation
- Zustand for state management
- Connect to the backend API at localhost:3001
代理模式自動運行 npx 創建下一個應用程式,安裝依賴項,配置TailwindCSS,以及腳手架專案結構。
2. 組件生成-Vibe Coding 的力量
2.1.佈局元件
// Prompt:
Create a responsive dashboard layout with:
- Sidebar navigation (collapsible on mobile)
- Top header with user avatar dropdown
- Main content area with breadcrumbs
- Use TailwindCSS, dark mode support
- Sidebar items: Dashboard, Projects, Tasks, Settings
Copilot 產生帶有響應斷點的完整佈局:
// src/components/layout/DashboardLayout.tsx
'use client';
import { useState } from 'react';
import { Menu, X, ChevronDown } from 'lucide-react';
import Link from 'next/link';
interface DashboardLayoutProps {
children: React.ReactNode;
}
export function DashboardLayout({ children }: DashboardLayoutProps) {
const [sidebarOpen, setSidebarOpen] = useState(false);
const navItems = [
{ href: '/dashboard', label: 'Dashboard', icon: '📊' },
{ href: '/projects', label: 'Projects', icon: '📁' },
{ href: '/tasks', label: 'Tasks', icon: '✅' },
{ href: '/settings', label: 'Settings', icon: '⚙️' },
];
return (
<div className="flex h-screen bg-gray-50 dark:bg-gray-900">
{/* Sidebar */}
<aside className={`
fixed inset-y-0 left-0 z-50 w-64 bg-white dark:bg-gray-800
transform transition-transform duration-200 ease-in-out
${sidebarOpen ? 'translate-x-0' : '-translate-x-full'}
lg:translate-x-0 lg:static lg:inset-0
`}>
<nav className="mt-8 px-4 space-y-2">
{navItems.map((item) => (
<Link key={item.href} href={item.href}
className="flex items-center px-4 py-3 rounded-lg
hover:bg-gray-100 dark:hover:bg-gray-700">
<span className="mr-3">{item.icon}</span>
{item.label}
</Link>
))}
</nav>
</aside>
{/* Main content */}
<main className="flex-1 overflow-y-auto p-6">
{children}
</main>
</div>
);
}
2.2.任務板組件
// Prompt:
Create a Kanban board component for tasks with:
- Columns: Todo, In Progress, In Review, Done
- Drag and drop between columns (use @dnd-kit)
- Task cards showing title, priority badge, assignee avatar, due date
- Add task button per column
- Animate card movement
3. AI 表單處理
// Prompt:
Create a task creation form with:
- Title (required, max 255 chars)
- Description (optional, rich text editor)
- Priority select (Low, Medium, High, Urgent)
- Due date picker
- Assignee select (fetch from API)
- Project select
- Use React Hook Form + Zod validation
- Show inline validation errors
- Submit to POST /api/tasks
// src/components/tasks/CreateTaskForm.tsx
'use client';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { api } from '@/lib/api';
const taskSchema = z.object({
title: z.string().min(1, 'Title is required').max(255),
description: z.string().optional(),
priority: z.enum(['LOW', 'MEDIUM', 'HIGH', 'URGENT']),
dueDate: z.string().optional(),
assigneeId: z.string().uuid().optional(),
projectId: z.string().uuid(),
});
type TaskFormData = z.infer<typeof taskSchema>;
export function CreateTaskForm({ projectId, onSuccess }: {
projectId: string;
onSuccess: () => void;
}) {
const { register, handleSubmit, formState: { errors, isSubmitting } } =
useForm<TaskFormData>({
resolver: zodResolver(taskSchema),
defaultValues: { projectId, priority: 'MEDIUM' },
});
const onSubmit = async (data: TaskFormData) => {
await api.post('/tasks', data);
onSuccess();
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<div>
<input {...register('title')}
placeholder="Task title"
className="w-full px-4 py-2 border rounded-lg" />
{errors.title &&
<p className="text-red-500 text-sm mt-1">{errors.title.message}</p>}
</div>
<div>
<select {...register('priority')} className="w-full px-4 py-2 border rounded-lg">
<option value="LOW">🟢 Low</option>
<option value="MEDIUM">🟡 Medium</option>
<option value="HIGH">🟠 High</option>
<option value="URGENT">🔴 Urgent</option>
</select>
</div>
<button type="submit" disabled={isSubmitting}
className="w-full bg-blue-600 text-white py-2 rounded-lg
hover:bg-blue-700 disabled:opacity-50">
{isSubmitting ? 'Creating...' : 'Create Task'}
</button>
</form>
);
}
4.API集成
// Prompt:
Create an API client module with:
- Axios instance with base URL and auth interceptor
- Automatic token refresh on 401
- Type-safe API functions for tasks, projects, auth
- Loading and error state hooks
// src/lib/api.ts
import axios from 'axios';
export const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001/api',
});
// Auth interceptor
api.interceptors.request.use((config) => {
const token = localStorage.getItem('accessToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Auto refresh on 401
api.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
const refreshToken = localStorage.getItem('refreshToken');
if (refreshToken) {
const { data } = await axios.post(
`${api.defaults.baseURL}/auth/refresh`,
{ refreshToken }
);
localStorage.setItem('accessToken', data.accessToken);
error.config.headers.Authorization = `Bearer ${data.accessToken}`;
return api.request(error.config);
}
}
return Promise.reject(error);
}
);
5. 使用 Zustand 進行狀態管理
// Prompt:
Create a Zustand store for task management with:
- Tasks list with CRUD operations
- Filter by status, priority, assignee
- Optimistic updates for status changes
- Integration with the API client
// src/stores/taskStore.ts
import { create } from 'zustand';
import { api } from '@/lib/api';
interface Task {
id: string;
title: string;
status: 'TODO' | 'IN_PROGRESS' | 'IN_REVIEW' | 'DONE';
priority: 'LOW' | 'MEDIUM' | 'HIGH' | 'URGENT';
assignee?: { id: string; name: string; avatar?: string };
}
interface TaskStore {
tasks: Task[];
loading: boolean;
fetchTasks: (projectId: string) => Promise<void>;
updateStatus: (taskId: string, status: Task['status']) => Promise<void>;
}
export const useTaskStore = create<TaskStore>((set, get) => ({
tasks: [],
loading: false,
fetchTasks: async (projectId) => {
set({ loading: true });
const { data } = await api.get(`/tasks?projectId=${projectId}`);
set({ tasks: data.data, loading: false });
},
updateStatus: async (taskId, status) => {
// Optimistic update
const previousTasks = get().tasks;
set({
tasks: previousTasks.map((t) =>
t.id === taskId ? { ...t, status } : t
),
});
try {
await api.put(`/tasks/${taskId}`, { status });
} catch {
// Rollback on error
set({ tasks: previousTasks });
}
},
}));
6.響應式設計
// Prompt:
Make the task board responsive:
- Desktop: 4-column Kanban layout
- Tablet: 2-column grid with horizontal scroll
- Mobile: single column with tab navigation for each status
- Preserve drag and drop on tablet, disable on mobile
Copilot 根據螢幕尺寸產生反應斷點和條件渲染。
7. 深色模式
// Prompt:
Add dark mode toggle with:
- System preference detection
- Manual toggle in header
- Persist preference in localStorage
- Smooth transition animation
8. 測試前端組件
// Prompt:
Write tests for CreateTaskForm component:
- Renders all form fields
- Shows validation errors for empty title
- Submits form data to API
- Disables button while submitting
- Calls onSuccess after successful submit
Use React Testing Library + Jest
// src/components/tasks/__tests__/CreateTaskForm.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { CreateTaskForm } from '../CreateTaskForm';
import { api } from '@/lib/api';
jest.mock('@/lib/api');
describe('CreateTaskForm', () => {
const mockOnSuccess = jest.fn();
const projectId = 'test-project-id';
it('shows validation error for empty title', async () => {
render(<CreateTaskForm projectId={projectId} onSuccess={mockOnSuccess} />);
await userEvent.click(screen.getByText('Create Task'));
await waitFor(() => {
expect(screen.getByText('Title is required')).toBeInTheDocument();
});
});
it('submits form data correctly', async () => {
(api.post as jest.Mock).mockResolvedValue({ data: {} });
render(<CreateTaskForm projectId={projectId} onSuccess={mockOnSuccess} />);
await userEvent.type(screen.getByPlaceholderText('Task title'), 'New Task');
await userEvent.click(screen.getByText('Create Task'));
await waitFor(() => {
expect(api.post).toHaveBeenCalledWith('/tasks', expect.objectContaining({
title: 'New Task',
projectId,
priority: 'MEDIUM',
}));
expect(mockOnSuccess).toHaveBeenCalled();
});
});
});
9. 使用 Copilot 調試前端
當遇到運行時錯誤:
// Copy error vào chat: I'm getting this error in the browser console: "TypeError: Cannot read properties of undefined (reading 'map')" at TaskBoard.tsx:45
The tasks data from the API might be null on first render. Fix this with proper null checks and loading state.
Copilot 分析錯誤、尋找根本原因並修復程式碼 - 通常會新增空檢查和載入狀態。
10. 總結
| 任務 | Vibe 編碼提示 | 輸出 |
|---|---|---|
| 佈局 | 描述佈局結構 | 響應式儀表板 |
| 組件 | 描述 UI 行為 | 互動組件 |
| 表格 | 描述字段+驗證 | 類型安全的表單 |
| 應用程式介面 | 描述端點 | 類型安全的客戶端 |
| 狀態 | 描述資料流 | 祖斯坦店 |
| 測試 | “為 X 編寫測試” | RTL 測試套件 |
前端 Vibe 編碼非常強大,因為 UI 元件是 模式重複很多 — AI 擅長產生重複的 UI 程式碼。
下一篇:擴展至 行動和跨平台 — 使用 Vibe 編碼回應 Native 和 Flutter。