Initial commit: ERP system with advance verification fixes
This commit is contained in:
@@ -0,0 +1,552 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Table, Button, Card, Space, Tag, Modal, Form, Input, InputNumber, Select, DatePicker, message, Descriptions, Image, Divider, Cascader, 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 PAYEE_TYPES = [
|
||||
{ value: 'subcontractor', label: '分包商' },
|
||||
{ value: 'supplier', label: '供应商' },
|
||||
{ value: 'customer', label: '客户' },
|
||||
{ value: 'other', label: '其他' }
|
||||
];
|
||||
|
||||
// 支出类型
|
||||
const EXPENSE_TYPES = [
|
||||
{ value: 'company', label: '公司支出' },
|
||||
{ value: 'project', label: '项目支出' }
|
||||
];
|
||||
|
||||
// 项目支出分类
|
||||
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 PaymentRequestsPage: React.FC = () => {
|
||||
const { user } = useAuthStore();
|
||||
const [requests, setRequests] = useState<any[]>([]);
|
||||
const [completedRequests, setCompletedRequests] = useState<any[]>([]);
|
||||
const [activeTab, setActiveTab] = useState('active');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [detailModalVisible, setDetailModalVisible] = useState(false);
|
||||
const [editingId, setEditingId] = useState<number | null>(null);
|
||||
const [selectedRecord, setSelectedRecord] = useState<any>(null);
|
||||
const [form] = Form.useForm();
|
||||
const [exchangeRates, setExchangeRates] = useState<Record<string, number>>({});
|
||||
|
||||
// 数据列表
|
||||
const [subcontractors, setSubcontractors] = useState<any[]>([]);
|
||||
const [suppliers, setSuppliers] = useState<any[]>([]);
|
||||
const [customers, setCustomers] = useState<any[]>([]);
|
||||
const [projects, setProjects] = useState<any[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchRequests();
|
||||
fetchSubcontractors();
|
||||
fetchSuppliers();
|
||||
fetchCustomers();
|
||||
fetchProjects();
|
||||
fetchExchangeRates();
|
||||
}, []);
|
||||
|
||||
const fetchRequests = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await fetch('/api/payment-requests');
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
// 解析JSON字符串字段
|
||||
const parsedRequests = data.data.map((request: any) => ({
|
||||
...request,
|
||||
detail_items: typeof request.detail_items === 'string' ? JSON.parse(request.detail_items) : request.detail_items || [],
|
||||
attachments: typeof request.attachments === 'string' ? JSON.parse(request.attachments) : request.attachments || []
|
||||
}));
|
||||
// 分离活跃的和已完结的付款申请
|
||||
const active = parsedRequests.filter((item: any) => ['pending', 'approved', 'rejected', 'withdrawn'].includes(item.status));
|
||||
const completed = parsedRequests.filter((item: any) => ['executed', 'paid'].includes(item.status));
|
||||
setRequests(active);
|
||||
setCompletedRequests(completed);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取付款申请列表失败:', error);
|
||||
message.error('获取付款申请列表失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchSubcontractors = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/subcontractors');
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
setSubcontractors(data.data || []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取分包商列表失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchSuppliers = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/suppliers');
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
setSuppliers(data.data || []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取供应商列表失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchCustomers = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/customers');
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
setCustomers(data.data || []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取客户列表失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchProjects = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/projects');
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
setProjects(data.data || []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取项目列表失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchExchangeRates = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/exchange-rates/latest');
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
const rates: Record<string, number> = {};
|
||||
Object.keys(data.data).forEach(key => {
|
||||
rates[key] = parseFloat(data.data[key]) || 1;
|
||||
});
|
||||
setExchangeRates(rates);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取汇率失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreate = () => {
|
||||
setEditingId(null);
|
||||
form.resetFields();
|
||||
form.setFieldsValue({
|
||||
payment_date: dayjs(),
|
||||
currency: 'CNY',
|
||||
applicant: user?.name || user?.username || '当前用户',
|
||||
attachments: [],
|
||||
payee_type: 'other',
|
||||
expense_type: 'company'
|
||||
});
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
const handleEdit = (record: any) => {
|
||||
setEditingId(record.id);
|
||||
form.setFieldsValue({
|
||||
...record,
|
||||
payment_date: record.payment_date ? dayjs(record.payment_date) : null,
|
||||
attachments: record.attachments || []
|
||||
});
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
const handleView = (record: any) => {
|
||||
setSelectedRecord(record);
|
||||
setDetailModalVisible(true);
|
||||
};
|
||||
|
||||
const handleDelete = async (id: number) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '确定要删除这条付款申请吗?',
|
||||
onOk: async () => {
|
||||
try {
|
||||
await fetch('/api/payment-requests/' + id, { method: 'DELETE' });
|
||||
message.success('删除成功');
|
||||
fetchRequests();
|
||||
} catch (error) {
|
||||
message.error('删除失败');
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleWithdraw = async (id: number) => {
|
||||
Modal.confirm({
|
||||
title: '确认撤回',
|
||||
content: '撤回后可重新编辑提交,确认撤回吗?',
|
||||
onOk: async () => {
|
||||
try {
|
||||
await fetch('/api/payment-requests/' + id + '/withdraw', { method: 'POST' });
|
||||
message.success('已撤回,可重新编辑');
|
||||
fetchRequests();
|
||||
} catch (error) {
|
||||
message.error('撤回失败');
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
|
||||
// 处理收款单位
|
||||
let payee = '';
|
||||
let payee_id = null;
|
||||
if (values.payee_type === 'subcontractor') {
|
||||
const sub = subcontractors.find(s => s.id === values.payee_select);
|
||||
payee = sub?.name || '';
|
||||
payee_id = values.payee_select;
|
||||
} else if (values.payee_type === 'supplier') {
|
||||
const sup = suppliers.find(s => s.id === values.payee_select);
|
||||
payee = sup?.name || '';
|
||||
payee_id = values.payee_select;
|
||||
} else if (values.payee_type === 'customer') {
|
||||
const cust = customers.find(c => c.id === values.payee_select);
|
||||
payee = cust?.name || '';
|
||||
payee_id = values.payee_select;
|
||||
} else {
|
||||
payee = values.payee_input || '';
|
||||
}
|
||||
|
||||
const data = {
|
||||
...values,
|
||||
payee,
|
||||
payee_id,
|
||||
payment_date: values.payment_date?.format('YYYY-MM-DD'),
|
||||
applicant: user?.name || user?.username
|
||||
};
|
||||
|
||||
// 删除临时字段
|
||||
delete data.payee_select;
|
||||
delete data.payee_input;
|
||||
|
||||
const url = editingId ? '/api/payment-requests/' + editingId : '/api/payment-requests';
|
||||
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();
|
||||
if (result.success) {
|
||||
message.success(editingId ? '更新成功' : '创建成功');
|
||||
setModalVisible(false);
|
||||
fetchRequests();
|
||||
} else {
|
||||
message.error(result.error || '操作失败');
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('操作失败');
|
||||
}
|
||||
};
|
||||
|
||||
const convertToCNY = (amount: number, curr: string): number => {
|
||||
if (curr === "CNY") return amount;
|
||||
const rateKey = curr + "_CNY";
|
||||
const rate = exchangeRates[rateKey] || 1;
|
||||
return amount * rate;
|
||||
};
|
||||
|
||||
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: '已撤回' },
|
||||
paid: { color: 'blue', text: '已付款' },
|
||||
};
|
||||
const config = statusMap[status] || { color: 'default', text: status };
|
||||
return <Tag color={config.color}>{config.text}</Tag>;
|
||||
};
|
||||
|
||||
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 getExpenseCategoryLabel = (type: string, category: string) => {
|
||||
if (type === 'project') {
|
||||
return PROJECT_EXPENSE_CATEGORIES.find(c => c.value === category)?.label || category;
|
||||
} else {
|
||||
return COMPANY_EXPENSE_CATEGORIES.find(c => c.value === category)?.label || category;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取收款单位类型标签
|
||||
const getPayeeTypeLabel = (type: string) => {
|
||||
return PAYEE_TYPES.find(t => t.value === type)?.label || type;
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{ title: '事由', dataIndex: 'reason', key: 'reason', ellipsis: true, render: (v: string, r: any) => <a onClick={() => handleView(r)}>{v}</a> },
|
||||
{ title: '申请人', dataIndex: 'applicant', key: 'applicant', width: 80 },
|
||||
{ title: '收款单位', dataIndex: 'payee', key: 'payee', width: 150, ellipsis: true },
|
||||
{ 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: 'payment_date', key: 'payment_date', width: 100 },
|
||||
{ title: '状态', dataIndex: 'status', key: 'status', width: 100, render: (status: string) => getStatusTag(status) },
|
||||
{ title: '编号', dataIndex: 'request_code', key: 'request_code', width: 120 },
|
||||
{
|
||||
title: '操作', key: 'action', width: 250,
|
||||
render: (_: any, record: any) => (
|
||||
<Space wrap>
|
||||
<Button size="small" icon={<EyeOutlined />} onClick={() => handleView(record)}>详情</Button>
|
||||
{record.status === 'pending' && (
|
||||
<>
|
||||
<Button size="small" icon={<EditOutlined />} onClick={() => handleEdit(record)}>编辑</Button>
|
||||
<Button size="small" icon={<UndoOutlined />} onClick={() => handleWithdraw(record.id)}>撤回</Button>
|
||||
</>
|
||||
)}
|
||||
{(record.status === 'rejected' || record.status === 'withdrawn') && (
|
||||
<Button size="small" type="primary" icon={<EditOutlined />} onClick={() => handleEdit(record)}>编辑重提</Button>
|
||||
)}
|
||||
<Button size="small" danger icon={<DeleteOutlined />} onClick={() => handleDelete(record.id)}>删除</Button>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
// 监听表单值变化
|
||||
const payeeType = Form.useWatch('payee_type', form);
|
||||
const expenseType = Form.useWatch('expense_type', form);
|
||||
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]);
|
||||
|
||||
return (
|
||||
<div style={{ padding: 24 }}>
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<h2 style={{ marginBottom: 8 }}>付款申请</h2>
|
||||
<p style={{ color: '#888', marginBottom: 0 }}>管理对外付款申请</p>
|
||||
</div>
|
||||
|
||||
<Card extra={<Button type="primary" icon={<PlusOutlined />} onClick={handleCreate}>新建付款申请</Button>}>
|
||||
<Tabs activeKey={activeTab} onChange={setActiveTab}>
|
||||
<Tabs.TabPane tab="活跃申请" key="active">
|
||||
<Table dataSource={requests} columns={columns} rowKey="id" loading={loading} pagination={{ pageSize: 20 }} size="middle" scroll={{ x: 1200 }} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane tab="已完结" key="completed">
|
||||
<Table dataSource={completedRequests} columns={columns} rowKey="id" loading={loading} pagination={{ pageSize: 20 }} size="middle" scroll={{ x: 1200 }} />
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Card>
|
||||
|
||||
<Modal title={editingId ? '编辑付款申请' : '新建付款申请'} open={modalVisible} onOk={handleSubmit} onCancel={() => setModalVisible(false)} width={900}>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item name="applicant" label="申请人">
|
||||
<Input disabled style={{ color: 'rgba(0,0,0,0.85)', backgroundColor: '#f5f5f5' }} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="payment_date" label="付款日期" rules={[{ required: true }]}>
|
||||
<DatePicker style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
|
||||
{/* 收款单位 - 二级选择 */}
|
||||
<Form.Item name="payee_type" label="收款单位类型" rules={[{ required: true }]}>
|
||||
<Select placeholder="选择收款单位类型">
|
||||
{PAYEE_TYPES.map(type => (
|
||||
<Option key={type.value} value={type.value}>{type.label}</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
{payeeType === 'subcontractor' && (
|
||||
<Form.Item name="payee_select" label="选择分包商" rules={[{ required: true }]}>
|
||||
<Select placeholder="选择分包商" showSearch optionFilterProp="children">
|
||||
{subcontractors.map(sub => (
|
||||
<Option key={sub.id} value={sub.id}>{sub.name}</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
{payeeType === 'supplier' && (
|
||||
<Form.Item name="payee_select" label="选择供应商" rules={[{ required: true }]}>
|
||||
<Select placeholder="选择供应商" showSearch optionFilterProp="children">
|
||||
{suppliers.map(sup => (
|
||||
<Option key={sup.id} value={sup.id}>{sup.name}</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
{payeeType === 'customer' && (
|
||||
<Form.Item name="payee_select" label="选择客户" rules={[{ required: true }]}>
|
||||
<Select placeholder="选择客户" showSearch optionFilterProp="children">
|
||||
{customers.map(cust => (
|
||||
<Option key={cust.id} value={cust.id}>{cust.name}</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
{payeeType === 'other' && (
|
||||
<Form.Item name="payee_input" label="收款单位" rules={[{ required: true }]}>
|
||||
<Input placeholder="手动输入收款单位名称" />
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<Form.Item name="bank_account" label="银行账号">
|
||||
<Input placeholder="收款银行账号" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="bank_name" label="开户银行">
|
||||
<Input placeholder="开户银行名称" />
|
||||
</Form.Item>
|
||||
|
||||
{/* 支出类型 */}
|
||||
<Form.Item name="expense_type" label="支出类型" rules={[{ required: true }]}>
|
||||
<Select placeholder="选择支出类型">
|
||||
{EXPENSE_TYPES.map(type => (
|
||||
<Option key={type.value} value={type.value}>{type.label}</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
{/* 项目支出 - 选择项目 */}
|
||||
{expenseType === 'project' && (
|
||||
<Form.Item name="project_id" label="关联项目" rules={[{ required: true }]}>
|
||||
<Select placeholder="选择项目" showSearch optionFilterProp="children">
|
||||
{projects.map(proj => (
|
||||
<Option key={proj.id} value={proj.id}>{proj.name}</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
{/* 支出分类 */}
|
||||
<Form.Item name="expense_category" label="支出分类" rules={[{ required: true }]}>
|
||||
<Select placeholder="选择支出分类">
|
||||
{(expenseType === 'project' ? PROJECT_EXPENSE_CATEGORIES : COMPANY_EXPENSE_CATEGORIES).map(cat => (
|
||||
<Option key={cat.value} value={cat.value}>{cat.label}</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="currency" label="币种" rules={[{ required: true }]}>
|
||||
<Select style={{ width: 200 }}>
|
||||
<Option value="CNY">人民币 (CNY)</Option>
|
||||
<Option value="USD">美元 (USD)</Option>
|
||||
<Option value="LAK">老挝基普 (LAK)</Option>
|
||||
<Option value="THB">泰铢 (THB)</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
{/* 金额 - 直接输入 */}
|
||||
<Form.Item name="amount" label="付款金额" rules={[{ required: true }]}>
|
||||
<InputNumber min={0} precision={2} style={{ width: '100%' }} placeholder="输入付款金额" />
|
||||
{amount && currency !== 'CNY' && amountCNY > 0 && (
|
||||
<div style={{ marginTop: 8, color: '#888', fontSize: 13 }}>
|
||||
等价人民币:¥ {amountCNY.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
|
||||
</div>
|
||||
)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="reason" label="付款事由" rules={[{ required: true }]}>
|
||||
<TextArea rows={2} placeholder="付款原因" />
|
||||
</Form.Item>
|
||||
|
||||
<Divider>凭证附件</Divider>
|
||||
<Form.Item name="attachments" label="上传凭证附件">
|
||||
<FileUpload maxCount={9} accept="image/*" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Modal title="付款申请详情" open={detailModalVisible} onCancel={() => setDetailModalVisible(false)} footer={null} width={900}>
|
||||
{selectedRecord && (
|
||||
<>
|
||||
<Descriptions bordered column={2} size="small">
|
||||
<Descriptions.Item label="申请编号">{selectedRecord.request_code}</Descriptions.Item>
|
||||
<Descriptions.Item label="状态">{getStatusTag(selectedRecord.status)}</Descriptions.Item>
|
||||
<Descriptions.Item label="申请人">{selectedRecord.applicant}</Descriptions.Item>
|
||||
<Descriptions.Item label="付款日期">{selectedRecord.payment_date}</Descriptions.Item>
|
||||
<Descriptions.Item label="收款单位类型">{getPayeeTypeLabel(selectedRecord.payee_type)}</Descriptions.Item>
|
||||
<Descriptions.Item label="收款单位">{selectedRecord.payee}</Descriptions.Item>
|
||||
<Descriptions.Item label="银行账号">{selectedRecord.bank_account || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="开户银行">{selectedRecord.bank_name || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="支出类型">
|
||||
{selectedRecord.expense_type === 'company' ? '公司支出' : '项目支出'}
|
||||
</Descriptions.Item>
|
||||
{selectedRecord.expense_type === 'project' && (
|
||||
<Descriptions.Item label="关联项目">
|
||||
{projects.find(p => p.id === selectedRecord.project_id)?.name || '-'}
|
||||
</Descriptions.Item>
|
||||
)}
|
||||
<Descriptions.Item label="支出分类">
|
||||
{getExpenseCategoryLabel(selectedRecord.expense_type, selectedRecord.expense_category)}
|
||||
</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>
|
||||
|
||||
{selectedRecord.attachments && selectedRecord.attachments.length > 0 && (
|
||||
<>
|
||||
<Divider>凭证附件</Divider>
|
||||
<Image.PreviewGroup>
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
||||
{selectedRecord.attachments.map((url: string, index: number) => (
|
||||
<Image key={index} src={url} width={100} height={100} style={{ objectFit: 'cover', borderRadius: 4 }} />
|
||||
))}
|
||||
</div>
|
||||
</Image.PreviewGroup>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PaymentRequestsPage;
|
||||
Reference in New Issue
Block a user