From bf791d7a678db87c6f20a184530ce579af21d25c Mon Sep 17 00:00:00 2001 From: shunli Date: Fri, 15 May 2026 22:55:00 +0800 Subject: [PATCH] =?UTF-8?q?R7E-16E/R7E-16F=20=E9=AB=98=E9=A3=8E=E9=99=A9?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E7=A1=AE=E8=AE=A4=E7=BB=9F=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/common/ActionConfirmDialog.tsx | 52 +++ .../src/components/common/ConfirmDialog.tsx | 6 +- .../travel/TravelReconciliationTab.tsx | 71 +++- client/src/lib/actionConfirm.ts | 142 ++++++++ client/src/pages/EmployeeProfilePage.tsx | 39 ++- client/src/pages/PaymentBatchesPage.tsx | 45 ++- client/src/pages/ProjectDetailPage.tsx | 79 ++++- client/src/pages/PurchaseRequestsPage.tsx | 34 +- client/src/pages/ReimbursementsPage.tsx | 43 ++- client/src/pages/TravelRequestsPage.tsx | 25 +- client/src/pages/WorkflowDefinitionsPage.tsx | 42 ++- .../pages/invoice-pool/InvoiceDetailPanel.tsx | 101 +++++- .../e2e/system-action-confirmation.spec.ts | 321 ++++++++++++++++++ docs/trialrun/人工试运行报告.md | 28 +- docs/产品设计规则.md | 7 +- docs/需求-统一审批中心规约.md | 2 + ...2026-05-13_17-11-44-r7e-human-trialrun.csv | 8 +- ...05-13_17-30-00-qaops8-test-release-ops.csv | 2 +- 18 files changed, 979 insertions(+), 68 deletions(-) create mode 100644 client/src/components/common/ActionConfirmDialog.tsx create mode 100644 client/src/lib/actionConfirm.ts create mode 100644 client/tests/e2e/system-action-confirmation.spec.ts diff --git a/client/src/components/common/ActionConfirmDialog.tsx b/client/src/components/common/ActionConfirmDialog.tsx new file mode 100644 index 00000000..772d9f22 --- /dev/null +++ b/client/src/components/common/ActionConfirmDialog.tsx @@ -0,0 +1,52 @@ +import type { ReactNode } from "react"; +import { getActionConfirmCopy, type ActionConfirmKind } from "../../lib/actionConfirm"; +import { ConfirmDialog } from "./ConfirmDialog"; + +type ActionConfirmDialogProps = { + open: boolean; + onOpenChange: (open: boolean) => void; + kind: ActionConfirmKind; + targetName?: string; + count?: number; + title?: string; + description?: ReactNode; + confirmLabel?: string; + variant?: "default" | "destructive"; + testId?: string; + confirmTestId?: string; + cancelTestId?: string; + onConfirm: () => void | Promise; +}; + +export function ActionConfirmDialog({ + open, + onOpenChange, + kind, + targetName, + count, + title, + description, + confirmLabel, + variant, + testId, + confirmTestId, + cancelTestId, + onConfirm, +}: ActionConfirmDialogProps) { + const copy = getActionConfirmCopy(kind, { targetName, count, detail: description }); + + return ( + + ); +} diff --git a/client/src/components/common/ConfirmDialog.tsx b/client/src/components/common/ConfirmDialog.tsx index 5cc2e6dd..3f06bac1 100644 --- a/client/src/components/common/ConfirmDialog.tsx +++ b/client/src/components/common/ConfirmDialog.tsx @@ -31,9 +31,9 @@ export function ConfirmDialog({ confirmLabel = "确认", cancelLabel = "取消", variant = "default", - testId, - confirmTestId, - cancelTestId, + testId = "action-confirm-dialog", + confirmTestId = "action-confirm-submit", + cancelTestId = "action-confirm-cancel", onConfirm, }: ConfirmDialogProps) { const [pending, setPending] = useState(false); diff --git a/client/src/components/travel/TravelReconciliationTab.tsx b/client/src/components/travel/TravelReconciliationTab.tsx index 4cbb45a6..badb0337 100644 --- a/client/src/components/travel/TravelReconciliationTab.tsx +++ b/client/src/components/travel/TravelReconciliationTab.tsx @@ -25,8 +25,10 @@ import { EmptyStateCard } from '../common/EmptyStateCard'; import { Pagination } from '../common/Pagination'; import { StatusBadge } from '../common/StatusBadge'; import { Button } from '../ui/button'; +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '../ui/dialog'; import { Input } from '../ui/input'; import { Label } from '../ui/label'; +import { Textarea } from '../ui/textarea'; const PAGE_SIZE = 10; @@ -68,6 +70,8 @@ export function TravelReconciliationTab({ selectedBatchId, onSelectBatch }: Trav const [costCenterFilter, setCostCenterFilter] = useState(''); const [periodStartFilter, setPeriodStartFilter] = useState(''); const [periodEndFilter, setPeriodEndFilter] = useState(''); + const [confirmItemTarget, setConfirmItemTarget] = useState(null); + const [confirmItemNote, setConfirmItemNote] = useState(''); const [page, setPage] = useState(1); const [actionItemId, setActionItemId] = useState(null); @@ -248,11 +252,8 @@ export function TravelReconciliationTab({ selectedBatchId, onSelectBatch }: Trav }); function handleConfirm(item: TravelReconciliationItem) { - const note = window.prompt('确认备注(可选)', item.confirmNote ?? ''); - if (note === null) { - return; - } - confirmMutation.mutate({ item, note: note.trim() || null }); + setConfirmItemTarget(item); + setConfirmItemNote(item.confirmNote ?? ''); } const detail = detailQuery.data ?? null; @@ -260,6 +261,7 @@ export function TravelReconciliationTab({ selectedBatchId, onSelectBatch }: Trav const totalPages = Math.max(1, Math.ceil((listQuery.data?.total ?? 0) / PAGE_SIZE)); return ( + <>
{canCreateBatch ? ( @@ -520,5 +522,64 @@ export function TravelReconciliationTab({ selectedBatchId, onSelectBatch }: Trav ) : null}
+ { + if (!open) { + setConfirmItemTarget(null); + setConfirmItemNote(''); + } + }} + > + + + 确认对账异常项 + + 确认后该异常项将进入已确认状态,并写入差旅对账处理记录。备注可空。 + + +
+ +