From 2ca6ac2e480d25443e826e05fb860eb616ed2e31 Mon Sep 17 00:00:00 2001 From: yangqianqian <5845211314@qq.com> Date: Mon, 22 Jun 2026 10:10:33 +0800 Subject: [PATCH] =?UTF-8?q?C1:=20=E6=96=B0=E5=A2=9E=E8=BD=BB=E9=87=8F?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E7=9C=8B=E6=9D=BF/board(=E6=8C=89=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E5=88=86=E5=88=97=E5=8F=AA=E8=AF=BB=E8=A7=86=E5=9B=BE?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - board/components.tsx: BOARD_COLUMNS(4列:生成中/待审核/已通过/已打回)+TaskCard(只读点击进详情)+BoardColumn+BoardSkeleton - board/page.tsx: 复用 GET /api/v1/tasks?status=...(跟随角色不写前端权限),failed/archived不进看板 - Sidebar: 加「任务看板」📋入口;顺修两项同href的重复key warning(改key=label) - 卡片跳转:进行中/待审核→confirm,已通过/打回→images(路由均已存在) 独立agent交叉复核6项全过(状态全覆盖/多status参数与history一致/无前端权限/无死链/字段判空/边界安全),无must-fix Co-Authored-By: Claude Opus 4.8 --- frontend/src/app/board/components.tsx | 100 +++++++++++++++++++++ frontend/src/app/board/page.tsx | 79 ++++++++++++++++ frontend/src/components/layout/Sidebar.tsx | 3 +- 3 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 frontend/src/app/board/components.tsx create mode 100644 frontend/src/app/board/page.tsx 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 (