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 = [