feat(08-05-interaction-pattern-unification): 统一前端交互与反馈模式
这个提交包含在:
@@ -1,9 +1,9 @@
|
||||
import { resolveErrorMessage as resolveUserErrorMessage } from "../lib/resolveErrorMessage";
|
||||
import type { ReactNode } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
import { Link, Navigate, useLocation, useNavigate, useParams } from "react-router-dom";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { isApiError } from "../api/types";
|
||||
import { mapApiErrorMessage } from "../api/errorMessages";
|
||||
import {
|
||||
approveWorkflowTask,
|
||||
fetchWorkflowApprovers,
|
||||
@@ -31,6 +31,7 @@ import { MetricCard } from "../components/patterns/MetricCard";
|
||||
import { PageScaffold } from "../components/patterns/PageScaffold";
|
||||
import { SectionPanel } from "../components/patterns/SectionPanel";
|
||||
import { Button } from "../components/ui/button";
|
||||
import { Label } from "../components/ui/label";
|
||||
import { Textarea } from "../components/ui/textarea";
|
||||
import { approvalRoleLabel, resolvePurchaseDisplayCode, workflowActionLabel } from "../lib/approvalDisplay";
|
||||
import { formatDisplayDateTime } from "../lib/displayFormat";
|
||||
@@ -177,14 +178,7 @@ export function WorkflowTaskDetailPage() {
|
||||
queryClient.invalidateQueries({ queryKey: ["workflow", "task", taskId] }),
|
||||
]);
|
||||
}
|
||||
let msg: string;
|
||||
if (error instanceof Error && !isApiError(error)) {
|
||||
msg = error.message;
|
||||
} else if (isApiError(error)) {
|
||||
msg = `${error.code}: ${mapApiErrorMessage(error.code, error.details, error.message)}`;
|
||||
} else {
|
||||
msg = "动作执行失败,请稍后重试";
|
||||
}
|
||||
const msg = resolveUserErrorMessage(error, "动作执行失败,请稍后重试");
|
||||
setActionError(msg);
|
||||
toast.error(msg);
|
||||
},
|
||||
@@ -238,13 +232,7 @@ export function WorkflowTaskDetailPage() {
|
||||
}
|
||||
|
||||
if (detailQuery.isError || !detailQuery.data) {
|
||||
const message = isApiError(detailQuery.error)
|
||||
? `${detailQuery.error.code}: ${mapApiErrorMessage(
|
||||
detailQuery.error.code,
|
||||
detailQuery.error.details,
|
||||
detailQuery.error.message,
|
||||
)}`
|
||||
: "加载失败,请返回列表重试";
|
||||
const message = resolveUserErrorMessage(detailQuery.error, "加载失败,请返回列表重试");
|
||||
|
||||
return (
|
||||
<div className="card-std space-y-3 p-5">
|
||||
@@ -265,6 +253,11 @@ export function WorkflowTaskDetailPage() {
|
||||
const canReturn = allowedActions.has("return");
|
||||
const canReject = allowedActions.has("reject");
|
||||
const canTransfer = allowedActions.has("transfer");
|
||||
const transferDisabledReason = !selectedApprover
|
||||
? "请先选择目标审批人。"
|
||||
: transferReason.trim().length < 10
|
||||
? `请填写转审理由,至少 10 个字(还差 ${10 - transferReason.trim().length} 个字)。`
|
||||
: "转审理由已满足要求。";
|
||||
const allowedActionLabels = detail.allowedActions.map(workflowActionLabel).join(" / ");
|
||||
const businessStatusLabel = workflowInstanceStatusLabel(detail.workflowInstance?.currentStatus);
|
||||
|
||||
@@ -420,7 +413,11 @@ export function WorkflowTaskDetailPage() {
|
||||
当前 ¥{item.amount} / 标准 ¥{item.maxAmount}/{item.perUnit}
|
||||
</div>
|
||||
<div>超出 ¥{item.exceededAmount} · {item.standardName}</div>
|
||||
<div>管控模式:{item.controlMode === "block" ? "阻断" : "预警"}</div>
|
||||
<div>
|
||||
{item.controlMode === "block"
|
||||
? "处理影响:当前单据不应继续通过,请申请人调整金额或补充合规处理依据。"
|
||||
: "处理影响:可继续审批,但请核对超标原因并在审批意见中记录判断。"}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -510,6 +507,13 @@ export function WorkflowTaskDetailPage() {
|
||||
)}
|
||||
testId="workflow-task-action-panel"
|
||||
>
|
||||
{canReturn || canReject ? (
|
||||
<div className="rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2 text-xs leading-5 text-zinc-600" data-testid="workflow-action-consequence-help">
|
||||
<div><span className="font-medium text-zinc-800">退回:</span>原单回到申请人修改,保留单号和历史,修改后可重新提交。</div>
|
||||
<div><span className="font-medium text-zinc-800">驳回:</span>本次流程终止或进入驳回态,申请人通常需要复制或重新发起。</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{actionError ? (
|
||||
<p
|
||||
className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600"
|
||||
@@ -521,8 +525,10 @@ export function WorkflowTaskDetailPage() {
|
||||
|
||||
{activeSecondaryAction === "reject" ? (
|
||||
<div className="detail-action-card space-y-2">
|
||||
<p className="text-sm font-medium text-zinc-800">驳回意见</p>
|
||||
<Label className="text-sm font-medium text-zinc-800" htmlFor="workflow-task-reject-comment">驳回意见</Label>
|
||||
<p className="text-xs leading-5 text-zinc-500">驳回会终止本次流程或进入驳回态,申请人通常需要重新发起。</p>
|
||||
<Textarea
|
||||
id="workflow-task-reject-comment"
|
||||
data-testid="workflow-task-reject-comment"
|
||||
onChange={(event) => setRejectComment(event.target.value)}
|
||||
rows={3}
|
||||
@@ -542,8 +548,10 @@ export function WorkflowTaskDetailPage() {
|
||||
|
||||
{activeSecondaryAction === "return" ? (
|
||||
<div className="detail-action-card space-y-2">
|
||||
<p className="text-sm font-medium text-zinc-800">退回意见</p>
|
||||
<Label className="text-sm font-medium text-zinc-800" htmlFor="workflow-task-return-comment">退回意见</Label>
|
||||
<p className="text-xs leading-5 text-zinc-500">退回后原单可修改并重新提交,单号和审批历史会保留。</p>
|
||||
<Textarea
|
||||
id="workflow-task-return-comment"
|
||||
data-testid="workflow-task-return-comment"
|
||||
onChange={(event) => setReturnComment(event.target.value)}
|
||||
rows={3}
|
||||
@@ -564,7 +572,9 @@ export function WorkflowTaskDetailPage() {
|
||||
{activeSecondaryAction === "transfer" && canTransfer ? (
|
||||
<div className="detail-action-card space-y-2">
|
||||
<p className="text-sm font-medium text-zinc-800">转审</p>
|
||||
<Label className="text-xs text-zinc-600" htmlFor="workflow-task-transfer-approver">目标审批人</Label>
|
||||
<select
|
||||
id="workflow-task-transfer-approver"
|
||||
className="select-std"
|
||||
data-testid="workflow-task-transfer-approver"
|
||||
onChange={(event) => setSelectedApprover(event.target.value)}
|
||||
@@ -577,13 +587,24 @@ export function WorkflowTaskDetailPage() {
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<Label className="text-xs text-zinc-600" htmlFor="workflow-task-transfer-reason">转审理由</Label>
|
||||
<Textarea
|
||||
id="workflow-task-transfer-reason"
|
||||
aria-describedby="workflow-task-transfer-help"
|
||||
data-testid="workflow-task-transfer-reason"
|
||||
onChange={(event) => setTransferReason(event.target.value)}
|
||||
rows={3}
|
||||
value={transferReason}
|
||||
/>
|
||||
<p
|
||||
id="workflow-task-transfer-help"
|
||||
className={transferReason.trim().length >= 10 && selectedApprover ? "text-xs text-emerald-700" : "text-xs text-amber-700"}
|
||||
data-testid="workflow-task-transfer-disabled-reason"
|
||||
>
|
||||
{transferDisabledReason}
|
||||
</p>
|
||||
<Button
|
||||
aria-describedby="workflow-task-transfer-help"
|
||||
data-testid="workflow-task-transfer"
|
||||
variant="secondary"
|
||||
disabled={
|
||||
|
||||
在新工单中引用
屏蔽一个用户