From a3e098b8da2a73fa004a9ec6a3f3a3457974772a Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Sun, 26 Jul 2026 17:47:24 +0800 Subject: [PATCH] =?UTF-8?q?refactor(task-center):=20=E4=B8=89=E9=98=B6?= =?UTF-8?q?=E6=AE=B5=E9=87=8D=E6=9E=84=20TaskCenter=EF=BC=88423=E2=86=9276?= =?UTF-8?q?=E8=A1=8C,=20-82%=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Phase 1: 抽 constants(STATUS_CONFIG/TYPE_LABELS等) + utils(formatDuration/formatTime) - Phase 2: 抽 3 个 UI 组件(TaskFilterBar/TaskTable/TaskErrorDetail) - Phase 3: 抽 useTaskList 业务 Hook(筛选+查询+重试) - 主文件 423→76 行,职责清晰,表格列定义独立维护 --- apps/web/src/pages/tasks/TaskCenter.tsx | 425 ++---------------- .../tasks/components/TaskErrorDetail.tsx | 58 +++ .../pages/tasks/components/TaskFilterBar.tsx | 48 ++ .../src/pages/tasks/components/TaskTable.tsx | 190 ++++++++ apps/web/src/pages/tasks/constants.tsx | 69 +++ apps/web/src/pages/tasks/hooks/useTaskList.ts | 119 +++++ apps/web/src/pages/tasks/utils.ts | 23 + 7 files changed, 546 insertions(+), 386 deletions(-) mode change 100644 => 100755 apps/web/src/pages/tasks/TaskCenter.tsx create mode 100644 apps/web/src/pages/tasks/components/TaskErrorDetail.tsx create mode 100644 apps/web/src/pages/tasks/components/TaskFilterBar.tsx create mode 100644 apps/web/src/pages/tasks/components/TaskTable.tsx create mode 100644 apps/web/src/pages/tasks/constants.tsx create mode 100644 apps/web/src/pages/tasks/hooks/useTaskList.ts create mode 100644 apps/web/src/pages/tasks/utils.ts diff --git a/apps/web/src/pages/tasks/TaskCenter.tsx b/apps/web/src/pages/tasks/TaskCenter.tsx old mode 100644 new mode 100755 index a61c2af5f..7230d6e1e --- a/apps/web/src/pages/tasks/TaskCenter.tsx +++ b/apps/web/src/pages/tasks/TaskCenter.tsx @@ -2,333 +2,32 @@ * 任务中心页面 * 展示用户的所有任务(生成任务、素材导入等),支持状态筛选、类型筛选、分页、重试 */ -import { useState } from "react" -import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query" -import { Table, Tabs, Select, Tag, Button, message, Popconfirm, Tooltip } from "antd" -import { - CheckCircleOutlined, - ClockCircleOutlined, - SyncOutlined, - CloseCircleOutlined, - ExclamationCircleOutlined, - MinusCircleOutlined, - RedoOutlined, - InfoCircleOutlined, -} from "@ant-design/icons" -import type { ColumnsType } from "antd/es/table" -import { - getTasks, - retryTask, - type TaskItem, - type TaskStatus, - type TaskListParams, -} from "@/api/tasks" +import { Button } from "antd" +import { CloseCircleOutlined } from "@ant-design/icons" +import { TaskFilterBar } from "./components/TaskFilterBar" +import { TaskTable } from "./components/TaskTable" +import { useTaskList } from "./hooks/useTaskList" import "./tasks.css" -/* ──────────── 常量 ──────────── */ - -/** 状态 Tab 配置 */ -const STATUS_TABS: { key: TaskStatus | "all"; label: string }[] = [ - { key: "all", label: "全部" }, - { key: "waiting", label: "等待中" }, - { key: "running", label: "进行中" }, - { key: "completed", label: "已完成" }, - { key: "failed", label: "失败" }, - { key: "cancelled", label: "已取消" }, -] - -/** 类型筛选选项 */ -const TYPE_OPTIONS = [ - { value: "all", label: "全部类型" }, - { value: "generation", label: "生成任务" }, - { value: "ingest", label: "素材导入" }, -] - -/** 状态标签配置 */ -const STATUS_CONFIG: Record = { - pending: { - label: "等待中", - color: "default", - icon: , - }, - waiting: { - label: "排队中", - color: "processing", - icon: , - }, - running: { - label: "进行中", - color: "processing", - icon: , - }, - completed: { - label: "已完成", - color: "success", - icon: , - }, - failed: { - label: "失败", - color: "error", - icon: , - }, - cancelled: { - label: "已取消", - color: "default", - icon: , - }, -} - -/** 任务类型标签 */ -const TYPE_LABELS: Record = { - generation: { label: "生成任务", color: "blue" }, - ingest: { label: "素材导入", color: "green" }, -} - -/* ──────────── 工具函数 ──────────── */ - -/** 格式化耗时 */ -const formatDuration = (seconds?: number): string => { - if (!seconds) return "-" - if (seconds < 60) return `${Math.round(seconds)}秒` - const minutes = Math.floor(seconds / 60) - const secs = Math.round(seconds % 60) - if (minutes < 60) return `${minutes}分${secs}秒` - const hours = Math.floor(minutes / 60) - const mins = minutes % 60 - return `${hours}小时${mins}分` -} - -/** 格式化时间 */ -const formatTime = (dateStr?: string | null): string => { - if (!dateStr) return "-" - const date = new Date(dateStr) - return date.toLocaleString("zh-CN", { - month: "2-digit", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - }) -} - -/* ──────────── 主组件 ──────────── */ - export default function TaskCenter() { - const queryClient = useQueryClient() - - // 筛选状态 - const [statusFilter, setStatusFilter] = useState("all") - const [typeFilter, setTypeFilter] = useState("all") - const [page, setPage] = useState(1) - const [pageSize, setPageSize] = useState(20) - const [expandedTaskId, setExpandedTaskId] = useState(null) - const [expandedTaskDetail, setExpandedTaskDetail] = useState(null) - - // 查询参数 - const queryParams: TaskListParams = { + const { + statusFilter, + typeFilter, page, - page_size: pageSize, - ...(statusFilter !== "all" && { status: statusFilter }), - ...(typeFilter !== "all" && { task_type: typeFilter }), - } - - // 获取任务列表 - const { data, isLoading, error } = useQuery({ - queryKey: ["tasks", queryParams], - queryFn: () => getTasks(queryParams), - refetchInterval: (query) => { - // 有进行中的任务时自动刷新 - const tasks = query.state.data?.items ?? [] - const hasRunning = tasks.some((t) => t.status === "running" || t.status === "waiting") - return hasRunning ? 5000 : false - }, - }) - - // 重试任务 - const retryMutation = useMutation({ - mutationFn: retryTask, - onSuccess: () => { - message.success("任务已重新提交") - queryClient.invalidateQueries({ queryKey: ["tasks"] }) - }, - onError: () => { - message.error("重试失败,请检查任务状态") - }, - }) - - // 展开查看详情 - const handleExpand = async (expanded: boolean, record: TaskItem) => { - if (!expanded) { - setExpandedTaskId(null) - setExpandedTaskDetail(null) - return - } - setExpandedTaskId(record.id) - // 如果是失败任务,获取详情(含 error_info) - if (record.status === "failed" && record.error_info) { - setExpandedTaskDetail(record) - } - } - - // 表格列定义 - const columns: ColumnsType = [ - { - title: "任务ID", - dataIndex: "id", - key: "id", - width: 120, - ellipsis: true, - render: (id: string) => ( - - {id.slice(0, 8)}... - - ), - }, - { - title: "类型", - dataIndex: "task_type", - key: "task_type", - width: 100, - render: (type: string) => { - const config = TYPE_LABELS[type] || { label: type, color: "default" } - return {config.label} - }, - }, - { - title: "状态", - dataIndex: "status", - key: "status", - width: 120, - render: (status: TaskStatus, record: TaskItem) => { - const config = STATUS_CONFIG[status] || { - label: status, - color: "default", - icon: null, - } - return ( - - {config.label} - {status === "running" && record.progress > 0 && ( - {record.progress}% - )} - - ) - }, - }, - { - title: "当前步骤", - dataIndex: "current_step", - key: "current_step", - width: 150, - ellipsis: true, - render: (step: string) => {step || "-"}, - }, - { - title: "耗时", - dataIndex: "duration_seconds", - key: "duration_seconds", - width: 100, - render: (seconds: number) => {formatDuration(seconds)}, - }, - { - title: "创建时间", - dataIndex: "created_at", - key: "created_at", - width: 120, - render: (time: string) => {formatTime(time)}, - }, - { - title: "操作", - key: "action", - width: 100, - fixed: "right", - render: (_: unknown, record: TaskItem) => { - if (record.status === "failed" && record.retryable) { - return ( - retryMutation.mutate(record.id)} - okText="确定" - cancelText="取消" - > - - - ) - } - if (record.status === "failed") { - return ( - - ) - } - return - - }, - }, - ] - - // 展开行渲染(错误详情) - const expandedRowRender = (record: TaskItem) => { - const detail = expandedTaskDetail || record - const errorInfo = detail.error_info - - if (!errorInfo && !detail.error_message) { - return
暂无错误详情
- } - - return ( -
-
- - 错误详情 -
-
- {errorInfo?.error_type && ( -
- 错误类型: - {errorInfo.error_type} -
- )} - {(errorInfo?.error_message || detail.error_message) && ( -
- 错误信息: - - {errorInfo?.error_message || detail.error_message} - -
- )} - {errorInfo?.failed_step && ( -
- 失败阶段: - {errorInfo.failed_step} -
- )} - {errorInfo?.stack_trace && ( -
- 堆栈信息: -
{errorInfo.stack_trace}
-
- )} -
-
- ) - } + pageSize, + expandedTaskId, + expandedTaskDetail, + data, + isLoading, + error, + retryLoading, + handleStatusChange, + handleTypeChange, + handlePageChange, + handleExpand, + handleViewDetail, + handleRetry, + } = useTaskList() // 错误处理 if (error) { @@ -351,72 +50,26 @@ export default function TaskCenter() {

