import React, { useState, useEffect } from 'react'; import { Table, Button, Card, Space, Tag, Modal, Form, Input, InputNumber, Select, DatePicker, message, Upload } from 'antd'; import { PlusOutlined, EditOutlined, DeleteOutlined, UploadOutlined } from '@ant-design/icons'; import axios from 'axios'; import dayjs from 'dayjs'; const { Option } = Select; const { TextArea } = Input; const ReimbursementList: React.FC = () => { const [reimbursements, setReimbursements] = useState([]); const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(false); const [modalVisible, setModalVisible] = useState(false); const [editingId, setEditingId] = useState(null); const [form] = Form.useForm(); const [isMobile, setIsMobile] = useState(false); const [exchangeRates, setExchangeRates] = useState>({}); useEffect(() => { const checkMobile = () => setIsMobile(window.innerWidth <= 768); checkMobile(); window.addEventListener('resize', checkMobile); return () => window.removeEventListener('resize', checkMobile); }, []); useEffect(() => { fetchReimbursements(); fetchProjects(); fetchExchangeRates(); }, []); const fetchReimbursements = async () => { setLoading(true); try { const res = await axios.get('/api/reimbursements'); if (res.data.success) setReimbursements(res.data.data); } catch (error) { message.error('获取报销列表失败'); } finally { setLoading(false); } }; const fetchProjects = async () => { try { const res = await axios.get('/api/projects'); if (res.data.success) setProjects(res.data.data); } catch (error) {} }; const fetchExchangeRates = async () => { try { const res = await axios.get('/api/exchange-rates/latest'); if (res.data.success) { const rates: Record = {}; Object.keys(res.data.data).forEach(key => { rates[key] = parseFloat(res.data.data[key]) || 1; }); setExchangeRates(rates); } } catch (error) {} }; const handleCreate = () => { setEditingId(null); form.resetFields(); setModalVisible(true); }; const handleEdit = (record: any) => { setEditingId(record.id); form.setFieldsValue({ ...record, reimbursement_date: record.reimbursement_date ? dayjs(record.reimbursement_date) : null, }); setModalVisible(true); }; const handleDelete = async (id: number) => { try { await axios.delete('/api/reimbursements/' + id); message.success('删除成功'); fetchReimbursements(); } catch (error) { message.error('删除失败'); } }; const handleSubmit = async () => { try { const values = await form.validateFields(); const data = { ...values, reimbursement_date: values.reimbursement_date?.format('YYYY-MM-DD'), amount_cny: values.currency === 'CNY' ? values.amount : convertToCNY(values.amount, values.currency), }; if (editingId) { await axios.put('/api/reimbursements/' + editingId, data); message.success('更新成功'); } else { await axios.post('/api/reimbursements', data); message.success('创建成功'); } setModalVisible(false); fetchReimbursements(); } catch (error) { message.error('操作失败'); } }; // 汇率换算 const convertToCNY = (amount: number, currency: string): number => { if (currency === 'CNY') return amount; const rateKey = 'CNY_' + currency; const rate = exchangeRates[rateKey] || 1; return amount / rate; }; // 监听金额和币种变化 const amount = Form.useWatch('amount', form); const currency = Form.useWatch('currency', form); const expenseType = Form.useWatch('expense_type', form); const amountCNY = amount && currency ? convertToCNY(amount, currency) : 0; const getStatusTag = (status: string) => { const statusMap: Record = { pending: { color: 'processing', text: '待审批' }, approved: { color: 'success', text: '已批准' }, rejected: { color: 'error', text: '已拒绝' }, paid: { color: 'blue', text: '已付款' }, }; const config = statusMap[status] || { color: 'default', text: status }; return {config.text}; }; const formatAmount = (amount: number, currency: string = 'CNY') => { const symbols: Record = { CNY: '¥', USD: '$', LAK: '₭', THB: '฿' }; return (symbols[currency] || '¥') + (amount || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); }; const columns = [ { title: '报销编号', dataIndex: 'reimbursement_code', key: 'reimbursement_code', width: 120 }, { title: '报销日期', dataIndex: 'reimbursement_date', key: 'reimbursement_date', width: 100 }, { title: '支出类型', dataIndex: 'expense_type', key: 'expense_type', render: (v: string) => v === 'project' ? '项目支出' : '公用支出' }, { title: '项目', dataIndex: 'project_name', key: 'project_name', render: (v: string) => v || '-' }, { title: '金额', dataIndex: 'amount', key: 'amount', render: (v: number, r: any) => formatAmount(v, r.currency) }, { title: '等价人民币', dataIndex: 'amount_cny', key: 'amount_cny', render: (v: number) => {formatAmount(v)} }, { title: '摘要', dataIndex: 'description', key: 'description', ellipsis: true }, { title: '状态', dataIndex: 'status', key: 'status', render: (status: string) => getStatusTag(status) }, { title: '操作', key: 'action', width: 180, render: (_: any, record: any) => ( )} ]; return (

报销管理

管理员工报销申请

} onClick={handleCreate}>新建报销}> setModalVisible(false)} width={600}>
{expenseType === 'project' && ( )} v ? v.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') : ''} parser={v => v ? v.replace(/,/g, '') : ''} placeholder="输入金额" /> {amountCNY > 0 && (
等价人民币:¥ {amountCNY.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
)}