统一首批页面成熟设计基线(05-30-unified-page-design-review)
这个提交包含在:
@@ -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>
|
||||
|
||||
在新工单中引用
屏蔽一个用户