文件
hyb-oa/client/src/pages/ApOperationsPage.tsx
T
wangsli 6dee2daf3e P25L-1~P25L-5 实现预算控制与AP最小可用运营
合同 CSV 已回填 reviewer/QA 结果;运行时回归受当前环境缺少 node/npm 阻塞。
2026-03-29 09:21:17 +08:00

79 行
4.2 KiB
TypeScript

import { useQuery } from "@tanstack/react-query";
import { fetchApOperations } from "../api/apOperations";
import { PageHeader } from "../components/common/PageHeader";
import { RiskFlagsCard } from "../components/common/RiskFlagsCard";
export function ApOperationsPage() {
const summaryQuery = useQuery({
queryKey: ["ap-operations"],
queryFn: () => fetchApOperations(),
});
if (summaryQuery.isLoading) {
return <div className="card-std p-6 text-sm text-zinc-500">加载 AP 运营视图...</div>;
}
if (summaryQuery.isError || !summaryQuery.data) {
return <div className="card-std p-6 text-sm text-red-600">AP 运营视图加载失败。</div>;
}
const data = summaryQuery.data;
return (
<div className="space-y-6">
<PageHeader title="AP 运营" subtitle="财务视角查看待付款、部分匹配、异常和预算超支四类最小运营摘要。" />
<div className="grid gap-4 md:grid-cols-4">
<div className="card-std p-5"><div className="text-sm text-zinc-500">待付款</div><div className="mt-2 text-2xl font-bold text-zinc-900">{data.summary.pendingPaymentCount}</div></div>
<div className="card-std p-5"><div className="text-sm text-zinc-500">部分匹配</div><div className="mt-2 text-2xl font-bold text-zinc-900">{data.summary.partialMatchCount}</div></div>
<div className="card-std p-5"><div className="text-sm text-zinc-500">异常</div><div className="mt-2 text-2xl font-bold text-zinc-900">{data.summary.exceptionCount}</div></div>
<div className="card-std p-5"><div className="text-sm text-zinc-500">预算超支</div><div className="mt-2 text-2xl font-bold text-zinc-900">{data.summary.budgetOverspendCount}</div></div>
</div>
<div className="grid gap-6 xl:grid-cols-2">
<div className="card-std space-y-3 p-6">
<h3 className="text-base font-semibold text-zinc-900">待付款批次</h3>
<div className="space-y-2 text-sm">
{data.pendingPaymentBatches.length ? data.pendingPaymentBatches.map((item) => (
<div key={item.id} className="rounded-lg border border-zinc-200 px-3 py-3">
<div className="font-medium text-zinc-900">{item.batchNo}</div>
<div className="text-zinc-600">{item.name}</div>
<div className="text-xs text-zinc-500">{item.entityType} · {item.status} · {item.totalAmount}{item.highRisk ? " · 高风险" : ""}</div>
</div>
)) : <div className="text-zinc-500">暂无待付款批次</div>}
</div>
</div>
<div className="card-std space-y-3 p-6">
<h3 className="text-base font-semibold text-zinc-900">异常付款项</h3>
<div className="space-y-2 text-sm">
{data.failedPaymentBatchItems.length ? data.failedPaymentBatchItems.map((item) => (
<div key={item.id} className="rounded-lg border border-red-200 bg-red-50 px-3 py-3">
<div className="font-medium text-zinc-900">{item.entityCode}</div>
<div className="text-zinc-600">{item.failureMessage ?? item.failureCode ?? "执行失败"}</div>
<div className="text-xs text-zinc-500">{item.amount} {item.currencyCode}</div>
</div>
)) : <div className="text-zinc-500">暂无失败项</div>}
</div>
</div>
</div>
<div className="grid gap-6 xl:grid-cols-2">
<RiskFlagsCard items={data.exceptionRiskFlags} title="异常风险项" />
<RiskFlagsCard items={data.budgetOverspendRisks} title="预算超支风险" />
</div>
<div className="card-std space-y-3 p-6">
<h3 className="text-base font-semibold text-zinc-900">部分匹配与对账异常</h3>
<div className="space-y-2 text-sm">
{data.partialMatchItems.length ? data.partialMatchItems.map((item) => (
<div key={item.id} className="rounded-lg border border-zinc-200 px-3 py-3">
<div className="font-medium text-zinc-900">{item.anomalyCode ?? item.matchStatus}</div>
<div className="text-zinc-600">订单 {item.orderAmount ?? "-"} / 交易 {item.transactionAmount ?? "-"} {item.currency}</div>
</div>
)) : <div className="text-zinc-500">暂无异常</div>}
</div>
</div>
</div>
);
}