const express = require('express'); const router = express.Router(); const db = require('../db'); const { verifyToken } = require('../middleware/auth'); // 获取统计数据 (所有登录用户均可访问) router.get('/', verifyToken, async (req, res) => { try { const { role } = req.user; // 权限控制: // 1. 业务统计图表(雷达图、趋势图、部门分布等):统一只统计“已通过”的数据,反映真实有效的成果。 // 2. 状态分布图:管理员可以看到所有状态,普通用户只能看到已通过。 const approvedCondition = "status = 'approved'"; let allStatusCondition = "status = 'approved'"; if (['super_admin', 'maintainer'].includes(role)) { allStatusCondition = "1=1"; // 允许所有状态 } // 1. 按类型统计 (雷达图) - 仅统计已通过 const typeStats = await db.query(` SELECT type, COUNT(*) as count FROM achievements WHERE ${approvedCondition} GROUP BY type `); // 2. 按状态统计 (饼图) - 管理员看全量,用户看已通过 const statusStats = await db.query(` SELECT status, COUNT(*) as count FROM achievements WHERE ${allStatusCondition} GROUP BY status `); // 3. 按部门统计成果总数 (部门排名) - 仅统计已通过 // 口径:每条成果只归属一个部门(优先首个非院领导部门,否则回退首个部门) const departmentStats = await db.query(` WITH leader_depts AS ( SELECT name FROM dict_leader_departments ), leader_dept_array AS ( SELECT COALESCE(array_agg(name::text), ARRAY[]::text[]) AS names FROM leader_depts ), department_scope AS ( SELECT d.name AS department FROM departments d, leader_dept_array lda WHERE d.name::text <> ALL(lda.names) ), achievement_owner AS ( SELECT a.id, COALESCE(non_leader.dept, first_any.dept) AS owner_department FROM achievements a CROSS JOIN leader_dept_array lda LEFT JOIN LATERAL ( SELECT u.dept FROM unnest(COALESCE(a.assigned_departments::text[], ARRAY[]::text[])) WITH ORDINALITY AS u(dept, ord) WHERE u.dept IS NOT NULL AND BTRIM(u.dept) <> '' AND u.dept <> ALL(lda.names) ORDER BY u.ord LIMIT 1 ) non_leader ON TRUE LEFT JOIN LATERAL ( SELECT u.dept FROM unnest(COALESCE(a.assigned_departments::text[], ARRAY[]::text[])) WITH ORDINALITY AS u(dept, ord) WHERE u.dept IS NOT NULL AND BTRIM(u.dept) <> '' ORDER BY u.ord LIMIT 1 ) first_any ON TRUE WHERE ${approvedCondition} ) SELECT ds.department, COUNT(ao.id)::int as count FROM department_scope ds LEFT JOIN achievement_owner ao ON ds.department = ao.owner_department GROUP BY ds.department ORDER BY count DESC `); // 4. 部门成果种类交叉统计 (堆叠柱状图) - 仅统计已通过 const deptTypeStats = await db.query(` WITH leader_depts AS ( SELECT name FROM dict_leader_departments ), leader_dept_array AS ( SELECT COALESCE(array_agg(name::text), ARRAY[]::text[]) AS names FROM leader_depts ), department_scope AS ( SELECT d.name AS department FROM departments d, leader_dept_array lda WHERE d.name::text <> ALL(lda.names) ), achievement_owner AS ( SELECT a.id, a.type, a.achievement_date, COALESCE(non_leader.dept, first_any.dept) AS owner_department FROM achievements a CROSS JOIN leader_dept_array lda LEFT JOIN LATERAL ( SELECT u.dept FROM unnest(COALESCE(a.assigned_departments::text[], ARRAY[]::text[])) WITH ORDINALITY AS u(dept, ord) WHERE u.dept IS NOT NULL AND BTRIM(u.dept) <> '' AND u.dept <> ALL(lda.names) ORDER BY u.ord LIMIT 1 ) non_leader ON TRUE LEFT JOIN LATERAL ( SELECT u.dept FROM unnest(COALESCE(a.assigned_departments::text[], ARRAY[]::text[])) WITH ORDINALITY AS u(dept, ord) WHERE u.dept IS NOT NULL AND BTRIM(u.dept) <> '' ORDER BY u.ord LIMIT 1 ) first_any ON TRUE WHERE ${approvedCondition} ) SELECT ds.department as department, ao.type, EXTRACT(YEAR FROM ao.achievement_date) as year, COUNT(*)::int as count FROM department_scope ds JOIN achievement_owner ao ON ds.department = ao.owner_department WHERE ao.achievement_date IS NOT NULL GROUP BY ds.department, ao.type, EXTRACT(YEAR FROM ao.achievement_date) ORDER BY ds.department `); // 5. 全单位月度趋势统计 (累计折线图) - 仅统计已通过 // 前端会进行累计计算,后端只需返回每月增量 const trendStats = await db.query(` SELECT TO_CHAR(achievement_date, 'YYYY-MM') as month, COUNT(*) as count FROM achievements WHERE ${approvedCondition} AND achievement_date IS NOT NULL GROUP BY month ORDER BY month `); // 6. 各部门月度趋势统计 (累计面积图) - 仅统计已通过 // 前端会进行累计计算,后端只需返回每月增量 const deptTrendStats = await db.query(` WITH leader_depts AS ( SELECT name FROM dict_leader_departments ), leader_dept_array AS ( SELECT COALESCE(array_agg(name::text), ARRAY[]::text[]) AS names FROM leader_depts ), department_scope AS ( SELECT d.name AS department FROM departments d, leader_dept_array lda WHERE d.name::text <> ALL(lda.names) ), achievement_owner AS ( SELECT a.id, a.achievement_date, COALESCE(non_leader.dept, first_any.dept) AS owner_department FROM achievements a CROSS JOIN leader_dept_array lda LEFT JOIN LATERAL ( SELECT u.dept FROM unnest(COALESCE(a.assigned_departments::text[], ARRAY[]::text[])) WITH ORDINALITY AS u(dept, ord) WHERE u.dept IS NOT NULL AND BTRIM(u.dept) <> '' AND u.dept <> ALL(lda.names) ORDER BY u.ord LIMIT 1 ) non_leader ON TRUE LEFT JOIN LATERAL ( SELECT u.dept FROM unnest(COALESCE(a.assigned_departments::text[], ARRAY[]::text[])) WITH ORDINALITY AS u(dept, ord) WHERE u.dept IS NOT NULL AND BTRIM(u.dept) <> '' ORDER BY u.ord LIMIT 1 ) first_any ON TRUE WHERE ${approvedCondition} ) SELECT TO_CHAR(ao.achievement_date, 'YYYY-MM') as month, ds.department as department, COUNT(*)::int as count FROM department_scope ds JOIN achievement_owner ao ON ds.department = ao.owner_department WHERE ao.achievement_date IS NOT NULL GROUP BY TO_CHAR(ao.achievement_date, 'YYYY-MM'), ds.department ORDER BY month `); // 7. 按部门统计“独立完成 / 合作项目” // 当前口径下每条成果仅归属一个部门,因此合作计数恒为 0。 const projectModeStats = await db.query(` WITH leader_depts AS ( SELECT name FROM dict_leader_departments ), leader_dept_array AS ( SELECT COALESCE(array_agg(name::text), ARRAY[]::text[]) AS names FROM leader_depts ), department_scope AS ( SELECT d.name AS department FROM departments d, leader_dept_array lda WHERE d.name::text <> ALL(lda.names) ), achievement_owner AS ( SELECT a.id, COALESCE(non_leader.dept, first_any.dept) AS owner_department FROM achievements a CROSS JOIN leader_dept_array lda LEFT JOIN LATERAL ( SELECT u.dept FROM unnest(COALESCE(a.assigned_departments::text[], ARRAY[]::text[])) WITH ORDINALITY AS u(dept, ord) WHERE u.dept IS NOT NULL AND BTRIM(u.dept) <> '' AND u.dept <> ALL(lda.names) ORDER BY u.ord LIMIT 1 ) non_leader ON TRUE LEFT JOIN LATERAL ( SELECT u.dept FROM unnest(COALESCE(a.assigned_departments::text[], ARRAY[]::text[])) WITH ORDINALITY AS u(dept, ord) WHERE u.dept IS NOT NULL AND BTRIM(u.dept) <> '' ORDER BY u.ord LIMIT 1 ) first_any ON TRUE WHERE ${approvedCondition} ) SELECT ds.department, COUNT(ao.id)::int AS independent_count, 0::int AS collaboration_count, COUNT(ao.id)::int AS total_count FROM department_scope ds LEFT JOIN achievement_owner ao ON ao.owner_department = ds.department GROUP BY ds.department ORDER BY total_count DESC, ds.department `); res.json({ byType: typeStats.rows, byStatus: statusStats.rows, byDepartment: departmentStats.rows, deptTypeDetails: deptTypeStats.rows, byMonth: trendStats.rows, deptTrendDetails: deptTrendStats.rows, projectModeByDepartment: projectModeStats.rows }); } catch (err) { console.error(err); res.status(500).json({ message: '服务器错误' }); } }); module.exports = router;