4bdb09068c
C 端门店列表拼接省市区县地址;门店多银行账户与默认打款账户;子账号独立 sa_ 关联码及统计维度;同步 v4.0.18 开发文档。 Co-authored-by: Cursor <cursoragent@cursor.com>
179 lines
5.8 KiB
TypeScript
179 lines
5.8 KiB
TypeScript
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: '请填写 8~32 位数字账号' },
|
||
]}
|
||
>
|
||
<Input maxLength={32} placeholder="请输入银行账号" />
|
||
</Form.Item>
|
||
<Form.Item name="bankBranch" label="开户行名称">
|
||
<Input maxLength={128} placeholder="如 中国工商银行郑州分行" />
|
||
</Form.Item>
|
||
</Form>
|
||
</Modal>
|
||
</div>
|
||
);
|
||
}
|