统一首批页面成熟设计基线(05-30-unified-page-design-review)

这个提交包含在:
2026-05-30 17:47:00 +08:00
父节点 6ae6d82355
当前提交 218ea9ad10
共修改 24 个文件,包含 837 行新增和 326 行删除
+66 -97
查看文件
@@ -1,4 +1,3 @@
import { useState } from "react";
import { Link, useSearchParams } from "react-router-dom";
import { useQuery } from "@tanstack/react-query";
import { isApiError } from "../api/types";
@@ -15,11 +14,9 @@ import {
FilterBar,
ListWorkspace,
PrimaryTable,
StatStrip,
WorkspaceEmptyState,
WorkspaceHeader,
} from "../components/patterns/ListWorkspace";
import { MetricCard } from "../components/patterns/MetricCard";
import { Button } from "../components/ui/button";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/ui/table";
import { Tabs, TabsList, TabsTrigger } from "../components/ui/tabs";
@@ -210,7 +207,7 @@ export function WorkflowTasksPage() {
const status = normalizeStatusFilter(searchParams.get("status"));
const entityType = normalizeEntityFilter(searchParams.get("entityType"));
const page = Number(searchParams.get("page") ?? "1") || 1;
const [showAdvanced, setShowAdvanced] = useState(() => status !== "pending" || entityType !== "all");
const taskTab = status === "pending" ? "pending" : "processed";
function updateSearchParams(next: {
status?: WorkflowStatusFilter;
@@ -248,9 +245,7 @@ export function WorkflowTasksPage() {
const totalPages = Math.max(1, Math.ceil((query.data?.total ?? 0) / PAGE_SIZE));
const taskItems = query.data?.items ?? [];
const isDefaultTaskView = status === "pending" && entityType === "all";
const advancedVisible = showAdvanced || !isDefaultTaskView;
const pendingTotal = summaryQuery.data?.pendingTotal ?? "-";
const overdueTotal = summaryQuery.data?.overdueTotal ?? "-";
const pendingEntitySummary =
(summaryQuery.data?.byEntityType ?? [])
.filter((item) => item.pendingTotal > 0)
@@ -260,111 +255,85 @@ export function WorkflowTasksPage() {
? formatWaitingDuration(summaryQuery.data.earliestPendingAt)
: "暂无待处理";
function returnToPendingTasks() {
setShowAdvanced(false);
updateSearchParams({ status: "pending", entityType: "all", page: 1 });
}
function openProcessedRecords() {
setShowAdvanced(true);
updateSearchParams({ status: "completed", entityType: "all", page: 1 });
}
return (
<ListWorkspace>
<WorkspaceHeader
eyebrow="审批"
title="待我处理"
actions={
<Button
type="button"
variant="secondary"
data-testid="workflow-tasks-advanced-toggle"
onClick={advancedVisible ? returnToPendingTasks : openProcessedRecords}
>
{advancedVisible ? "回到待处理" : "已处理记录"}
</Button>
}
/>
<WorkspaceHeader title="待我处理" />
<FilterBar className="ops-command-bar !p-3 md:!p-3.5">
<div className="ops-command-bar__row">
<div className="flex min-w-0 flex-wrap items-center gap-3" data-testid="workflow-tasks-focus">
<span className="badge-neutral">待处理</span>
<span className="text-2xl font-semibold text-zinc-900">{pendingTotal}</span>
<span className="text-sm text-zinc-500">
最早:{earliestPendingWaiting}
</span>
{summaryQuery.data?.overdueTotal ? (
<span className="rounded-full border border-amber-200 bg-amber-50 px-2.5 py-1 text-xs font-medium text-amber-700">
超时 {summaryQuery.data.overdueTotal}
</span>
) : null}
<div className="flex min-w-0 flex-col gap-3 md:flex-row md:items-center">
<Tabs
data-testid="workflow-tasks-status-tabs"
value={taskTab}
onValueChange={(value) =>
updateSearchParams({
status: value === "pending" ? "pending" : "completed",
page: 1,
})
}
>
<TabsList className="ops-command-bar__tabs !h-auto !justify-start !bg-slate-50">
<TabsTrigger data-testid="workflow-tasks-tab-pending" value="pending">
待处理
</TabsTrigger>
<TabsTrigger data-testid="workflow-tasks-tab-processed" value="processed">
已处理
</TabsTrigger>
</TabsList>
</Tabs>
<div className="flex min-w-0 flex-wrap items-center gap-3" data-testid="workflow-tasks-focus">
<span className="text-sm font-medium text-zinc-900">待处理 {pendingTotal}</span>
<span className="text-sm text-zinc-500">{earliestPendingWaiting}</span>
{summaryQuery.data?.overdueTotal ? (
<span className="rounded-full border border-amber-200 bg-amber-50 px-2.5 py-1 text-xs font-medium text-amber-700">
超时 {summaryQuery.data.overdueTotal}
</span>
) : null}
</div>
</div>
<div className="ops-command-bar__actions">
<span className="text-xs text-zinc-500">业务:{pendingEntitySummary}</span>
</div>
</div>
</FilterBar>
{advancedVisible ? (
<div className="space-y-3" data-testid="workflow-tasks-advanced-panel">
<div data-testid="workflow-task-summary">
<StatStrip columns={4} className="ops-status-strip">
<MetricCard title="待办总数" value={pendingTotal} />
<MetricCard title="超时待办" value={overdueTotal} tone="warning" />
<MetricCard title="最早待处理" value={earliestPendingWaiting} />
<MetricCard
title="业务分布"
value={<span className="block truncate text-base">{pendingEntitySummary}</span>}
/>
</StatStrip>
</div>
<FilterBar className="ops-command-bar !p-3 md:!p-3.5">
<div className="ops-command-bar__row">
<Tabs
data-testid="workflow-tasks-status-tabs"
value={status === "pending" ? "completed" : status}
onValueChange={(value) => updateSearchParams({ status: value as WorkflowStatusFilter, page: 1 })}
>
<TabsList className="ops-command-bar__tabs !h-auto !justify-start !bg-slate-50">
{WORKFLOW_HISTORY_STATUS_OPTIONS.map((item) => (
<TabsTrigger
data-testid={`workflow-tasks-tab-${item.value}`}
key={item.value}
value={item.value}
>
{item.label}
</TabsTrigger>
))}
</TabsList>
</Tabs>
<div className="ops-command-bar__actions">
<label className="ops-filter-label" htmlFor="workflow-entity-type-filter">
业务类型
<span className="hidden text-xs text-zinc-500 xl:inline">业务:{pendingEntitySummary}</span>
{taskTab === "processed" ? (
<>
<label className="ops-filter-label" htmlFor="workflow-status-filter">
记录
</label>
<select
id="workflow-entity-type-filter"
className="select-sm min-w-32"
data-testid="workflow-tasks-entity-filter"
value={entityType}
onChange={(event) =>
updateSearchParams({ entityType: event.target.value as WorkflowEntityFilter, page: 1 })
}
id="workflow-status-filter"
className="select-sm min-w-28"
data-testid="workflow-tasks-processed-status-filter"
value={status === "pending" ? "completed" : status}
onChange={(event) => updateSearchParams({ status: event.target.value as WorkflowStatusFilter, page: 1 })}
>
{WORKFLOW_ENTITY_OPTIONS.map((item) => (
{WORKFLOW_HISTORY_STATUS_OPTIONS.map((item) => (
<option key={item.value} value={item.value}>
{item.label}
</option>
))}
</select>
</div>
</div>
</FilterBar>
</>
) : null}
<label className="ops-filter-label" htmlFor="workflow-entity-type-filter">
业务
</label>
<select
id="workflow-entity-type-filter"
className="select-sm min-w-32"
data-testid="workflow-tasks-entity-filter"
value={entityType}
onChange={(event) =>
updateSearchParams({ entityType: event.target.value as WorkflowEntityFilter, page: 1 })
}
>
{WORKFLOW_ENTITY_OPTIONS.map((item) => (
<option key={item.value} value={item.value}>
{item.label}
</option>
))}
</select>
</div>
</div>
) : null}
</FilterBar>
{query.isLoading ? <TableSkeleton columns={4} rows={5} /> : null}
@@ -421,7 +390,7 @@ export function WorkflowTasksPage() {
data-testid={TEST_IDS.workflow.detailLink}
>
<Link to={`/workflow/tasks/${task.id}`}>
{task.status === "pending" ? "查看处理" : "查看详情"}
{task.status === "pending" ? "处理" : "查看详情"}
</Link>
</Button>
</div>
@@ -482,7 +451,7 @@ export function WorkflowTasksPage() {
data-testid={TEST_IDS.workflow.detailLink}
>
<Link to={`/workflow/tasks/${task.id}`}>
{task.status === "pending" ? "查看处理" : "查看详情"}
{task.status === "pending" ? "处理" : "查看详情"}
</Link>
</Button>
</div>