Files
video-gen/video-gen-admin/src/pages/AdminBankTransactions.tsx
T
2026-08-14 14:03:36 +08:00

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;