修正企微主办边界与工作台摘要(portal-task-first-redesign)

这个提交包含在:
2026-05-29 23:30:30 +08:00
父节点 86f887afa5
当前提交 8bd184f0b3
共修改 9 个文件,包含 296 行新增和 264 行删除
+2 -127
查看文件
@@ -1,17 +1,14 @@
import { useState } from "react";
import { Link, useSearchParams } from "react-router-dom";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { useQuery } from "@tanstack/react-query";
import { isApiError } from "../api/types";
import { mapApiErrorMessage } from "../api/errorMessages";
import {
approveWorkflowTask,
fetchWorkflowTasks,
fetchWorkflowTaskSummary,
type WorkflowTaskSummary,
} from "../api/workflow";
import { Pagination } from "../components/common/Pagination";
import { ConfirmDialog } from "../components/common/ConfirmDialog";
import { StatusBadge } from "../components/common/StatusBadge";
import { TableSkeleton } from "../components/common/TableSkeleton";
import {
@@ -28,7 +25,6 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from ".
import { Tabs, TabsList, TabsTrigger } from "../components/ui/tabs";
import { formatDisplayDateTime } from "../lib/displayFormat";
import { TEST_IDS } from "../lib/testIds";
import { getWorkflowConfirmCopy } from "../lib/workflowActionConfirm";
const PAGE_SIZE = 20;
const WORKFLOW_DEFAULT_DUE_MS = 24 * 60 * 60 * 1000;
@@ -191,18 +187,6 @@ function isTaskDefaultOverdue(task: WorkflowTaskSummary) {
return task.status === "pending" && !isWithinDefaultDueWindow(task.createdAt);
}
function parseDurationDays(value: string | null | undefined) {
if (!value) {
return null;
}
const match = /([0-9]+(?:\.[0-9]+)?)/.exec(value);
if (!match) {
return null;
}
const days = Number(match[1]);
return Number.isFinite(days) ? days : null;
}
function workflowTaskStatusLabel(status: string) {
const match = WORKFLOW_STATUS_OPTIONS.find((item) => item.value === status);
return match?.label ?? status;
@@ -221,37 +205,12 @@ function taskSecondaryMeta(task: WorkflowTaskSummary) {
return [formatReceivedTime(task.createdAt), displayReference(task)].join(" · ");
}
function isLowRiskQuickApproveTask(task: WorkflowTaskSummary) {
if (task.status !== "pending" || !(task.allowedActions ?? []).includes("approve")) {
return false;
}
if (!displayTitle(task) || displayApplicant(task) === "-") {
return false;
}
const entityType = task.workflowInstance?.entityType;
if (entityType === "leave") {
const days = parseDurationDays(task.businessSummary?.duration);
if (days === null || days > 1) {
return false;
}
} else if (entityType !== "attendance_correction") {
return false;
}
return isWithinDefaultDueWindow(task.createdAt);
}
function isAlreadyProcessedTaskError(error: unknown) {
return isApiError(error) && error.details?.task === "already_processed";
}
export function WorkflowTasksPage() {
const [searchParams, setSearchParams] = useSearchParams();
const status = normalizeStatusFilter(searchParams.get("status"));
const entityType = normalizeEntityFilter(searchParams.get("entityType"));
const page = Number(searchParams.get("page") ?? "1") || 1;
const [confirmApproveTaskId, setConfirmApproveTaskId] = useState<string | null>(null);
const [showAdvanced, setShowAdvanced] = useState(() => status !== "pending" || entityType !== "all");
const queryClient = useQueryClient();
function updateSearchParams(next: {
status?: WorkflowStatusFilter;
@@ -286,35 +245,8 @@ export function WorkflowTasksPage() {
queryFn: () => fetchWorkflowTaskSummary(),
});
const approveMutation = useMutation({
mutationFn: (taskId: string) => approveWorkflowTask(taskId),
onSuccess: async () => {
await Promise.all([
queryClient.invalidateQueries({ queryKey: ["workflow", "tasks"] }),
queryClient.invalidateQueries({ queryKey: ["portal-home"] }),
queryClient.invalidateQueries({ queryKey: ["notifications"] }),
]);
toast.success("审批已通过");
},
onError: (error) => {
if (isAlreadyProcessedTaskError(error)) {
void queryClient.invalidateQueries({ queryKey: ["workflow", "tasks"] });
}
const message = isApiError(error)
? `${error.code}: ${mapApiErrorMessage(error.code, error.details, error.message)}`
: error instanceof Error
? error.message
: "审批失败,请稍后重试";
toast.error(message);
},
});
const totalPages = Math.max(1, Math.ceil((query.data?.total ?? 0) / PAGE_SIZE));
const taskItems = query.data?.items ?? [];
const confirmCopy = confirmApproveTaskId ? getWorkflowConfirmCopy("approve") : null;
const confirmApproveTask = confirmApproveTaskId
? taskItems.find((task) => task.id === confirmApproveTaskId) ?? null
: null;
const isDefaultTaskView = status === "pending" && entityType === "all";
const advancedVisible = showAdvanced || !isDefaultTaskView;
const pendingTotal = summaryQuery.data?.pendingTotal ?? "-";
@@ -339,7 +271,6 @@ export function WorkflowTasksPage() {
}
return (
<>
<ListWorkspace>
<WorkspaceHeader
eyebrow="审批"
@@ -485,7 +416,7 @@ export function WorkflowTasksPage() {
<div className="mt-3 grid grid-cols-2 gap-2">
<Button
asChild
className={`min-h-11 ${isLowRiskQuickApproveTask(task) ? "" : "col-span-2"}`}
className="col-span-2 min-h-11"
size="sm"
data-testid={TEST_IDS.workflow.detailLink}
>
@@ -493,19 +424,6 @@ export function WorkflowTasksPage() {
{task.status === "pending" ? "查看处理" : "查看详情"}
</Link>
</Button>
{isLowRiskQuickApproveTask(task) ? (
<Button
className="min-h-11"
size="sm"
variant="secondary"
type="button"
data-testid={TEST_IDS.workflow.quickApprove}
disabled={approveMutation.isPending && approveMutation.variables === task.id}
onClick={() => setConfirmApproveTaskId(task.id)}
>
{approveMutation.isPending && approveMutation.variables === task.id ? "通过中..." : "通过"}
</Button>
) : null}
</div>
</article>
))
@@ -567,18 +485,6 @@ export function WorkflowTasksPage() {
{task.status === "pending" ? "查看处理" : "查看详情"}
</Link>
</Button>
{isLowRiskQuickApproveTask(task) ? (
<Button
size="sm"
variant="secondary"
type="button"
data-testid={TEST_IDS.workflow.quickApprove}
disabled={approveMutation.isPending && approveMutation.variables === task.id}
onClick={() => setConfirmApproveTaskId(task.id)}
>
{approveMutation.isPending && approveMutation.variables === task.id ? "通过中..." : "通过"}
</Button>
) : null}
</div>
</TableCell>
</TableRow>
@@ -603,36 +509,5 @@ export function WorkflowTasksPage() {
testIdPrefix="workflow-tasks"
/>
</ListWorkspace>
<ConfirmDialog
open={Boolean(confirmCopy)}
onOpenChange={(open) => {
if (!open) {
setConfirmApproveTaskId(null);
}
}}
title={confirmCopy?.title ?? ""}
description={confirmCopy ? (
<>
<span>{confirmCopy.description}</span>
{confirmApproveTask ? (
<span className="mt-2 block rounded-lg bg-slate-50 px-3 py-2 text-xs leading-5 text-slate-600">
当前事项:{displayTaskTitle(confirmApproveTask)} · {taskLeadMeta(confirmApproveTask)}
</span>
) : null}
</>
) : undefined}
confirmLabel={confirmCopy?.confirmLabel}
variant={confirmCopy?.variant}
testId="workflow-action-confirm-dialog"
confirmTestId="workflow-action-confirm-submit"
cancelTestId="workflow-action-confirm-cancel"
onConfirm={async () => {
if (!confirmApproveTaskId) {
return;
}
await approveMutation.mutateAsync(confirmApproveTaskId);
}}
/>
</>
);
}