优化详情与移动关键页视觉 Wave3(05-27-pc-detail-mobile-visual-wave3)
这个提交包含在:
@@ -961,7 +961,7 @@ export function ReimbursementDetailPage() {
|
||||
<PageScaffold>
|
||||
<EntityOverviewHero
|
||||
title={`报销详情 ${displayCode}`}
|
||||
subtitle="明细、发票和审批。"
|
||||
subtitle="状态、票据、审批和付款集中处理。"
|
||||
meta={(
|
||||
<>
|
||||
<StatusBadge status={detail.status} />
|
||||
@@ -1005,11 +1005,11 @@ export function ReimbursementDetailPage() {
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="detail-shell-grid">
|
||||
<div className="space-y-6">
|
||||
<SectionPanel title="概览与关联">
|
||||
<div className="space-y-6" data-testid="reimbursement-overview">
|
||||
<StatStrip columns={4}>
|
||||
<div className="detail-workspace-grid">
|
||||
<div className="space-y-5">
|
||||
<SectionPanel title="办理摘要">
|
||||
<div className="space-y-4" data-testid="reimbursement-overview">
|
||||
<StatStrip columns={4} className="ops-status-strip">
|
||||
<MetricCard
|
||||
title="关联项目"
|
||||
value={
|
||||
@@ -1026,20 +1026,32 @@ export function ReimbursementDetailPage() {
|
||||
/>
|
||||
</StatStrip>
|
||||
|
||||
<div className="rounded-lg border border-zinc-200 p-4 text-sm" data-testid="reimbursement-summary">
|
||||
<div>报销编号:{displayCode}</div>
|
||||
<div>报销日期:{detail.reimbursementDate ?? "-"}</div>
|
||||
<div>金额:{detail.totalAmount ?? "-"}</div>
|
||||
<div>提交人:{detail.submitter?.name ?? "-"}</div>
|
||||
<div>报销人:{detail.owner?.name ?? "-"}</div>
|
||||
<div>说明:{detail.description || "-"}</div>
|
||||
<div className="detail-command-panel" data-testid="reimbursement-summary">
|
||||
<div className="detail-fact-grid">
|
||||
<div className="detail-fact-card">
|
||||
<div className="detail-fact-card__label">报销编号</div>
|
||||
<div className="detail-fact-card__value">{displayCode}</div>
|
||||
</div>
|
||||
<div className="detail-fact-card">
|
||||
<div className="detail-fact-card__label">报销日期</div>
|
||||
<div className="detail-fact-card__value">{detail.reimbursementDate ?? "-"}</div>
|
||||
</div>
|
||||
<div className="detail-fact-card">
|
||||
<div className="detail-fact-card__label">提交人 / 报销人</div>
|
||||
<div className="detail-fact-card__value">{detail.submitter?.name ?? "-"} / {detail.owner?.name ?? "-"}</div>
|
||||
</div>
|
||||
<div className="detail-fact-card">
|
||||
<div className="detail-fact-card__label">说明</div>
|
||||
<div className="detail-fact-card__value">{detail.description || "-"}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-zinc-200 p-4 text-sm" data-testid="reimbursement-payment-status">
|
||||
<div className="detail-command-panel" data-testid="reimbursement-payment-status">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<h4 className="text-base font-semibold text-zinc-900">付款状态</h4>
|
||||
<p className="text-xs text-zinc-500">付款备注写入审计日志,凭证、日期和操作人用于财务台账核对。</p>
|
||||
<p className="text-xs text-zinc-500">凭证、日期和操作人用于财务台账核对。</p>
|
||||
</div>
|
||||
<span
|
||||
className={`inline-flex rounded-full border px-2 py-0.5 text-xs font-medium ${
|
||||
@@ -1051,22 +1063,22 @@ export function ReimbursementDetailPage() {
|
||||
{detail.status === "paid" ? "已付款" : "未付款"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-3 grid gap-3 md:grid-cols-2 xl:grid-cols-4">
|
||||
<div className="rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2">
|
||||
<div className="text-xs text-zinc-500">付款日期</div>
|
||||
<div className="mt-1 font-medium text-zinc-900">{payment.paymentDate ?? "-"}</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="rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2">
|
||||
<div className="text-xs text-zinc-500">付款凭证号</div>
|
||||
<div className="mt-1 font-medium text-zinc-900">{payment.paymentVoucherNo ?? "-"}</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="rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2">
|
||||
<div className="text-xs text-zinc-500">付款渠道</div>
|
||||
<div className="mt-1 font-medium text-zinc-900">{payment.paymentChannel ?? "-"}</div>
|
||||
<div className="detail-fact-card">
|
||||
<div className="detail-fact-card__label">付款渠道</div>
|
||||
<div className="detail-fact-card__value">{payment.paymentChannel ?? "-"}</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2">
|
||||
<div className="text-xs text-zinc-500">操作人 / 时间</div>
|
||||
<div className="mt-1 font-medium text-zinc-900">
|
||||
<div className="detail-fact-card">
|
||||
<div className="detail-fact-card__label">操作人 / 时间</div>
|
||||
<div className="detail-fact-card__value">
|
||||
{payment.paidBy?.name ?? "-"} · {formatDateTime(payment.paidAt)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1110,11 +1122,11 @@ export function ReimbursementDetailPage() {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="rounded-lg border border-zinc-200 p-4 text-sm">
|
||||
<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>
|
||||
<p className="text-xs text-zinc-500">报销可选挂到项目,用于项目执行面板汇总。</p>
|
||||
<p className="text-xs text-zinc-500">用于项目执行面板汇总。</p>
|
||||
</div>
|
||||
{detail.project ? (
|
||||
<Link to={`/projects/${detail.project.id}`} className="text-sm text-primary hover:underline">
|
||||
@@ -1179,7 +1191,7 @@ export function ReimbursementDetailPage() {
|
||||
{(detail.attachments ?? []).length ? (
|
||||
<div className="space-y-2">
|
||||
{(detail.attachments ?? []).map((attachment) => (
|
||||
<div key={attachment.id} className="flex flex-col gap-3 rounded-lg border border-zinc-200 px-4 py-3 md:flex-row md:items-center md:justify-between">
|
||||
<div key={attachment.id} className="detail-evidence-card flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
|
||||
<div className="min-w-0">
|
||||
<div className="truncate font-medium text-zinc-900">{attachment.originalName}</div>
|
||||
<div className="text-xs text-zinc-500">
|
||||
@@ -1254,7 +1266,7 @@ export function ReimbursementDetailPage() {
|
||||
<p className="text-sm text-zinc-500">正在刷新费用标准校验结果...</p>
|
||||
) : null}
|
||||
|
||||
<div className="rounded-lg border border-zinc-200">
|
||||
<div className="ops-table-focus overflow-x-auto border border-zinc-200">
|
||||
<Table data-testid="reimbursement-items-table">
|
||||
<TableHeader>
|
||||
<tr>
|
||||
@@ -1401,7 +1413,7 @@ export function ReimbursementDetailPage() {
|
||||
const invoiceHint = resolveInvoiceNextStepText(item);
|
||||
return (
|
||||
<div
|
||||
className="rounded-lg border border-zinc-200 px-4 py-3"
|
||||
className="detail-evidence-card"
|
||||
key={item.id}
|
||||
>
|
||||
<div className="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
|
||||
@@ -1421,7 +1433,7 @@ export function ReimbursementDetailPage() {
|
||||
</div>
|
||||
<div className="flex flex-col items-start gap-2 md:items-end">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="rounded-full bg-zinc-100 px-2 py-1 text-xs text-zinc-600">
|
||||
<span className="badge-neutral">
|
||||
{resolveInvoiceStatusLabel(item.status)}
|
||||
</span>
|
||||
{canUnbindInvoices ? (
|
||||
@@ -1455,7 +1467,7 @@ export function ReimbursementDetailPage() {
|
||||
<SectionPanel title="预算与审核提示">
|
||||
<div className="space-y-6">
|
||||
{budgetHint ? (
|
||||
<div className="rounded-lg border border-zinc-200 p-4 text-sm" data-testid="reimbursement-budget-hint">
|
||||
<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>
|
||||
@@ -1519,22 +1531,22 @@ export function ReimbursementDetailPage() {
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
<div className="grid gap-3 text-sm md:grid-cols-2 xl:grid-cols-4" data-testid="reimbursement-erp-export">
|
||||
<div className="rounded-lg border border-zinc-200 bg-zinc-50 p-3">
|
||||
<div className="text-xs text-zinc-500">导出状态</div>
|
||||
<div className="mt-1 font-medium text-zinc-900">{erpSyncStatus}</div>
|
||||
<div className="detail-fact-grid text-sm xl:grid-cols-4" data-testid="reimbursement-erp-export">
|
||||
<div className="detail-fact-card">
|
||||
<div className="detail-fact-card__label">导出状态</div>
|
||||
<div className="detail-fact-card__value">{erpSyncStatus}</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-zinc-200 bg-zinc-50 p-3">
|
||||
<div className="text-xs text-zinc-500">最近导出</div>
|
||||
<div className="mt-1 font-medium text-zinc-900">{formatDateTime(erpPreviewQuery.data?.exportState.exportedAt ?? null)}</div>
|
||||
<div className="detail-fact-card">
|
||||
<div className="detail-fact-card__label">最近导出</div>
|
||||
<div className="detail-fact-card__value">{formatDateTime(erpPreviewQuery.data?.exportState.exportedAt ?? null)}</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-zinc-200 bg-zinc-50 p-3">
|
||||
<div className="text-xs text-zinc-500">导出任务</div>
|
||||
<div className="mt-1 break-all font-medium text-zinc-900">{erpPreviewQuery.data?.exportState.lastOutboxId ?? "-"}</div>
|
||||
<div className="detail-fact-card">
|
||||
<div className="detail-fact-card__label">导出任务</div>
|
||||
<div className="detail-fact-card__value break-all">{erpPreviewQuery.data?.exportState.lastOutboxId ?? "-"}</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-zinc-200 bg-zinc-50 p-3">
|
||||
<div className="text-xs text-zinc-500">外部凭证号</div>
|
||||
<div className="mt-1 break-all font-medium text-zinc-900">{erpPreviewQuery.data?.exportState.externalId ?? "-"}</div>
|
||||
<div className="detail-fact-card">
|
||||
<div className="detail-fact-card__label">外部凭证号</div>
|
||||
<div className="detail-fact-card__value break-all">{erpPreviewQuery.data?.exportState.externalId ?? "-"}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1549,7 +1561,7 @@ export function ReimbursementDetailPage() {
|
||||
</SectionPanel>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-5">
|
||||
<DetailSideMeta title="报销摘要">
|
||||
<div>报销编号:{displayCode}</div>
|
||||
<div>报销人:{detail.owner?.name ?? "-"}</div>
|
||||
@@ -1565,16 +1577,16 @@ export function ReimbursementDetailPage() {
|
||||
title="审批进度"
|
||||
description={`当前位于第 ${detail.workflowProgress.currentStepIndex + 1} / ${detail.workflowProgress.totalSteps} 步。`}
|
||||
>
|
||||
<div className="space-y-3" data-testid={TEST_IDS.reimbursement.workflowProgress}>
|
||||
<div className="detail-timeline" data-testid={TEST_IDS.reimbursement.workflowProgress}>
|
||||
{detail.workflowProgress.steps.map((step, index) => (
|
||||
<div
|
||||
key={step.id}
|
||||
className={
|
||||
step.status === "current"
|
||||
? "rounded-lg border border-primary-200 bg-primary-50 px-3 py-2"
|
||||
? "detail-timeline-item detail-timeline-item--current"
|
||||
: step.status === "completed"
|
||||
? "rounded-lg border border-emerald-200 bg-emerald-50 px-3 py-2"
|
||||
: "rounded-lg border border-zinc-200 bg-white px-3 py-2"
|
||||
? "detail-timeline-item detail-timeline-item--done"
|
||||
: "detail-timeline-item"
|
||||
}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3 text-sm">
|
||||
@@ -1593,9 +1605,9 @@ export function ReimbursementDetailPage() {
|
||||
|
||||
<DetailSideMeta title="审批记录">
|
||||
{detail.approvalHistory.length ? (
|
||||
<div className="space-y-3" data-testid="reimbursement-approval-history">
|
||||
<div className="detail-timeline" data-testid="reimbursement-approval-history">
|
||||
{detail.approvalHistory.map((item) => (
|
||||
<div key={item.id} className="rounded-lg border border-zinc-200 bg-white px-3 py-2 text-sm">
|
||||
<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">{formatDateTime(item.createdAt)}</span>
|
||||
|
||||
在新工单中引用
屏蔽一个用户