Files
yunhaifinance/company-finance-system/frontend/src/pages/approval/ApprovalManagement.tsx
T

813 lines
34 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect } from 'react';
import { Card, Table, Tag, Button, Space, Modal, Form, Input, message, Tabs, Badge, Descriptions, Divider, List, Upload } from 'antd';
import { CheckOutlined, CloseOutlined, EyeOutlined, EditOutlined, UndoOutlined, FileImageOutlined } from '@ant-design/icons';
const { TextArea } = Input;
// 项目支出分类
const PROJECT_EXPENSE_CATEGORIES = [
{ value: 'material_purchase', label: '材料采购' },
{ value: 'equipment_purchase', label: '设备采购' },
{ value: 'pole_crossarm', label: '电杆横担支出' },
{ value: 'freight', label: '运费支出' },
{ value: 'construction', label: '施工费支出' },
{ value: 'other', label: '其他支出' }
];
// 公司支出分类
const COMPANY_EXPENSE_CATEGORIES = [
{ value: 'office_operations', label: '通用运营(房租/耗材)' },
{ value: 'transportation', label: '交通通勤' },
{ value: 'marketing', label: '业扩营销' },
{ value: 'power_system', label: '电力系统关系' },
{ value: 'employee_welfare', label: '员工福利' },
{ value: 'logistics', label: '快递物流' },
{ value: 'other', label: '其他支出' }
];
const ApprovalManagement: React.FC = () => {
const [loading, setLoading] = useState(false);
const [detailModalVisible, setDetailModalVisible] = useState(false);
const [editModalVisible, setEditModalVisible] = useState(false);
const [historyModalVisible, setHistoryModalVisible] = useState(false);
const [selectedRecord, setSelectedRecord] = useState<any>(null);
const [approvalType, setApprovalType] = useState<'approve' | 'reject'>('approve');
const [form] = Form.useForm();
const [editForm] = Form.useForm();
// 审批记录
const [approvalHistory, setApprovalHistory] = useState<any[]>([]);
// 待审批数据
const [pendingData, setPendingData] = useState<any[]>([]);
// 已审批数据
const [approvedData, setApprovedData] = useState<any[]>([]);
// 项目列表
const [projects, setProjects] = useState<any[]>([]);
// 加载数据
useEffect(() => {
fetchPendingData();
fetchProjects();
fetchApprovalHistory();
}, []);
// 获取审批历史记录
const fetchApprovalHistory = async () => {
setLoading(true);
try {
console.log('开始获取审批历史记录');
// 获取所有类型的申请记录
const types = ['advances', 'reimbursements', 'payment-requests', 'verifications'];
const historyData = [];
for (const type of types) {
const response = await fetch(`http://localhost:3005/api/${type}`);
const data = await response.json();
if (data.success && data.data) {
data.data.forEach((item: any) => {
// 对于预支申请,包含所有状态
// 对于其他类型,保持原有逻辑
if (type === 'advances' || item.status === 'approved' || item.status === 'rejected' || (type === 'verifications' && item.status === 'pending_edit')) {
let typeText = '';
let code = '';
let date = '';
let action = '';
switch (type) {
case 'advances':
typeText = '预支申请';
code = item.advance_code;
date = item.advance_date;
// 根据预支申请的状态设置操作文本
switch (item.status) {
case 'pending':
action = '待审批';
break;
case 'approved':
action = '通过';
break;
case 'rejected':
action = '退回';
break;
case 'executed':
action = '已执行';
break;
case 'partial_verification':
action = '部分核销';
break;
case 'completed':
action = '已完结';
break;
default:
action = item.status;
}
break;
case 'reimbursements':
typeText = '报销申请';
code = item.reimbursement_code;
date = item.reimbursement_date;
action = item.status === 'approved' ? '通过' : '退回';
break;
case 'payment-requests':
typeText = '付款申请';
code = item.request_code;
date = item.payment_date;
action = item.status === 'approved' ? '通过' : '退回';
break;
case 'verifications':
typeText = '核销申请';
code = item.verification_code;
date = item.verification_date;
action = item.status === 'approved' ? '通过' : item.status === 'rejected' ? '退回' : '待编辑';
break;
}
historyData.push({
id: `${type}-${item.id}`,
applyCode: code,
applyType: typeText,
applicant: item.applicant,
amount: item.amount,
currency: item.currency,
action: action,
operator: '系统管理员', // 实际应该从数据库中获取
remark: item.approval_remark || '',
timestamp: date
});
}
});
}
}
console.log('审批历史记录:', historyData);
setApprovalHistory(historyData);
} catch (error) {
console.error('获取审批历史记录失败:', error);
message.error('获取审批历史记录失败');
} finally {
setLoading(false);
}
};
// 获取项目列表
const fetchProjects = async () => {
try {
const response = await fetch('http://localhost:3005/api/projects');
if (response.ok) {
const data = await response.json();
if (data.success && Array.isArray(data.data)) {
setProjects(data.data);
}
}
} catch (error) {
console.error('获取项目列表失败:', error);
}
};
// 获取待审批数据
const fetchPendingData = async () => {
setLoading(true);
try {
console.log('开始获取待审批数据');
// 获取预支申请
const advancesRes = await fetch('http://localhost:3005/api/advances');
console.log('Advances response status:', advancesRes.status);
const advancesData = await advancesRes.json();
console.log('Advances data:', advancesData);
// 获取报销申请
const reimbursementsRes = await fetch('http://localhost:3005/api/reimbursements');
console.log('Reimbursements response status:', reimbursementsRes.status);
const reimbursementsData = await reimbursementsRes.json();
console.log('Reimbursements data:', reimbursementsData);
// 获取付款申请
const paymentsRes = await fetch('http://localhost:3005/api/payment-requests');
console.log('Payments response status:', paymentsRes.status);
const paymentsData = await paymentsRes.json();
console.log('Payments data:', paymentsData);
// 获取核销申请
const verificationsRes = await fetch('http://localhost:3005/api/verifications');
console.log('Verifications response status:', verificationsRes.status);
const verificationsData = await verificationsRes.json();
console.log('Verifications data:', verificationsData);
// 合并数据
const allPendingData = [];
// 添加预支申请
if (advancesData.success && advancesData.data) {
console.log('Advances data length:', advancesData.data.length);
advancesData.data.forEach((item: any) => {
console.log('Advance item:', item);
if (item.status === 'pending') {
allPendingData.push({
key: `adv-${item.id}`,
id: item.id,
type: '预支申请',
code: item.advance_code,
applicant: item.applicant,
amount: item.amount,
currency: item.currency,
date: item.advance_date,
reason: item.reason,
status: item.status,
rawData: item
});
}
});
}
// 添加报销申请
if (reimbursementsData.success && reimbursementsData.data) {
console.log('Reimbursements data length:', reimbursementsData.data.length);
reimbursementsData.data.forEach((item: any) => {
console.log('Reimbursement item:', item);
if (item.status === 'pending') {
allPendingData.push({
key: `reimb-${item.id}`,
id: item.id,
type: '报销申请',
code: item.reimbursement_code,
applicant: item.applicant,
amount: item.amount,
currency: item.currency,
date: item.reimbursement_date,
reason: item.reason,
status: item.status,
rawData: item
});
}
});
}
// 添加付款申请
if (paymentsData.success && paymentsData.data) {
console.log('Payments data length:', paymentsData.data.length);
paymentsData.data.forEach((item: any) => {
console.log('Payment item:', item);
if (item.status === 'pending') {
allPendingData.push({
key: `pay-${item.id}`,
id: item.id,
type: '付款申请',
code: item.request_code,
applicant: item.applicant,
amount: item.amount,
currency: item.currency,
date: item.payment_date,
reason: item.reason,
status: item.status,
rawData: item
});
}
});
}
// 添加核销申请
if (verificationsData.success && verificationsData.data) {
console.log('Verifications data length:', verificationsData.data.length);
verificationsData.data.forEach((item: any) => {
console.log('Verification item:', item);
if (item.status === 'pending') {
allPendingData.push({
key: `ver-${item.id}`,
id: item.id,
type: '核销申请',
code: item.verification_code,
applicant: item.applicant,
amount: item.amount,
currency: item.currency,
date: item.verification_date,
reason: item.reason,
status: item.status,
rawData: item
});
}
});
}
console.log('Final pending data:', allPendingData);
setPendingData(allPendingData);
} catch (error) {
console.error('获取待审批数据失败:', error);
message.error('获取待审批数据失败');
} finally {
setLoading(false);
}
};
// 格式化金额
const formatAmount = (amount: number, currency: string = 'CNY') => {
const symbols: Record<string, string> = { CNY: '¥', USD: '$', LAK: '₭', THB: '฿' };
return (symbols[currency] || '¥') + (amount || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
};
// 获取类型标签
const getTypeTag = (type: string) => {
const colors: Record<string, string> = { '预支申请': 'blue', '报销申请': 'green', '付款申请': 'orange', '核销申请': 'purple' };
return <Tag color={colors[type] || 'default'}>{type}</Tag>;
};
// 获取状态标签
const getStatusTag = (status: string) => {
const statusMap: Record<string, { color: string; text: string }> = {
pending: { color: 'processing', text: '待审批' },
approved: { color: 'success', text: '已通过' },
rejected: { color: 'error', text: '已退回' },
withdrawn: { color: 'default', text: '已撤回' }
};
const config = statusMap[status] || { color: 'default', text: status };
return <Tag color={config.color}>{config.text}</Tag>;
};
// 根据项目ID获取项目名称
const getProjectName = (projectId: any) => {
if (!projectId) return '-';
const project = projects.find(p => p.id === projectId);
return project ? project.name : `项目ID: ${projectId}`;
};
// 查看详情
const handleViewDetail = (record: any) => {
setSelectedRecord(record);
setApprovalType('approve');
form.resetFields();
setDetailModalVisible(true);
};
// 处理审批通过
const handleApprove = async () => {
try {
const values = await form.validateFields();
// 构建API请求URL
const isAdvance = selectedRecord.key.startsWith('adv-');
const isReimbursement = selectedRecord.key.startsWith('reimb-');
const isPayment = selectedRecord.key.startsWith('pay-');
const isVerification = selectedRecord.key.startsWith('ver-');
const id = selectedRecord.id;
let url = '';
if (isAdvance) url = `http://localhost:3005/api/advances/${id}/approve`;
else if (isReimbursement) url = `http://localhost:3005/api/reimbursements/${id}/approve`;
else if (isPayment) url = `http://localhost:3005/api/payment-requests/${id}/approve`;
else if (isVerification) url = `http://localhost:3005/api/verifications/${id}/approve`;
// 发送API请求
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(values)
});
const result = await res.json();
if (result.success) {
// 从待审批列表中移除该申请
setPendingData(pendingData.filter(item => item.key !== selectedRecord.key));
message.success(`审批通过:${selectedRecord.code}`);
setDetailModalVisible(false);
} else {
message.error(result.message || '操作失败');
}
} catch (error) {
console.error('审批操作失败:', error);
message.error('操作失败');
}
};
// 处理审批退回
const handleReject = async () => {
try {
const values = await form.validateFields();
// 构建API请求URL
const isAdvance = selectedRecord.key.startsWith('adv-');
const isReimbursement = selectedRecord.key.startsWith('reimb-');
const isPayment = selectedRecord.key.startsWith('pay-');
const isVerification = selectedRecord.key.startsWith('ver-');
const id = selectedRecord.id;
let url = '';
if (isAdvance) url = `http://localhost:3005/api/advances/${id}/reject`;
else if (isReimbursement) url = `http://localhost:3005/api/reimbursements/${id}/reject`;
else if (isPayment) url = `http://localhost:3005/api/payment-requests/${id}/reject`;
else if (isVerification) url = `http://localhost:3005/api/verifications/${id}/reject`;
// 发送API请求
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(values)
});
const result = await res.json();
if (result.success) {
// 从待审批列表中移除该申请
setPendingData(pendingData.filter(item => item.key !== selectedRecord.key));
message.success(`已退回:${selectedRecord.code}`);
setDetailModalVisible(false);
} else {
message.error(result.message || '操作失败');
}
} catch (error) {
console.error('审批操作失败:', error);
message.error('操作失败');
}
};
// 处理撤回申请
const handleWithdraw = (record: any) => {
Modal.confirm({
title: '撤回申请',
content: `确认撤回申请 ${record.code} 吗?`,
okText: '确认撤回',
cancelText: '取消',
onOk: () => {
setPendingData(pendingData.filter(item => item.key !== record.key));
message.success('申请已撤回');
}
});
};
// 处理编辑申请
const handleEdit = (record: any) => {
setSelectedRecord(record);
editForm.setFieldsValue({ amount: record.amount, reason: record.reason });
setEditModalVisible(true);
};
// 处理编辑提交
const handleEditSubmit = () => {
editForm.validateFields().then(values => {
message.success('修改成功,已重新提交审批');
setEditModalVisible(false);
});
};
// 获取申请类型对应的API端点
const getApiEndpoint = (key: string) => {
if (key.startsWith('adv-')) return 'advances';
if (key.startsWith('reimb-')) return 'reimbursements';
if (key.startsWith('pay-')) return 'payment-requests';
if (key.startsWith('ver-')) return 'verifications';
return '';
};
// 渲染附件列表
const renderAttachments = (attachments: any) => {
// 处理字符串类型的 attachmentsJSON字符串)
let attachmentList = attachments;
if (typeof attachments === 'string') {
try {
attachmentList = JSON.parse(attachments);
} catch (e) {
return null;
}
}
// 确保是数组
if (!Array.isArray(attachmentList) || attachmentList.length === 0) return null;
return (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
{attachmentList.map((url: string, index: number) => (
<div key={index} style={{ position: 'relative' }}>
{url && url.match(/\.(jpg|jpeg|png|gif|webp)$/i) ? (
<img
src={url}
alt={`附件${index + 1}`}
style={{ width: 120, height: 120, objectFit: 'cover', borderRadius: 4, border: '1px solid #f0f0f0', cursor: 'pointer' }}
onClick={() => window.open(url, '_blank')}
/>
) : (
<a href={url} target="_blank" rel="noopener noreferrer">
<div style={{ width: 120, height: 120, display: 'flex', alignItems: 'center', justifyContent: 'center', border: '1px solid #f0f0f0', borderRadius: 4, background: '#f5f5f5' }}>
<FileImageOutlined style={{ fontSize: 32, color: '#999' }} />
</div>
</a>
)}
</div>
))}
</div>
);
};
// 获取支出分类的中文名称
const getCategoryName = (category: string) => {
if (!category) return '-';
// 特殊分类映射
const specialCategories: Record<string, string> = {
// Project expense categories
accommodation: '住宿',
food: '餐饮',
fuel: '加油',
materials: '零散材料',
customer_relations: '客户关系',
subcontract_relations: '分包关系',
edl_relations: 'EDL关系',
extra_construction: '额外施工',
// Company expense categories
general_operations: '通用运营(房租/耗材)',
transportation: '交通通勤',
business_expansion: '业扩营销',
power_system_relations: '电力系统关系',
employee_benefits: '员工福利',
express_logistics: '快递物流',
other: '其他'
};
// 先检查特殊分类
if (specialCategories[category]) return specialCategories[category];
// 再从项目支出分类中查找
const projectCategory = PROJECT_EXPENSE_CATEGORIES.find(c => c.value === category);
if (projectCategory) return projectCategory.label;
// 再从公司支出分类中查找
const companyCategory = COMPANY_EXPENSE_CATEGORIES.find(c => c.value === category);
if (companyCategory) return companyCategory.label;
// 如果都找不到,返回原始值
return category;
};
// 渲染明细清单
const renderDetailItems = (detailItems: any) => {
// 处理字符串类型的 detailItemsJSON字符串)
let itemsList = detailItems;
if (typeof detailItems === 'string') {
try {
itemsList = JSON.parse(detailItems);
} catch (e) {
return null;
}
}
// 确保是数组
if (!Array.isArray(itemsList) || itemsList.length === 0) return null;
return (
<List
size="small"
bordered
dataSource={itemsList}
renderItem={(item: any, index: number) => (
<List.Item>
<div style={{ width: '100%' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8 }}>
<span><strong>明细 {index + 1}:</strong> {item.description || getCategoryName(item.category) || '-'}</span>
<span style={{ fontWeight: 'bold', color: '#1890ff' }}>{formatAmount(item.amount, item.currency)}</span>
</div>
{item.category && (
<div style={{ marginBottom: 8, fontSize: 13, color: '#666' }}>
<strong>支出分类:</strong>{getCategoryName(item.category)}
</div>
)}
{item.attachments && (
<div style={{ marginTop: 8 }}>
<span style={{ color: '#666', fontSize: 12 }}>明细附件:</span>
{renderAttachments(item.attachments)}
</div>
)}
</div>
</List.Item>
)}
/>
);
};
// 待审批列
const pendingColumns = [
{ title: '事由', dataIndex: 'reason', key: 'reason', ellipsis: true, render: (v: string, r: any) => <a onClick={() => handleViewDetail(r)}>{v}</a> },
{ title: '类型', dataIndex: 'type', key: 'type', width: 100, render: (v: string) => getTypeTag(v) },
{ title: '申请人', dataIndex: 'applicant', key: 'applicant', width: 80 },
{ title: '金额', dataIndex: 'amount', key: 'amount', width: 140, render: (v: number, r: any) => formatAmount(v, r.currency) },
{ title: '申请日期', dataIndex: 'date', key: 'date', width: 100 },
{ title: '状态', dataIndex: 'status', key: 'status', width: 100, render: (v: string) => getStatusTag(v) },
{ title: '编号', dataIndex: 'code', key: 'code', width: 140 },
{
title: '操作', key: 'action', width: 200,
render: (_: any, record: any) => (
<Space wrap>
<Button size="small" type="primary" icon={<EyeOutlined />} onClick={() => handleViewDetail(record)}>审批</Button>
<Button size="small" icon={<UndoOutlined />} onClick={() => handleWithdraw(record)}>撤回</Button>
<Button size="small" icon={<EditOutlined />} onClick={() => handleEdit(record)}>编辑</Button>
</Space>
)
}
];
// 审批记录列
const historyColumns = [
{ title: '时间', dataIndex: 'timestamp', key: 'timestamp', width: 140 },
{ title: '操作', dataIndex: 'action', key: 'action', width: 100 },
{ title: '申请编号', dataIndex: 'applyCode', key: 'applyCode', width: 140 },
{ title: '类型', dataIndex: 'applyType', key: 'applyType', width: 100, render: (v: string) => getTypeTag(v) },
{ title: '申请人', dataIndex: 'applicant', key: 'applicant', width: 80 },
{ title: '金额', dataIndex: 'amount', key: 'amount', width: 140, render: (v: number, r: any) => formatAmount(v, r.currency) },
{ title: '操作人', dataIndex: 'operator', key: 'operator', width: 100 },
{ title: '备注/原因', dataIndex: 'remark', key: 'remark', ellipsis: true }
];
const tabItems = [
{ key: 'pending', label: <span>待审批 <Badge count={pendingData.length} style={{ marginLeft: 8 }} /></span>, children: <Table columns={pendingColumns} dataSource={pendingData} rowKey="key" loading={loading} pagination={{ pageSize: 10 }} scroll={{ x: 1200 }} /> },
{ key: 'history', label: <span>审批记录 <Badge count={approvalHistory.length} style={{ marginLeft: 8 }} /></span>, children: <Table columns={historyColumns} dataSource={approvalHistory} rowKey="id" loading={loading} pagination={{ pageSize: 10 }} scroll={{ x: 1400 }} /> },
];
// 获取完整的申请详情
const getFullDetail = () => {
if (!selectedRecord || !selectedRecord.rawData) return null;
return selectedRecord.rawData;
};
const fullDetail = getFullDetail();
return (
<div style={{ padding: 24 }}>
<div style={{ marginBottom: 24 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
<h2>审批管理</h2>
<Button type="primary" onClick={fetchPendingData} loading={loading}>
刷新数据
</Button>
</div>
<p style={{ color: '#888', marginBottom: 0 }}>审批预支、报销、付款等申请</p>
</div>
<Card><Tabs items={tabItems} /></Card>
{/* 详情模态框 */}
<Modal
title={`${selectedRecord?.type}详情:${selectedRecord?.code}`}
open={detailModalVisible}
onCancel={() => setDetailModalVisible(false)}
width={900}
footer={
selectedRecord?.status === 'pending' ? (
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
<Button onClick={() => setDetailModalVisible(false)}>取消</Button>
<Button danger icon={<CloseOutlined />} onClick={handleReject}>退回</Button>
<Button type="primary" icon={<CheckOutlined />} onClick={handleApprove}>通过</Button>
</div>
) : (
<Button onClick={() => setDetailModalVisible(false)}>关闭</Button>
)
}
>
{fullDetail && (
<>
{/* 基本信息 */}
<Descriptions bordered column={2} size="small">
<Descriptions.Item label="申请类型">{getTypeTag(selectedRecord.type)}</Descriptions.Item>
<Descriptions.Item label="申请编号">{selectedRecord.code}</Descriptions.Item>
<Descriptions.Item label="申请人">{selectedRecord.applicant}</Descriptions.Item>
<Descriptions.Item label="申请日期">{selectedRecord.date}</Descriptions.Item>
<Descriptions.Item label="金额">
{formatAmount(selectedRecord.amount, selectedRecord.currency)}
{selectedRecord.currency !== 'CNY' && fullDetail.amount_cny > 0 && (
<span style={{ color: '#999', marginLeft: 8 }}> ¥{fullDetail.amount_cny.toLocaleString('zh-CN', {minimumFractionDigits: 2})}</span>
)}
</Descriptions.Item>
<Descriptions.Item label="状态">{getStatusTag(selectedRecord.status)}</Descriptions.Item>
<Descriptions.Item label="事由" span={2}>{selectedRecord.reason}</Descriptions.Item>
{/* 付款申请特有字段 */}
{selectedRecord.type === '付款申请' && (
<>
<Descriptions.Item label="收款单位类型">
{fullDetail.payee_type === 'subcontractor' ? '分包商' :
fullDetail.payee_type === 'supplier' ? '供应商' :
fullDetail.payee_type === 'customer' ? '客户' : '其他'}
</Descriptions.Item>
<Descriptions.Item label="收款方">{fullDetail.payee || '-'}</Descriptions.Item>
<Descriptions.Item label="银行名称">{fullDetail.bank_name || '-'}</Descriptions.Item>
<Descriptions.Item label="银行账号">{fullDetail.bank_account || '-'}</Descriptions.Item>
<Descriptions.Item label="支出类型">
{fullDetail.expense_type === 'company' ? '公司支出' : '项目支出'}
</Descriptions.Item>
{fullDetail.expense_type === 'project' && fullDetail.project_id && (
<Descriptions.Item label="关联项目">{getProjectName(fullDetail.project_id)}</Descriptions.Item>
)}
<Descriptions.Item label="支出分类">
{fullDetail.expense_type === 'project'
? (PROJECT_EXPENSE_CATEGORIES.find(c => c.value === fullDetail.expense_category)?.label || fullDetail.expense_category)
: (COMPANY_EXPENSE_CATEGORIES.find(c => c.value === fullDetail.expense_category)?.label || fullDetail.expense_category)
}
</Descriptions.Item>
</>
)}
{/* 报销申请特有字段 */}
{selectedRecord.type === '报销申请' && fullDetail.expense_type && (
<>
<Descriptions.Item label="支出类型">
{fullDetail.expense_type === 'company' ? '公司支出' : '项目支出'}
</Descriptions.Item>
{fullDetail.project_id && (
<Descriptions.Item label="关联项目">{getProjectName(fullDetail.project_id)}</Descriptions.Item>
)}
</>
)}
{/* 核销申请特有字段 */}
{selectedRecord.type === '核销申请' && fullDetail.advance_code && (
<>
<Descriptions.Item label="关联预支单">{fullDetail.advance_code}</Descriptions.Item>
<Descriptions.Item label="预支金额">{formatAmount(fullDetail.advance_amount, fullDetail.currency)}</Descriptions.Item>
<Descriptions.Item label="结算核销">
<span style={{ fontWeight: 'bold', color: fullDetail.settlement ? '#52c41a' : '#fa8c16' }}>
{fullDetail.settlement ? '是' : '否'}
</span>
</Descriptions.Item>
<Descriptions.Item label="已核销金额">{formatAmount(fullDetail.total_reimbursed || 0, fullDetail.currency)}</Descriptions.Item>
<Descriptions.Item label="剩余核销金额">{formatAmount((fullDetail.advance_amount || 0) - (fullDetail.total_reimbursed || 0), fullDetail.currency)}</Descriptions.Item>
{fullDetail.settlement && fullDetail.settlement_amount && (
<Descriptions.Item label="核销结算金额" span={2}>
{fullDetail.settlement_amount > 0 ? `退款 ${formatAmount(fullDetail.settlement_amount, fullDetail.currency)}` : `补款 ${formatAmount(Math.abs(fullDetail.settlement_amount), fullDetail.currency)}`}
</Descriptions.Item>
)}
</>
)}
</Descriptions>
{/* 明细清单 */}
{fullDetail.detail_items && fullDetail.detail_items.length > 0 && (
<>
<Divider>明细清单</Divider>
{renderDetailItems(fullDetail.detail_items)}
</>
)}
{/* 凭证附件或退款凭证 */}
{fullDetail.attachments && fullDetail.attachments.length > 0 && (
<>
<Divider>{fullDetail.settlement && fullDetail.settlement_amount > 0 ? '退款凭证' : '凭证附件'}</Divider>
{renderAttachments(fullDetail.attachments)}
</>
)}
{/* 审批备注表单 */}
{selectedRecord.status === 'pending' && (
<>
<Divider>审批意见</Divider>
<Form form={form} layout="vertical">
<Form.Item name="remark" label="审批备注">
<TextArea rows={3} placeholder="可选:填写审批备注" />
</Form.Item>
<Form.Item name="rejectReason" label="退回原因" style={{ display: 'none' }}>
<TextArea rows={3} placeholder="请填写退回原因" />
</Form.Item>
</Form>
</>
)}
</>
)}
</Modal>
<Modal title={`编辑申请:${selectedRecord?.code}`} open={editModalVisible} onCancel={() => setEditModalVisible(false)} onOk={handleEditSubmit} width={600}>
<Form form={editForm} layout="vertical">
<Form.Item label="申请类型"><Input value={selectedRecord?.type} disabled /></Form.Item>
<Form.Item label="申请人"><Input value={selectedRecord?.applicant} disabled /></Form.Item>
<Form.Item name="amount" label="金额" rules={[{ required: true }]}><Input type="number" style={{ width: '100%' }} /></Form.Item>
<Form.Item name="reason" label="事由" rules={[{ required: true }]}><TextArea rows={3} /></Form.Item>
</Form>
</Modal>
<Modal title={`${selectedRecord?.advance_code ? '预支申请' : '报销申请'}详情:${selectedRecord?.advance_code || selectedRecord?.reimbursement_code}`} open={historyModalVisible} onCancel={() => setHistoryModalVisible(false)} footer={null} width={800}>
{selectedRecord && (
<>
<Descriptions bordered column={2} size="small">
<Descriptions.Item label="申请编号">{selectedRecord.advance_code || selectedRecord.reimbursement_code}</Descriptions.Item>
<Descriptions.Item label="状态">{getStatusTag(selectedRecord.status)}</Descriptions.Item>
<Descriptions.Item label="申请人">{selectedRecord.applicant}</Descriptions.Item>
<Descriptions.Item label="申请日期">{selectedRecord.advance_date || selectedRecord.reimbursement_date}</Descriptions.Item>
<Descriptions.Item label="金额">
{formatAmount(selectedRecord.amount, selectedRecord.currency)}
{selectedRecord.currency !== 'CNY' && selectedRecord.amount_cny && (
<span style={{ color: '#999', marginLeft: 8 }}> ¥{selectedRecord.amount_cny.toLocaleString('zh-CN', {minimumFractionDigits: 2})}</span>
)}
</Descriptions.Item>
<Descriptions.Item label="事由" span={2}>{selectedRecord.reason}</Descriptions.Item>
</Descriptions>
{Array.isArray(selectedRecord.attachments) && selectedRecord.attachments.length > 0 && (
<>
<Divider>凭证附件</Divider>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
{selectedRecord.attachments.map((url: string, index: number) => (
<img key={index} src={url} width={100} height={100} style={{ objectFit: 'cover', borderRadius: 4, border: '1px solid #f0f0f0' }} />
))}
</div>
</>
)}
</>
)}
</Modal>
</div>
);
};
export default ApprovalManagement;