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",