263 lines
11 KiB
JavaScript
263 lines
11 KiB
JavaScript
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;
|