309 lines
9.0 KiB
TypeScript
309 lines
9.0 KiB
TypeScript
import React, { useRef, useState } from 'react';
|
|
import {
|
|
ActionType,
|
|
DrawerForm,
|
|
PageContainer,
|
|
ProColumns,
|
|
ProFormSelect,
|
|
ProTable,
|
|
} from '@ant-design/pro-components';
|
|
import { App, Button, Tag, Drawer, List } from 'antd';
|
|
import dayjs from 'dayjs';
|
|
import { request } from 'umi';
|
|
import {
|
|
subscriptioncontrollerList,
|
|
subscriptioncontrollerSync,
|
|
} from '@/servers/api/subscription';
|
|
import { sitecontrollerAll } from '@/servers/api/site';
|
|
|
|
/**
|
|
* 订阅状态枚举(用于筛选与展示)
|
|
* 保持与后端同步的原始状态值
|
|
*/
|
|
const SUBSCRIPTION_STATUS_ENUM: Record<string, { text: string }> = {
|
|
active: { text: '激活' },
|
|
cancelled: { text: '已取消' },
|
|
expired: { text: '已过期' },
|
|
pending: { text: '待处理' },
|
|
'on-hold': { text: '暂停' },
|
|
};
|
|
|
|
/**
|
|
* 订阅列表页:展示、筛选、触发订阅同步
|
|
*/
|
|
const ListPage: React.FC = () => {
|
|
// 表格操作引用:用于在同步后触发表格刷新
|
|
const actionRef = useRef<ActionType>();
|
|
const { message } = App.useApp();
|
|
|
|
// 关联订单抽屉状态
|
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
const [drawerTitle, setDrawerTitle] = useState('详情');
|
|
const [relatedOrders, setRelatedOrders] = useState<any[]>([]);
|
|
|
|
// 表格列定义(尽量与项目风格保持一致)
|
|
const columns: ProColumns<API.Subscription>[] = [
|
|
{
|
|
title: '站点',
|
|
dataIndex: 'siteId',
|
|
valueType: 'select',
|
|
// 动态加载站点选项
|
|
request: async () => {
|
|
const { data = [] } = await sitecontrollerAll();
|
|
return data.map((item) => ({
|
|
label: item.siteName,
|
|
value: item.id,
|
|
}));
|
|
},
|
|
render: (_, row) => row?.siteId ?? '-',
|
|
},
|
|
{
|
|
title: '订阅ID',
|
|
dataIndex: 'externalSubscriptionId',
|
|
hideInSearch: true,
|
|
width: 120,
|
|
},
|
|
{
|
|
title: '父订单号',
|
|
dataIndex: 'parent_id',
|
|
hideInSearch: true,
|
|
width: 120,
|
|
render: (_, row) => (row?.parent_id ? <Tag>{row.parent_id}</Tag> : '-'),
|
|
},
|
|
{
|
|
title: '状态',
|
|
dataIndex: 'status',
|
|
valueType: 'select',
|
|
valueEnum: SUBSCRIPTION_STATUS_ENUM,
|
|
// 以 Tag 形式展示,更易辨识
|
|
render: (_, row) =>
|
|
row?.status ? <Tag>{SUBSCRIPTION_STATUS_ENUM[row.status]?.text || row.status}</Tag> : '-',
|
|
width: 120,
|
|
},
|
|
{
|
|
title: '客户邮箱',
|
|
dataIndex: 'customer_email',
|
|
width: 180,
|
|
},
|
|
{
|
|
title: '金额',
|
|
dataIndex: 'total',
|
|
hideInSearch: true,
|
|
width: 100,
|
|
},
|
|
{
|
|
title: '币种',
|
|
dataIndex: 'currency',
|
|
hideInSearch: true,
|
|
width: 80,
|
|
},
|
|
{
|
|
title: '开始时间',
|
|
dataIndex: 'start_date',
|
|
hideInSearch: true,
|
|
width: 160,
|
|
},
|
|
{
|
|
title: '下次支付',
|
|
dataIndex: 'next_payment_date',
|
|
hideInSearch: true,
|
|
width: 160,
|
|
},
|
|
{
|
|
title: '结束时间',
|
|
dataIndex: 'end_date',
|
|
hideInSearch: true,
|
|
width: 160,
|
|
},
|
|
{
|
|
title: '创建时间',
|
|
dataIndex: 'createdAt',
|
|
valueType: 'dateTime',
|
|
hideInSearch: true,
|
|
width: 160,
|
|
},
|
|
{
|
|
title: '更新时间',
|
|
dataIndex: 'updatedAt',
|
|
valueType: 'dateTime',
|
|
hideInSearch: true,
|
|
width: 160,
|
|
},
|
|
{
|
|
title: '操作',
|
|
dataIndex: 'actions',
|
|
hideInSearch: true,
|
|
width: 120,
|
|
render: (_, row) => (
|
|
<Button
|
|
size="small"
|
|
onClick={async () => {
|
|
try {
|
|
const parentNumber = String(row?.parent_id || '');
|
|
if (!parentNumber) {
|
|
message.warning('该订阅缺少父订单号');
|
|
return;
|
|
}
|
|
// 通过父订单号查询关联订单(模糊匹配)
|
|
const resp = await request('/order/getOrderByNumber', {
|
|
method: 'POST',
|
|
data: { number: parentNumber },
|
|
});
|
|
const { success, data, message: errMsg } = resp as any;
|
|
if (!success) throw new Error(errMsg || '获取失败');
|
|
// 仅保留与父订单号完全一致的订单(避免模糊匹配误入)
|
|
const candidates: any[] = (Array.isArray(data) ? data : []).filter(
|
|
(c: any) => String(c?.externalOrderId) === parentNumber
|
|
);
|
|
// 拉取详情,补充状态、金额、时间
|
|
const details = [] as any[];
|
|
for (const c of candidates) {
|
|
const d = await request(`/order/${c.id}`, { method: 'GET' });
|
|
if ((d as any)?.success) {
|
|
const od = (d as any)?.data || {};
|
|
details.push({
|
|
id: c.id,
|
|
externalOrderId: c.externalOrderId,
|
|
siteName: c.siteName,
|
|
status: od?.status,
|
|
total: od?.total,
|
|
currency_symbol: od?.currency_symbol,
|
|
date_created: od?.date_created,
|
|
relationship: 'Parent Order',
|
|
});
|
|
} else {
|
|
details.push({
|
|
id: c.id,
|
|
externalOrderId: c.externalOrderId,
|
|
siteName: c.siteName,
|
|
relationship: 'Parent Order',
|
|
});
|
|
}
|
|
}
|
|
setRelatedOrders(details);
|
|
setDrawerTitle(`详情`);
|
|
setDrawerOpen(true);
|
|
} catch (e: any) {
|
|
message.error(e?.message || '获取失败');
|
|
}
|
|
}}
|
|
>
|
|
查看详情
|
|
</Button>
|
|
),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<PageContainer header={{ title: '订阅列表' }}>
|
|
<ProTable<API.Subscription>
|
|
headerTitle="查询表格"
|
|
rowKey="id"
|
|
actionRef={actionRef}
|
|
/**
|
|
* 列表数据请求;保持与后端分页参数一致
|
|
* 兼容后端 data.items 或 data.list 返回字段
|
|
*/
|
|
request={async (params) => {
|
|
const { data, success } = await subscriptioncontrollerList(params);
|
|
return {
|
|
total: data?.total || 0,
|
|
data: data?.items || data?.list || [],
|
|
success,
|
|
};
|
|
}}
|
|
columns={columns}
|
|
// 工具栏:订阅同步入口
|
|
toolBarRender={() => [<SyncForm key="sync" tableRef={actionRef} />]}
|
|
/>
|
|
{/* 关联订单抽屉:展示订单号、关系、时间、状态与金额 */}
|
|
<Drawer
|
|
open={drawerOpen}
|
|
title={drawerTitle}
|
|
width={720}
|
|
onClose={() => setDrawerOpen(false)}
|
|
>
|
|
<List
|
|
header={<div>关联订单</div>}
|
|
dataSource={relatedOrders}
|
|
renderItem={(item: any) => (
|
|
<List.Item>
|
|
<List.Item.Meta
|
|
title={`#${item?.externalOrderId || '-'}`}
|
|
description={`关系:${item?.relationship || '-'},站点:${item?.siteName || '-'}`}
|
|
/>
|
|
<div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
|
|
<span>{item?.date_created ? dayjs(item.date_created).format('YYYY-MM-DD HH:mm') : '-'}</span>
|
|
<Tag>{item?.status || '-'}</Tag>
|
|
<span>
|
|
{item?.currency_symbol || ''}
|
|
{typeof item?.total === 'number' ? item.total.toFixed(2) : item?.total ?? '-'}
|
|
</span>
|
|
</div>
|
|
</List.Item>
|
|
)}
|
|
/>
|
|
</Drawer>
|
|
</PageContainer>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* 同步订阅抽屉表单:选择站点后触发同步
|
|
*/
|
|
const SyncForm: React.FC<{
|
|
tableRef: React.MutableRefObject<ActionType | undefined>;
|
|
}> = ({ tableRef }) => {
|
|
const { message } = App.useApp();
|
|
|
|
return (
|
|
<DrawerForm<API.subscriptioncontrollerSyncParams>
|
|
title="同步订阅"
|
|
trigger={
|
|
<Button key="syncSite" type="primary">
|
|
同步订阅
|
|
</Button>
|
|
}
|
|
autoFocusFirstInput
|
|
drawerProps={{ destroyOnHidden: true }}
|
|
/**
|
|
* 提交逻辑:
|
|
* 1. 必填校验由 ProForm + rules 保证
|
|
* 2. 调用同步接口,失败时友好提示
|
|
* 3. 成功后刷新列表
|
|
*/
|
|
onFinish={async (values) => {
|
|
try {
|
|
const { success, message: errMsg } = await subscriptioncontrollerSync(values);
|
|
if (!success) {
|
|
throw new Error(errMsg);
|
|
}
|
|
message.success('同步成功');
|
|
tableRef.current?.reload();
|
|
return true;
|
|
} catch (error: any) {
|
|
message.error(error.message || '同步失败');
|
|
}
|
|
}}
|
|
>
|
|
<ProFormSelect
|
|
name="siteId"
|
|
width="lg"
|
|
label="站点"
|
|
placeholder="请选择站点"
|
|
// 动态加载站点选项
|
|
request={async () => {
|
|
const { data = [] } = await sitecontrollerAll();
|
|
return data.map((item) => ({
|
|
label: item.siteName,
|
|
value: item.id,
|
|
}));
|
|
}}
|
|
rules={[{ required: true, message: '请选择站点' }]}
|
|
/>
|
|
</DrawerForm>
|
|
);
|
|
};
|
|
|
|
export default ListPage;
|