import { useState } from 'react' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { Check, TrendingUp, Download, Wallet, ChevronLeft, ChevronRight } from 'lucide-react' import api from '../../lib/api' import Card from '../../components/ui/Card' import Button from '../../components/ui/Button' import EmptyState from '../../components/ui/EmptyState' /** 金额格式化:保留两位小数 + 千分位 */ const fmt = (n: number) => (n || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) /** 员工端 API 实例(自动携带 portalToken) */ const portalApi = api.create({ baseURL: '/api/v1/portal' }) portalApi.interceptors.request.use((config: any) => { const token = localStorage.getItem('portalToken') if (token) config.headers.Authorization = `Bearer ${token}` return config }) export default function Payslip() { const queryClient = useQueryClient() const [month, setMonth] = useState(new Date().toISOString().slice(0, 7)) const [showHistory, setShowHistory] = useState(false) const { data, isLoading } = useQuery({ queryKey: ['payslip', month], queryFn: async () => { const res = await portalApi.get('/payslip', { params: { month } }) as any return res.data?.data ?? null }, }) const { data: history } = useQuery({ queryKey: ['payslip-history'], queryFn: async () => { const res = await portalApi.get('/payslip/history') as any return res.data?.data ?? [] }, }) const confirmMutation = useMutation({ mutationFn: (id: string) => portalApi.post(`/payslip/${id}/confirm`), onSuccess: () => queryClient.invalidateQueries({ queryKey: ['payslip'] }), }) const employee = JSON.parse(localStorage.getItem('portalEmployee') || '{}') /** 月份切换 */ const handleMonthChange = (delta: number) => { const d = new Date(month + '-01') d.setMonth(d.getMonth() + delta) setMonth(d.toISOString().slice(0, 7)) } /** 导出 CSV */ const handleExport = () => { if (!history || history.length === 0) return const headers = ['月份', '基本工资', '加班费', '津贴', '扣款', '应发合计', '确认状态'] const rows = history.map((p: any) => [ p.month, p.baseSalary, p.overtimePay, p.allowance, p.deduction, p.totalPay, p.confirmedAt ? '已确认' : '未确认', ]) const csv = [headers, ...rows].map(r => r.join(',')).join('\n') const blob = new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `工资条_${employee.name || '员工'}_${new Date().toISOString().slice(0, 10)}.csv` a.click() URL.revokeObjectURL(url) } const sortedHistory = [...(history || [])].sort((a: any, b: any) => b.month.localeCompare(a.month)) const maxPay = Math.max(...sortedHistory.map((p: any) => Number(p.totalPay) || 0), 1) /** 工资明细行 */ const SalaryRow = ({ label, value, danger }: { label: string; value: number; danger?: boolean }) => { if (!value || value === 0) return null return (
{label} {danger ? '-' : ''}¥{fmt(Math.abs(Number(value)))}
) } return (
{/* 页面标题 */}

我的工资条

{/* 月份选择器 */}
{month.replace('-', '年')}月
{/* 趋势图 */} {showHistory && sortedHistory.length > 0 && (

近 {sortedHistory.length} 个月工资趋势

{sortedHistory.map((p: any) => (
{p.month.slice(5)}月
{Number(p.totalPay) / maxPay > 0.4 && ( ¥{fmt(Number(p.totalPay))} )}
{Number(p.totalPay) / maxPay <= 0.4 && ( ¥{fmt(Number(p.totalPay))} )}
))}
)} {/* 工资条卡片 */} {isLoading ? (
) : !data ? ( ) : ( <> {/* 应发合计大卡片 */}
应发合计
¥{fmt(Number(data.totalPay))}
{month.replace('-', '年')}月
{/* 工资明细 */}

工资明细

{/* 确认状态 */} {data.confirmedAt ? (
已确认查收
{new Date(data.confirmedAt).toLocaleString()}
) : ( )}
)}
) }