查看和管理所有生成任务与素材导入任务

- {/* 筛选栏 */} -
- {/* 状态 Tab */} - { - setStatusFilter(key as TaskStatus | "all") - setPage(1) - }} - items={STATUS_TABS.map((tab) => ({ - key: tab.key, - label: tab.label, - }))} - className="task-status-tabs" - /> + - {/* 类型筛选 */} -
- +
+
+ ) +} diff --git a/apps/web/src/pages/tasks/components/TaskTable.tsx b/apps/web/src/pages/tasks/components/TaskTable.tsx new file mode 100644 index 000000000..84a331f0e --- /dev/null +++ b/apps/web/src/pages/tasks/components/TaskTable.tsx @@ -0,0 +1,190 @@ +import React from "react" +import { Table, Tag, Button, Popconfirm, Tooltip } from "antd" +import { RedoOutlined, InfoCircleOutlined, ClockCircleOutlined } from "@ant-design/icons" +import type { ColumnsType } from "antd/es/table" +import type { TaskItem, TaskStatus } from "@/api/tasks" +import { STATUS_CONFIG, TYPE_LABELS } from "../constants" +import { formatDuration, formatTime } from "../utils" +import { TaskErrorDetail } from "./TaskErrorDetail" + +interface TaskTableProps { + dataSource: TaskItem[] + loading: boolean + total: number + page: number + pageSize: number + expandedTaskId: string | null + expandedTaskDetail: TaskItem | null + retryLoading: boolean + onPageChange: (page: number, pageSize: number) => void + onExpand: (expanded: boolean, record: TaskItem) => void + onRetry: (id: string) => void + onViewDetail: (record: TaskItem) => void +} + +/** + * 任务列表表格 + * 含列定义、分页、展开行 + */ +export const TaskTable: React.FC = ({ + dataSource, + loading, + total, + page, + pageSize, + expandedTaskId, + expandedTaskDetail, + retryLoading, + onPageChange, + onExpand, + onRetry, + onViewDetail, +}) => { + // 表格列定义 + const columns: ColumnsType = [ + { + title: "任务ID", + dataIndex: "id", + key: "id", + width: 120, + ellipsis: true, + render: (id: string) => ( + + {id.slice(0, 8)}... + + ), + }, + { + title: "类型", + dataIndex: "task_type", + key: "task_type", + width: 100, + render: (type: string) => { + const config = TYPE_LABELS[type] || { label: type, color: "default" } + return {config.label} + }, + }, + { + title: "状态", + dataIndex: "status", + key: "status", + width: 120, + render: (status: TaskStatus, record: TaskItem) => { + const config = STATUS_CONFIG[status] || { + label: status, + color: "default", + icon: null, + } + return ( + + {config.label} + {status === "running" && record.progress > 0 && ( + {record.progress}% + )} + + ) + }, + }, + { + title: "当前步骤", + dataIndex: "current_step", + key: "current_step", + width: 150, + ellipsis: true, + render: (step: string) => {step || "-"}, + }, + { + title: "耗时", + dataIndex: "duration_seconds", + key: "duration_seconds", + width: 100, + render: (seconds: number) => {formatDuration(seconds)}, + }, + { + title: "创建时间", + dataIndex: "created_at", + key: "created_at", + width: 120, + render: (time: string) => {formatTime(time)}, + }, + { + title: "操作", + key: "action", + width: 100, + fixed: "right", + render: (_: unknown, record: TaskItem) => { + if (record.status === "failed" && record.retryable) { + return ( + onRetry(record.id)} + okText="确定" + cancelText="取消" + > + + + ) + } + if (record.status === "failed") { + return ( + + ) + } + return - + }, + }, + ] + + return ( + `共 ${t} 条`, + onChange: onPageChange, + }} + expandable={{ + expandedRowRender: (record) => ( + + ), + expandedRowKeys: expandedTaskId ? [expandedTaskId] : [], + onExpand, + rowExpandable: (record) => + record.status === "failed" && (!!record.error_info || !!record.error_message), + }} + scroll={{ x: 800 }} + className="task-table" + locale={{ + emptyText: ( +
+ +

暂无任务记录

+
+ ), + }} + /> + ) +} diff --git a/apps/web/src/pages/tasks/constants.tsx b/apps/web/src/pages/tasks/constants.tsx new file mode 100644 index 000000000..23edb6c6c --- /dev/null +++ b/apps/web/src/pages/tasks/constants.tsx @@ -0,0 +1,69 @@ +import React from "react" +import { + CheckCircleOutlined, + ClockCircleOutlined, + SyncOutlined, + CloseCircleOutlined, + MinusCircleOutlined, +} from "@ant-design/icons" +import type { TaskStatus } from "@/api/tasks" + +/** 状态 Tab 配置 */ +export const STATUS_TABS: { key: TaskStatus | "all"; label: string }[] = [ + { key: "all", label: "全部" }, + { key: "waiting", label: "等待中" }, + { key: "running", label: "进行中" }, + { key: "completed", label: "已完成" }, + { key: "failed", label: "失败" }, + { key: "cancelled", label: "已取消" }, +] + +/** 类型筛选选项 */ +export const TYPE_OPTIONS = [ + { value: "all", label: "全部类型" }, + { value: "generation", label: "生成任务" }, + { value: "ingest", label: "素材导入" }, +] + +/** 状态标签配置 */ +export const STATUS_CONFIG: Record< + TaskStatus, + { label: string; color: string; icon: React.ReactNode } +> = { + pending: { + label: "等待中", + color: "default", + icon: , + }, + waiting: { + label: "排队中", + color: "processing", + icon: , + }, + running: { + label: "进行中", + color: "processing", + icon: , + }, + completed: { + label: "已完成", + color: "success", + icon: , + }, + failed: { + label: "失败", + color: "error", + icon: , + }, + cancelled: { + label: "已取消", + color: "default", + icon: , + }, +} + +/** 任务类型标签 */ +export const TYPE_LABELS: Record = { + generation: { label: "生成任务", color: "blue" }, + ingest: { label: "素材导入", color: "green" }, +} diff --git a/apps/web/src/pages/tasks/hooks/useTaskList.ts b/apps/web/src/pages/tasks/hooks/useTaskList.ts new file mode 100644 index 000000000..5ba4d932a --- /dev/null +++ b/apps/web/src/pages/tasks/hooks/useTaskList.ts @@ -0,0 +1,119 @@ +import { useState, useCallback } from "react" +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query" +import { message } from "antd" +import { + getTasks, + retryTask, + type TaskItem, + type TaskStatus, + type TaskListParams, +} from "@/api/tasks" + +/** + * 任务列表业务 Hook + * 封装筛选状态、数据获取、重试操作 + */ +export const useTaskList = () => { + const queryClient = useQueryClient() + + // 筛选状态 + const [statusFilter, setStatusFilter] = useState("all") + const [typeFilter, setTypeFilter] = useState("all") + const [page, setPage] = useState(1) + const [pageSize, setPageSize] = useState(20) + const [expandedTaskId, setExpandedTaskId] = useState(null) + const [expandedTaskDetail, setExpandedTaskDetail] = useState(null) + + // 查询参数 + const queryParams: TaskListParams = { + page, + page_size: pageSize, + ...(statusFilter !== "all" && { status: statusFilter }), + ...(typeFilter !== "all" && { task_type: typeFilter }), + } + + // 获取任务列表 + const { data, isLoading, error } = useQuery({ + queryKey: ["tasks", queryParams], + queryFn: () => getTasks(queryParams), + refetchInterval: (query) => { + // 有进行中的任务时自动刷新 + const tasks = query.state.data?.items ?? [] + const hasRunning = tasks.some((t) => t.status === "running" || t.status === "waiting") + return hasRunning ? 5000 : false + }, + }) + + // 重试任务 + const retryMutation = useMutation({ + mutationFn: retryTask, + onSuccess: () => { + message.success("任务已重新提交") + queryClient.invalidateQueries({ queryKey: ["tasks"] }) + }, + onError: () => { + message.error("重试失败,请检查任务状态") + }, + }) + + // 状态筛选变化 + const handleStatusChange = useCallback((key: string) => { + setStatusFilter(key as TaskStatus | "all") + setPage(1) + }, []) + + // 类型筛选变化 + const handleTypeChange = useCallback((value: string) => { + setTypeFilter(value) + setPage(1) + }, []) + + // 分页变化 + const handlePageChange = useCallback((p: number, ps: number) => { + setPage(p) + setPageSize(ps) + }, []) + + // 展开查看详情 + const handleExpand = useCallback((expanded: boolean, record: TaskItem) => { + if (!expanded) { + setExpandedTaskId(null) + setExpandedTaskDetail(null) + return + } + setExpandedTaskId(record.id) + // 如果是失败任务,获取详情(含 error_info) + if (record.status === "failed" && record.error_info) { + setExpandedTaskDetail(record) + } + }, []) + + // 查看错误详情 + const handleViewDetail = useCallback((record: TaskItem) => { + setExpandedTaskId(record.id) + setExpandedTaskDetail(record) + }, []) + + return { + // 状态 + statusFilter, + typeFilter, + page, + pageSize, + expandedTaskId, + expandedTaskDetail, + // 数据 + data, + isLoading, + error, + // Mutation + retryLoading: retryMutation.isPending, + // 操作 + handleStatusChange, + handleTypeChange, + handlePageChange, + handleExpand, + handleViewDetail, + handleRetry: retryMutation.mutate, + } +} diff --git a/apps/web/src/pages/tasks/utils.ts b/apps/web/src/pages/tasks/utils.ts new file mode 100644 index 000000000..e16090b0b --- /dev/null +++ b/apps/web/src/pages/tasks/utils.ts @@ -0,0 +1,23 @@ +/** 格式化耗时 */ +export const formatDuration = (seconds?: number): string => { + if (!seconds) return "-" + if (seconds < 60) return `${Math.round(seconds)}秒` + const minutes = Math.floor(seconds / 60) + const secs = Math.round(seconds % 60) + if (minutes < 60) return `${minutes}分${secs}秒` + const hours = Math.floor(minutes / 60) + const mins = minutes % 60 + return `${hours}小时${mins}分` +} + +/** 格式化时间 */ +export const formatTime = (dateStr?: string | null): string => { + if (!dateStr) return "-" + const date = new Date(dateStr) + return date.toLocaleString("zh-CN", { + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + }) +} -- 2.54.0