/** * 薪酬分析看板 — 展示薪酬分布、部门对比、同比环比趋势 */ import { useState, useMemo } from 'react' import { useQuery } from '@tanstack/react-query' import { BarChart3, TrendingUp, TrendingDown, Users, Wallet, DollarSign, Gauge, } from 'lucide-react' import Card from '../components/ui/Card' import { Select } from '../components/ui/Input' import { InlineAlert } from '../components/ui/InlineAlert' import PageGuide from '../components/ui/PageGuide' import QueryError from '../components/ui/QueryError' import { salaryDashboardApi } from '../lib/api-services' /** 金额格式化 */ const fmt = (n: number) => (n || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) const fmtShort = (n: number) => { if (!n) return '0' if (n >= 10000) return `${(n / 10000).toFixed(1)}万` return n.toFixed(0) } export default function SalaryDashboard() { const [year, setYear] = useState(new Date().getFullYear().toString()) const { data, isLoading, isError, error, refetch } = useQuery({ queryKey: ['salary-dashboard', year], queryFn: async () => { return await salaryDashboardApi.data(Number(year)) }, }) const departments = data?.departments || [] const monthlyTrend = data?.monthlyTrend || [] const summary = data?.summary || {} const maxDeptAvg = useMemo(() => { if (departments.length === 0) return 1 return Math.max(...departments.map((d: any) => d.avgSalary || 0), 1) }, [departments]) const maxMonthly = useMemo(() => { if (monthlyTrend.length === 0) return 1 return Math.max(...monthlyTrend.map((t: any) => t.total || 0), 1) }, [monthlyTrend]) const summaryCards = [ { icon: Users, label: '员工总数', value: summary.totalEmployees || 0, suffix: '', color: 'text-blue-600', bg: 'bg-blue-50', border: 'border-blue-100' }, { icon: Wallet, label: '月均薪酬', value: `¥${fmt(summary.avgSalary)}`, suffix: '', color: 'text-emerald-600', bg: 'bg-emerald-50', border: 'border-emerald-100' }, { icon: Gauge, label: '薪酬中位数', value: `¥${fmt(summary.medianSalary)}`, suffix: '', color: 'text-purple-600', bg: 'bg-purple-50', border: 'border-purple-100' }, { icon: DollarSign, label: '年度总薪酬', value: `¥${fmt(summary.totalAnnual)}`, suffix: '', color: 'text-amber-600', bg: 'bg-amber-50', border: 'border-amber-100' }, ] return (
薪酬分析看板展示企业薪酬分布、部门间薪酬对比及年度趋势变化。选择年份后查看各部门薪酬数据、同比环比变化。用于辅助薪酬决策和成本控制。 {/* 页头 */}

薪酬分析看板

薪酬分布、部门对比、趋势分析

{isLoading ? (
加载中...
) : isError ? ( ) : !data ? (
暂无数据
) : ( <> {/* 概览卡片 */}
{summaryCards.map((card, i) => { const Icon = card.icon return (
{card.label}
{card.value}{card.suffix}
) })}
{/* 同比环比 */} {summary.yoy !== undefined && (
同比增长率
= 0 ? 'text-emerald-600' : 'text-rose-600'}`}> {summary.yoy >= 0 ? : } {summary.yoy >= 0 ? '+' : ''}{(summary.yoy || 0).toFixed(1)}%
环比增长率
= 0 ? 'text-emerald-600' : 'text-rose-600'}`}> {summary.mom >= 0 ? : } {summary.mom >= 0 ? '+' : ''}{(summary.mom || 0).toFixed(1)}%
)} {/* 部门薪酬对比 + 月度趋势 双栏布局 */}
{/* 部门薪酬对比 */}

部门薪酬对比

{departments.length > 0 && ( 共 {departments.length} 个部门 )}
{departments.length === 0 ? (
暂无部门数据
) : (
{departments.map((dept: any, idx: number) => { const pct = (dept.avgSalary / maxDeptAvg) * 100 const isTop3 = idx < 3 return (
{isTop3 && ( {idx + 1} )} {dept.name}
{dept.count}人 人均 ¥{fmt(dept.avgSalary)}
) })}
)} {/* 月度趋势 */}

月度薪酬趋势

{monthlyTrend.length > 0 && ( 单位:万元 )}
{monthlyTrend.length === 0 ? (
暂无月度数据
) : (
{/* 网格参考线 */}
{[0, 1, 2, 3].map(i => (
))}
{monthlyTrend.map((m: any) => { const height = ((m.total || 0) / maxMonthly) * 100 return (
{m.total ? `¥${fmtShort(m.total)}` : ''}
{m.month}
) })}
)}
{summary.totalEmployees === 0 && ( 当前年度暂无薪酬数据,请确保已发布发薪批次。 )} )}
) }