import React, { useState, useEffect } from 'react'; import { Table, Button, Card, Space, Tag, Modal, Form, Input, InputNumber, Select, DatePicker, message } from 'antd'; import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons'; import axios from 'axios'; import dayjs from 'dayjs'; const { Option } = Select; const { TextArea } = Input; const AdvanceList: React.FC = () => { const [advances, setAdvances] = 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(() => { fetchAdvances(); fetchProjects(); fetchExchangeRates(); }, []); const fetchAdvances = async () => { setLoading(true); try { const res = await axios.get('/api/advances'); if (res.data.success) setAdvances(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, advance_date: record.advance_date ? dayjs(record.advance_date) : null, }); setModalVisible(true); }; const handleDelete = async (id: number) => { try { await axios.delete('/api/advances/' + id); message.success('删除成功'); fetchAdvances(); } catch (error) { message.error('删除失败'); } }; const handleSubmit = async () => { try { const values = await form.validateFields(); const data = { ...values, advance_date: values.advance_date?.format('YYYY-MM-DD'), amount_cny: values.currency === 'CNY' ? values.amount : convertToCNY(values.amount, values.currency), }; if (editingId) { await axios.put('/api/advances/' + editingId, data); message.success('更新成功'); } else { await axios.post('/api/advances', data); message.success('创建成功'); } setModalVisible(false); fetchAdvances(); } catch (error) { message.error('操作失败'); } }; // 汇率换算 - 将外币转换为人民币 const convertToCNY = (amount: number, currency: string): number => { if (currency === 'CNY') return amount; // 外币转人民币:需要知道 1外币 = ?人民币 // 数据库存的是 CNY_XXX,即 1人民币 = ?外币 // 所以 1外币 = 1/rate 人民币 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: '已拒绝' }, settled: { 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: 'advance_code', key: 'advance_code', width: 120 }, { title: '预支日期', dataIndex: 'advance_date', key: 'advance_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: 'reason', key: 'reason', 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 })}
)}