diff --git a/frontend/src/app/board/components.tsx b/frontend/src/app/board/components.tsx
new file mode 100644
index 0000000..c397150
--- /dev/null
+++ b/frontend/src/app/board/components.tsx
@@ -0,0 +1,100 @@
+'use client';
+/**
+ * 任务看板子组件:BoardColumn(状态列)+ TaskCard(任务卡,只读,点击进详情)
+ * C1(倩倩姐2026-06-18拍板「做轻量看板」):按状态分列只读视图,无拖拽。
+ * 复用 history 的状态色 + Link 跳转风格,保持视觉一致不割裂。
+ */
+import Link from 'next/link';
+import { clsx } from 'clsx';
+import { TaskListItem } from '@/types';
+
+// 列定义:4 列主流程,每列收哪些 status(倩倩姐拍板 failed/archived 不进看板)
+export interface ColumnDef {
+ key: string;
+ label: string;
+ statuses: string[];
+ accent: string; // 列头强调色
+}
+
+export const BOARD_COLUMNS: ColumnDef[] = [
+ { key: 'generating', label: '生成中',
+ statuses: ['pending', 'generating', 'pending_selection'],
+ accent: 'text-blue-600 bg-blue-50' },
+ { key: 'review', label: '待审核',
+ statuses: ['pending_review'], accent: 'text-amber-600 bg-amber-50' },
+ { key: 'approved', label: '已通过',
+ statuses: ['approved'], accent: 'text-green-600 bg-green-50' },
+ { key: 'rejected', label: '已打回',
+ statuses: ['rejected'], accent: 'text-red-500 bg-red-50' },
+];
+
+// 卡片点击目标:生成中/待审核进 confirm 看文案,其余进 images 排图/查看
+function cardHref(task: TaskListItem): string {
+ if (['pending', 'generating', 'pending_selection', 'pending_review'].includes(task.status)) {
+ return `/tasks/${task.id}/confirm`;
+ }
+ return `/tasks/${task.id}/images`;
+}
+
+// --- TaskCard --- 单个任务卡,点击进详情
+export function TaskCard({ task }: { task: TaskListItem }) {
+ return (
+
+
{task.theme}
+ {task.product_name ?? '—'}
+
+ {task.text_count} 文
+ ·
+ {task.image_count} 图
+ {new Date(task.created_at).toLocaleDateString('zh-CN')}
+
+ {/* 被打回任务:列内直接显示原因摘要,运营一眼看到为何被打回 */}
+ {task.status === 'rejected' && task.reject_reason && (
+
+ {task.reject_reason}
+
+ )}
+
+ );
+}
+
+// --- BoardColumn --- 状态列:列头 + 计数 + 卡片堆叠
+export function BoardColumn({ col, tasks }: { col: ColumnDef; tasks: TaskListItem[] }) {
+ return (
+
+
+
+ {col.label}
+
+ {tasks.length}
+
+
+ {tasks.length === 0 ? (
+
暂无任务
+ ) : (
+ tasks.map((t) =>
)
+ )}
+
+
+ );
+}
+
+// --- BoardSkeleton --- 加载骨架
+export function BoardSkeleton() {
+ return (
+
+ {BOARD_COLUMNS.map((c) => (
+
+
+ {Array.from({ length: 3 }).map((_, i) => (
+
+ ))}
+
+ ))}
+
+ );
+}
diff --git a/frontend/src/app/board/page.tsx b/frontend/src/app/board/page.tsx
new file mode 100644
index 0000000..0ad59c8
--- /dev/null
+++ b/frontend/src/app/board/page.tsx
@@ -0,0 +1,79 @@
+'use client';
+/**
+ * 任务看板 /board
+ * C1(倩倩姐2026-06-18拍板「做轻量看板」):按状态分列只读视图,点卡片进任务,无拖拽。
+ * 数据:GET /api/v1/tasks?status=...(跟随角色——运营看自己/组长管理员看全工作区,
+ * 权限逻辑与 history/review 一致,复用后端 list_tasks 不改接口)。
+ * failed/archived 不进看板(倩倩姐拍板),留在历史归档页看。
+ */
+import { useEffect, useState, useCallback } from 'react';
+import { api } from '@/lib/api';
+import { TaskListItem, PaginatedResponse } from '@/types';
+import { getErrorAction } from '@/types/errors';
+import { BOARD_COLUMNS, BoardColumn, BoardSkeleton } from './components';
+
+// 看板需要的全部状态(4 列并集),一次拉齐再前端分组
+const BOARD_STATUSES = BOARD_COLUMNS.flatMap((c) => c.statuses);
+const PAGE_SIZE = 100; // 看板一屏尽量全展示,进行中任务通常不会超百量级
+
+export default function BoardPage() {
+ const [tasks, setTasks] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ const fetchTasks = useCallback(async () => {
+ setLoading(true);
+ setError(null);
+ try {
+ const params = new URLSearchParams({ page: '1', page_size: String(PAGE_SIZE) });
+ BOARD_STATUSES.forEach((s) => params.append('status', s));
+ const res = await api.get>(`/api/v1/tasks?${params}`);
+ setTasks(res.items);
+ } catch (e: unknown) {
+ const ae = e as { code?: number };
+ setError(getErrorAction(ae.code ?? 50001).message);
+ } finally {
+ setLoading(false);
+ }
+ }, []);
+
+ useEffect(() => { fetchTasks(); }, [fetchTasks]);
+
+ // 按列分组:每列收自己 statuses 命中的任务
+ const byColumn = BOARD_COLUMNS.map((col) => ({
+ col,
+ items: tasks.filter((t) => col.statuses.includes(t.status)),
+ }));
+
+ return (
+
+
+
任务看板
+
+
+
+ {error && (
+
+ {error}
+
+
+ )}
+
+ {loading ? (
+
+ ) : (
+
+ {byColumn.map(({ col, items }) => (
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/frontend/src/components/layout/Sidebar.tsx b/frontend/src/components/layout/Sidebar.tsx
index 2ee4c95..cab6475 100644
--- a/frontend/src/components/layout/Sidebar.tsx
+++ b/frontend/src/components/layout/Sidebar.tsx
@@ -21,6 +21,7 @@ interface NavItem {
// 故落到 /history,每行提供"去排图"入口,不造无 task-id 的死链);裂变扩散=独立裂变页。
const NAV_ITEMS: NavItem[] = [
{ label: '工作台', href: '/dashboard', icon: '🏠' },
+ { label: '任务看板', href: '/board', icon: '📋' },
{ label: '文案创作', href: '/tasks/new', icon: '✏️' },
{ label: '图片排版', href: '/history', icon: '🖼️' },
{ label: '裂变扩散', href: '/fission', icon: '🌱' },
@@ -47,7 +48,7 @@ export function Sidebar() {
const active = pathname === item.href || pathname.startsWith(item.href + '/');
return (