R7E-16E/R7E-16F 高风险操作确认统一

这个提交包含在:
2026-05-15 22:55:00 +08:00
父节点 5c38d810c1
当前提交 bf791d7a67
共修改 18 个文件,包含 979 行新增和 68 行删除
+35 -8
查看文件
@@ -26,6 +26,7 @@ import { toast } from "sonner";
import { downloadBlob } from "../lib/download";
import { resolveReimbursementDisplayCode } from "../lib/reimbursementDisplay";
import { Pagination } from "../components/common/Pagination";
import { ActionConfirmDialog } from "../components/common/ActionConfirmDialog";
import { StatusBadge } from "../components/common/StatusBadge";
import { TableSkeleton } from "../components/common/TableSkeleton";
import { FilterBar, ListWorkspace, PrimaryTable, StatStrip, WorkspaceHeader } from "../components/patterns/ListWorkspace";
@@ -52,6 +53,7 @@ import { Textarea } from "../components/ui/textarea";
const PAGE_SIZE = 20;
type BatchAction = "approve" | "reject";
type ExportFormat = "csv" | "xlsx";
type BatchResultState = {
action: BatchAction;
@@ -104,6 +106,8 @@ export function ReimbursementsPage() {
const [batchDialogAction, setBatchDialogAction] = useState<BatchAction | null>(null);
const [batchComment, setBatchComment] = useState("");
const [batchResult, setBatchResult] = useState<BatchResultState | null>(null);
const [deleteTarget, setDeleteTarget] = useState<ReimbursementListItem | null>(null);
const [exportConfirmFormat, setExportConfirmFormat] = useState<ExportFormat | null>(null);
const status = (searchParams.get("status") as ReimbursementStatus | null) ?? "draft";
const page = Number(searchParams.get("page") ?? "1") || 1;
@@ -216,15 +220,11 @@ export function ReimbursementsPage() {
});
const handleDeleteDraft = (item: ReimbursementListItem) => {
const displayCode = resolveReimbursementDisplayCode(item);
if (!window.confirm(`确认删除 ${displayCode}?删除后无法恢复。`)) {
return;
}
deleteMutation.mutate(item.id);
setDeleteTarget(item);
};
const exportMutation = useMutation({
mutationFn: (format: "csv" | "xlsx") => exportReimbursements({ format, status, scope }),
mutationFn: (format: ExportFormat) => exportReimbursements({ format, status, scope }),
onSuccess: (response, format) => {
downloadBlob(response.blob, response.filename ?? `reimbursements.${format}`);
setExportError("");
@@ -461,13 +461,13 @@ export function ReimbursementsPage() {
<DropdownMenuContent align="end">
<DropdownMenuItem
data-testid="reimbursements-export-csv"
onSelect={() => exportMutation.mutate("csv")}
onSelect={() => setExportConfirmFormat("csv")}
>
导出 CSV
</DropdownMenuItem>
<DropdownMenuItem
data-testid="reimbursements-export-xlsx"
onSelect={() => exportMutation.mutate("xlsx")}
onSelect={() => setExportConfirmFormat("xlsx")}
>
导出 Excel
</DropdownMenuItem>
@@ -943,6 +943,33 @@ export function ReimbursementsPage() {
</DialogFooter>
</DialogContent>
</Dialog>
<ActionConfirmDialog
open={Boolean(deleteTarget)}
onOpenChange={(open) => {
if (!open) setDeleteTarget(null);
}}
kind="delete"
targetName={deleteTarget ? resolveReimbursementDisplayCode(deleteTarget) : undefined}
description="删除后无法恢复,已提交或审批中的报销单不会受到影响。"
onConfirm={async () => {
if (deleteTarget) {
deleteMutation.mutate(deleteTarget.id);
}
}}
/>
<ActionConfirmDialog
open={Boolean(exportConfirmFormat)}
onOpenChange={(open) => {
if (!open) setExportConfirmFormat(null);
}}
kind="sensitiveExport"
targetName={exportConfirmFormat === "xlsx" ? "报销 Excel" : "报销 CSV"}
onConfirm={async () => {
if (exportConfirmFormat) {
exportMutation.mutate(exportConfirmFormat);
}
}}
/>
</ListWorkspace>
);
}