修复报销详情时区并按 KISS 收敛页面(07-27-yang-jun-reimbursement-readiness)
这个提交包含在:
@@ -59,6 +59,7 @@ import {
|
||||
} from "../lib/invoiceStatus";
|
||||
import { resolveErrorMessage } from "../lib/resolveErrorMessage";
|
||||
import { resolveReimbursementDisplayCode } from "../lib/reimbursementDisplay";
|
||||
import { formatDisplayDateTime } from "../lib/displayFormat";
|
||||
import type { AttachmentGovernanceFields } from "../lib/attachmentGovernance";
|
||||
import { TEST_IDS } from "../lib/testIds";
|
||||
import { withUploadTimeout } from "../lib/uploadTimeout";
|
||||
@@ -122,13 +123,6 @@ function formatDate(value: string | null | undefined) {
|
||||
return value || "未填写";
|
||||
}
|
||||
|
||||
function formatDateTime(value: string | null | undefined) {
|
||||
if (!value) {
|
||||
return "-";
|
||||
}
|
||||
return value.slice(0, 19).replace("T", " ");
|
||||
}
|
||||
|
||||
function workflowActionLabel(action: string) {
|
||||
const labels: Record<string, string> = {
|
||||
start: "提交",
|
||||
@@ -171,34 +165,6 @@ function itemLabel(item: ReimbursementItem) {
|
||||
return desc ? `${typeName} · ${desc}` : typeName;
|
||||
}
|
||||
|
||||
function resolveMobileReimbursementNextStep(input: {
|
||||
status: ReimbursementStatus;
|
||||
canEdit: boolean;
|
||||
itemCount: number;
|
||||
invoiceCount: number;
|
||||
attachmentCount: number;
|
||||
}) {
|
||||
if (input.canEdit && input.itemCount === 0) {
|
||||
return "先添加报销明细,再绑定发票或补充附件。";
|
||||
}
|
||||
if (input.canEdit && input.invoiceCount === 0) {
|
||||
return "建议先添加或上传发票,提交前会做完整性检查。";
|
||||
}
|
||||
if (input.status === "draft" || input.status === "rejected") {
|
||||
return "核对金额、发票和附件后,使用底部按钮提交审批。";
|
||||
}
|
||||
if (input.status === "reviewing") {
|
||||
return "正在审批中,可查看节点和历史;需要补材料时按审批意见处理。";
|
||||
}
|
||||
if (input.status === "approved") {
|
||||
return "已通过审批,等待财务付款和归档。";
|
||||
}
|
||||
if (input.status === "paid") {
|
||||
return "已完成付款,本页用于查看发票、附件和审批留痕。";
|
||||
}
|
||||
return input.attachmentCount > 0 ? "查看附件、发票和审批记录。" : "查看当前报销状态和审批记录。";
|
||||
}
|
||||
|
||||
export function MobileReimbursementDetailPage() {
|
||||
const { reimbursementId = "" } = useParams();
|
||||
const navigate = useNavigate();
|
||||
@@ -848,14 +814,7 @@ export function MobileReimbursementDetailPage() {
|
||||
const displayCode = resolveReimbursementDisplayCode(detail);
|
||||
const showSubmitAction = Boolean(canEdit && canSubmitExpense);
|
||||
const showStickyActions = Boolean(showSubmitAction || canOpenInvoiceDialog);
|
||||
const showSecondaryActions = Boolean(actionError || canDeleteDraft || !canEdit);
|
||||
const detailNextStep = resolveMobileReimbursementNextStep({
|
||||
status: detail.status,
|
||||
canEdit,
|
||||
itemCount: detail.items.length,
|
||||
invoiceCount: detail.invoices.length,
|
||||
attachmentCount: detail.attachments?.length ?? 0,
|
||||
});
|
||||
const showSecondaryActions = Boolean(actionError || canDeleteDraft);
|
||||
|
||||
return (
|
||||
<PageScaffold
|
||||
@@ -883,31 +842,23 @@ export function MobileReimbursementDetailPage() {
|
||||
|
||||
<section className="mobile-card mobile-focus-card mobile-page-stack" data-testid="mobile-reimbursement-detail-focus">
|
||||
<div className="mobile-section-header mobile-section-header--top">
|
||||
<div className="min-w-0">
|
||||
<h2 className="mobile-section-title">当前办理摘要</h2>
|
||||
<p className="mobile-list-card__subtitle mt-1">{detailNextStep}</p>
|
||||
<div>
|
||||
<h2 className="mobile-section-title">报销概况</h2>
|
||||
</div>
|
||||
<span className={cn("rounded-full px-2.5 py-1 text-xs font-medium", STATUS_TONE[detail.status])}>
|
||||
{STATUS_LABEL[detail.status]}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div data-testid="mobile-reimbursement-summary">
|
||||
<div className="text-xs text-zinc-500">合计金额</div>
|
||||
<div className="mt-1 text-3xl font-semibold text-zinc-900">{formatCurrency(detail.totalAmount)}</div>
|
||||
</div>
|
||||
<div className="mobile-metric-grid mt-4 text-sm">
|
||||
<div className="mt-4 grid grid-cols-2 gap-3 text-sm">
|
||||
<div className="rounded-lg bg-zinc-50 px-3 py-2">
|
||||
<div className="text-xs text-zinc-500">明细</div>
|
||||
<div className="mt-1 font-semibold text-zinc-900">{detail.items.length}</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 font-semibold text-zinc-900">{detail.invoices.length}</div>
|
||||
</div>
|
||||
<div className="rounded-lg bg-zinc-50 px-3 py-2">
|
||||
<div className="text-xs text-zinc-500">日期</div>
|
||||
<div className="text-xs text-zinc-500">报销日期</div>
|
||||
<div className="mt-1 truncate font-semibold text-zinc-900">{formatDate(detail.reimbursementDate)}</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.owner?.name ?? "-"}</div>
|
||||
</div>
|
||||
</div>
|
||||
{detail.description ? <p className="mt-3 text-sm leading-6 text-zinc-600">{detail.description}</p> : null}
|
||||
</section>
|
||||
@@ -1092,7 +1043,7 @@ export function MobileReimbursementDetailPage() {
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<h2 className="text-base font-semibold text-zinc-900">附件材料</h2>
|
||||
<p className="mt-1 text-xs text-zinc-500">PDF/JPG/PNG/WebP,单次最多 5 个</p>
|
||||
{canManageAttachments ? <p className="mt-1 text-xs text-zinc-500">PDF/JPG/PNG/WebP,单次最多 5 个</p> : null}
|
||||
</div>
|
||||
{canManageAttachments ? (
|
||||
<Button
|
||||
@@ -1154,7 +1105,7 @@ export function MobileReimbursementDetailPage() {
|
||||
<div className="min-w-0">
|
||||
<div className="truncate text-sm font-semibold text-zinc-900">{attachment.originalName}</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">
|
||||
{Math.ceil(attachment.fileSize / 1024)} KB · {formatDateTime(attachment.createdAt)}
|
||||
{Math.ceil(attachment.fileSize / 1024)} KB · {formatDisplayDateTime(attachment.createdAt)}
|
||||
</div>
|
||||
<AttachmentGovernanceBadges attachment={attachment} className="mt-2" />
|
||||
</div>
|
||||
@@ -1218,7 +1169,7 @@ export function MobileReimbursementDetailPage() {
|
||||
<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">{formatDateTime(item.createdAt)}</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 || "系统"}
|
||||
@@ -1253,7 +1204,6 @@ export function MobileReimbursementDetailPage() {
|
||||
删除草稿
|
||||
</Button>
|
||||
) : null}
|
||||
{!canEdit ? <div className="text-sm text-zinc-500">当前状态不可编辑。</div> : null}
|
||||
</div>
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
在新工单中引用
屏蔽一个用户