import React, { useState, useEffect } from 'react'; import { Card, Typography, Table, DatePicker, Row, Col, Statistic, Tag, Spin } from 'antd'; import { RiseOutlined, FallOutlined, DollarOutlined } from '@ant-design/icons'; import apiClient from '../../utils/request'; import dayjs from 'dayjs'; const { Title, Paragraph } = Typography; const LEVEL2_LABELS: Record = { contract_payment: '项目合同收款', deposit_refund: '质保金退回', shareholder_investment: '股东投资入股', other_income: '其他收入', material: '材料采购', equipment: '设备采购', subcontract: '施工分包', labor: '人工工资', travel: '差旅交通', accommodation: '食宿费用', freight: '运输物流', design: '勘测设计', tools: '小型工具', client_relations: '客户/EDL关系', other_project: '其他项目支出', salary: '工资薪酬', rent: '房租物业', office: '办公费用', commute: '交通通勤', vehicle_maintenance: '车辆维保', assets: '固定资产', marketing: '营销拓展', entertainment: '招待费用', welfare: '员工福利', logistics: '快递物流', other_company: '其他公司支出' }; const ReportsPage: React.FC = () => { const [loading, setLoading] = useState(false); const [isMobile, setIsMobile] = useState(false); const [summary, setSummary] = useState({ total_income: 0, total_expense: 0, net_profit: 0 }); const [byMonth, setByMonth] = useState([]); const [byCategory, setByCategory] = useState([]); const [selectedMonth, setSelectedMonth] = useState(null); useEffect(() => { const checkMobile = () => setIsMobile(window.innerWidth <= 768); checkMobile(); window.addEventListener('resize', checkMobile); return () => window.removeEventListener('resize', checkMobile); }, []); const fetchData = async () => { setLoading(true); try { const params: any = {}; if (selectedMonth) { params.date_from = selectedMonth.startOf('month').format('YYYY-MM-DD'); params.date_to = selectedMonth.endOf('month').format('YYYY-MM-DD'); } const res = await apiClient.get('/financial-records/summary', { params }); if (res.data.success) { setSummary(res.data.data.totals); setByMonth(res.data.data.byMonth); setByCategory(res.data.data.byCategory); } } catch (e) { console.error(e); } setLoading(false); }; useEffect(() => { fetchData(); }, [selectedMonth]); const monthData = byMonth.map(m => ({ ...m, profit: m.income - m.expense })); const desktopColumns = [ { title: '月份', dataIndex: 'month', key: 'month' }, { title: '总收入', dataIndex: 'income', key: 'income', render: (v: number) => `¥${parseFloat(v).toLocaleString()}` }, { title: '总支出', dataIndex: 'expense', key: 'expense', render: (v: number) => `¥${parseFloat(v).toLocaleString()}` }, { title: '净利润', dataIndex: 'profit', key: 'profit', render: (v: number) => = 0 ? 'green' : 'red', fontWeight: 'bold' }}>¥{v.toLocaleString()} }, ]; const mobileColumns = [ { title: '月份', dataIndex: 'month', key: 'month', render: (v: string) => v.replace('-', '/') }, { title: '收入', dataIndex: 'income', key: 'income', render: (v: number) => ¥{(parseFloat(v) / 10000).toFixed(1)}万 }, { title: '支出', dataIndex: 'expense', key: 'expense', render: (v: number) => ¥{(parseFloat(v) / 10000).toFixed(1)}万 }, { title: '利润', dataIndex: 'profit', key: 'profit', render: (v: number) => = 0 ? 'green' : 'red' }}>¥{(v / 10000).toFixed(1)}万 }, ]; const categoryColumns = [ { title: '分类', dataIndex: 'category_level2', render: (v: string) => LEVEL2_LABELS[v] || v }, { title: '金额(¥)', dataIndex: 'total_amount', render: (v: number) => parseFloat(v).toLocaleString(), align: 'right' as const }, { title: '笔数', dataIndex: 'count', align: 'center' as const }, { title: '占比', render: (_: unknown, r: any) => { const total = byCategory.filter(c => c.category_level1 === r.category_level1).reduce((s, c) => s + parseFloat(c.total_amount), 0); return total > 0 ? `${(parseFloat(r.total_amount) / total * 100).toFixed(1)}%` : '-'; } }, ]; const projectCategories = byCategory.filter(c => c.category_level1 === 'project'); const companyCategories = byCategory.filter(c => c.category_level1 === 'company'); const incomeCategories = byCategory.filter(c => c.category_level1 === 'income'); return (
统计报表 查看项目财务报表和统计分析数据
= 0 ? '#3f8600' : '#cf1322', fontSize: isMobile ? 16 : undefined }} /> setSelectedMonth(d)} placeholder="选择月份" />} > { let ti = 0, te = 0; pageData.forEach(({ income, expense }) => { ti += parseFloat(income); te += parseFloat(expense); }); return ( 合计 ¥{ti.toLocaleString()} ¥{te.toLocaleString()} = 0 ? 'green' : 'red' }}>¥{(ti - te).toLocaleString()} ); }} /> {incomeCategories.length > 0 && ( 收入收入分类} size="small">
)} {projectCategories.length > 0 && ( 项目项目支出分类} size="small">
)} {companyCategories.length > 0 && ( 公司公司支出分类} size="small">
)} ); }; export default ReportsPage;