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
这个提交包含在:
@@ -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>
|
||||
);
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户