251 lines
7.2 KiB
TypeScript
251 lines
7.2 KiB
TypeScript
import React, { useState, useEffect, useCallback } from 'react';
|
|
import { Table, Button, Space, Typography, message, Card, DatePicker, Select } from 'antd';
|
|
import { BankOutlined, SearchOutlined } from '@ant-design/icons';
|
|
import { queryBankTransactions, listBankAccounts } from '../api';
|
|
import { formatDate } from '../utils/formatDate';
|
|
import type { BankAccount } from '../types';
|
|
|
|
const { RangePicker } = DatePicker;
|
|
|
|
interface TransactionRecord {
|
|
counterAcctNo?: string;
|
|
cnterName?: string;
|
|
cnterBankName?: string;
|
|
acctNo?: string;
|
|
transAmt?: number | string;
|
|
dcFlag?: number;
|
|
dcFlagLabel?: string;
|
|
transTimeStr?: string;
|
|
digestCode?: string;
|
|
remark?: string;
|
|
balance?: number | string;
|
|
tellerSeqno?: string;
|
|
transferId?: string;
|
|
}
|
|
|
|
const AdminBankTransactions: React.FC = () => {
|
|
const [data, setData] = useState<TransactionRecord[]>([]);
|
|
const [total, setTotal] = useState(0);
|
|
const [loading, setLoading] = useState(false);
|
|
const [page, setPage] = useState(1);
|
|
const [pageSize, setPageSize] = useState(20);
|
|
|
|
// 银行账户
|
|
const [accounts, setAccounts] = useState<BankAccount[]>([]);
|
|
const [selectedAccountId, setSelectedAccountId] = useState<string | undefined>(undefined);
|
|
|
|
// 搜索条件
|
|
const [dateRange, setDateRange] = useState<[string | null, string | null]>([null, null]);
|
|
const [dcFlag, setDcFlag] = useState<number | undefined>(undefined);
|
|
|
|
useEffect(() => {
|
|
loadAccounts();
|
|
}, []);
|
|
|
|
const loadAccounts = async () => {
|
|
try {
|
|
const res = await listBankAccounts();
|
|
setAccounts(res.items || []);
|
|
} catch (err: any) {
|
|
message.error(err?.message || '加载银行账户失败');
|
|
}
|
|
};
|
|
|
|
const fetchData = useCallback(async () => {
|
|
if (!selectedAccountId || !dateRange[0] || !dateRange[1]) return;
|
|
setLoading(true);
|
|
try {
|
|
const res = await queryBankTransactions({
|
|
accountId: selectedAccountId,
|
|
startDate: dateRange[0],
|
|
endDate: dateRange[1],
|
|
dcFlag,
|
|
page,
|
|
pageSize,
|
|
});
|
|
setData(res.items || []);
|
|
setTotal(res.total || 0);
|
|
} catch (err: any) {
|
|
message.error(err?.message || '查询失败');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [selectedAccountId, dateRange, dcFlag, page, pageSize]);
|
|
|
|
useEffect(() => {
|
|
if (selectedAccountId && dateRange[0] && dateRange[1]) {
|
|
fetchData();
|
|
}
|
|
}, [fetchData]);
|
|
|
|
const handleSearch = () => {
|
|
if (!selectedAccountId) {
|
|
message.warning('请选择银行账户');
|
|
return;
|
|
}
|
|
if (!dateRange[0] || !dateRange[1]) {
|
|
message.warning('请选择交易时间区间');
|
|
return;
|
|
}
|
|
setPage(1);
|
|
fetchData();
|
|
};
|
|
|
|
const selectedAccount = accounts.find(a => a.id === selectedAccountId);
|
|
|
|
const columns = [
|
|
{
|
|
title: '收款账号',
|
|
dataIndex: 'counterAcctNo',
|
|
width: 180,
|
|
render: (v: string) => v || '-',
|
|
},
|
|
{
|
|
title: '收款户名',
|
|
dataIndex: 'cnterName',
|
|
width: 120,
|
|
render: (v: string) => v || '-',
|
|
},
|
|
{
|
|
title: '收款开户行',
|
|
dataIndex: 'cnterBankName',
|
|
width: 150,
|
|
render: (v: string) => v || '-',
|
|
},
|
|
{
|
|
title: '我方付款账号',
|
|
dataIndex: 'acctNo',
|
|
width: 180,
|
|
render: (v: string) => v || '-',
|
|
},
|
|
{
|
|
title: '打款金额',
|
|
dataIndex: 'transAmt',
|
|
width: 120,
|
|
align: 'right' as const,
|
|
render: (v: number | string) => v != null ? Number(v).toFixed(2) : '-',
|
|
},
|
|
{
|
|
title: '借贷方向',
|
|
dataIndex: 'dcFlagLabel',
|
|
width: 100,
|
|
render: (v: string, r: TransactionRecord) => v || (r.dcFlag === 0 ? '借/出金' : r.dcFlag === 1 ? '贷/入金' : '-'),
|
|
},
|
|
{
|
|
title: '打款时间',
|
|
dataIndex: 'transTimeStr',
|
|
width: 160,
|
|
render: (v: string) => v ? formatDate(v) : '-',
|
|
},
|
|
{
|
|
title: '摘要',
|
|
dataIndex: 'digestCode',
|
|
width: 100,
|
|
render: (v: string) => v || '-',
|
|
},
|
|
{
|
|
title: '用途/备注',
|
|
dataIndex: 'remark',
|
|
width: 150,
|
|
render: (v: string) => v || '-',
|
|
},
|
|
{
|
|
title: '账户余额',
|
|
dataIndex: 'balance',
|
|
width: 120,
|
|
align: 'right' as const,
|
|
render: (v: number | string) => v != null ? Number(v).toFixed(2) : '-',
|
|
},
|
|
{
|
|
title: '流水号',
|
|
dataIndex: 'tellerSeqno',
|
|
width: 150,
|
|
render: (v: string) => v || '-',
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div>
|
|
<Card variant="outlined" style={{ borderRadius: 12, border: '1px solid #f0f0f5', marginBottom: 16 }}>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 20 }}>
|
|
<div style={{
|
|
width: 44, height: 44, borderRadius: 10,
|
|
background: 'rgba(99,102,241,0.08)',
|
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
fontSize: 20, color: '#6366f1',
|
|
}}>
|
|
<BankOutlined />
|
|
</div>
|
|
<div>
|
|
<Typography.Title level={4} style={{ margin: 0 }}>银行交易查询</Typography.Title>
|
|
<Typography.Text type="secondary">查询银行账户交易流水记录</Typography.Text>
|
|
</div>
|
|
</div>
|
|
|
|
<Space wrap style={{ marginBottom: 16 }}>
|
|
<Select
|
|
placeholder="选择银行账户"
|
|
value={selectedAccountId}
|
|
onChange={setSelectedAccountId}
|
|
style={{ width: 260 }}
|
|
allowClear
|
|
options={accounts.map(a => ({
|
|
value: a.id,
|
|
label: `${a.bankName} - ${a.accountNo} (${a.accountName})`,
|
|
}))}
|
|
/>
|
|
<RangePicker
|
|
onChange={(dates, dateStrings) => {
|
|
if (Array.isArray(dateStrings)) {
|
|
setDateRange([dateStrings[0] || null, dateStrings[1] || null]);
|
|
}
|
|
}}
|
|
/>
|
|
<Select
|
|
placeholder="借贷方向"
|
|
value={dcFlag}
|
|
onChange={setDcFlag}
|
|
style={{ width: 140 }}
|
|
allowClear
|
|
>
|
|
<Select.Option value={0}>借/出金</Select.Option>
|
|
<Select.Option value={1}>贷/入金</Select.Option>
|
|
</Select>
|
|
<Button type="primary" icon={<SearchOutlined />} onClick={handleSearch}>
|
|
查询
|
|
</Button>
|
|
</Space>
|
|
|
|
{selectedAccount && (
|
|
<div style={{ padding: '8px 12px', background: '#f8fafc', borderRadius: 8, fontSize: 13, color: '#64748b' }}>
|
|
<strong>当前查询账户:</strong>{selectedAccount.bankName} | {selectedAccount.accountNo} | {selectedAccount.accountName}
|
|
</div>
|
|
)}
|
|
</Card>
|
|
|
|
<Card variant="outlined" style={{ borderRadius: 12, border: '1px solid #f0f0f5' }}>
|
|
<Table
|
|
rowKey={(r, i) => `${r.tellerSeqno || ''}-${r.transferId || ''}-${i}`}
|
|
columns={columns}
|
|
dataSource={data}
|
|
loading={loading}
|
|
scroll={{ x: 1400 }}
|
|
pagination={{
|
|
current: page,
|
|
pageSize,
|
|
total,
|
|
showSizeChanger: true,
|
|
showTotal: (t) => `共 ${t} 条`,
|
|
onChange: (p, ps) => {
|
|
setPage(p);
|
|
setPageSize(ps);
|
|
},
|
|
}}
|
|
/>
|
|
</Card>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default AdminBankTransactions;
|