From 59d3f4d05409234c31379e6e506292486504dbd6 Mon Sep 17 00:00:00 2001 From: afei A <57030625+NewHubBoy@users.noreply.github.com> Date: Sat, 28 Mar 2026 00:28:51 +0800 Subject: [PATCH] feat: rewrite workflows page as dense list --- .../workflows/components/workflow-table.tsx | 108 ++++++++++ .../workflows/components/workflow-toolbar.tsx | 81 +++++++ src/features/workflows/workflow-lookup.tsx | 203 +++--------------- src/lib/adapters/workflows.ts | 8 + src/lib/types/view-models.ts | 4 + tests/app/api/workflow-lookup.route.test.ts | 4 + .../workflows/workflow-lookup.test.tsx | 12 +- tests/lib/adapters/workflows.test.ts | 3 + 8 files changed, 250 insertions(+), 173 deletions(-) create mode 100644 src/features/workflows/components/workflow-table.tsx create mode 100644 src/features/workflows/components/workflow-toolbar.tsx diff --git a/src/features/workflows/components/workflow-table.tsx b/src/features/workflows/components/workflow-table.tsx new file mode 100644 index 0000000..2405d29 --- /dev/null +++ b/src/features/workflows/components/workflow-table.tsx @@ -0,0 +1,108 @@ +import { Button } from "@/components/ui/button"; +import { EmptyState } from "@/components/ui/empty-state"; +import { StatusBadge } from "@/components/ui/status-badge"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import type { WorkflowLookupItemVM } from "@/lib/types/view-models"; + +type WorkflowTableProps = { + isLoading: boolean; + items: WorkflowLookupItemVM[]; + onOpenWorkflow?: (orderId: string) => void; +}; + +function formatTimestamp(timestamp: string) { + return timestamp.replace("T", " ").replace("Z", " UTC"); +} + +export function WorkflowTable({ + isLoading, + items, + onOpenWorkflow, +}: WorkflowTableProps) { + if (isLoading) { + return ( +
+ 正在加载流程列表… +
+ ); + } + + if (!items.length) { + return ( + + ); + } + + return ( + + + + 订单号 + workflowId + 流程类型 + 流程状态 + 当前步骤 + 失败次数 + 修订数 + 更新时间 + 操作 + + + + {items.map((item) => ( + + #{item.orderId} + {item.workflowId} + {item.workflowType} + + + + +
+ + + {item.currentStepLabel} + +
+
+ {item.failureCount} + +
+ + {item.revisionCount} + + {item.pendingManualConfirm ? ( + + 待确认 + + ) : null} +
+
+ + {formatTimestamp(item.updatedAt)} + + + + +
+ ))} +
+
+ ); +} diff --git a/src/features/workflows/components/workflow-toolbar.tsx b/src/features/workflows/components/workflow-toolbar.tsx new file mode 100644 index 0000000..89d7ffa --- /dev/null +++ b/src/features/workflows/components/workflow-toolbar.tsx @@ -0,0 +1,81 @@ +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { PageToolbar } from "@/components/ui/page-toolbar"; +import { Select } from "@/components/ui/select"; +import { ORDER_STATUS_META } from "@/lib/types/status"; +import type { OrderStatus } from "@/lib/types/backend"; + +export type WorkflowFilterStatus = OrderStatus | "all"; + +type WorkflowToolbarProps = { + currentPage: number; + query: string; + status: WorkflowFilterStatus; + totalPages: number; + onPageChange?: (page: number) => void; + onQueryChange: (value: string) => void; + onQuerySubmit?: (query: string) => void; + onStatusChange?: (value: WorkflowFilterStatus) => void; +}; + +export function WorkflowToolbar({ + currentPage, + query, + status, + totalPages, + onPageChange, + onQueryChange, + onQuerySubmit, + onStatusChange, +}: WorkflowToolbarProps) { + const safeTotalPages = Math.max(totalPages, 1); + + return ( + +
+ onQueryChange(event.target.value)} + /> + + +
+ +
+ + 第 {Math.min(currentPage, safeTotalPages)} / {safeTotalPages} 页 + + + +
+
+ ); +} diff --git a/src/features/workflows/workflow-lookup.tsx b/src/features/workflows/workflow-lookup.tsx index 19b0daf..c63bc0f 100644 --- a/src/features/workflows/workflow-lookup.tsx +++ b/src/features/workflows/workflow-lookup.tsx @@ -3,16 +3,15 @@ import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardDescription, CardEyebrow, CardHeader, CardTitle } from "@/components/ui/card"; -import { EmptyState } from "@/components/ui/empty-state"; +import { MetricChip } from "@/components/ui/metric-chip"; import { PageHeader } from "@/components/ui/page-header"; -import { StatusBadge } from "@/components/ui/status-badge"; -import { ORDER_STATUS_META } from "@/lib/types/status"; -import type { OrderStatus } from "@/lib/types/backend"; import type { WorkflowLookupItemVM } from "@/lib/types/view-models"; +import { + WorkflowToolbar, + type WorkflowFilterStatus, +} from "@/features/workflows/components/workflow-toolbar"; +import { WorkflowTable } from "@/features/workflows/components/workflow-table"; -type FilterStatus = OrderStatus | "all"; type PaginationData = { page: number; limit: number; @@ -28,9 +27,9 @@ type WorkflowLookupProps = { onOpenWorkflow?: (orderId: string) => void; onPageChange?: (page: number) => void; onQuerySubmit?: (query: string) => void; - onStatusChange?: (status: FilterStatus) => void; + onStatusChange?: (status: WorkflowFilterStatus) => void; selectedQuery?: string; - selectedStatus?: FilterStatus; + selectedStatus?: WorkflowFilterStatus; totalPages?: number; }; @@ -52,17 +51,6 @@ const DEFAULT_PAGINATION: PaginationData = { total: 0, totalPages: 0, }; -const WORKFLOW_STATUS_FILTER_OPTIONS: Array<{ - label: string; - value: FilterStatus; -}> = [ - { value: "all", label: "全部状态" }, - ...Object.entries(ORDER_STATUS_META).map(([value, meta]) => ({ - value: value as OrderStatus, - label: meta.label, - })), -]; - export function WorkflowLookup({ currentPage = 1, isLoading = false, @@ -76,171 +64,45 @@ export function WorkflowLookup({ selectedStatus = "all", totalPages = 0, }: WorkflowLookupProps) { - const [lookupValue, setLookupValue] = useState(""); const [queryValue, setQueryValue] = useState(selectedQuery); - const normalizedLookup = lookupValue.trim(); - const canLookup = /^\d+$/.test(normalizedLookup); - const effectiveTotalPages = Math.max(totalPages, 1); useEffect(() => { setQueryValue(selectedQuery); }, [selectedQuery]); return ( -
+
-
- {message} +
+ + +
-
- - -
- Direct lookup -
- 按订单号打开流程 - - 除了最近流程列表,也支持按订单号直接进入真实流程详情页。 - -
-
-
- - setLookupValue(event.target.value)} - placeholder="输入订单号,例如 4201" - className="min-h-12 w-full rounded-[24px] border border-[var(--border-soft)] bg-[var(--surface-muted)] px-4 text-sm text-[var(--ink-strong)] outline-none transition placeholder:text-[var(--ink-faint)] focus:border-[var(--accent-primary)] focus:bg-[var(--surface)]" - /> - - -
+

{message}

- - -
-
- Placeholder index -
- 流程索引占位 - - 这里已经接入真实后端最近流程列表,继续沿用首版查询页结构。 - -
-
-
- - -
-
-
- - {isLoading ? ( -
- 正在加载流程索引… -
- ) : null} + - {!isLoading && items.length ? ( - items.map((item) => ( -
-
-
-

- 订单 #{item.orderId} -

-

- {item.workflowId} / {item.workflowType} -

-
- -
-
- - {item.currentStepLabel} -
-
- )) - ) : null} - - {!isLoading && !items.length ? ( - - ) : null} - -
-

- 第 {Math.min(currentPage, effectiveTotalPages)} / {effectiveTotalPages} 页 -

-
- - -
-
-
-
-
+
); } @@ -251,7 +113,8 @@ export function WorkflowLookupScreen() { const [message, setMessage] = useState(DEFAULT_MESSAGE); const [isLoading, setIsLoading] = useState(true); const [selectedQuery, setSelectedQuery] = useState(""); - const [selectedStatus, setSelectedStatus] = useState("all"); + const [selectedStatus, setSelectedStatus] = + useState("all"); const [pagination, setPagination] = useState(DEFAULT_PAGINATION); useEffect(() => { diff --git a/src/lib/adapters/workflows.ts b/src/lib/adapters/workflows.ts index e2c907a..da9f857 100644 --- a/src/lib/adapters/workflows.ts +++ b/src/lib/adapters/workflows.ts @@ -102,6 +102,10 @@ export function adaptWorkflowLookupItem( | "workflow_type" | "workflow_status" | "current_step" + | "failure_count" + | "review_task_status" + | "revision_count" + | "pending_manual_confirm" | "updated_at" >, ): WorkflowLookupItemVM { @@ -113,6 +117,10 @@ export function adaptWorkflowLookupItem( statusMeta: getOrderStatusMeta(workflow.workflow_status), currentStep: workflow.current_step, currentStepLabel: getWorkflowStepMeta(workflow.current_step).label, + failureCount: workflow.failure_count, + reviewTaskStatus: workflow.review_task_status, + revisionCount: workflow.revision_count, + pendingManualConfirm: workflow.pending_manual_confirm, updatedAt: workflow.updated_at, }; } diff --git a/src/lib/types/view-models.ts b/src/lib/types/view-models.ts index 8de3de6..e0b3ccd 100644 --- a/src/lib/types/view-models.ts +++ b/src/lib/types/view-models.ts @@ -174,6 +174,10 @@ export type WorkflowLookupItemVM = { statusMeta: StatusMeta; currentStep: WorkflowStepName | null; currentStepLabel: string; + failureCount: number; + reviewTaskStatus: ReviewTaskStatus | null; + revisionCount: number; + pendingManualConfirm: boolean; updatedAt: string; }; diff --git a/tests/app/api/workflow-lookup.route.test.ts b/tests/app/api/workflow-lookup.route.test.ts index b97ca18..9e22609 100644 --- a/tests/app/api/workflow-lookup.route.test.ts +++ b/tests/app/api/workflow-lookup.route.test.ts @@ -70,6 +70,10 @@ test("proxies workflow lookup items from the backend workflows list api", async }, currentStep: "review", currentStepLabel: "人工审核", + failureCount: 0, + reviewTaskStatus: "revision_uploaded", + revisionCount: 1, + pendingManualConfirm: true, updatedAt: "2026-03-27T14:00:03Z", }, ], diff --git a/tests/features/workflows/workflow-lookup.test.tsx b/tests/features/workflows/workflow-lookup.test.tsx index 567e3b6..09fc579 100644 --- a/tests/features/workflows/workflow-lookup.test.tsx +++ b/tests/features/workflows/workflow-lookup.test.tsx @@ -16,15 +16,21 @@ const WORKFLOW_ITEMS: WorkflowLookupItemVM[] = [ }, currentStep: "review", currentStepLabel: "人工审核", + failureCount: 2, + reviewTaskStatus: "revision_uploaded", + revisionCount: 1, + pendingManualConfirm: true, updatedAt: "2026-03-27T09:15:00Z", }, ]; -test("shows the real recent-workflows entry state", () => { +test("renders workflows as a high-density table with shared toolbar controls", () => { render(); - expect(screen.getByText("流程追踪首页当前显示真实后端最近流程。")).toBeInTheDocument(); - expect(screen.getByText("订单 #4201")).toBeInTheDocument(); + expect(screen.getByRole("columnheader", { name: "流程类型" })).toBeInTheDocument(); + expect(screen.getByRole("columnheader", { name: "失败次数" })).toBeInTheDocument(); + expect(screen.getByLabelText("流程状态筛选")).toBeInTheDocument(); + expect(screen.getByText("#4201")).toBeInTheDocument(); }); test("supports workflow status filtering and pagination actions", () => { diff --git a/tests/lib/adapters/workflows.test.ts b/tests/lib/adapters/workflows.test.ts index a5c80d0..a008f47 100644 --- a/tests/lib/adapters/workflows.test.ts +++ b/tests/lib/adapters/workflows.test.ts @@ -79,6 +79,9 @@ test("maps workflow lookup status and current step labels", () => { status: "running", currentStep: "fusion", currentStepLabel: "融合", + failureCount: 0, + revisionCount: 0, + pendingManualConfirm: false, statusMeta: { label: "处理中", tone: "info",