feat: rewrite orders page as dense list
This commit is contained in:
118
src/features/orders/components/orders-table.tsx
Normal file
118
src/features/orders/components/orders-table.tsx
Normal file
@@ -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 (
|
||||
<div className="rounded-[var(--panel-radius)] border border-dashed border-[var(--border-strong)] bg-[var(--surface-muted)] px-5 py-6 text-sm text-[var(--ink-muted)]">
|
||||
正在加载订单列表…
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!items.length) {
|
||||
return (
|
||||
<EmptyState
|
||||
eyebrow="Orders empty"
|
||||
title="当前筛选下没有订单"
|
||||
description="调整关键词、状态或服务模式后再试。"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>订单号</TableHead>
|
||||
<TableHead>workflowId</TableHead>
|
||||
<TableHead>客户等级</TableHead>
|
||||
<TableHead>服务模式</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>当前步骤</TableHead>
|
||||
<TableHead>修订数</TableHead>
|
||||
<TableHead>更新时间</TableHead>
|
||||
<TableHead className="text-right">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{items.map((order) => (
|
||||
<TableRow key={order.orderId}>
|
||||
<TableCell className="font-medium">#{order.orderId}</TableCell>
|
||||
<TableCell>{order.workflowId ?? "未关联"}</TableCell>
|
||||
<TableCell>{order.customerLevel}</TableCell>
|
||||
<TableCell>{order.serviceMode}</TableCell>
|
||||
<TableCell>
|
||||
<StatusBadge status={order.status} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-col gap-1">
|
||||
<StatusBadge variant="workflowStep" status={order.currentStep} />
|
||||
<span className="text-xs text-[var(--ink-muted)]">
|
||||
{order.currentStepLabel}
|
||||
</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<span className="text-sm text-[var(--ink-strong)]">
|
||||
{order.revisionCount}
|
||||
</span>
|
||||
{order.pendingManualConfirm ? (
|
||||
<span className="rounded-full bg-[rgba(88,106,56,0.12)] px-2 py-0.5 font-[var(--font-mono)] text-[11px] uppercase tracking-[0.14em] text-[#50633b]">
|
||||
待确认
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-xs text-[var(--ink-muted)]">
|
||||
{formatTimestamp(order.updatedAt)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => onOpenOrder?.(String(order.orderId))}
|
||||
>
|
||||
查看订单
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => onOpenWorkflow?.(String(order.orderId))}
|
||||
>
|
||||
查看流程
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
97
src/features/orders/components/orders-toolbar.tsx
Normal file
97
src/features/orders/components/orders-toolbar.tsx
Normal file
@@ -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 (
|
||||
<PageToolbar className="justify-between gap-4">
|
||||
<div className="flex flex-1 flex-wrap items-center gap-3">
|
||||
<Input
|
||||
aria-label="订单关键词搜索"
|
||||
className="min-w-[220px] flex-1 md:max-w-[320px]"
|
||||
placeholder="搜索订单号或 workflow_id"
|
||||
value={query}
|
||||
onChange={(event) => onQueryChange(event.target.value)}
|
||||
/>
|
||||
<Button onClick={() => onQuerySubmit?.(query.trim())}>搜索订单</Button>
|
||||
<Select
|
||||
aria-label="订单状态筛选"
|
||||
value={status}
|
||||
onChange={(event) =>
|
||||
onStatusChange?.(event.target.value as OrderFilterStatus)
|
||||
}
|
||||
>
|
||||
<option value="all">全部状态</option>
|
||||
{Object.entries(ORDER_STATUS_META).map(([value, meta]) => (
|
||||
<option key={value} value={value}>
|
||||
{meta.label}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
<Select
|
||||
aria-label="服务模式筛选"
|
||||
value={serviceMode}
|
||||
onChange={(event) =>
|
||||
onServiceModeChange(event.target.value as OrderFilterServiceMode)
|
||||
}
|
||||
>
|
||||
<option value="all">全部服务模式</option>
|
||||
<option value="auto_basic">auto_basic</option>
|
||||
<option value="semi_pro">semi_pro</option>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-xs text-[var(--ink-muted)]">
|
||||
第 {Math.min(currentPage, safeTotalPages)} / {safeTotalPages} 页
|
||||
</span>
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={currentPage <= 1}
|
||||
onClick={() => onPageChange?.(currentPage - 1)}
|
||||
>
|
||||
上一页
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={currentPage >= safeTotalPages}
|
||||
onClick={() => onPageChange?.(currentPage + 1)}
|
||||
>
|
||||
下一页
|
||||
</Button>
|
||||
</div>
|
||||
</PageToolbar>
|
||||
);
|
||||
}
|
||||
@@ -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<OrderFilterServiceMode>("all");
|
||||
|
||||
useEffect(() => {
|
||||
setQueryValue(selectedQuery);
|
||||
}, [selectedQuery]);
|
||||
|
||||
const visibleOrders =
|
||||
serviceModeFilter === "all"
|
||||
? recentOrders
|
||||
: recentOrders.filter((order) => order.serviceMode === serviceModeFilter);
|
||||
|
||||
return (
|
||||
<section className="space-y-8">
|
||||
<section className="space-y-6">
|
||||
<PageHeader
|
||||
eyebrow="Orders home"
|
||||
title="订单总览"
|
||||
description="这里是后台首页入口,当前已经接入真实最近订单列表,并保留订单号直达入口方便快速处理。"
|
||||
meta="真实列表入口"
|
||||
description="订单页直接承担扫描、筛选和跳转职责,不再把首屏浪费在入口说明和大卡片上。"
|
||||
meta="真实列表页"
|
||||
/>
|
||||
|
||||
<div className="rounded-[28px] border border-[rgba(145,104,46,0.2)] bg-[rgba(202,164,97,0.12)] px-6 py-5">
|
||||
<p className="text-lg font-semibold tracking-[-0.02em] text-[#7a5323]">
|
||||
{TITLE_MESSAGE}
|
||||
</p>
|
||||
<p className="mt-2 text-sm leading-7 text-[#7a5323]">
|
||||
{message}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<MetricChip label="mode" value="真实订单列表" />
|
||||
<MetricChip label="rows" value={visibleOrders.length} />
|
||||
<MetricChip label="message" value={TITLE_MESSAGE} />
|
||||
</div>
|
||||
|
||||
<div className="grid gap-6 xl:grid-cols-[minmax(0,1fr)_380px]">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="space-y-2">
|
||||
<CardEyebrow>Direct lookup</CardEyebrow>
|
||||
<div className="space-y-1">
|
||||
<CardTitle>订单号直达</CardTitle>
|
||||
<CardDescription>
|
||||
保留订单号和流程号的直接入口,适合在列表之外快速跳转到指定订单。
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<label className="block space-y-2">
|
||||
<span className="text-sm font-medium text-[var(--ink-strong)]">
|
||||
订单号
|
||||
</span>
|
||||
<input
|
||||
value={lookupValue}
|
||||
onChange={(event) => 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)]"
|
||||
/>
|
||||
</label>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Button
|
||||
disabled={!canLookup}
|
||||
onClick={() => onOpenOrder?.(normalizedLookup)}
|
||||
>
|
||||
打开订单详情
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={!canLookup}
|
||||
onClick={() => onOpenWorkflow?.(normalizedLookup)}
|
||||
>
|
||||
打开流程详情
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<p className="text-sm text-[var(--ink-muted)]">{message}</p>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
|
||||
<div className="space-y-2">
|
||||
<CardEyebrow>Recent visits</CardEyebrow>
|
||||
<div className="space-y-1">
|
||||
<CardTitle>最近访问</CardTitle>
|
||||
<CardDescription>
|
||||
这里已经接入真实后端最近订单列表,页面结构继续沿用首版设计。
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 lg:min-w-[360px]">
|
||||
<label className="flex flex-col gap-2 text-sm text-[var(--ink-strong)]">
|
||||
<span className="font-medium">订单关键词搜索</span>
|
||||
<div className="flex gap-3">
|
||||
<input
|
||||
aria-label="订单关键词搜索"
|
||||
value={queryValue}
|
||||
onChange={(event) => setQueryValue(event.target.value)}
|
||||
placeholder="搜索订单号或 workflow_id"
|
||||
className="min-h-12 flex-1 rounded-[18px] border border-[var(--border-strong)] 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)] focus:ring-2 focus:ring-[var(--accent-ring)]"
|
||||
/>
|
||||
<Button onClick={() => onQuerySubmit?.(queryValue.trim())}>
|
||||
搜索订单
|
||||
</Button>
|
||||
</div>
|
||||
</label>
|
||||
<label className="flex flex-col gap-2 text-sm text-[var(--ink-strong)]">
|
||||
<span className="font-medium">订单状态筛选</span>
|
||||
<select
|
||||
aria-label="订单状态筛选"
|
||||
className="min-h-12 rounded-[18px] border border-[var(--border-strong)] bg-[var(--surface-muted)] px-4 text-sm text-[var(--ink-strong)] focus:outline-none focus:ring-2 focus:ring-[var(--accent-ring)]"
|
||||
value={selectedStatus}
|
||||
onChange={(event) =>
|
||||
onStatusChange?.(event.target.value as FilterStatus)
|
||||
}
|
||||
>
|
||||
{ORDER_STATUS_FILTER_OPTIONS.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{isLoadingRecent ? (
|
||||
<div className="rounded-[24px] border border-dashed border-[var(--border-strong)] bg-[var(--surface-muted)] px-5 py-6 text-sm text-[var(--ink-muted)]">
|
||||
正在加载最近访问记录…
|
||||
</div>
|
||||
) : null}
|
||||
<OrdersToolbar
|
||||
currentPage={currentPage}
|
||||
query={queryValue}
|
||||
serviceMode={serviceModeFilter}
|
||||
status={selectedStatus}
|
||||
totalPages={totalPages}
|
||||
onPageChange={onPageChange}
|
||||
onQueryChange={setQueryValue}
|
||||
onQuerySubmit={onQuerySubmit}
|
||||
onServiceModeChange={setServiceModeFilter}
|
||||
onStatusChange={onStatusChange}
|
||||
/>
|
||||
|
||||
{!isLoadingRecent && recentOrders.length ? (
|
||||
recentOrders.map((order) => (
|
||||
<div
|
||||
key={order.orderId}
|
||||
className="rounded-[24px] border border-[var(--border-soft)] bg-[var(--surface-muted)] px-4 py-4"
|
||||
>
|
||||
<div className="flex flex-wrap items-start justify-between gap-3">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-semibold text-[var(--ink-strong)]">
|
||||
订单 #{order.orderId}
|
||||
</p>
|
||||
<p className="text-xs text-[var(--ink-muted)]">
|
||||
工作流 {order.workflowId ?? "未关联"}
|
||||
</p>
|
||||
</div>
|
||||
<StatusBadge status={order.status} />
|
||||
</div>
|
||||
<div className="mt-4 flex flex-wrap items-center gap-3 text-xs text-[var(--ink-muted)]">
|
||||
<StatusBadge variant="workflowStep" status={order.currentStep} />
|
||||
<span>{order.currentStepLabel}</span>
|
||||
<span>{formatTimestamp(order.updatedAt)}</span>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : null}
|
||||
|
||||
{!isLoadingRecent && !recentOrders.length ? (
|
||||
<EmptyState
|
||||
eyebrow="No recent orders"
|
||||
title="暂无最近访问记录"
|
||||
description="当前筛选条件下还没有可展示的订单。"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<div className="flex flex-wrap items-center justify-between gap-3 border-t border-[var(--border-soft)] pt-3">
|
||||
<p className="text-xs text-[var(--ink-muted)]">
|
||||
第 {Math.min(currentPage, effectiveTotalPages)} / {effectiveTotalPages} 页
|
||||
</p>
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={currentPage <= 1}
|
||||
onClick={() => onPageChange?.(currentPage - 1)}
|
||||
>
|
||||
上一页
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={currentPage >= effectiveTotalPages}
|
||||
onClick={() => onPageChange?.(currentPage + 1)}
|
||||
>
|
||||
下一页
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<OrdersTable
|
||||
isLoading={isLoadingRecent}
|
||||
items={visibleOrders}
|
||||
onOpenOrder={onOpenOrder}
|
||||
onOpenWorkflow={onOpenWorkflow}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -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<FilterStatus>("all");
|
||||
const [selectedStatus, setSelectedStatus] =
|
||||
useState<OrderFilterStatus>("all");
|
||||
const [pagination, setPagination] = useState<PaginationData>(DEFAULT_PAGINATION);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
],
|
||||
|
||||
@@ -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(<OrdersHome recentOrders={RECENT_ORDERS} />);
|
||||
|
||||
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", () => {
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user