DG9 统一全站 UI 布局样板迁移
关联 Issue: DG9-0..DG9-12
这个提交包含在:
@@ -9,10 +9,19 @@ import {
|
||||
fetchWorkflowTaskSummary,
|
||||
type WorkflowTaskSummary,
|
||||
} from "../api/workflow";
|
||||
import { PageHeader } from "../components/common/PageHeader";
|
||||
import { Pagination } from "../components/common/Pagination";
|
||||
import { StatusBadge } from "../components/common/StatusBadge";
|
||||
import { TableSkeleton } from "../components/common/TableSkeleton";
|
||||
import {
|
||||
ActionToolbar,
|
||||
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";
|
||||
@@ -187,35 +196,34 @@ export function WorkflowTasksPage() {
|
||||
const taskItems = query.data?.items ?? [];
|
||||
|
||||
return (
|
||||
<div className="space-y-4 md:space-y-6">
|
||||
<PageHeader title="审批中心" subtitle="集中处理待办、查看已办和业务审批历史。" />
|
||||
<ListWorkspace>
|
||||
<WorkspaceHeader
|
||||
eyebrow="审批"
|
||||
title="审批中心"
|
||||
subtitle="集中处理待办、查看已办和业务审批历史。"
|
||||
/>
|
||||
|
||||
<div className="grid gap-3 md:grid-cols-4" data-testid="workflow-task-summary">
|
||||
<div className="rounded-lg border border-zinc-200 bg-white px-4 py-3">
|
||||
<div className="text-xs text-zinc-500">待办总数</div>
|
||||
<div className="mt-2 text-2xl font-semibold text-zinc-900">{summaryQuery.data?.pendingTotal ?? "-"}</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-zinc-200 bg-white px-4 py-3">
|
||||
<div className="text-xs text-zinc-500">超时待办</div>
|
||||
<div className="mt-2 text-2xl font-semibold text-amber-700">{summaryQuery.data?.overdueTotal ?? "-"}</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-zinc-200 bg-white px-4 py-3">
|
||||
<div className="text-xs text-zinc-500">最早待处理</div>
|
||||
<div className="mt-2 text-sm font-medium text-zinc-900">{formatSummaryTime(summaryQuery.data?.earliestPendingAt)}</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-zinc-200 bg-white px-4 py-3">
|
||||
<div className="text-xs text-zinc-500">业务分布</div>
|
||||
<div className="mt-2 truncate text-sm font-medium text-zinc-900">
|
||||
{(summaryQuery.data?.byEntityType ?? [])
|
||||
.filter((item) => item.pendingTotal > 0)
|
||||
.map((item) => `${entityLabel(item.key)} ${item.pendingTotal}`)
|
||||
.join(" / ") || "-"}
|
||||
</div>
|
||||
</div>
|
||||
<div data-testid="workflow-task-summary">
|
||||
<StatStrip columns={4}>
|
||||
<MetricCard title="待办总数" value={summaryQuery.data?.pendingTotal ?? "-"} />
|
||||
<MetricCard title="超时待办" value={summaryQuery.data?.overdueTotal ?? "-"} tone="warning" />
|
||||
<MetricCard title="最早待处理" value={formatSummaryTime(summaryQuery.data?.earliestPendingAt)} />
|
||||
<MetricCard
|
||||
title="业务分布"
|
||||
value={
|
||||
<span className="block truncate text-base">
|
||||
{(summaryQuery.data?.byEntityType ?? [])
|
||||
.filter((item) => item.pendingTotal > 0)
|
||||
.map((item) => `${entityLabel(item.key)} ${item.pendingTotal}`)
|
||||
.join(" / ") || "-"}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</StatStrip>
|
||||
</div>
|
||||
|
||||
<div className="card-std space-y-4 p-4 md:p-5">
|
||||
<div className="flex flex-col gap-3 xl:flex-row xl:items-center xl:justify-between">
|
||||
<FilterBar>
|
||||
<ActionToolbar className="items-start">
|
||||
<Tabs
|
||||
data-testid="workflow-tasks-status-tabs"
|
||||
value={status}
|
||||
@@ -234,13 +242,13 @@ export function WorkflowTasksPage() {
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex w-full items-center gap-2 sm:w-auto">
|
||||
<label className="text-sm font-medium text-zinc-700" htmlFor="workflow-entity-type-filter">
|
||||
业务类型
|
||||
</label>
|
||||
<select
|
||||
id="workflow-entity-type-filter"
|
||||
className="select-std min-w-36"
|
||||
className="select-std min-w-36 flex-1 sm:flex-none"
|
||||
data-testid="workflow-tasks-entity-filter"
|
||||
value={entityType}
|
||||
onChange={(event) => updateSearchParams({ entityType: event.target.value as WorkflowEntityFilter, page: 1 })}
|
||||
@@ -252,7 +260,8 @@ export function WorkflowTasksPage() {
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</ActionToolbar>
|
||||
</FilterBar>
|
||||
|
||||
{query.isLoading ? <TableSkeleton columns={7} rows={5} /> : null}
|
||||
|
||||
@@ -270,24 +279,36 @@ export function WorkflowTasksPage() {
|
||||
|
||||
{!query.isLoading && !query.isError ? (
|
||||
<>
|
||||
<div className="space-y-3 md:hidden" data-testid="workflow-tasks-mobile-list">
|
||||
<div className="mobile-page-stack md:hidden" data-testid="workflow-tasks-mobile-list">
|
||||
{taskItems.length ? (
|
||||
taskItems.map((task) => (
|
||||
<article className="rounded-xl border border-zinc-200 bg-white p-4 shadow-sm" key={task.id}>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<article className="mobile-list-card" key={task.id}>
|
||||
<div className="mobile-list-card__header">
|
||||
<div className="min-w-0">
|
||||
<div className="truncate text-sm font-semibold text-zinc-900">{displayTitle(task)}</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">
|
||||
<div className="mobile-list-card__title">{displayTitle(task)}</div>
|
||||
<div className="mobile-list-card__subtitle">
|
||||
{entityLabel(task.workflowInstance?.entityType)} · {displayReference(task)}
|
||||
</div>
|
||||
</div>
|
||||
<StatusBadge className="shrink-0" status={task.status} />
|
||||
<StatusBadge className="mobile-status-slot" status={task.status} />
|
||||
</div>
|
||||
<div className="mt-3 grid gap-1 rounded-lg bg-zinc-50 px-3 py-2 text-xs text-zinc-600">
|
||||
<div>申请人:{displayApplicant(task)}</div>
|
||||
<div>金额/天数:{displayMetric(task)}</div>
|
||||
<div>当前节点:{task.stepName}</div>
|
||||
<div>业务状态:{businessStatusLabel(task.businessSummary?.businessStatus)}</div>
|
||||
<div className="mobile-field-list mt-3 rounded-lg bg-zinc-50 px-3 py-2">
|
||||
<div className="mobile-field-row">
|
||||
<span className="mobile-field-label">申请人</span>
|
||||
<span className="mobile-field-value">{displayApplicant(task)}</span>
|
||||
</div>
|
||||
<div className="mobile-field-row">
|
||||
<span className="mobile-field-label">金额/天数</span>
|
||||
<span className="mobile-field-value">{displayMetric(task)}</span>
|
||||
</div>
|
||||
<div className="mobile-field-row">
|
||||
<span className="mobile-field-label">当前节点</span>
|
||||
<span className="mobile-field-value">{task.stepName}</span>
|
||||
</div>
|
||||
<div className="mobile-field-row">
|
||||
<span className="mobile-field-label">业务状态</span>
|
||||
<span className="mobile-field-value">{businessStatusLabel(task.businessSummary?.businessStatus)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 grid grid-cols-2 gap-2">
|
||||
{status === "pending" && (task.allowedActions ?? []).includes("approve") ? (
|
||||
@@ -317,13 +338,11 @@ export function WorkflowTasksPage() {
|
||||
</article>
|
||||
))
|
||||
) : (
|
||||
<div className="rounded-xl border border-dashed border-zinc-300 px-4 py-8 text-center text-sm text-zinc-500">
|
||||
暂无数据
|
||||
</div>
|
||||
<WorkspaceEmptyState />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="hidden rounded-lg border border-zinc-200 md:block">
|
||||
<PrimaryTable className="hidden md:block">
|
||||
<Table data-testid="workflow-tasks-table">
|
||||
<TableHeader>
|
||||
<tr>
|
||||
@@ -381,14 +400,14 @@ export function WorkflowTasksPage() {
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell className="py-3 text-zinc-500" colSpan={8}>
|
||||
<TableCell className="px-6 py-8 text-center text-zinc-500" colSpan={8}>
|
||||
暂无数据
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</PrimaryTable>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
@@ -398,7 +417,6 @@ export function WorkflowTasksPage() {
|
||||
onPageChange={(p) => updateSearchParams({ page: p })}
|
||||
testIdPrefix="workflow-tasks"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ListWorkspace>
|
||||
);
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户