From 31a25a73202fea79a97531979b3907493cbf5239 Mon Sep 17 00:00:00 2001 From: selfrelease Date: Sat, 1 Aug 2026 14:41:45 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=8A=B1=E5=90=8D=E5=86=8C=E5=90=88?= =?UTF-8?q?=E5=90=8C=E9=A3=8E=E9=99=A9=E7=BB=9F=E8=AE=A1=E4=B8=8E=E9=A3=8E?= =?UTF-8?q?=E9=99=A9=E4=B8=AD=E5=BF=83=E4=B8=8D=E4=B8=80=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 花名册快捷筛选标签基于当前页数据统计,改为后端返回全局RiskItem统计 - 后端roster API返回globalRiskStats(expiring/expired/unsigned) - 前端优先使用全局统计,fallback到当前页统计 --- backend/src/routes/roster.routes.ts | 12 ++++++++++++ frontend/src/pages/Roster.tsx | 7 +++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/backend/src/routes/roster.routes.ts b/backend/src/routes/roster.routes.ts index 2aa702a..a83376e 100644 --- a/backend/src/routes/roster.routes.ts +++ b/backend/src/routes/roster.routes.ts @@ -210,6 +210,13 @@ router.get('/', authMiddleware, async (req: AuthRequest, res, next) => { result = result.slice(skip, skip + pageSize) } + // 全局合同风险统计(基于 RiskItem,与风险中心同口径) + const [globalExpiring, globalExpired, globalUnsigned] = await Promise.all([ + prisma.riskItem.count({ where: { orgId: req.user!.orgId, status: 'PENDING', type: 'CONTRACT', level: 'MEDIUM' } }), + prisma.riskItem.count({ where: { orgId: req.user!.orgId, status: 'PENDING', type: 'CONTRACT', level: 'HIGH' } }), + prisma.riskItem.count({ where: { orgId: req.user!.orgId, status: 'PENDING', type: 'ONBOARDING' } }), + ]) + res.json({ success: true, data: result, @@ -219,6 +226,11 @@ router.get('/', authMiddleware, async (req: AuthRequest, res, next) => { total: filteredTotal, totalPages: Math.ceil(filteredTotal / pageSize), }, + globalRiskStats: { + expiring: globalExpiring, + expired: globalExpired, + unsigned: globalUnsigned, + }, }) } catch (err) { next(err) diff --git a/frontend/src/pages/Roster.tsx b/frontend/src/pages/Roster.tsx index 9ed2d38..48e1789 100644 --- a/frontend/src/pages/Roster.tsx +++ b/frontend/src/pages/Roster.tsx @@ -235,14 +235,17 @@ export default function Roster() { const hasActiveFilters = search || filterStatus || filterContractStatus || filterDepartment - /** 计算合同风险统计(基于当前页数据) */ + /** 全局合同风险统计(来自后端,与风险中心同口径) */ + const globalRiskStats = rosterData?.globalRiskStats const riskStats = useMemo(() => { + if (globalRiskStats) return globalRiskStats + // fallback: 当前页统计 const expiring = employees.filter((e: any) => e.contractStatus === 'expiring').length const expired = employees.filter((e: any) => e.contractStatus === 'expired').length const unsigned = employees.filter((e: any) => ['unsigned', 'unsigned_over_30', 'unsigned_over_year'].includes(e.contractStatus)).length const probation = employees.filter((e: any) => e.probationInfo?.isProbation && e.probationInfo?.isExpiring).length return { expiring, expired, unsigned, probation } - }, [employees]) + }, [globalRiskStats, employees]) /** 快捷筛选标签 */ const quickFilters = [