优化财务与管理页视觉 Wave4(05-27-pc-finance-admin-visual-wave4)

这个提交包含在:
2026-05-27 22:23:45 +08:00
父节点 8b3db8fa4e
当前提交 c5eb27eac8
共修改 18 个文件,包含 1379 行新增和 96 行删除
+54 -23
查看文件
@@ -123,10 +123,18 @@ export function PaymentBatchesPage() {
});
const selectedBatchId = searchParams.get("id") ?? "";
const batches = useMemo(() => listQuery.data?.items ?? [], [listQuery.data?.items]);
const selectedBatch = useMemo<PaymentBatch | null>(() => {
const items = listQuery.data?.items ?? [];
return items.find((item) => item.id === selectedBatchId) ?? items[0] ?? null;
}, [listQuery.data?.items, selectedBatchId]);
return batches.find((item) => item.id === selectedBatchId) ?? batches[0] ?? null;
}, [batches, selectedBatchId]);
const batchStats = useMemo(() => {
return {
total: batches.length,
actionable: batches.filter((item) => ["draft", "reviewed", "failed"].includes(item.status)).length,
highRisk: batches.filter((item) => item.highRisk).length,
failed: batches.filter((item) => item.status === "failed").length,
};
}, [batches]);
const receiptAttachmentsQuery = useQuery({
queryKey: ["payment-batch-receipts", selectedBatch?.id],
@@ -332,12 +340,35 @@ export function PaymentBatchesPage() {
return (
<ListWorkspace>
<WorkspaceHeader title="付款批次" subtitle="线下银行付款后的 OA 复核、回单留存和已付款标记;高风险批次达到 5 万元后需完成二次复核。" />
<WorkspaceHeader title="付款批次" subtitle="复核、回单和已付款标记集中处理。" />
{error ? <div className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">{error}</div> : null}
<div className="grid gap-6 xl:grid-cols-[360px,1fr]">
<div className="card-std space-y-4 p-4 md:p-5">
<div className="finance-status-strip" data-testid="payment-batches-summary">
<div className="finance-metric-card">
<div className="finance-metric-card__label">批次数</div>
<div className="finance-metric-card__value">{batchStats.total}</div>
</div>
<div className="finance-metric-card">
<div className="finance-metric-card__label">可处理</div>
<div className="finance-metric-card__value">{batchStats.actionable}</div>
</div>
<div className="finance-metric-card">
<div className="finance-metric-card__label">高风险</div>
<div className="finance-metric-card__value">{batchStats.highRisk}</div>
</div>
<div className="finance-metric-card">
<div className="finance-metric-card__label">失败批次</div>
<div className="finance-metric-card__value">{batchStats.failed}</div>
</div>
<div className="finance-metric-card">
<div className="finance-metric-card__label">当前批次</div>
<div className="finance-metric-card__value text-base">{selectedBatch?.batchNo ?? "-"}</div>
</div>
</div>
<div className="finance-workspace-grid">
<div className="finance-command-panel space-y-4">
<h3 className="text-base font-semibold text-zinc-900">新建批次</h3>
{!canManageBatch ? (
<div className="rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2 text-sm text-zinc-600">
@@ -411,15 +442,15 @@ export function PaymentBatchesPage() {
</Button>
</div>
<div className="space-y-6">
<div className="card-std p-4 md:p-5">
<div className="space-y-5">
<div className="finance-command-panel">
<h3 className="mb-3 text-base font-semibold text-zinc-900">批次列表</h3>
<div className="space-y-2">
{(listQuery.data?.items ?? []).map((item) => (
{batches.map((item) => (
<button
key={item.id}
type="button"
className={selectedBatch?.id === item.id ? "w-full rounded-lg border border-primary bg-primary/5 px-3 py-3 text-left" : "w-full rounded-lg border border-zinc-200 px-3 py-3 text-left hover:bg-zinc-50"}
className={selectedBatch?.id === item.id ? "finance-list-card finance-list-card--active" : "finance-list-card"}
onClick={() => {
const next = new URLSearchParams(searchParams);
next.set("id", item.id);
@@ -443,8 +474,8 @@ export function PaymentBatchesPage() {
</div>
{selectedBatch ? (
<div className="space-y-6">
<div className="card-std space-y-3 p-4 md:p-5">
<div className="space-y-5">
<div className="finance-command-panel space-y-3">
<div className="flex flex-wrap items-center justify-between gap-3">
<div>
<div className="text-xs text-zinc-500">{selectedBatch.batchNo}</div>
@@ -499,13 +530,13 @@ export function PaymentBatchesPage() {
) : null}
</div>
</div>
<div className="grid gap-3 text-sm md:grid-cols-3 xl:grid-cols-6">
<div>状态:{batchStatusLabel(selectedBatch.status)}</div>
<div>实体:{entityTypeLabel(selectedBatch.entityType)}</div>
<div>总金额:{selectedBatch.totalAmount}</div>
<div>默认日期:{formatDisplayDate(selectedBatch.defaultPaymentDate)}</div>
<div>默认渠道:{paymentChannelLabel(selectedBatch.defaultPaymentChannel)}</div>
<div>复核进度:{selectedBatch.reviewRequirement.completedReviewCount}/{selectedBatch.reviewRequirement.requiredReviewCount}</div>
<div className="detail-fact-grid text-sm xl:grid-cols-6">
<div className="detail-fact-card"><div className="detail-fact-card__label">状态</div><div className="detail-fact-card__value">{batchStatusLabel(selectedBatch.status)}</div></div>
<div className="detail-fact-card"><div className="detail-fact-card__label">实体</div><div className="detail-fact-card__value">{entityTypeLabel(selectedBatch.entityType)}</div></div>
<div className="detail-fact-card"><div className="detail-fact-card__label">总金额</div><div className="detail-fact-card__value">{selectedBatch.totalAmount}</div></div>
<div className="detail-fact-card"><div className="detail-fact-card__label">默认日期</div><div className="detail-fact-card__value">{formatDisplayDate(selectedBatch.defaultPaymentDate)}</div></div>
<div className="detail-fact-card"><div className="detail-fact-card__label">默认渠道</div><div className="detail-fact-card__value">{paymentChannelLabel(selectedBatch.defaultPaymentChannel)}</div></div>
<div className="detail-fact-card"><div className="detail-fact-card__label">复核进度</div><div className="detail-fact-card__value">{selectedBatch.reviewRequirement.completedReviewCount}/{selectedBatch.reviewRequirement.requiredReviewCount}</div></div>
</div>
<div className="rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-3 text-sm text-zinc-600">
当前要求:{selectedBatch.reviewRequirement.requiredReviewCount === 2 ? `批次金额达到 ${selectedBatch.reviewRequirement.thresholdAmount},需两轮复核` : "单轮复核后可执行"}。
@@ -515,7 +546,7 @@ export function PaymentBatchesPage() {
</div>
</div>
<div className="card-std space-y-4 p-4 md:p-5">
<div className="finance-command-panel space-y-4">
<div className="flex flex-wrap items-start justify-between gap-3">
<div>
<h3 className="text-base font-semibold text-zinc-900">银行回单附件</h3>
@@ -562,7 +593,7 @@ export function PaymentBatchesPage() {
) : null}
<div className="space-y-2">
{(receiptAttachmentsQuery.data?.items ?? []).map((attachment) => (
<div key={attachment.id} className="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-zinc-200 px-3 py-3">
<div key={attachment.id} className="detail-evidence-card flex flex-wrap items-center justify-between gap-3">
<div className="min-w-0">
<div className="truncate text-sm font-medium text-zinc-900">{attachment.originalName}</div>
<div className="mt-1 text-xs text-zinc-500">
@@ -602,11 +633,11 @@ export function PaymentBatchesPage() {
</div>
</div>
<div className="card-std space-y-4 p-4 md:p-5">
<div className="finance-command-panel space-y-4">
<h3 className="text-base font-semibold text-zinc-900">批次项</h3>
<div className="space-y-4">
{selectedBatch.items.map((item) => (
<div key={item.id} className="rounded-lg border border-zinc-200 p-4">
<div key={item.id} className="detail-evidence-card">
<div className="flex flex-wrap items-center justify-between gap-3">
<div>
<div className="font-medium text-zinc-900">{item.entityCode}</div>