Files
yunhaifinance/backups/20260325_002423/frontend/src/pages/approval/ExecutionManagement.tsx
T

640 lines
28 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, Select, DatePicker, message, Tabs, Badge, Descriptions, Divider, List, Upload } from 'antd';
import { CheckOutlined, CloseOutlined, EyeOutlined, DollarOutlined, EditOutlined, UndoOutlined, ClockCircleOutlined, FileImageOutlined, UploadOutlined } from '@ant-design/icons';
import dayjs from 'dayjs';
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: '其他支出' }
];
// 执行记录类型
interface ExecutionRecord {
id: string;
applyCode: string;
applyType: string;
applicant: string;
amount: number;
currency: string;
action: 'execute' | 'reject';
operator: string;
operatorRole: string;
timestamp: string;
executeMethod?: string;
voucherNo?: string;
rejectReason?: string;
remark?: string;
}
const ExecutionManagement: 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 [executionType, setExecutionType] = useState<'execute' | 'reject'>('execute');
const [form] = Form.useForm();
const [editForm] = Form.useForm();
const [voucherFiles, setVoucherFiles] = useState<any[]>([]);
// 执行记录
const [executionHistory, setExecutionHistory] = useState<ExecutionRecord[]>([]);
// 待执行数据
const [pendingData, setPendingData] = useState([]);
// 已执行数据
const [executedData, setExecutedData] = useState([]);
// 从后端获取待执行数据
useEffect(() => {
const fetchPendingData = async () => {
setLoading(true);
try {
const response = await fetch('http://localhost:3005/api/executions/pending');
if (response.ok) {
const data = await response.json();
if (data.success && Array.isArray(data.data)) {
setPendingData(data.data.map((item: any, index: number) => ({
...item,
key: item.id || index,
rawData: item
})));
} else {
message.error('获取待执行数据失败:数据格式错误');
}
} else {
message.error('获取待执行数据失败:' + response.statusText);
}
} catch (error) {
console.error('获取待执行数据错误:', error);
message.error('网络错误,获取待执行数据失败');
} finally {
setLoading(false);
}
};
fetchPendingData();
}, []);
// 从后端获取已执行数据
useEffect(() => {
const fetchExecutedData = async () => {
setLoading(true);
try {
const response = await fetch('http://localhost:3005/api/executions/executed');
if (response.ok) {
const data = await response.json();
if (data.success && Array.isArray(data.data)) {
setExecutedData(data.data.map((item: any, index: number) => ({
...item,
key: item.id || index,
rawData: item
})));
} else {
message.error('获取已执行数据失败:数据格式错误');
}
} else {
message.error('获取已执行数据失败:' + response.statusText);
}
} catch (error) {
console.error('获取已执行数据错误:', error);
message.error('网络错误,获取已执行数据失败');
} finally {
setLoading(false);
}
};
fetchExecutedData();
}, []);
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 formatNumberWithSeparator = (value: number | undefined, currency: string): string => {
if (value === undefined || value === null) return '';
const symbols: Record<string, string> = { CNY: '¥', USD: '$', LAK: '₭', THB: '฿' };
return (symbols[currency] || '¥') + value.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
};
const parseFormattedNumber = (value: string): number => {
const cleaned = value.replace(/[¥$₭฿,]/g, '');
return parseFloat(cleaned) || 0;
};
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: '待执行' },
executed: { color: 'success', text: '已执行' },
rejected: { color: 'error', text: '已退回' },
};
const config = statusMap[status] || { color: 'default', text: status };
return <Tag color={config.color}>{config.text}</Tag>;
};
// 添加执行记录
const addExecutionRecord = (record: any, action: 'execute' | 'reject', operator: string, operatorRole: string, data?: any) => {
const newRecord: ExecutionRecord = {
id: Date.now().toString(),
applyCode: record.code,
applyType: record.type,
applicant: record.applicant,
amount: record.amount,
currency: record.currency,
action,
operator,
operatorRole,
timestamp: dayjs().format('YYYY-MM-DD HH:mm'),
executeMethod: data?.executeMethod,
voucherNo: data?.voucherNo,
rejectReason: data?.rejectReason,
remark: data?.remark
};
setExecutionHistory([newRecord, ...executionHistory]);
};
// 查看详情
const handleViewDetail = (record: any) => {
setSelectedRecord(record);
setExecutionType('execute');
form.resetFields();
form.setFieldsValue({ execute_date: dayjs(), execute_method: 'bank' });
setVoucherFiles([]);
setDetailModalVisible(true);
};
// 处理执行
const handleExecute = async () => {
try {
const values = await form.validateFields();
// 检查是否上传了付款凭证
if (!voucherFiles || voucherFiles.length === 0) {
message.error('请上传付款凭证');
return;
}
setLoading(true);
// 获取已上传文件的URL列表
const voucherFileUrls = voucherFiles
.map(f => f.url || f.response?.data?.url || f.response?.url)
.filter(url => url); // 过滤掉空值
console.log('上传的凭证文件:', voucherFiles);
console.log('凭证文件URL列表:', voucherFileUrls);
// 调用执行API
const executeResponse = await fetch('http://localhost:3005/api/executions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
apply_id: selectedRecord.id,
apply_type: selectedRecord.type === '预支申请' ? 'advance' : selectedRecord.type === '报销申请' ? 'reimbursement' : selectedRecord.type === '付款申请' ? 'payment' : 'verification',
action: 'execute',
execute_method: values.execute_method,
voucher_files: voucherFileUrls,
remark: values.remark
})
});
if (executeResponse.ok) {
addExecutionRecord(selectedRecord, 'execute', '系统管理员', '管理员', {
executeMethod: values.execute_method === 'bank' ? '银行转账' : values.execute_method === 'cash' ? '现金' : '其他',
remark: values.remark
});
setPendingData(pendingData.filter(item => item.key !== selectedRecord.key));
message.success(`执行成功:${selectedRecord.code}`);
setDetailModalVisible(false);
} else {
message.error('执行操作失败,请重试');
}
} catch (error) {
console.error('执行操作失败:', error);
message.error('网络错误,操作失败');
} finally {
setLoading(false);
}
};
// 处理退回
const handleReject = async () => {
try {
const values = await form.validateFields();
setLoading(true);
// 调用退回API
const rejectResponse = await fetch('http://localhost:3005/api/executions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
apply_id: selectedRecord.id,
apply_type: selectedRecord.type === '预支申请' ? 'advance' : selectedRecord.type === '报销申请' ? 'reimbursement' : selectedRecord.type === '付款申请' ? 'payment' : 'verification',
action: 'reject',
reject_reason: values.rejectReason
})
});
if (rejectResponse.ok) {
addExecutionRecord(selectedRecord, 'reject', '系统管理员', '管理员', { rejectReason: values.rejectReason });
setPendingData(pendingData.filter(item => item.key !== selectedRecord.key));
message.success(`已退回:${selectedRecord.code},申请人可编辑后重新提交`);
setDetailModalVisible(false);
} else {
message.error('退回操作失败,请重试');
}
} catch (error) {
console.error('退回操作失败:', error);
message.error('网络错误,操作失败');
} finally {
setLoading(false);
}
};
const handleViewHistory = (record: any) => {
setSelectedRecord(record);
setHistoryModalVisible(true);
};
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);
});
};
// 渲染附件列表
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 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 || item.category || '-'}</span>
<span style={{ fontWeight: 'bold', color: '#1890ff' }}>{formatAmount(item.amount, item.currency)}</span>
</div>
{item.attachments && (
<div style={{ marginTop: 8 }}>
<span style={{ color: '#666', fontSize: 12 }}>明细附件:</span>
{renderAttachments(item.attachments)}
</div>
)}
</div>
</List.Item>
)}
/>
);
};
// 执行记录列
const historyColumns = [
{ title: '时间', dataIndex: 'timestamp', key: 'timestamp', width: 140 },
{ title: '操作', dataIndex: 'action', key: 'action', width: 100, render: (v: string) => {
const map: Record<string, { color: string; icon: any; text: string }> = {
execute: { color: 'green', icon: <CheckOutlined />, text: '执行' },
reject: { color: 'red', icon: <CloseOutlined />, text: '退回' }
};
const m = map[v] || { color: 'default', icon: null, text: v };
return <Tag color={m.color} icon={m.icon}>{m.text}</Tag>;
}},
{ 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: ExecutionRecord) => (
<>
<div>{formatAmount(v, r.currency)}</div>
{r.currency !== 'CNY' && r.amount_cny && <div style={{ color: '#999', fontSize: 12 }}> ¥{r.amount_cny.toLocaleString('zh-CN', {minimumFractionDigits: 2})}</div>}
</>
) },
{ title: '执行方式', dataIndex: 'executeMethod', key: 'executeMethod', width: 100 },
{ title: '操作人', dataIndex: 'operator', key: 'operator', width: 100 },
{ title: '角色', dataIndex: 'operatorRole', key: 'operatorRole', width: 80 },
{ title: '退回原因', dataIndex: 'rejectReason', key: 'rejectReason', ellipsis: true },
];
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: 120, render: (v: number, r: any) => <span style={{ fontWeight: 'bold', color: '#1890ff' }}>{formatAmount(v, r.currency)}</span> },
{ title: '收款方', dataIndex: 'payee', key: 'payee', ellipsis: true, render: (v: string, r: any) => v || r.applicant },
{ title: '审批日期', dataIndex: 'approveDate', key: 'approveDate', width: 100 },
{ title: '编号', dataIndex: 'code', key: 'code', width: 140 },
{
title: '操作', key: 'action', width: 200,
render: (_: any, record: any) => (
<Space wrap>
<Button size="small" type="primary" icon={<DollarOutlined />} onClick={() => handleViewDetail(record)}>执行</Button>
<Button size="small" icon={<EditOutlined />} onClick={() => handleEdit(record)}>编辑</Button>
</Space>
)
}
];
const executedColumns = [
{ 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) => (
<>
<div>{formatAmount(v, r.currency)}</div>
{r.currency !== 'CNY' && r.amount_cny && <div style={{ color: '#999', fontSize: 12 }}> ¥{r.amount_cny.toLocaleString('zh-CN', {minimumFractionDigits: 2})}</div>}
</>
) },
{ title: '执行日期', dataIndex: 'executeDate', key: 'executeDate', width: 100 },
{ title: '执行方式', dataIndex: 'executeMethod', key: 'executeMethod', 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: 100,
render: (_: any, record: any) => (
<Button size="small" icon={<EyeOutlined />} onClick={() => handleViewHistory(record)}>记录</Button>
)
}
];
const tabItems = [
{ key: 'pending', label: <span>待执行 <Badge count={pendingData.length} style={{ marginLeft: 8 }} /></span>, children: <Table columns={pendingColumns} dataSource={pendingData} loading={loading} pagination={{ pageSize: 10 }} scroll={{ x: 1300 }} /> },
{ key: 'executed', label: '已执行', children: <Table columns={executedColumns} dataSource={executedData} loading={loading} pagination={{ pageSize: 10 }} scroll={{ x: 1400 }} /> },
{ key: 'history', label: <span>执行记录 <Badge count={executionHistory.length} style={{ marginLeft: 8 }} /></span>, children: <Table columns={historyColumns} dataSource={executionHistory} rowKey="id" loading={loading} pagination={{ pageSize: 10 }} scroll={{ x: 1500 }} /> },
];
// 获取完整的申请详情
const getFullDetail = () => {
if (!selectedRecord || !selectedRecord.rawData) return selectedRecord;
return selectedRecord.rawData;
};
const fullDetail = getFullDetail();
// 上传配置
const uploadProps = {
name: 'file',
action: 'http://localhost:3005/api/upload/single',
headers: {
authorization: 'authorization-text',
},
onChange(info: any) {
// 更新文件列表状态
setVoucherFiles(info.fileList);
if (info.file.status === 'done') {
message.success(`${info.file.name} 上传成功`);
// 如果上传成功,将返回的URL添加到文件对象中
const updatedFileList = info.fileList.map((file: any) => {
if (file.uid === info.file.uid && file.response) {
return {
...file,
url: file.response.data?.url || file.response.url || file.response
};
}
return file;
});
setVoucherFiles(updatedFileList);
} else if (info.file.status === 'error') {
message.error(`${info.file.name} 上传失败`);
}
},
fileList: voucherFiles,
};
return (
<div style={{ padding: 24 }}>
<div style={{ marginBottom: 24 }}><h2 style={{ marginBottom: 8 }}>执行管理</h2><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 === 'approved' || 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={handleExecute}>执行</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 || fullDetail.advance_date || fullDetail.reimbursement_date || fullDetail.payment_date || fullDetail.verification_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="关联项目">项目ID: {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="关联项目">项目ID: {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>
{/* 明细清单 */}
{fullDetail.detail_items && fullDetail.detail_items.length > 0 && (
<>
<Divider>明细清单</Divider>
{renderDetailItems(fullDetail.detail_items)}
</>
)}
{/* 凭证附件 */}
{fullDetail.attachments && fullDetail.attachments.length > 0 && (
<>
<Divider>申请凭证附件</Divider>
{renderAttachments(fullDetail.attachments)}
</>
)}
{/* 执行表单 */}
{(selectedRecord.status === 'approved' || selectedRecord.status === 'pending') && (
<>
<Divider>执行信息</Divider>
<Form form={form} layout="vertical">
<Form.Item name="execute_date" label="执行日期" rules={[{ required: true }]}>
<DatePicker style={{ width: '100%' }} />
</Form.Item>
<Form.Item name="execute_method" label="执行方式" rules={[{ required: true }]}>
<Select options={[{ value: 'bank', label: '银行转账' }, { value: 'cash', label: '现金' }, { value: 'check', label: '支票' }, { value: 'other', label: '其他' }]} />
</Form.Item>
<Form.Item label="付款凭证" required>
<Upload {...uploadProps}>
<Button icon={<UploadOutlined />}>上传付款凭证</Button>
</Upload>
<div style={{ marginTop: 8, color: '#666', fontSize: 12 }}>
请上传付款凭证(银行转账回单、现金收据等),支持图片和PDF格式
</div>
</Form.Item>
<Form.Item name="remark" label="备注">
<TextArea rows={2} 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?.code}`} open={historyModalVisible} onCancel={() => setHistoryModalVisible(false)} footer={null} width={1000}>
<Table columns={historyColumns} dataSource={executionHistory.filter(r => r.applyCode === selectedRecord?.code)} rowKey="id" pagination={false} size="small" />
</Modal>
</div>
);
};
export default ExecutionManagement;