import React, { useState, useEffect } from 'react'; import { Table, Button, Card, Space, Tag, Modal, Form, Input, InputNumber, Select, DatePicker, message, Descriptions, Image, Divider, Tabs } from 'antd'; import { PlusOutlined, EditOutlined, DeleteOutlined, EyeOutlined, UndoOutlined } from '@ant-design/icons'; import dayjs from 'dayjs'; import { useAuthStore } from '../../store/authStore'; import FileUpload from '../../components/FileUpload'; const { Option } = Select; const { TextArea } = Input; const AdvancesPage: React.FC = () => { const { user } = useAuthStore(); const [advances, setAdvances] = useState([]); const [completedAdvances, setCompletedAdvances] = useState([]); const [activeTab, setActiveTab] = useState('active'); const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(false); const [modalVisible, setModalVisible] = useState(false); const [detailModalVisible, setDetailModalVisible] = useState(false); const [editingId, setEditingId] = useState(null); const [selectedRecord, setSelectedRecord] = useState(null); const [form] = Form.useForm(); const [deleteForm] = Form.useForm(); const [currentEditingStatus, setCurrentEditingStatus] = useState(''); const [exchangeRates, setExchangeRates] = useState>({}); useEffect(() => { fetchAdvances(); fetchProjects(); fetchExchangeRates(); }, []); const fetchAdvances = async () => { setLoading(true); try { const res = await fetch('http://localhost:3005/api/advances'); const data = await res.json(); if (data.success) { // 分离活跃的和已完结的预支 const active = data.data.filter((item: any) => ['pending', 'approved', 'rejected', 'withdrawn', 'pending_edit'].includes(item.status)); const completed = data.data.filter((item: any) => ['executed', 'settled'].includes(item.status)); setAdvances(active); setCompletedAdvances(completed); } } catch (error) { message.error('获取预支列表失败'); } finally { setLoading(false); } }; const fetchProjects = async () => { try { const res = await fetch('http://localhost:3005/api/projects'); const data = await res.json(); if (data.success) setProjects(data.data); } catch (error) {} }; const fetchExchangeRates = async () => { try { const res = await fetch('http://localhost:3005/api/exchange-rates/latest'); const data = await res.json(); if (data.success) { const rates: Record = {}; Object.keys(data.data).forEach(key => { rates[key] = parseFloat(data.data[key]) || 1; }); setExchangeRates(rates); } } catch (error) {} }; const handleCreate = () => { setEditingId(null); form.resetFields(); form.setFieldsValue({ advance_date: dayjs(), currency: 'CNY', applicant: user?.name || user?.username || '当前用户', attachments: [] }); setModalVisible(true); }; const handleEdit = (record: any) => { setEditingId(record.id); setCurrentEditingStatus(record.status); form.setFieldsValue({ ...record, advance_date: record.advance_date ? dayjs(record.advance_date) : null, attachments: record.attachments || [] }); setModalVisible(true); }; const handleView = async (record: any) => { try { const res = await fetch(`http://localhost:3005/api/advances/${record.id}`); const data = await res.json(); if (data.success) { setSelectedRecord(data.data); setDetailModalVisible(true); } else { message.error('获取详情失败'); } } catch (error) { message.error('获取详情失败'); } }; const handleDelete = async (id: number) => { // 重置删除表单 deleteForm.resetFields(); Modal.confirm({ title: '确认删除', content: (
), onOk: async () => { try { const values = await deleteForm.validateFields(); // 这里可以添加密码验证逻辑,暂时直接删除 await fetch('http://localhost:3005/api/advances/' + id, { method: 'DELETE' }); message.success('删除成功'); fetchAdvances(); } catch (error) { message.error('删除失败'); } } }); }; const handleWithdraw = async (id: number) => { Modal.confirm({ title: '确认撤回', content: '撤回后可重新编辑提交,确认撤回吗?', onOk: async () => { try { await fetch('http://localhost:3005/api/advances/' + id + '/withdraw', { method: 'POST' }); message.success('已撤回,可重新编辑'); fetchAdvances(); } catch (error) { message.error('撤回失败'); } } }); }; // 保存操作:只保存信息,不改变状态 const handleSave = async () => { try { const values = await form.validateFields(); // 保存时使用编辑时的状态 const saveStatus = currentEditingStatus || 'pending_edit'; console.log('保存操作 - 状态:', saveStatus); console.log('currentEditingStatus:', currentEditingStatus); const data = { ...values, advance_date: values.advance_date?.format('YYYY-MM-DD'), amount_cny: values.currency === 'CNY' ? values.amount : convertToCNY(values.amount, values.currency), applicant: user?.name || user?.username, status: saveStatus }; console.log('保存操作 - 提交的数据:', data); const url = editingId ? 'http://localhost:3005/api/advances/' + editingId : 'http://localhost:3005/api/advances'; const method = editingId ? 'PUT' : 'POST'; const res = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); const result = await res.json(); console.log('保存操作 - 响应:', result); if (result.success) { message.success(editingId ? '保存成功' : '创建成功'); setModalVisible(false); fetchAdvances(); } else { message.error(result.error || '保存失败'); } } catch (error) { console.error('保存操作 - 错误:', error); message.error('保存失败'); } }; // 提交操作:提交到待审批状态 const handleSubmit = async () => { try { const values = await form.validateFields(); // 提交时使用pending状态 const saveStatus = 'pending'; console.log('提交操作 - 状态:', saveStatus); const data = { ...values, advance_date: values.advance_date?.format('YYYY-MM-DD'), amount_cny: values.currency === 'CNY' ? values.amount : convertToCNY(values.amount, values.currency), applicant: user?.name || user?.username, status: saveStatus }; console.log('提交操作 - 提交的数据:', data); const url = editingId ? 'http://localhost:3005/api/advances/' + editingId : 'http://localhost:3005/api/advances'; const method = editingId ? 'PUT' : 'POST'; const res = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); const result = await res.json(); console.log('提交操作 - 响应:', result); if (result.success) { message.success(editingId ? '提交成功' : '创建成功'); setModalVisible(false); fetchAdvances(); } else { message.error(result.error || '提交失败'); } } catch (error) { console.error('提交操作 - 错误:', error); message.error('提交失败'); } }; const handleSubmitAndSubmit = async () => { await handleSubmit(); }; 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 amountCNY = React.useMemo(() => { return amount && currency ? convertToCNY(amount, currency) : 0; }, [amount, currency, exchangeRates]); const getStatusTag = (status: string) => { const statusMap: Record = { pending: { color: 'processing', text: '待审批' }, approved: { color: 'success', text: '已批准' }, rejected: { color: 'error', text: '已退回' }, withdrawn: { color: 'default', text: '已撤回' }, settled: { color: 'blue', text: '已核销' }, pending_edit: { color: 'warning', 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 }); }; // Format number with thousand separator for input display const formatNumberWithSeparator = (value: number | undefined, currency: string): string => { if (value === undefined || value === null) return ''; const symbols: Record = { CNY: '¥', USD: '$', LAK: '₭', THB: '฿' }; const symbol = symbols[currency] || '¥'; return symbol + value.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); }; // Parse formatted string back to number const parseFormattedNumber = (value: string): number => { // Remove currency symbols and thousand separators const cleaned = value.replace(/[¥$₭฿,]/g, ''); return parseFloat(cleaned) || 0; }; const columns = [ { title: '事由', dataIndex: 'reason', key: 'reason', ellipsis: true, render: (v: string, r: any) => handleView(r)}>{v} }, { title: '申请人', dataIndex: 'applicant', key: 'applicant', width: 80 }, { title: '金额', dataIndex: 'amount', key: 'amount', width: 140, render: (v: number, r: any) => ( <>
{formatAmount(v, r.currency)}
{r.currency !== 'CNY' && r.amount_cny &&
≈ ¥{r.amount_cny.toLocaleString('zh-CN', {minimumFractionDigits: 2})}
} ) }, { title: '预支日期', dataIndex: 'advance_date', key: 'advance_date', width: 100 }, { title: '状态', dataIndex: 'status', key: 'status', width: 100, render: (status: string) => getStatusTag(status) }, { title: '编号', dataIndex: 'advance_code', key: 'advance_code', width: 120 }, { title: '操作', key: 'action', width: 250, render: (_: any, record: any) => ( {record.status === 'pending' && ( <> )} {(record.status === 'rejected' || record.status === 'withdrawn' || record.status === 'pending_edit') && ( )} ) } ]; return (

预支申请

管理员工预支申请

} onClick={handleCreate}>新建预支}>
{/* 新建/编辑弹窗 */} setModalVisible(false)} footer={[ , , ]} width={700} >
formatNumberWithSeparator(value as number, currency || 'CNY')} parser={(value) => parseFormattedNumber(value || '0')} /> {amountCNY > 0 && (
等价人民币:¥ {amountCNY.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
)}