收敛报销详情并展示五阶段进度(07-27-yang-jun-reimbursement-readiness)

这个提交包含在:
2026-08-01 12:37:11 +08:00
父节点 4cf73ce0d2
当前提交 421c2d6e1e
共修改 11 个文件,包含 840 行新增和 352 行删除
@@ -40,9 +40,11 @@ import { AttachmentGovernanceBadges } from "../components/common/AttachmentGover
import { AttachmentDeleteConfirmDialog } from "../components/common/AttachmentDeleteConfirmDialog";
import { useAttachmentDownloadReasonDialog } from "../components/common/useAttachmentDownloadReasonDialog";
import { ConfirmDialog } from "../components/common/ConfirmDialog";
import { RiskFlagsCard } from "../components/common/RiskFlagsCard";
import { MobileIcon } from "../components/mobile/MobileIcon";
import { MobileDetailHeader } from "../components/mobile/MobileDetailHeader";
import { ReimbursementTravelSummary } from "../components/reimbursements/ReimbursementTravelSummary";
import { ReimbursementProcessingProgress } from "../components/reimbursements/ReimbursementProcessingProgress";
import { PageScaffold } from "../components/patterns/PageScaffold";
import { Button } from "../components/ui/button";
import {
@@ -132,20 +134,6 @@ function formatDate(value: string | null | undefined) {
return value || "未填写";
}
function workflowActionLabel(action: string) {
const labels: Record<string, string> = {
start: "提交",
approve: "通过",
reject: "驳回",
return: "退回",
transfer: "转审",
withdraw: "撤回",
remind: "催办",
overdue_remind: "超时提醒",
};
return labels[action] ?? action;
}
function matchInvoiceSearch(
invoice: {
invoiceCode?: string | null;
@@ -933,17 +921,40 @@ export function MobileReimbursementDetailPage() {
<div className="text-xs text-zinc-500">报销人</div>
<div className="mt-1 truncate font-semibold text-zinc-900">{detail.owner?.name ?? "-"}</div>
</div>
<div className="rounded-lg bg-zinc-50 px-3 py-2">
<div className="text-xs text-zinc-500">部门</div>
<div className="mt-1 truncate font-semibold text-zinc-900">{detail.department?.name ?? "-"}</div>
</div>
<div className="rounded-lg bg-zinc-50 px-3 py-2">
<div className="text-xs text-zinc-500">申请人</div>
<div className="mt-1 truncate font-semibold text-zinc-900">{detail.submitter?.name ?? "-"}</div>
</div>
</div>
{detail.description ? <p className="mt-3 text-sm leading-6 text-zinc-600">{detail.description}</p> : null}
<ReimbursementTravelSummary
detail={detail}
reimbursementId={reimbursementId}
editable={Boolean(canEdit && isParticipant)}
mobile
embedded
/>
{detail.project || detail.linkedTravelRequest ? (
<div className="mt-4 border-t border-zinc-200 pt-4 text-sm text-zinc-600">
{detail.project ? <p>项目:{detail.project.projectNo} · {detail.project.name}</p> : null}
{detail.linkedTravelRequest ? (
<Link className="mt-2 inline-flex min-h-11 items-center text-primary hover:underline" to={`/travel-requests?tab=requests&id=${detail.linkedTravelRequest.id}`}>
差旅申请:{detail.linkedTravelRequest.requestNo ?? "未编号"}
</Link>
) : null}
</div>
) : null}
{detail.riskFlags.length ? (
<div className="mt-4 border-t border-zinc-200 pt-4">
<RiskFlagsCard items={detail.riskFlags} title="待处理风险项" />
</div>
) : null}
</section>
<ReimbursementTravelSummary
detail={detail}
reimbursementId={reimbursementId}
editable={Boolean(canEdit && isParticipant)}
mobile
/>
<section className="mobile-card" data-testid="mobile-reimbursement-items">
<div className="flex items-center justify-between gap-3">
<h2 className="text-base font-semibold text-zinc-900">报销明细</h2>
@@ -1164,7 +1175,6 @@ export function MobileReimbursementDetailPage() {
) : null}
{detail.invoices.length || (detail.attachments ?? []).length ? (
canOpenInvoiceDialog || canManageAttachments || Boolean(materialPreviewError) ? (
<div className="mt-3 space-y-2">
<div data-testid={TEST_IDS.reimbursement.invoices} className="space-y-2">
{detail.invoices.map((invoice) => {
@@ -1247,7 +1257,6 @@ export function MobileReimbursementDetailPage() {
))}
</div>
</div>
) : null
) : (
<div className="mt-3 rounded-lg border border-dashed border-zinc-200 p-5 text-center text-sm text-zinc-500">
暂无报销材料
@@ -1255,14 +1264,10 @@ export function MobileReimbursementDetailPage() {
)}
</section>
{detail.status === "reviewing" || detail.approvalHistory.length ? (
<section className="mobile-card" data-testid="mobile-reimbursement-approval-history">
<section className="mobile-card" data-testid="mobile-reimbursement-processing-section">
<div className="flex items-start justify-between gap-3">
<div>
<h2 className="text-base font-semibold text-zinc-900">审批</h2>
<p className="mt-1 text-xs text-zinc-500">
当前节点:{detail.pendingWorkflow?.task?.step_name ?? "-"}
</p>
<h2 className="text-base font-semibold text-zinc-900">处理进度</h2>
</div>
{canRemindPendingWorkflow && pendingTaskId ? (
<Button
@@ -1276,27 +1281,10 @@ export function MobileReimbursementDetailPage() {
</Button>
) : null}
</div>
{detail.approvalHistory.length ? (
<div className="mt-3 space-y-2">
{detail.approvalHistory.map((item) => (
<article key={item.id} className="mobile-list-card">
<div className="flex items-center justify-between gap-3">
<div className="text-sm font-semibold text-zinc-900">{workflowActionLabel(item.action)}</div>
<div className="shrink-0 text-xs text-zinc-500">{formatDisplayDateTime(item.createdAt)}</div>
</div>
<div className="mt-1 text-xs text-zinc-500">
{item.stepName ?? "-"} · {item.actorName || "系统"}
</div>
{item.comment ? <p className="mt-2 text-sm leading-5 text-zinc-600">{item.comment}</p> : null}
</article>
))}
</div>
) : (
<p className="mt-3 text-sm text-zinc-500">暂无审批记录。</p>
)}
<div className="mt-3">
<ReimbursementProcessingProgress progress={detail.processingProgress ?? { stages: [] }} mobile />
</div>
</section>
) : null}
{showSecondaryActions ? (
<section className="mobile-card" data-testid="mobile-reimbursement-actions">
+65 -284
查看文件
@@ -47,6 +47,7 @@ import { AttachmentDeleteConfirmDialog } from "../components/common/AttachmentDe
import { useAttachmentDownloadReasonDialog } from "../components/common/useAttachmentDownloadReasonDialog";
import { RiskFlagsCard } from "../components/common/RiskFlagsCard";
import { ReimbursementTravelSummary } from "../components/reimbursements/ReimbursementTravelSummary";
import { ReimbursementProcessingProgress } from "../components/reimbursements/ReimbursementProcessingProgress";
import { StatusBadge } from "../components/common/StatusBadge";
import { Button } from "../components/ui/button";
import {
@@ -58,7 +59,6 @@ import {
} from "../components/ui/dialog";
import { Input } from "../components/ui/input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../components/ui/table";
import { DetailSideMeta } from "../components/patterns/DetailSideMeta";
import { EntityOverviewHero } from "../components/patterns/EntityOverviewHero";
import { PageScaffold } from "../components/patterns/PageScaffold";
import { SectionPanel } from "../components/patterns/SectionPanel";
@@ -98,42 +98,6 @@ function buildActionErrorMessage(error: unknown, fallbackMessage: string) {
return fallbackMessage;
}
function workflowActionLabel(action: string) {
const labels: Record<string, string> = {
start: "提交",
approve: "通过",
reject: "驳回",
return: "退回",
transfer: "转审",
withdraw: "撤回",
remind: "催办",
overdue_remind: "超时提醒",
};
return labels[action] ?? action;
}
function budgetControlModeLabel(controlMode: string | null | undefined) {
const labels: Record<string, string> = {
block: "超额阻断",
warn: "超额提醒",
manual_review: "人工复核",
};
return controlMode ? labels[controlMode] ?? "按制度处理" : "按制度处理";
}
function materialCompletenessLabel(count: number, noun: string) {
return count > 0 ? `${count} ${noun}` : `缺${noun}`;
}
function paymentChannelLabel(value: string | null | undefined) {
if (value === "bank") return "银行转账";
if (value === "alipay") return "支付宝";
if (value === "wechat") return "微信";
if (value === "cash") return "现金";
if (value === "other") return "其他";
return value || "-";
}
export function ReimbursementDetailPage() {
const { reimbursementId = "" } = useParams();
const navigate = useNavigate();
@@ -792,16 +756,8 @@ export function ReimbursementDetailPage() {
}
const detail = detailQuery.data;
const payment = detail.payment ?? {
paymentVoucherNo: null,
paymentDate: null,
paymentChannel: null,
paidAt: null,
paidBy: null,
};
const displayCode = resolveReimbursementDisplayCode(detail);
const budgetHint = detail.budgetHint;
const canPay = hasPermission(permissions, "expense.finance.pay");
const canEditItems = ["draft", "rejected"].includes(detail.status);
const isReimbursementParticipant = Boolean(me?.id) && [detail.ownerId, detail.submitterId].includes(me?.id ?? "");
const pendingTaskId = detail.pendingWorkflow?.task?.id ?? null;
@@ -992,21 +948,13 @@ export function ReimbursementDetailPage() {
const itemCount = detail.items.length;
const invoiceCount = detail.invoices.length;
const hasBlockingPrecheck = blockStandardChecks.length > 0;
const currentHandlerText = (() => {
if (detail.status === "reviewing") {
return detail.pendingWorkflow?.task?.step_name ?? "当前审批节点";
}
if (detail.status === "approved") {
return canPay ? "财务处理" : "等待财务";
}
if (detail.status === "paid") {
return payment.paidBy?.name ?? "财务已处理";
}
if (["draft", "rejected"].includes(detail.status)) {
return detail.owner?.name ?? "报销人";
}
return "-";
})();
const processingProgress = detail.processingProgress ?? { stages: [] };
const currentProcessingStage = processingProgress.stages.find((stage) =>
stage.state === "current" || stage.state === "exception",
);
const currentHandlerText = currentProcessingStage
? `${currentProcessingStage.label}${currentProcessingStage.actorName ? ` · ${currentProcessingStage.actorName}` : ""}`
: detail.status === "paid" ? "已完成" : "待处理";
const nextStepText = (() => {
if (["draft", "rejected"].includes(detail.status)) {
if (!itemCount) {
@@ -1021,9 +969,7 @@ export function ReimbursementDetailPage() {
return "提交审批";
}
if (detail.status === "reviewing") {
return detail.workflowProgress
? `查看第 ${detail.workflowProgress.currentStepIndex + 1} / ${detail.workflowProgress.totalSteps} 步`
: "查看审批进度";
return currentProcessingStage?.detail ?? "查看处理进度";
}
if (detail.status === "approved") {
return "等待财务处理";
@@ -1033,9 +979,7 @@ export function ReimbursementDetailPage() {
}
return "查看记录";
})();
const showRelatedRecords = canEditItems || Boolean(detail.project || detail.linkedTravelRequest);
const showReviewHints = Boolean(budgetHint || detail.riskFlags.length);
const showApprovalRecords = Boolean(detail.workflowProgress || detail.approvalHistory.length);
const primaryAction = (() => {
if (["draft", "rejected"].includes(detail.status) && canEditItems) {
if (!itemCount) {
@@ -1085,7 +1029,7 @@ export function ReimbursementDetailPage() {
if (detail.status === "approved") {
return (
<Button asChild data-testid="reimbursement-primary-action">
<a href="#reimbursement-payment-panel">看付款状态</a>
<a href="#reimbursement-processing-progress-panel">看处理进度</a>
</Button>
);
}
@@ -1093,7 +1037,7 @@ export function ReimbursementDetailPage() {
if (detail.status === "paid") {
return (
<Button asChild data-testid="reimbursement-primary-action">
<a href="#reimbursement-payment-panel">查看付款结果</a>
<a href="#reimbursement-processing-progress-panel">查看付款结果</a>
</Button>
);
}
@@ -1155,7 +1099,7 @@ export function ReimbursementDetailPage() {
)}
/>
<div className="detail-workspace-grid">
<div className="mx-auto w-full max-w-5xl space-y-5">
<div className="space-y-5">
<SectionPanel title="报销概况">
<div className="detail-command-panel space-y-4" data-testid="reimbursement-overview">
@@ -1175,9 +1119,9 @@ export function ReimbursementDetailPage() {
<div className="detail-fact-card__value">{currentHandlerText}</div>
</div>
<div className="detail-fact-card">
<div className="detail-fact-card__label">材料</div>
<div className="detail-fact-card__label">部门</div>
<div className="detail-fact-card__value">
{materialCompletenessLabel(itemCount, "条明细")} / {materialCompletenessLabel(invoiceCount, "张发票")}
{detail.department?.name ?? "-"}
</div>
</div>
<div className="detail-fact-card">
@@ -1200,6 +1144,42 @@ export function ReimbursementDetailPage() {
{detail.description}
</div>
) : null}
<ReimbursementTravelSummary
detail={detail}
reimbursementId={reimbursementId}
editable={Boolean(canEditItems && isReimbursementParticipant)}
embedded
/>
{canEditItems || detail.project || detail.linkedTravelRequest ? (
<div className="mt-4 border-t border-zinc-200 pt-4 text-sm" data-testid="reimbursement-related-records">
<div className="flex flex-wrap items-center justify-between gap-3">
<span className="font-medium text-zinc-900">关联事项</span>
{detail.linkedTravelRequest ? (
<Link to={`/travel-requests?tab=requests&id=${detail.linkedTravelRequest.id}`} className="text-primary hover:underline">
差旅申请 {detail.linkedTravelRequest.requestNo ?? "未编号"}
</Link>
) : null}
</div>
{canEditItems ? (
<select
className="select-std mt-3 max-w-xl"
value={detail.projectId ?? ""}
onChange={(event) => projectLinkMutation.mutate(event.target.value || null)}
disabled={projectLinkMutation.isPending}
aria-label="关联项目"
>
<option value="">不关联项目</option>
{(projectsQuery.data?.items ?? []).map((item) => (
<option key={item.id} value={item.id}>{item.projectNo} · {item.name}</option>
))}
</select>
) : detail.project ? (
<Link className="mt-2 inline-block text-primary hover:underline" to={`/projects/${detail.project.id}`}>
项目 {detail.project.projectNo} · {detail.project.name}
</Link>
) : null}
</div>
) : null}
</div>
{actionError ? (
@@ -1220,45 +1200,19 @@ export function ReimbursementDetailPage() {
请修改超标明细后提交。
</p>
) : null}
{showReviewHints ? (
<div className="space-y-3" data-testid="reimbursement-review-hints">
{budgetHint ? (
<p className="rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-800">
预算 {budgetHint.budgetCode}:提交后预计使用 {budgetHint.nextUsagePercent.toFixed(2)}%
{budgetHint.exceedsThreshold ? ",已达到预警阈值。" : "。"}
</p>
) : null}
{detail.riskFlags.length ? <RiskFlagsCard items={detail.riskFlags} title="待处理风险项" /> : null}
</div>
) : null}
</div>
{["approved", "paid"].includes(detail.status) ? (
<div id="reimbursement-payment-panel" className="detail-command-panel mt-4" data-testid="reimbursement-payment-status">
<div className="flex items-center justify-between gap-3">
<h4 className="text-base font-semibold text-zinc-900">付款状态</h4>
<span
className={`inline-flex rounded-full border px-2 py-0.5 text-xs font-medium ${
detail.status === "paid"
? "border-emerald-200 bg-emerald-50 text-emerald-700"
: "border-amber-200 bg-amber-50 text-amber-700"
}`}
>
{detail.status === "paid" ? "已付款" : "未付款"}
</span>
</div>
<div className="detail-fact-grid mt-3 xl:grid-cols-4">
<div className="detail-fact-card">
<div className="detail-fact-card__label">付款日期</div>
<div className="detail-fact-card__value">{payment.paymentDate ?? "-"}</div>
</div>
<div className="detail-fact-card">
<div className="detail-fact-card__label">付款凭证号</div>
<div className="detail-fact-card__value">{payment.paymentVoucherNo ?? "-"}</div>
</div>
<div className="detail-fact-card">
<div className="detail-fact-card__label">付款渠道</div>
<div className="detail-fact-card__value">{paymentChannelLabel(payment.paymentChannel)}</div>
</div>
<div className="detail-fact-card">
<div className="detail-fact-card__label">操作人 / 时间</div>
<div className="detail-fact-card__value">
{payment.paidBy?.name ?? "-"} · {formatDisplayDateTime(payment.paidAt)}
</div>
</div>
</div>
</div>
) : null}
{showSecondaryDetail ? (
<div className="space-y-4 pt-4" data-testid="reimbursement-secondary-detail">
<div className="detail-command-panel">
@@ -1291,12 +1245,6 @@ export function ReimbursementDetailPage() {
) : null}
</SectionPanel>
<ReimbursementTravelSummary
detail={detail}
reimbursementId={reimbursementId}
editable={Boolean(canEditItems && isReimbursementParticipant)}
/>
<SectionPanel title="报销明细">
<div className="space-y-4">
<div className="space-y-4" data-testid="reimbursement-items">
@@ -1489,7 +1437,6 @@ export function ReimbursementDetailPage() {
</p>
) : null}
{canOpenInvoiceBindDialog || materialPreviewError ? (
<div className="space-y-2" data-testid={TEST_IDS.reimbursement.invoices}>
{detail.invoices.map((invoice) => {
const invoiceHint = resolveInvoiceNextStepText(invoice);
@@ -1529,7 +1476,6 @@ export function ReimbursementDetailPage() {
);
})}
</div>
) : null}
<div className="space-y-4" data-testid="reimbursement-attachments">
{canEditItems ? (
@@ -1575,7 +1521,7 @@ export function ReimbursementDetailPage() {
</div>
) : null}
{(detail.attachments ?? []).length && (canEditItems || Boolean(materialPreviewError)) ? (
{(detail.attachments ?? []).length ? (
<div className="space-y-2">
{(detail.attachments ?? []).map((attachment) => (
<div key={attachment.id} className="detail-evidence-card flex flex-col gap-3 md:flex-row md:items-center">
@@ -1617,179 +1563,14 @@ export function ReimbursementDetailPage() {
</div>
</SectionPanel>
{showRelatedRecords ? (
<SectionPanel title="关联事项">
<div className="space-y-4" data-testid="reimbursement-related-records">
<div className="detail-command-panel">
<div className="flex items-center justify-between gap-3">
<div>
<h4 className="text-base font-semibold text-zinc-900">关联项目</h4>
</div>
{detail.project ? (
<Link to={`/projects/${detail.project.id}`} className="text-sm text-primary hover:underline">
查看项目
</Link>
) : null}
</div>
{canEditItems ? (
<select
className="select-std mt-4 max-w-xl"
value={detail.projectId ?? ""}
onChange={(event) => projectLinkMutation.mutate(event.target.value || null)}
disabled={projectLinkMutation.isPending}
>
<option value="">不关联项目</option>
{(projectsQuery.data?.items ?? []).map((item) => (
<option key={item.id} value={item.id}>
{item.projectNo} · {item.name}
</option>
))}
</select>
) : (
<div className="mt-3 text-sm text-zinc-600">
{detail.project ? `${detail.project.projectNo} · ${detail.project.name}` : "未关联项目"}
</div>
)}
</div>
{detail.linkedTravelRequest ? (
<div className="rounded-lg border border-sky-200 bg-sky-50/70 p-4 text-sm">
<div className="flex items-start justify-between gap-3">
<div>
<h4 className="text-base font-semibold text-zinc-900">
关联差旅申请:{detail.linkedTravelRequest.requestNo ?? "未生成申请编号"}
</h4>
<p className="mt-1 text-zinc-600">{detail.linkedTravelRequest.purpose || "未填写出差目的"}</p>
</div>
<Link
to={`/travel-requests?tab=requests&id=${detail.linkedTravelRequest.id}`}
className="text-sm text-primary hover:underline"
>
查看申请
</Link>
</div>
<div className="mt-3 grid gap-3 md:grid-cols-3">
<div className="rounded-lg border border-white/80 bg-white/80 px-3 py-2">
<div className="text-xs text-zinc-500">日期范围</div>
<div className="mt-1 font-medium text-zinc-900">
{detail.linkedTravelRequest.startDate ?? "-"} 至 {detail.linkedTravelRequest.endDate ?? "-"}
</div>
</div>
<div className="rounded-lg border border-white/80 bg-white/80 px-3 py-2">
<div className="text-xs text-zinc-500">预估金额</div>
<div className="mt-1 font-medium text-zinc-900">¥{detail.linkedTravelRequest.estimatedAmount}</div>
</div>
<div className="rounded-lg border border-white/80 bg-white/80 px-3 py-2">
<div className="text-xs text-zinc-500">申请编号</div>
<div className="mt-1 font-medium text-zinc-900">
{detail.linkedTravelRequest.requestNo ?? "未生成申请编号"}
</div>
</div>
</div>
</div>
) : null}
<SectionPanel title="处理进度">
<div id="reimbursement-processing-progress-panel">
<ReimbursementProcessingProgress progress={processingProgress} />
</div>
</SectionPanel>
) : null}
{showReviewHints ? (
<SectionPanel title="预算与审核提示">
<div className="space-y-4">
{budgetHint ? (
<div className="detail-command-panel text-sm" data-testid="reimbursement-budget-hint">
<h3 className="text-base font-semibold text-zinc-900">预算提示</h3>
<div className="mt-3">预算池:{budgetHint.budgetCode} / {budgetHint.budgetName}</div>
<div>
本次提交后预计占用:{budgetHint.nextUsedAmount} / {budgetHint.allocatedAmount}
({budgetHint.nextUsagePercent.toFixed(2)}%)
</div>
<div>控制方式:{budgetControlModeLabel(budgetHint.controlMode)}</div>
{budgetHint.exceedsBudget && budgetHint.controlMode === "block" ? (
<p className="mt-3 rounded-md border border-red-200 bg-red-50 px-2 py-1 text-red-600">
当前金额将超出预算上限,提交时会被阻断。
</p>
) : budgetHint.exceedsThreshold ? (
<p className="mt-3 rounded-md border border-amber-200 bg-amber-50 px-2 py-1 text-amber-700">
当前金额已达到预算预警阈值,提交后会记录预算预警审计。
</p>
) : (
<p className="mt-3 text-zinc-500">当前预算充足,可继续提交。</p>
)}
</div>
) : null}
{detail.riskFlags.length ? <RiskFlagsCard items={detail.riskFlags} title="待处理风险项" /> : null}
</div>
</SectionPanel>
) : null}
{showApprovalRecords ? (
<SectionPanel title="审批与处理记录">
<div id="reimbursement-workflow-progress-panel" className="space-y-4">
{detail.workflowProgress ? (
<div className="detail-command-panel">
<div className="mb-3 text-sm text-zinc-500">
当前位于第 {detail.workflowProgress.currentStepIndex + 1} / {detail.workflowProgress.totalSteps} 步。
</div>
<div className="detail-timeline" data-testid={TEST_IDS.reimbursement.workflowProgress}>
{detail.workflowProgress.steps.map((step, index) => (
<div
key={step.id}
className={
step.status === "current"
? "detail-timeline-item detail-timeline-item--current"
: step.status === "completed"
? "detail-timeline-item detail-timeline-item--done"
: "detail-timeline-item"
}
>
<div className="flex items-center justify-between gap-3 text-sm">
<span className="font-medium text-zinc-900">
第 {index + 1} 步 · {step.name}
</span>
<span className="text-xs text-zinc-500">
{step.status === "completed" ? "已完成" : step.status === "current" ? "当前节点" : "待处理"}
</span>
</div>
</div>
))}
</div>
</div>
) : null}
{detail.approvalHistory.length ? (
<div className="detail-command-panel">
<h3 className="text-base font-semibold text-zinc-900">审批记录</h3>
<div className="detail-timeline mt-3" data-testid="reimbursement-approval-history">
{detail.approvalHistory.map((item) => (
<div key={item.id} className="detail-timeline-item">
<div className="flex items-center justify-between gap-3">
<span className="font-medium text-zinc-900">{workflowActionLabel(item.action)}</span>
<span className="text-xs text-zinc-500">{formatDisplayDateTime(item.createdAt)}</span>
</div>
<div className="mt-1 text-xs text-zinc-500">
{item.stepName ?? "-"} · {item.actorName || "系统"}
</div>
{item.comment ? <div className="mt-2 text-zinc-600">{item.comment}</div> : null}
</div>
))}
</div>
</div>
) : null}
</div>
</SectionPanel>
) : null}
</div>
<div className="space-y-5">
{detail.status === "approved" && canPay ? (
<DetailSideMeta title="付款">
<Button asChild variant="secondary" size="sm">
<Link to="/payment-batches">进入付款批次</Link>
</Button>
</DetailSideMeta>
) : null}
<ReimbursementActions
status={detail.status}
showSubmitAction={false}