统一业务列表 Ant Design 风格(05-31-unify-business-list-ant-design)
这个提交包含在:
@@ -22,6 +22,10 @@ import { mapApiErrorMessage } from "../api/errorMessages";
|
||||
import { useAuth } from "../auth/AuthContext";
|
||||
import { hasPermission } from "../auth/permissions";
|
||||
import { ListWorkspace, WorkspaceHeader } from "../components/patterns/ListWorkspace";
|
||||
import {
|
||||
BusinessMobileList,
|
||||
BusinessTable,
|
||||
} from "../components/patterns/BusinessListTemplate";
|
||||
import { ActionConfirmDialog } from "../components/common/ActionConfirmDialog";
|
||||
import { AttachmentGovernanceBadges } from "../components/common/AttachmentGovernanceBadges";
|
||||
import { useAttachmentDownloadReasonDialog } from "../components/common/useAttachmentDownloadReasonDialog";
|
||||
@@ -130,23 +134,6 @@ export function PaymentBatchesPage() {
|
||||
const selectedBatch = useMemo<PaymentBatch | null>(() => {
|
||||
return batches.find((item) => item.id === selectedBatchId) ?? null;
|
||||
}, [batches, selectedBatchId]);
|
||||
const batchStats = useMemo(() => {
|
||||
return {
|
||||
total: batches.length,
|
||||
needsReview: batches.filter((item) => item.reviewRequirement.nextStep !== "ready_for_execute" && ["draft", "reviewed"].includes(item.status)).length,
|
||||
readyToPay: batches.filter((item) => item.reviewRequirement.nextStep === "ready_for_execute" && ["reviewed", "failed"].includes(item.status)).length,
|
||||
needsReceipt: batches.filter((item) =>
|
||||
["reviewed", "failed"].includes(item.status) &&
|
||||
(item.receiptAttachmentCount ?? 0) === 0 &&
|
||||
item.items.some((entry) =>
|
||||
["pending", "failed"].includes(entry.status) &&
|
||||
(entry.paymentChannelOverride ?? item.defaultPaymentChannel) === "bank",
|
||||
),
|
||||
).length,
|
||||
exceptions: batches.filter((item) => item.highRisk || item.status === "failed").length,
|
||||
};
|
||||
}, [batches]);
|
||||
|
||||
const receiptAttachmentsQuery = useQuery({
|
||||
queryKey: ["payment-batch-receipts", selectedBatch?.id],
|
||||
enabled: Boolean(selectedBatch?.id),
|
||||
@@ -379,22 +366,6 @@ export function PaymentBatchesPage() {
|
||||
|
||||
{error ? <div className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">{error}</div> : null}
|
||||
|
||||
{!selectedBatchId ? (
|
||||
<div className="flex flex-wrap gap-2" data-testid="payment-batches-summary">
|
||||
{[
|
||||
["全部", batchStats.total],
|
||||
["待复核", batchStats.needsReview],
|
||||
["待付款", batchStats.readyToPay],
|
||||
["待补回单", batchStats.needsReceipt],
|
||||
["异常", batchStats.exceptions],
|
||||
].map(([label, value]) => (
|
||||
<span key={label} className="rounded-full border border-zinc-200 bg-white px-3 py-1 text-sm text-zinc-700">
|
||||
{label} <span className="font-semibold text-zinc-900">{value}</span>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{!selectedBatchId && showBatchTools ? (
|
||||
<div className="finance-command-panel space-y-4" data-testid="payment-batch-create-panel">
|
||||
<div>
|
||||
@@ -691,17 +662,13 @@ export function PaymentBatchesPage() {
|
||||
<div className="finance-command-panel text-sm text-zinc-500">未找到付款批次</div>
|
||||
)
|
||||
) : (
|
||||
<div className="finance-command-panel" data-testid="payment-batches-list-panel">
|
||||
<div className="mb-3 flex items-center justify-between gap-3">
|
||||
<h3 className="text-base font-semibold text-zinc-900">批次列表</h3>
|
||||
<span className="text-sm text-zinc-500">共 {batches.length} 条</span>
|
||||
</div>
|
||||
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
||||
<>
|
||||
<BusinessMobileList testId="payment-batches-mobile-list">
|
||||
{batches.map((item) => (
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
className="finance-list-card"
|
||||
className="w-full rounded-lg border border-zinc-200 bg-white p-3 text-left"
|
||||
onClick={() => {
|
||||
const next = new URLSearchParams(searchParams);
|
||||
next.set("id", item.id);
|
||||
@@ -718,12 +685,66 @@ export function PaymentBatchesPage() {
|
||||
{item.highRisk ? " · 高风险" : ""}
|
||||
{` · 复核 ${item.reviewRequirement.completedReviewCount}/${item.reviewRequirement.requiredReviewCount}`}
|
||||
</div>
|
||||
<div className="mt-3 text-sm font-medium text-primary">查看详情</div>
|
||||
</button>
|
||||
))}
|
||||
{!listQuery.data?.items?.length ? <div className="text-sm text-zinc-500">暂无付款批次</div> : null}
|
||||
</div>
|
||||
</div>
|
||||
{!listQuery.data?.items?.length ? <div className="rounded-lg border border-zinc-200 bg-white p-4 text-sm text-zinc-500">暂无付款批次</div> : null}
|
||||
</BusinessMobileList>
|
||||
<BusinessTable testId="payment-batches-table">
|
||||
<thead className="table-header">
|
||||
<tr>
|
||||
<th className="table-th">批次</th>
|
||||
<th className="table-th">实体</th>
|
||||
<th className="table-th">金额</th>
|
||||
<th className="table-th">复核</th>
|
||||
<th className="table-th">状态</th>
|
||||
<th className="table-th w-28">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-zinc-50">
|
||||
{batches.length ? (
|
||||
batches.map((item) => (
|
||||
<tr className="table-row" key={item.id}>
|
||||
<td className="table-td">
|
||||
<div className="font-semibold text-zinc-900">{item.batchNo}</div>
|
||||
<div className="text-xs text-zinc-500">{item.name}</div>
|
||||
</td>
|
||||
<td className="table-td">{entityTypeLabel(item.entityType)}</td>
|
||||
<td className="table-td-strong">{item.totalAmount}</td>
|
||||
<td className="table-td">
|
||||
{item.reviewRequirement.completedReviewCount}/{item.reviewRequirement.requiredReviewCount}
|
||||
</td>
|
||||
<td className="table-td">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<StatusBadge status={item.status} label={batchStatusLabel(item.status)} />
|
||||
{item.highRisk ? <span className="badge-warning">高风险</span> : null}
|
||||
</div>
|
||||
</td>
|
||||
<td className="table-td">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
const next = new URLSearchParams(searchParams);
|
||||
next.set("id", item.id);
|
||||
setSearchParams(next, { replace: true });
|
||||
}}
|
||||
>
|
||||
查看详情
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td className="py-8 text-center text-sm text-zinc-500" colSpan={6}>
|
||||
暂无付款批次
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</BusinessTable>
|
||||
</>
|
||||
)}
|
||||
<ActionConfirmDialog
|
||||
open={Boolean(confirmPaymentAction)}
|
||||
|
||||
在新工单中引用
屏蔽一个用户