备份:PWA配置前的完整版本

包含所有最新开发的功能和修复,作为PWA正确配置前的备份点。
This commit is contained in:
a273825743
2026-06-13 12:44:48 +08:00
parent 5fd19822a2
commit 706dcc24eb
83 changed files with 26590 additions and 13379 deletions
+150 -118
View File
@@ -5,6 +5,7 @@ import dayjs from 'dayjs';
import { useAuthStore } from '../store/authStore';
import FileUpload from '../components/FileUpload';
import useFormDraft from '../hooks/useFormDraft';
import { useLanguageStore } from '../store/languageStore';
const { Option } = Select;
const { TextArea } = Input;
@@ -59,6 +60,7 @@ interface PayeeEntity {
}
const PaymentRequestsPage: React.FC = () => {
const { t, currentLanguage } = useLanguageStore();
const { user } = useAuthStore();
const [requests, setRequests] = useState<any[]>([]);
const [completedRequests, setCompletedRequests] = useState<any[]>([]);
@@ -116,7 +118,7 @@ const PaymentRequestsPage: React.FC = () => {
}
} catch (error) {
console.error('获取付款申请列表失败:', error);
message.error('获取付款申请列表失败');
message.error(t('paymentRequest.getListFailed'));
} finally {
setLoading(false);
}
@@ -217,19 +219,21 @@ const PaymentRequestsPage: React.FC = () => {
form.setFieldsValue({
application_date: dayjs(),
currency: 'CNY',
applicant: user?.name || user?.username || '当前用户',
applicant: user?.name || user?.username || t('common.currentUser'),
attachments: [],
payee_type: 'other',
expense_type: 'company'
});
setWatchedAmount(null);
setWatchedCurrency('CNY');
setModalVisible(true);
setTimeout(() => {
if (hasDraft()) {
Modal.confirm({
title: '发现未完成的草稿',
content: '检测到上次未提交的付款申请,是否恢复?',
okText: '恢复草稿',
cancelText: '重新填写',
title: t('common.draftFound'),
content: t('common.draftRestore'),
okText: t('common.restoreDraft'),
cancelText: t('common.reFill'),
onOk: () => {
restoreDraft();
},
@@ -239,7 +243,7 @@ const PaymentRequestsPage: React.FC = () => {
form.setFieldsValue({
application_date: dayjs(),
currency: 'CNY',
applicant: user?.name || user?.username || '当前用户',
applicant: user?.name || user?.username || t('common.currentUser'),
attachments: [],
payee_type: 'other',
expense_type: 'company'
@@ -257,6 +261,8 @@ const PaymentRequestsPage: React.FC = () => {
application_date: record.application_date ? dayjs(record.application_date) : (record.payment_date ? dayjs(record.payment_date) : null),
attachments: record.attachments || []
});
setWatchedAmount(record.amount || null);
setWatchedCurrency(record.currency || 'CNY');
setModalVisible(true);
};
@@ -267,15 +273,15 @@ const PaymentRequestsPage: React.FC = () => {
const handleDelete = async (id: number) => {
Modal.confirm({
title: '确认删除',
content: '确定要删除这条付款申请吗?',
title: t('common.deleteConfirm'),
content: t('paymentRequest.deleteConfirmMsg') || t('common.confirmDeleteMsg'),
onOk: async () => {
try {
await fetch('/api/payment-requests/' + id, { method: 'DELETE' });
message.success('删除成功');
message.success(t('paymentRequest.deleteSuccess'));
fetchRequests();
} catch (error) {
message.error('删除失败');
message.error(t('paymentRequest.deleteFailed'));
}
}
});
@@ -283,15 +289,15 @@ const PaymentRequestsPage: React.FC = () => {
const handleWithdraw = async (id: number) => {
Modal.confirm({
title: '确认撤回',
content: '撤回后可重新编辑提交,确认撤回吗?',
title: t('paymentRequest.withdrawConfirm'),
content: t('paymentRequest.withdrawConfirmMsg'),
onOk: async () => {
try {
await fetch('/api/payment-requests/' + id + '/withdraw', { method: 'POST' });
message.success('已撤回,可重新编辑');
message.success(t('paymentRequest.withdrawSuccess'));
fetchRequests();
} catch (error) {
message.error('撤回失败');
message.error(t('paymentRequest.withdrawFailed'));
}
}
});
@@ -342,32 +348,56 @@ const PaymentRequestsPage: React.FC = () => {
});
const result = await res.json();
if (result.success) {
message.success(editingId ? '更新成功' : '创建成功');
message.success(editingId ? t('common.updateSuccess') : t('common.createSuccess'));
clearDraft();
setModalVisible(false);
fetchRequests();
} else {
message.error(result.error || '操作失败');
message.error(result.error || t('common.operationFailed'));
}
} catch (error) {
message.error('操作失败');
message.error(t('common.operationFailed'));
}
};
const convertToCNY = (amount: number, curr: string): number => {
if (curr === "CNY") return amount;
// 先尝试 XXX_CNY 格式
const rateKey = curr + "_CNY";
const rate = exchangeRates[rateKey] || 1;
return amount * rate;
if (exchangeRates[rateKey]) {
return amount * exchangeRates[rateKey];
}
// 尝试 CNY_XXX 格式的倒数
const reverseKey = "CNY_" + curr;
if (exchangeRates[reverseKey]) {
return amount / exchangeRates[reverseKey];
}
// 尝试通过 USD 中转: XXX -> USD -> CNY
const xxxUsdKey = curr + "_USD";
const usdCnyKey = "USD_CNY";
const cnyUsdKey = "CNY_USD";
if (exchangeRates[xxxUsdKey]) {
const usdAmount = amount * exchangeRates[xxxUsdKey];
if (exchangeRates[usdCnyKey]) return usdAmount * exchangeRates[usdCnyKey];
if (exchangeRates[cnyUsdKey]) return usdAmount / exchangeRates[cnyUsdKey];
}
// 通过 LAK 中转
const xxxLakKey = curr + "_LAK";
const cnyLakKey = "CNY_LAK";
if (exchangeRates[xxxLakKey] && exchangeRates[cnyLakKey]) {
const lakAmount = amount * exchangeRates[xxxLakKey];
return lakAmount / exchangeRates[cnyLakKey];
}
return amount;
};
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: '已付款' },
pending: { color: 'processing', text: t('paymentRequest.pendingApproval') },
approved: { color: 'success', text: t('paymentRequest.approved') },
rejected: { color: 'error', text: t('paymentRequest.rejected') },
withdrawn: { color: 'default', text: t('paymentRequest.withdrawn') },
paid: { color: 'blue', text: t('paymentRequest.paid') },
};
const config = statusMap[status] || { color: 'default', text: status };
return <Tag color={config.color}>{config.text}</Tag>;
@@ -393,48 +423,48 @@ const PaymentRequestsPage: React.FC = () => {
};
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) => (
{ title: t('paymentRequest.subject'), dataIndex: 'reason', key: 'reason', ellipsis: true, render: (v: string, r: any) => <a onClick={() => handleView(r)}>{v}</a> },
{ title: t('paymentRequest.applicant'), dataIndex: 'applicant', key: 'applicant', width: 80 },
{ title: t('paymentRequest.payee'), dataIndex: 'payee', key: 'payee', width: 150, ellipsis: true },
{ title: t('paymentRequest.amount'), 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: 'application_date', key: 'application_date', width: 100, render: (v: string, r: any) => v || r.payment_date },
{ title: '状态', dataIndex: 'status', key: 'status', width: 100, render: (status: string) => getStatusTag(status) },
{ title: '编号', dataIndex: 'request_code', key: 'request_code', width: 120 },
{ title: t('paymentRequest.applicationDate'), dataIndex: 'application_date', key: 'application_date', width: 100, render: (v: string, r: any) => v || r.payment_date },
{ title: t('paymentRequest.status'), dataIndex: 'status', key: 'status', width: 100, render: (status: string) => getStatusTag(status) },
{ title: t('paymentRequest.code'), dataIndex: 'request_code', key: 'request_code', width: 120 },
{
title: '操作', key: 'action', width: 250,
title: t('paymentRequest.action'), key: 'action', width: 250,
render: (_: any, record: any) => (
<Space wrap>
<Button size="small" icon={<EyeOutlined />} onClick={() => handleView(record)}></Button>
<Button size="small" icon={<EyeOutlined />} onClick={() => handleView(record)}>{t('common.detail')}</Button>
{record.status === 'pending' && (
<>
<Button size="small" icon={<EditOutlined />} onClick={() => handleEdit(record)}></Button>
<Button size="small" icon={<UndoOutlined />} onClick={() => handleWithdraw(record.id)}></Button>
<Button size="small" icon={<EditOutlined />} onClick={() => handleEdit(record)}>{t('paymentRequest.edit')}</Button>
<Button size="small" icon={<UndoOutlined />} onClick={() => handleWithdraw(record.id)}>{t('paymentRequest.withdraw')}</Button>
</>
)}
{(record.status === 'rejected' || record.status === 'withdrawn') && (
<Button size="small" type="primary" icon={<EditOutlined />} onClick={() => handleEdit(record)}></Button>
<Button size="small" type="primary" icon={<EditOutlined />} onClick={() => handleEdit(record)}>{t('paymentRequest.reEdit')}</Button>
)}
<Button size="small" danger icon={<DeleteOutlined />} onClick={() => handleDelete(record.id)}></Button>
<Button size="small" danger icon={<DeleteOutlined />} onClick={() => handleDelete(record.id)}>{t('paymentRequest.delete')}</Button>
</Space>
)
}
];
// 监听表单值变化
// 监听表单值变化 - 使用 useState + onValuesChange 替代 Form.useWatch 以确保稳定触发
const [watchedAmount, setWatchedAmount] = useState<number | null>(null);
const [watchedCurrency, setWatchedCurrency] = useState<string>('CNY');
const payeeType = Form.useWatch('payee_type', form);
const payeeSelect = Form.useWatch('payee_select', 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 watchedAmount && watchedCurrency ? convertToCNY(watchedAmount, watchedCurrency) : 0;
}, [watchedAmount, watchedCurrency, exchangeRates]);
// 当选择收款单位时,自动填充收款信息
useEffect(() => {
@@ -454,45 +484,47 @@ const PaymentRequestsPage: React.FC = () => {
return (
<div style={{ padding: 24 }}>
<div style={{ marginBottom: 24 }}>
<h2 style={{ marginBottom: 8 }}></h2>
<p style={{ color: '#888', marginBottom: 0 }}></p>
<h2 style={{ marginBottom: 8 }}>{t('paymentRequest.title')}</h2>
<p style={{ color: '#888', marginBottom: 0 }}>{t('paymentRequest.description')}</p>
</div>
<Card extra={<Button type="primary" icon={<PlusOutlined />} onClick={handleCreate}></Button>}>
<Card extra={<Button type="primary" icon={<PlusOutlined />} onClick={handleCreate}>{t('paymentRequest.newRequest')}</Button>}>
<Tabs activeKey={activeTab} onChange={setActiveTab}>
<Tabs.TabPane tab="活跃申请" key="active">
<Tabs.TabPane tab={t('paymentRequest.activeApplications')} 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">
<Tabs.TabPane tab={t('paymentRequest.completed')} 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={() => {
<Modal title={editingId ? t('paymentRequest.editPayment') : t('paymentRequest.newPayment')} open={modalVisible} onOk={handleSubmit} onCancel={() => {
if (form.isFieldsTouched()) {
Modal.confirm({
title: '确认关闭',
content: '表单数据尚未保存,关闭后可通过草稿恢复。确定关闭吗?',
okText: '关闭',
cancelText: '继续编辑',
title: t('common.closeConfirm'),
content: t('common.closeConfirmMsg'),
okText: t('common.close'),
cancelText: t('common.continueEdit'),
onOk: () => {
saveDraft();
form.resetFields();
setModalVisible(false);
},
});
} else {
form.resetFields();
setModalVisible(false);
}
}} maskClosable={false} width={900}>
}} maskClosable={false} width={900} destroyOnClose>
<Form form={form} layout="vertical" onValuesChange={handleFormChange}>
<Form.Item name="applicant" label="申请人">
<Form.Item name="applicant" label={t('paymentRequest.applicant')}>
<Input disabled style={{ color: 'rgba(0,0,0,0.85)', backgroundColor: '#f5f5f5' }} />
</Form.Item>
{/* 第2项:支出类型和支出分类 */}
<Form.Item name="expense_type" label="支出类型" rules={[{ required: true }]}>
<Select placeholder="选择支出类型">
<Form.Item name="expense_type" label={t('paymentRequest.expenseType')} rules={[{ required: true }]}>
<Select placeholder={t('paymentRequest.selectExpenseType')}>
{EXPENSE_TYPES.map(type => (
<Option key={type.value} value={type.value}>{type.label}</Option>
))}
@@ -501,8 +533,8 @@ const PaymentRequestsPage: React.FC = () => {
{/* 项目支出 - 选择项目 */}
{expenseType === 'project' && (
<Form.Item name="project_id" label="关联项目" rules={[{ required: true }]}>
<Select placeholder="选择项目" showSearch optionFilterProp="children">
<Form.Item name="project_id" label={t('paymentRequest.relatedProject')} rules={[{ required: true }]}>
<Select placeholder={t('paymentRequest.selectProject')} showSearch optionFilterProp="children">
{projects.map(proj => (
<Option key={proj.id} value={proj.id}>{proj.name}</Option>
))}
@@ -511,8 +543,8 @@ const PaymentRequestsPage: React.FC = () => {
)}
{/* 支出分类 */}
<Form.Item name="expense_category" label="支出分类" rules={[{ required: true }]}>
<Select placeholder="选择支出分类">
<Form.Item name="expense_category" label={t('paymentRequest.expenseCategory')} rules={[{ required: true }]}>
<Select placeholder={t('paymentRequest.selectCategory')}>
{(expenseType === 'project' ? PROJECT_EXPENSE_CATEGORIES : COMPANY_EXPENSE_CATEGORIES).map(cat => (
<Option key={cat.value} value={cat.value}>{cat.label}</Option>
))}
@@ -520,13 +552,13 @@ const PaymentRequestsPage: React.FC = () => {
</Form.Item>
{/* 申请日期(原付款日期,不显示) */}
<Form.Item name="application_date" label="申请日期" rules={[{ required: true }]} style={{ display: 'none' }}>
<Form.Item name="application_date" label={t('paymentRequest.applicationDate')} rules={[{ required: true }]} style={{ display: 'none' }}>
<DatePicker style={{ width: '100%' }} />
</Form.Item>
{/* 收款单位 - 二级选择 */}
<Form.Item name="payee_type" label="收款单位类型" rules={[{ required: true }]}>
<Select placeholder="选择收款单位类型">
<Form.Item name="payee_type" label={t('paymentRequest.payeeType')} rules={[{ required: true }]}>
<Select placeholder={t('paymentRequest.selectPayeeType')}>
{PAYEE_TYPES.map(type => (
<Option key={type.value} value={type.value}>{type.label}</Option>
))}
@@ -534,8 +566,8 @@ const PaymentRequestsPage: React.FC = () => {
</Form.Item>
{payeeType === 'subcontractor' && (
<Form.Item name="payee_select" label="选择分包商" rules={[{ required: true }]}>
<Select placeholder="选择分包商" showSearch optionFilterProp="children">
<Form.Item name="payee_select" label={t('paymentRequest.selectSubcontractor')} rules={[{ required: true }]}>
<Select placeholder={t('paymentRequest.selectSubcontractor')} showSearch optionFilterProp="children">
{subcontractors.map(sub => (
<Option key={sub.id} value={sub.id}>{sub.name}</Option>
))}
@@ -544,8 +576,8 @@ const PaymentRequestsPage: React.FC = () => {
)}
{payeeType === 'supplier' && (
<Form.Item name="payee_select" label="选择供应商" rules={[{ required: true }]}>
<Select placeholder="选择供应商" showSearch optionFilterProp="children">
<Form.Item name="payee_select" label={t('paymentRequest.selectSupplier')} rules={[{ required: true }]}>
<Select placeholder={t('paymentRequest.selectSupplier')} showSearch optionFilterProp="children">
{suppliers.map(sup => (
<Option key={sup.id} value={sup.id}>{sup.name}</Option>
))}
@@ -554,8 +586,8 @@ const PaymentRequestsPage: React.FC = () => {
)}
{payeeType === 'customer' && (
<Form.Item name="payee_select" label="选择客户" rules={[{ required: true }]}>
<Select placeholder="选择客户" showSearch optionFilterProp="children">
<Form.Item name="payee_select" label={t('paymentRequest.selectCustomer')} rules={[{ required: true }]}>
<Select placeholder={t('paymentRequest.selectCustomer')} showSearch optionFilterProp="children">
{customers.map(cust => (
<Option key={cust.id} value={cust.id}>{cust.name}</Option>
))}
@@ -564,102 +596,102 @@ const PaymentRequestsPage: React.FC = () => {
)}
{payeeType === 'other' && (
<Form.Item name="payee_input" label="收款单位" rules={[{ required: true }]}>
<Input placeholder="手动输入收款单位名称" />
<Form.Item name="payee_input" label={t('paymentRequest.payee')} rules={[{ required: true }]}>
<Input placeholder={t('paymentRequest.payeeNamePlaceholder')} />
</Form.Item>
)}
{/* 收款户名 - 新增字段 */}
<Form.Item name="account_name" label="收款户名">
<Input placeholder="收款户名(选择分包商/供应商/客户时自动填充)" readOnly={['subcontractor', 'supplier', 'customer'].includes(payeeType)} />
<Form.Item name="account_name" label={t('paymentRequest.accountName')}>
<Input placeholder={t('paymentRequest.accountNamePlaceholder')} readOnly={['subcontractor', 'supplier', 'customer'].includes(payeeType)} />
</Form.Item>
<Form.Item name="bank_account" label="银行账号">
<Input placeholder="收款银行账号(选择分包商/供应商/客户时自动填充)" readOnly={['subcontractor', 'supplier', 'customer'].includes(payeeType)} />
<Form.Item name="bank_account" label={t('paymentRequest.bankAccount')}>
<Input placeholder={t('paymentRequest.bankAccountPlaceholder')} readOnly={['subcontractor', 'supplier', 'customer'].includes(payeeType)} />
</Form.Item>
<Form.Item name="bank_name" label="开户银行">
<Input placeholder="开户银行名称(选择分包商/供应商/客户时自动填充)" readOnly={['subcontractor', 'supplier', 'customer'].includes(payeeType)} />
<Form.Item name="bank_name" label={t('paymentRequest.bankName')}>
<Input placeholder={t('paymentRequest.bankNamePlaceholder')} readOnly={['subcontractor', 'supplier', 'customer'].includes(payeeType)} />
</Form.Item>
{/* 收款码 - 新增字段 */}
<Form.Item name="qr_code" label="收款码">
<Form.Item name="qr_code" label={t('paymentRequest.qrCode')}>
<FileUpload maxCount={1} accept="image/*" />
</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>
<Form.Item name="currency" label={t('common.currency')} rules={[{ required: true }]}>
<Select style={{ width: 200 }} onChange={(value: string) => setWatchedCurrency(value)}>
<Option value="CNY">{t('paymentRequest.currencyCNY')}</Option>
<Option value="USD">{t('paymentRequest.currencyUSD')}</Option>
<Option value="LAK">{t('paymentRequest.currencyLAK')}</Option>
<Option value="THB">{t('paymentRequest.currencyTHB')}</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 name="amount" label={t('paymentRequest.paymentAmount')} rules={[{ required: true }]}>
<InputNumber min={0} precision={2} style={{ width: '100%' }} placeholder={t('paymentRequest.paymentAmountPlaceholder')} onChange={(value) => setWatchedAmount(value)} />
</Form.Item>
{watchedAmount && watchedCurrency !== 'CNY' && amountCNY > 0 && (
<div style={{ marginTop: -20, marginBottom: 24, color: '#888', fontSize: 13 }}>
{t('paymentRequest.equivalentCNY')}{amountCNY.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</div>
)}
<Form.Item name="reason" label={t('paymentRequest.paymentReason')} rules={[{ required: true }]}>
<TextArea rows={2} placeholder={t('paymentRequest.paymentReasonPlaceholder')} />
</Form.Item>
<Form.Item name="reason" label="付款事由" rules={[{ required: true }]}>
<TextArea rows={2} placeholder="付款原因" />
</Form.Item>
<Divider></Divider>
<Form.Item name="attachments" label="上传凭证附件">
<Divider>{t('paymentRequest.proofAttachment')}</Divider>
<Form.Item name="attachments" label={t('paymentRequest.uploadProof')}>
<FileUpload maxCount={9} accept="image/*" />
</Form.Item>
</Form>
</Modal>
<Modal title="付款申请详情" open={detailModalVisible} onCancel={() => setDetailModalVisible(false)} footer={null} width={900}>
<Modal title={t('paymentRequest.detailTitle')} 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.application_date || 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.account_name || '-'}</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 label={t('paymentRequest.applicationCode')}>{selectedRecord.request_code}</Descriptions.Item>
<Descriptions.Item label={t('common.status')}>{getStatusTag(selectedRecord.status)}</Descriptions.Item>
<Descriptions.Item label={t('paymentRequest.applicant')}>{selectedRecord.applicant}</Descriptions.Item>
<Descriptions.Item label={t('paymentRequest.applicationDate')}>{selectedRecord.application_date || selectedRecord.payment_date}</Descriptions.Item>
<Descriptions.Item label={t('paymentRequest.payeeType')}>{getPayeeTypeLabel(selectedRecord.payee_type)}</Descriptions.Item>
<Descriptions.Item label={t('paymentRequest.payee')}>{selectedRecord.payee}</Descriptions.Item>
<Descriptions.Item label={t('paymentRequest.accountName')}>{selectedRecord.account_name || '-'}</Descriptions.Item>
<Descriptions.Item label={t('paymentRequest.bankAccount')}>{selectedRecord.bank_account || '-'}</Descriptions.Item>
<Descriptions.Item label={t('paymentRequest.bankName')}>{selectedRecord.bank_name || '-'}</Descriptions.Item>
<Descriptions.Item label={t('paymentRequest.expenseType')}>
{selectedRecord.expense_type === 'company' ? t('paymentRequest.companyExpense') : t('paymentRequest.projectExpense')}
</Descriptions.Item>
{selectedRecord.expense_type === 'project' && (
<Descriptions.Item label="关联项目">
<Descriptions.Item label={t('paymentRequest.relatedProject')}>
{projects.find(p => p.id === selectedRecord.project_id)?.name || '-'}
</Descriptions.Item>
)}
<Descriptions.Item label="支出分类">
<Descriptions.Item label={t('paymentRequest.expenseCategory')}>
{getExpenseCategoryLabel(selectedRecord.expense_type, selectedRecord.expense_category)}
</Descriptions.Item>
<Descriptions.Item label="金额">
<Descriptions.Item label={t('common.amount')}>
{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.Item label={t('paymentRequest.paymentReason')} span={2}>{selectedRecord.reason}</Descriptions.Item>
</Descriptions>
{selectedRecord.qr_code && (
<>
<Divider></Divider>
<Divider>{t('paymentRequest.qrCode')}</Divider>
<Image src={selectedRecord.qr_code} width={200} style={{ borderRadius: 4 }} />
</>
)}
{selectedRecord.attachments && selectedRecord.attachments.length > 0 && (
<>
<Divider></Divider>
<Divider>{t('paymentRequest.proofAttachment')}</Divider>
<Image.PreviewGroup>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
{selectedRecord.attachments.map((url: string, index: number) => (
@@ -676,4 +708,4 @@ const PaymentRequestsPage: React.FC = () => {
);
};
export default PaymentRequestsPage;
export default PaymentRequestsPage;