import { useEffect, useMemo, useState } from 'react'; import { Button, Form, Input, Modal, Popconfirm, Select, Space, Table, Tag, message, } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import { FINANCE_BANK_ACCOUNT_TYPE_LABELS, FINANCE_BANK_ACCOUNT_TYPES, type FinanceBankAccountDto, type FinanceBankAccountType, } from '@dukang/shared-types'; import { request, type Paginated } from '../lib/api'; import { ADMIN_OPTIONS_PAGE_SIZE } from '../lib/constants'; import { downloadBase64File } from '../lib/exportExcel'; import { useAdminList } from '../lib/useAdminList'; import { useAdminListColumns } from '../lib/useAdminListColumns'; import { AdminListHeader } from '../components/AdminListHeader'; type CityOption = { id: string; name: string }; type ExportResult = { filename: string; mimeType: string; contentBase64: string; count: number; }; type OtherForm = { name?: string; bankAccountName: string; bankAccountNo: string; bankBranch?: string; remark?: string; }; const TYPE_COLORS: Record = { STORE: 'blue', WINERY: 'gold', PARTNER: 'purple', LOGISTICS: 'cyan', OTHER: 'default', }; function otherNumericId(id: string) { return id.startsWith('OTHER:') ? id.slice('OTHER:'.length) : id; } export default function BankAccountsPage() { const [filterForm] = Form.useForm<{ type?: FinanceBankAccountType; cityId?: string; keyword?: string; }>(); const [otherForm] = Form.useForm(); const [remarkForm] = Form.useForm<{ remark?: string }>(); const [filters, setFilters] = useState({ type: '', cityId: '', keyword: '' }); const [cities, setCities] = useState([]); const [otherOpen, setOtherOpen] = useState(false); const [editing, setEditing] = useState(null); const [remarkTarget, setRemarkTarget] = useState(null); const [saving, setSaving] = useState(false); const [exporting, setExporting] = useState<'xlsx' | 'pdf' | null>(null); useEffect(() => { void request>(`/admin/cities?pageSize=${ADMIN_OPTIONS_PAGE_SIZE}`) .then((res) => setCities(res.items ?? [])) .catch(() => setCities([])); }, []); const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList( '/admin/finance/bank-accounts', () => { const qs = new URLSearchParams(); if (filters.type) qs.set('type', filters.type); if (filters.cityId) qs.set('cityId', filters.cityId); if (filters.keyword) qs.set('keyword', filters.keyword); return qs; }, [filters.type, filters.cityId, filters.keyword], ); function openCreate() { setEditing(null); otherForm.resetFields(); setOtherOpen(true); } function openEdit(row: FinanceBankAccountDto) { setEditing(row); otherForm.setFieldsValue({ name: row.ownerName === row.bankAccountName ? undefined : row.ownerName, bankAccountName: row.bankAccountName, bankAccountNo: row.bankAccountNo, bankBranch: row.bankBranch ?? undefined, remark: row.remark ?? undefined, }); setOtherOpen(true); } function openRemark(row: FinanceBankAccountDto) { setRemarkTarget(row); remarkForm.setFieldsValue({ remark: row.remark ?? undefined }); } async function saveOther() { const values = await otherForm.validateFields(); setSaving(true); try { if (editing) { await request(`/admin/finance/bank-accounts/other/${otherNumericId(editing.id)}`, { method: 'PUT', body: JSON.stringify(values), }); message.success('已保存'); } else { await request('/admin/finance/bank-accounts', { method: 'POST', body: JSON.stringify(values), }); message.success('已新增'); } setOtherOpen(false); await reload(); } catch (e) { message.error(e instanceof Error ? e.message : '保存失败'); } finally { setSaving(false); } } async function saveRemark() { if (!remarkTarget) return; const values = await remarkForm.validateFields(); setSaving(true); try { await request(`/admin/finance/bank-accounts/${encodeURIComponent(remarkTarget.id)}/remark`, { method: 'PUT', body: JSON.stringify({ remark: values.remark ?? '' }), }); message.success('备注已保存'); setRemarkTarget(null); await reload(); } catch (e) { message.error(e instanceof Error ? e.message : '保存失败'); } finally { setSaving(false); } } async function removeOther(row: FinanceBankAccountDto) { try { await request(`/admin/finance/bank-accounts/other/${otherNumericId(row.id)}`, { method: 'DELETE' }); message.success('已删除'); await reload(); } catch (e) { message.error(e instanceof Error ? e.message : '删除失败'); } } async function exportFile(format: 'xlsx' | 'pdf') { setExporting(format); try { const qs = new URLSearchParams(); qs.set('format', format); if (filters.type) qs.set('type', filters.type); if (filters.cityId) qs.set('cityId', filters.cityId); if (filters.keyword) qs.set('keyword', filters.keyword); const result = await request(`/admin/finance/bank-accounts/export?${qs}`); downloadBase64File(result.contentBase64, result.filename, result.mimeType); message.success(`已导出 ${result.count} 条`); } catch (e) { message.error(e instanceof Error ? e.message : '导出失败'); } finally { setExporting(null); } } const typeOptions = useMemo( () => FINANCE_BANK_ACCOUNT_TYPES.map((value) => ({ value, label: FINANCE_BANK_ACCOUNT_TYPE_LABELS[value] })), [], ); const baseColumns: ColumnsType = [ { title: '类型', dataIndex: 'type', width: 88, render: (type: FinanceBankAccountType) => ( {FINANCE_BANK_ACCOUNT_TYPE_LABELS[type]} ), }, { title: '归属', dataIndex: 'ownerName', width: 180 }, { title: '城市', dataIndex: 'cityName', width: 100, render: (v?: string | null) => v || '—' }, { title: '户名', dataIndex: 'bankAccountName', width: 140 }, { title: '银行账号', dataIndex: 'bankAccountNo', width: 180 }, { title: '开户行', dataIndex: 'bankBranch', width: 180, render: (v?: string | null) => v || '—' }, { title: '默认', dataIndex: 'isDefault', width: 72, render: (v: boolean | undefined, row) => (row.type === 'STORE' ? (v ? '是' : '否') : '—'), }, { title: '备注', dataIndex: 'remark', width: 200, render: (v?: string | null) => v || '—' }, { title: '操作', key: 'actions', width: 160, fixed: 'right', render: (_, row) => ( {row.editable ? ( <> void removeOther(row)} > ) : null} ), }, ]; const { columns, settingsButton, settingsModal } = useAdminListColumns('finance-bank-accounts', baseColumns, { page, pageSize, }); return (
{settingsModal} 新增账户 } />
{ setFilters({ type: v.type || '', cityId: v.cityId || '', keyword: v.keyword?.trim() || '', }); setPage(1); }} > ({ value: c.id, label: c.name }))} />
{ setPage(p); setPageSize(ps); }, }} /> setOtherOpen(false)} onOk={() => void saveOther()} confirmLoading={saving} destroyOnClose >
setRemarkTarget(null)} onOk={() => void saveRemark()} confirmLoading={saving} destroyOnClose >
); }