feat(P25-2/P25-3/P26-2/P28-1/P24-2/P29-3): 落地内部OA够用版前端入口与运营页面

关联 Issue: P25-2, P25-3, P25-4, P26-2, P26-4, P28-1, P28-2, P28-3, P28-4, P24-2, P29-3
这个提交包含在:
2026-03-21 18:03:48 +08:00
父节点 e39e41287e
当前提交 9905c1c79d
共修改 44 个文件,包含 2029 行新增和 169 行删除
+74
查看文件
@@ -0,0 +1,74 @@
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.openPaymentBatchCount}</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.failedPaymentBatchItemCount}</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.openRiskFlagCount}</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.reconciliationAnomalyCount}</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.openPaymentBatches.length ? data.openPaymentBatches.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}</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>
)) : <div className="text-zinc-500">暂无失败项</div>}
</div>
</div>
</div>
<RiskFlagsCard items={data.openRiskFlags} title="开放风险项" />
<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.reconciliationAnomalies.length ? data.reconciliationAnomalies.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>
);
}