Files
dukang/apps/admin-web/src/components/StoreBankAccountsPanel.tsx
T
jacy 4bdb09068c feat(store): v4.0.18 门店多收款账户与子账号继承二维码
C 端门店列表拼接省市区县地址;门店多银行账户与默认打款账户;子账号独立 sa_ 关联码及统计维度;同步 v4.0.18 开发文档。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 15:56:16 +08:00

179 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useCallback, useEffect, useState } from 'react';
import { Button, Form, Input, Modal, Popconfirm, Space, Table, Tag, message } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import type { StoreBankAccountDto } from '@dukang/shared-types';
import { request } from '../lib/api';
type Props = { storeId: string };
type FormValues = { bankAccountName: string; bankAccountNo: string; bankBranch?: string };
export default function StoreBankAccountsPanel({ storeId }: Props) {
const [form] = Form.useForm<FormValues>();
const [items, setItems] = useState<StoreBankAccountDto[]>([]);
const [loading, setLoading] = useState(false);
const [editingId, setEditingId] = useState<string | null>(null);
const [modalOpen, setModalOpen] = useState(false);
const [submitting, setSubmitting] = useState(false);
const load = useCallback(async () => {
setLoading(true);
try {
const res = await request<StoreBankAccountDto[]>(`/admin/stores/${storeId}/bank-accounts`);
setItems(res ?? []);
} catch (e) {
message.error(e instanceof Error ? e.message : '加载失败');
} finally {
setLoading(false);
}
}, [storeId]);
useEffect(() => {
void load();
}, [load]);
function openCreate() {
setEditingId(null);
form.resetFields();
setModalOpen(true);
}
function openEdit(item: StoreBankAccountDto) {
setEditingId(item.id);
form.setFieldsValue({
bankAccountName: item.bankAccountName,
bankAccountNo: item.bankAccountNo,
bankBranch: item.bankBranch ?? '',
});
setModalOpen(true);
}
async function submit() {
const v = await form.validateFields();
setSubmitting(true);
try {
const body = JSON.stringify({
bankAccountName: v.bankAccountName.trim(),
bankAccountNo: v.bankAccountNo.replace(/\s+/g, ''),
bankBranch: v.bankBranch?.trim() || undefined,
});
if (editingId) {
await request(`/admin/stores/${storeId}/bank-accounts/${editingId}`, { method: 'PUT', body });
} else {
await request(`/admin/stores/${storeId}/bank-accounts`, { method: 'POST', body });
}
message.success('已保存');
setModalOpen(false);
void load();
} catch (e) {
message.error(e instanceof Error ? e.message : '保存失败');
} finally {
setSubmitting(false);
}
}
async function setDefault(id: string) {
try {
await request(`/admin/stores/${storeId}/bank-accounts/${id}/default`, { method: 'POST' });
message.success('已设为默认打款账户');
void load();
} catch (e) {
message.error(e instanceof Error ? e.message : '设置失败');
}
}
async function remove(id: string) {
try {
await request(`/admin/stores/${storeId}/bank-accounts/${id}`, { method: 'DELETE' });
message.success('已删除');
void load();
} catch (e) {
message.error(e instanceof Error ? e.message : '删除失败');
}
}
const columns: ColumnsType<StoreBankAccountDto> = [
{ title: '收款人', dataIndex: 'bankAccountName', width: 140 },
{ title: '银行账号', dataIndex: 'bankAccountNo', width: 180 },
{ title: '开户行', dataIndex: 'bankBranch', render: (v) => v || '—' },
{
title: '默认',
dataIndex: 'isDefault',
width: 80,
render: (v) => (v ? <Tag color="green">默认</Tag> : null),
},
{
title: '操作',
width: 220,
render: (_, row) => (
<Space size={0} wrap>
<Button type="link" size="small" onClick={() => openEdit(row)}>编辑</Button>
{!row.isDefault ? (
<Button type="link" size="small" onClick={() => void setDefault(row.id)}>设为默认</Button>
) : null}
{!row.isDefault ? (
<Popconfirm
title="确认删除该收款账户?"
okText="删除"
okButtonProps={{ danger: true }}
onConfirm={() => void remove(row.id)}
>
<Button type="link" size="small" danger>删除</Button>
</Popconfirm>
) : null}
</Space>
),
},
];
return (
<div>
<Space style={{ marginBottom: 12, width: '100%', justifyContent: 'space-between' }}>
<span style={{ color: '#888' }}>
每门店可维护多个收款账户,标记「默认」的账户用于核销结算与提现打款;切换即时生效、无需重启服务。
</span>
<Button type="primary" size="small" onClick={openCreate}>新增账户</Button>
</Space>
<Table
rowKey="id"
size="small"
loading={loading}
columns={columns}
dataSource={items}
pagination={false}
/>
<Modal
title={editingId ? '编辑收款账户' : '新增收款账户'}
open={modalOpen}
confirmLoading={submitting}
onCancel={() => setModalOpen(false)}
onOk={() => void submit()}
okText="保存"
>
<Form form={form} layout="vertical">
<Form.Item
name="bankAccountName"
label="收款人"
rules={[{ required: true, message: '请填写收款人' }]}
>
<Input maxLength={64} placeholder="户名 / 收款人姓名" />
</Form.Item>
<Form.Item
name="bankAccountNo"
label="银行账号"
rules={[
{ required: true, message: '请填写银行账号' },
{ pattern: /^\d{8,32}$/, message: '请填写 832 位数字账号' },
]}
>
<Input maxLength={32} placeholder="请输入银行账号" />
</Form.Item>
<Form.Item name="bankBranch" label="开户行名称">
<Input maxLength={128} placeholder="如 中国工商银行郑州分行" />
</Form.Item>
</Form>
</Modal>
</div>
);
}