From ae8ab2cf9cb1ac13816ed3a389c1b07770d18133 Mon Sep 17 00:00:00 2001 From: afei A <57030625+NewHubBoy@users.noreply.github.com> Date: Sat, 28 Mar 2026 00:25:45 +0800 Subject: [PATCH] feat: rewrite orders page as dense list --- .../orders/components/orders-table.tsx | 118 +++++++++ .../orders/components/orders-toolbar.tsx | 97 +++++++ src/features/orders/orders-home.tsx | 238 ++++-------------- src/lib/adapters/orders.ts | 16 +- src/lib/types/view-models.ts | 5 + tests/app/api/orders-overview.route.test.ts | 5 + tests/features/orders/orders-home.test.tsx | 13 +- tests/lib/adapters/orders.test.ts | 4 + 8 files changed, 299 insertions(+), 197 deletions(-) create mode 100644 src/features/orders/components/orders-table.tsx create mode 100644 src/features/orders/components/orders-toolbar.tsx diff --git a/src/features/orders/components/orders-table.tsx b/src/features/orders/components/orders-table.tsx new file mode 100644 index 0000000..5b751d7 --- /dev/null +++ b/src/features/orders/components/orders-table.tsx @@ -0,0 +1,118 @@ +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 { OrderSummaryVM } from "@/lib/types/view-models"; + +type OrdersTableProps = { + isLoading: boolean; + items: OrderSummaryVM[]; + onOpenOrder?: (orderId: string) => void; + onOpenWorkflow?: (orderId: string) => void; +}; + +function formatTimestamp(timestamp: string) { + return timestamp.replace("T", " ").replace("Z", " UTC"); +} + +export function OrdersTable({ + isLoading, + items, + onOpenOrder, + onOpenWorkflow, +}: OrdersTableProps) { + if (isLoading) { + return ( +
+ 正在加载订单列表… +
+ ); + } + + if (!items.length) { + return ( + + ); + } + + return ( + + + + 订单号 + workflowId + 客户等级 + 服务模式 + 状态 + 当前步骤 + 修订数 + 更新时间 + 操作 + + + + {items.map((order) => ( + + #{order.orderId} + {order.workflowId ?? "未关联"} + {order.customerLevel} + {order.serviceMode} + + + + +
+ + + {order.currentStepLabel} + +
+
+ +
+ + {order.revisionCount} + + {order.pendingManualConfirm ? ( + + 待确认 + + ) : null} +
+
+ + {formatTimestamp(order.updatedAt)} + + +
+ + +
+
+
+ ))} +
+
+ ); +} diff --git a/src/features/orders/components/orders-toolbar.tsx b/src/features/orders/components/orders-toolbar.tsx new file mode 100644 index 0000000..a464587 --- /dev/null +++ b/src/features/orders/components/orders-toolbar.tsx @@ -0,0 +1,97 @@ +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, ServiceMode } from "@/lib/types/backend"; + +export type OrderFilterStatus = OrderStatus | "all"; +export type OrderFilterServiceMode = ServiceMode | "all"; + +type OrdersToolbarProps = { + currentPage: number; + query: string; + serviceMode: OrderFilterServiceMode; + status: OrderFilterStatus; + totalPages: number; + onPageChange?: (page: number) => void; + onQueryChange: (value: string) => void; + onQuerySubmit?: (query: string) => void; + onServiceModeChange: (value: OrderFilterServiceMode) => void; + onStatusChange?: (value: OrderFilterStatus) => void; +}; + +export function OrdersToolbar({ + currentPage, + query, + serviceMode, + status, + totalPages, + onPageChange, + onQueryChange, + onQuerySubmit, + onServiceModeChange, + onStatusChange, +}: OrdersToolbarProps) { + const safeTotalPages = Math.max(totalPages, 1); + + return ( + +
+ onQueryChange(event.target.value)} + /> + + + +
+ +
+ + 第 {Math.min(currentPage, safeTotalPages)} / {safeTotalPages} 页 + + + +
+
+ ); +} diff --git a/src/features/orders/orders-home.tsx b/src/features/orders/orders-home.tsx index fa4f49c..57077f7 100644 --- a/src/features/orders/orders-home.tsx +++ b/src/features/orders/orders-home.tsx @@ -3,16 +3,16 @@ 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 { OrderSummaryVM } from "@/lib/types/view-models"; +import { + OrdersToolbar, + type OrderFilterServiceMode, + type OrderFilterStatus, +} from "@/features/orders/components/orders-toolbar"; +import { OrdersTable } from "@/features/orders/components/orders-table"; -type FilterStatus = OrderStatus | "all"; type PaginationData = { page: number; limit: number; @@ -28,10 +28,10 @@ type OrdersHomeProps = { onOpenWorkflow?: (orderId: string) => void; onPageChange?: (page: number) => void; onQuerySubmit?: (query: string) => void; - onStatusChange?: (status: FilterStatus) => void; + onStatusChange?: (status: OrderFilterStatus) => void; recentOrders: OrderSummaryVM[]; selectedQuery?: string; - selectedStatus?: FilterStatus; + selectedStatus?: OrderFilterStatus; totalPages?: number; }; @@ -47,27 +47,13 @@ type OrdersOverviewEnvelope = { }; const TITLE_MESSAGE = "最近订单已接入真实后端接口"; -const DEFAULT_MESSAGE = "当前首页展示真实最近订单,同时保留订单号直达入口,方便快速跳转详情和流程。"; +const DEFAULT_MESSAGE = "当前页面直接展示真实订单列表,支持关键词、状态和分页操作。"; const DEFAULT_PAGINATION: PaginationData = { page: 1, limit: 6, total: 0, totalPages: 0, }; -const ORDER_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, - })), -]; - -function formatTimestamp(timestamp: string) { - return timestamp.replace("T", " ").replace("Z", " UTC"); -} export function OrdersHome({ currentPage = 1, @@ -83,190 +69,55 @@ export function OrdersHome({ selectedStatus = "all", totalPages = 0, }: OrdersHomeProps) { - const [lookupValue, setLookupValue] = useState(""); const [queryValue, setQueryValue] = useState(selectedQuery); - const normalizedLookup = lookupValue.trim(); - const canLookup = /^\d+$/.test(normalizedLookup); - const effectiveTotalPages = Math.max(totalPages, 1); + const [serviceModeFilter, setServiceModeFilter] = + useState("all"); useEffect(() => { setQueryValue(selectedQuery); }, [selectedQuery]); + const visibleOrders = + serviceModeFilter === "all" + ? recentOrders + : recentOrders.filter((order) => order.serviceMode === serviceModeFilter); + return ( -
+
-
-

- {TITLE_MESSAGE} -

-

- {message} -

+
+ + +
-
- - -
- Direct lookup -
- 订单号直达 - - 保留订单号和流程号的直接入口,适合在列表之外快速跳转到指定订单。 - -
-
-
- - -
- - -
-
-
+

{message}

- - -
-
- Recent visits -
- 最近访问 - - 这里已经接入真实后端最近订单列表,页面结构继续沿用首版设计。 - -
-
-
- - -
-
-
- - {isLoadingRecent ? ( -
- 正在加载最近访问记录… -
- ) : null} + - {!isLoadingRecent && recentOrders.length ? ( - recentOrders.map((order) => ( -
-
-
-

- 订单 #{order.orderId} -

-

- 工作流 {order.workflowId ?? "未关联"} -

-
- -
-
- - {order.currentStepLabel} - {formatTimestamp(order.updatedAt)} -
-
- )) - ) : null} - - {!isLoadingRecent && !recentOrders.length ? ( - - ) : null} - -
-

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

-
- - -
-
-
-
-
+
); } @@ -277,7 +128,8 @@ export function OrdersHomeScreen() { const [message, setMessage] = useState(DEFAULT_MESSAGE); const [isLoadingRecent, setIsLoadingRecent] = 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/orders.ts b/src/lib/adapters/orders.ts index 3bcee45..24359e9 100644 --- a/src/lib/adapters/orders.ts +++ b/src/lib/adapters/orders.ts @@ -62,16 +62,30 @@ export function adaptAsset(asset: AssetDto): AssetViewModel { export function adaptOrderSummary( order: Pick< OrderDetailResponseDto | OrderListItemDto, - "order_id" | "workflow_id" | "status" | "current_step" | "updated_at" + | "order_id" + | "workflow_id" + | "customer_level" + | "service_mode" + | "status" + | "current_step" + | "review_task_status" + | "revision_count" + | "pending_manual_confirm" + | "updated_at" >, ): OrderSummaryVM { return { orderId: order.order_id, workflowId: order.workflow_id, + customerLevel: order.customer_level, + serviceMode: order.service_mode, status: order.status, statusMeta: getOrderStatusMeta(order.status), currentStep: order.current_step, currentStepLabel: getWorkflowStepMeta(order.current_step).label, + reviewTaskStatus: order.review_task_status, + revisionCount: order.revision_count, + pendingManualConfirm: order.pending_manual_confirm, updatedAt: order.updated_at, }; } diff --git a/src/lib/types/view-models.ts b/src/lib/types/view-models.ts index 04f4af8..8de3de6 100644 --- a/src/lib/types/view-models.ts +++ b/src/lib/types/view-models.ts @@ -43,10 +43,15 @@ export type AssetViewModel = { export type OrderSummaryVM = { orderId: number; workflowId: string | null; + customerLevel: CustomerLevel; + serviceMode: ServiceMode; status: OrderStatus; statusMeta: StatusMeta; currentStep: WorkflowStepName | null; currentStepLabel: string; + reviewTaskStatus: ReviewTaskStatus | null; + revisionCount: number; + pendingManualConfirm: boolean; updatedAt: string; }; diff --git a/tests/app/api/orders-overview.route.test.ts b/tests/app/api/orders-overview.route.test.ts index 96fd019..2a7060a 100644 --- a/tests/app/api/orders-overview.route.test.ts +++ b/tests/app/api/orders-overview.route.test.ts @@ -63,6 +63,8 @@ test("proxies recent orders overview from the backend list api", async () => { { orderId: 3, workflowId: "order-3", + customerLevel: "mid", + serviceMode: "semi_pro", status: "waiting_review", statusMeta: { label: "待审核", @@ -70,6 +72,9 @@ test("proxies recent orders overview from the backend list api", async () => { }, currentStep: "review", currentStepLabel: "人工审核", + reviewTaskStatus: "revision_uploaded", + revisionCount: 1, + pendingManualConfirm: true, updatedAt: "2026-03-27T14:00:03Z", }, ], diff --git a/tests/features/orders/orders-home.test.tsx b/tests/features/orders/orders-home.test.tsx index 137d67d..3ef475d 100644 --- a/tests/features/orders/orders-home.test.tsx +++ b/tests/features/orders/orders-home.test.tsx @@ -8,6 +8,8 @@ const RECENT_ORDERS: OrderSummaryVM[] = [ { orderId: 4201, workflowId: "wf-4201", + customerLevel: "mid", + serviceMode: "semi_pro", status: "waiting_review", statusMeta: { label: "待审核", @@ -15,15 +17,20 @@ const RECENT_ORDERS: OrderSummaryVM[] = [ }, currentStep: "review", currentStepLabel: "人工审核", + reviewTaskStatus: "revision_uploaded", + revisionCount: 1, + pendingManualConfirm: true, updatedAt: "2026-03-27T09:15:00Z", }, ]; -test("shows the real recent-orders entry state", () => { +test("renders orders 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 status filtering and pagination actions", () => { diff --git a/tests/lib/adapters/orders.test.ts b/tests/lib/adapters/orders.test.ts index 65b03d8..a5619f5 100644 --- a/tests/lib/adapters/orders.test.ts +++ b/tests/lib/adapters/orders.test.ts @@ -68,9 +68,13 @@ test("maps order summary status metadata and current step labels", () => { expect(viewModel).toMatchObject({ orderId: 101, workflowId: "wf-101", + customerLevel: "mid", + serviceMode: "semi_pro", status: "waiting_review", currentStep: "review", currentStepLabel: "人工审核", + revisionCount: 0, + pendingManualConfirm: false, statusMeta: { label: "待审核", tone: "warning",