Files
dukang/apps/admin-web/src/pages/PartnerAccountsPage.tsx
T
2026-07-01 08:27:26 +08:00

164 lines
7.7 KiB
TypeScript

import { useState } from 'react';
import {
Button, Descriptions, Drawer, Form, Input, Modal, Select, Space, Table, Tabs, Tag, Typography, message,
} from 'antd';
import type { ColumnsType } from 'antd/es/table';
import { request, type Paginated } from '../lib/api';
import { ACCOUNT_STATUS_LABELS, ORDER_STATUS_LABELS, PARTNER_BILL_STATUS_LABELS, fmtTime } from '../lib/constants';
import { useAdminList } from '../lib/useAdminList';
type Row = {
id: string; phone: string; name: string; status: string; isPrimary: number; createdAt: string;
partner?: { id: string; companyName: string };
};
type BillRow = {
id: string; billNo: string; totalAmount: number; orderCommission: number; redeemCommission: number;
status: string; periodStart: string; periodEnd: string; createdAt: string;
};
type OrderRow = {
id: string; orderNo: string; status: string; payAmount: number; createdAt: string;
user?: { userNo: string; phone: string | null };
};
type PartnerOption = { id: string; companyName: string };
type Detail = Row & { bills?: BillRow[]; orders?: OrderRow[] };
export default function PartnerAccountsPage() {
const [form] = Form.useForm();
const [createForm] = Form.useForm();
const [filters, setFilters] = useState<Record<string, string>>({});
const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList<Row>(
'/admin/partner-accounts',
() => {
const qs = new URLSearchParams();
if (filters.phone) qs.set('phone', filters.phone);
if (filters.status) qs.set('status', filters.status);
return qs;
},
[filters],
);
const [detail, setDetail] = useState<Detail | null>(null);
const [drawerOpen, setDrawerOpen] = useState(false);
const [createOpen, setCreateOpen] = useState(false);
const [partners, setPartners] = useState<PartnerOption[]>([]);
async function loadPartners() {
const res = await request<Paginated<PartnerOption>>('/admin/partners?pageSize=200');
setPartners(res.items);
}
const columns: ColumnsType<Row> = [
{ title: '姓名', dataIndex: 'name', width: 100 },
{ title: '手机', dataIndex: 'phone', width: 120 },
{ title: '开城合伙人', dataIndex: ['partner', 'companyName'], width: 140 },
{ title: '主账号', dataIndex: 'isPrimary', width: 80, render: (v) => (v === 1 ? '是' : '否') },
{ title: '状态', dataIndex: 'status', width: 80, render: (s) => <Tag>{ACCOUNT_STATUS_LABELS[s] || s}</Tag> },
{ title: '创建', dataIndex: 'createdAt', width: 160, render: fmtTime },
{
title: '操作', width: 80,
render: (_, row) => (
<Button type="link" size="small" onClick={async () => {
setDetail(await request<Detail>(`/admin/partner-accounts/${row.id}`));
setDrawerOpen(true);
}}>详情</Button>
),
},
];
const billColumns: ColumnsType<BillRow> = [
{ title: '账单号', dataIndex: 'billNo', width: 140 },
{ title: '总额', dataIndex: 'totalAmount', width: 90, render: (v) => ${v}` },
{ title: '订单佣金', dataIndex: 'orderCommission', width: 90, render: (v) => ${v}` },
{ title: '核销佣金', dataIndex: 'redeemCommission', width: 90, render: (v) => ${v}` },
{ title: '状态', dataIndex: 'status', width: 90, render: (s) => <Tag>{PARTNER_BILL_STATUS_LABELS[s] || s}</Tag> },
{ title: '周期', width: 200, render: (_, r) => `${fmtTime(r.periodStart)} ~ ${fmtTime(r.periodEnd)}` },
];
const orderColumns: ColumnsType<OrderRow> = [
{ title: '订单号', dataIndex: 'orderNo', width: 160 },
{ title: '用户', dataIndex: ['user', 'userNo'], width: 110 },
{ title: '金额', dataIndex: 'payAmount', width: 90, render: (v) => ${v}` },
{ title: '状态', dataIndex: 'status', width: 90, render: (s) => <Tag>{ORDER_STATUS_LABELS[s] || s}</Tag> },
{ title: '下单', dataIndex: 'createdAt', width: 160, render: fmtTime },
];
return (
<div>
<Space style={{ marginBottom: 16, width: '100%', justifyContent: 'space-between' }}>
<Typography.Title level={4} style={{ margin: 0 }}>开城合伙人账户</Typography.Title>
<Button type="primary" onClick={() => { void loadPartners(); setCreateOpen(true); }}>新建账户</Button>
</Space>
<Form form={form} layout="inline" style={{ marginBottom: 16 }} onFinish={(v) => { setFilters(v); setPage(1); }}>
<Form.Item name="phone" label="手机"><Input allowClear /></Form.Item>
<Form.Item name="status" label="状态">
<Select allowClear style={{ width: 100 }} options={Object.entries(ACCOUNT_STATUS_LABELS).map(([value, label]) => ({ value, label }))} />
</Form.Item>
<Form.Item><Button type="primary" htmlType="submit">查询</Button></Form.Item>
</Form>
<Table rowKey="id" className="admin-table-nowrap" loading={loading} columns={columns} dataSource={data?.items ?? []} scroll={{ x: 900 }}
pagination={{ current: page, pageSize, total: data?.total ?? 0, showSizeChanger: true, onChange: (p, ps) => { setPage(p); setPageSize(ps); } }} />
<Drawer title="开城合伙人账户" width={720} open={drawerOpen} onClose={() => setDrawerOpen(false)}
extra={detail && (
<Select defaultValue={detail.status} style={{ width: 100 }}
options={Object.entries(ACCOUNT_STATUS_LABELS).map(([value, label]) => ({ value, label }))}
onChange={async (status) => {
await request(`/admin/partner-accounts/${detail.id}`, { method: 'PUT', body: JSON.stringify({ status }) });
message.success('已更新');
void reload();
}} />
)}>
{detail && (
<Tabs items={[
{
key: 'info',
label: '基本信息',
children: (
<Descriptions column={1} bordered size="small">
<Descriptions.Item label="姓名">{detail.name}</Descriptions.Item>
<Descriptions.Item label="手机">{detail.phone}</Descriptions.Item>
<Descriptions.Item label="开城合伙人">{detail.partner?.companyName}</Descriptions.Item>
</Descriptions>
),
},
{
key: 'bills',
label: `账单 (${detail.bills?.length ?? 0})`,
children: (
<Table rowKey="id" className="admin-table-nowrap" size="small" columns={billColumns}
dataSource={detail.bills ?? []} pagination={false} scroll={{ x: 700 }} />
),
},
{
key: 'orders',
label: `名下订单 (${detail.orders?.length ?? 0})`,
children: (
<Table rowKey="id" className="admin-table-nowrap" size="small" columns={orderColumns}
dataSource={detail.orders ?? []} pagination={false} scroll={{ x: 650 }} />
),
},
]} />
)}
</Drawer>
<Modal title="新建开城合伙人账户" open={createOpen} onCancel={() => setCreateOpen(false)} onOk={async () => {
const v = await createForm.validateFields();
await request('/admin/partner-accounts', { method: 'POST', body: JSON.stringify(v) });
message.success('已创建');
setCreateOpen(false);
createForm.resetFields();
void reload();
}}>
<Form form={createForm} layout="vertical">
<Form.Item name="partnerId" label="开城合伙人" rules={[{ required: true }]}>
<Select showSearch optionFilterProp="label" options={partners.map((p) => ({ value: p.id, label: p.companyName }))} />
</Form.Item>
<Form.Item name="name" label="姓名" rules={[{ required: true }]}><Input /></Form.Item>
<Form.Item name="phone" label="手机" rules={[{ required: true }]}><Input /></Form.Item>
</Form>
</Modal>
</div>
);
}