feat(admin): 新增邀请管理页
This commit is contained in:
@@ -0,0 +1,472 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { keepPreviousData, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import {
|
||||
Alert,
|
||||
App,
|
||||
Button,
|
||||
Card,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Segmented,
|
||||
Space,
|
||||
Statistic,
|
||||
Switch,
|
||||
Tag,
|
||||
Typography,
|
||||
type TableColumnsType,
|
||||
} from 'antd'
|
||||
import { ReloadOutlined } from '@ant-design/icons'
|
||||
|
||||
import {
|
||||
fetchAdminInvitationConfig,
|
||||
fetchAdminInvitationRelations,
|
||||
fetchAdminInviteRebates,
|
||||
fetchAdminInviteRebateSummary,
|
||||
updateAdminInvitationConfig,
|
||||
} from '@/features/admin/api'
|
||||
import { AdminPageHeader } from '@/features/admin/components/AdminPageHeader'
|
||||
import { DataTable } from '@/features/admin/components/DataTable'
|
||||
import { FilterBar, FilterField } from '@/features/admin/components/FilterBar'
|
||||
import type {
|
||||
AdminInvitationRelation,
|
||||
AdminInviteRebate,
|
||||
} from '@/features/admin/types'
|
||||
import { useAdminSessionStore } from '@/stores/adminSession'
|
||||
import { formatDateTime, formatMoney, formatText } from '@/shared/utils/format'
|
||||
|
||||
const statusMeta: Record<string, { label: string; color: string }> = {
|
||||
pending: { label: '待实名', color: 'gold' },
|
||||
active: { label: '已生效', color: 'green' },
|
||||
}
|
||||
|
||||
function errorText(error: unknown) {
|
||||
const response = (error as { response?: { data?: { message?: string } } })?.response
|
||||
return response?.data?.message || (error instanceof Error ? error.message : '请求失败')
|
||||
}
|
||||
|
||||
const relationColumns: TableColumnsType<AdminInvitationRelation> = [
|
||||
{
|
||||
title: '邀请人',
|
||||
key: 'inviter',
|
||||
width: 200,
|
||||
render: (_, row) => (
|
||||
<div className="admin-stack">
|
||||
<strong>{formatText(row.inviter_nickname)}</strong>
|
||||
<span className="admin-mono">{formatText(row.inviter_phone)}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '被邀请人',
|
||||
key: 'invitee',
|
||||
width: 200,
|
||||
render: (_, row) => (
|
||||
<div className="admin-stack">
|
||||
<strong>{formatText(row.invitee_nickname)}</strong>
|
||||
<span className="admin-mono">{formatText(row.invitee_phone)}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '邀请码',
|
||||
dataIndex: 'invite_code',
|
||||
width: 140,
|
||||
render: value => <span className="admin-mono">{formatText(value)}</span>,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
width: 110,
|
||||
render: value => {
|
||||
const meta = statusMeta[value] ?? { label: value || '—', color: 'default' }
|
||||
return (
|
||||
<Tag bordered={false} color={meta.color}>
|
||||
{meta.label}
|
||||
</Tag>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '实名时间',
|
||||
dataIndex: 'verified_at',
|
||||
width: 170,
|
||||
render: value => (value ? formatDateTime(value) : '—'),
|
||||
},
|
||||
{
|
||||
title: '绑定时间',
|
||||
dataIndex: 'created_at',
|
||||
width: 170,
|
||||
render: value => formatDateTime(value),
|
||||
},
|
||||
]
|
||||
|
||||
const rebateColumns: TableColumnsType<AdminInviteRebate> = [
|
||||
{
|
||||
title: '订单',
|
||||
dataIndex: 'order_no',
|
||||
width: 200,
|
||||
render: value => <span className="admin-mono">{formatText(value)}</span>,
|
||||
},
|
||||
{
|
||||
title: '被邀请人',
|
||||
key: 'invitee',
|
||||
width: 180,
|
||||
render: (_, row) => (
|
||||
<div className="admin-stack">
|
||||
<span>{formatText(row.invitee_nickname)}</span>
|
||||
<span className="admin-mono">{formatText(row.invitee_masked_phone)}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '角色 / 周期',
|
||||
key: 'role',
|
||||
width: 160,
|
||||
render: (_, row) => (
|
||||
<Space size={4} wrap>
|
||||
<Tag bordered={false}>{row.trade_role === 'buyer' ? '买家' : '卖家'}</Tag>
|
||||
<Tag bordered={false}>{formatText(row.cycle_stage)}</Tag>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '基数',
|
||||
dataIndex: 'single_side_basis_cent',
|
||||
width: 130,
|
||||
align: 'right',
|
||||
render: value => formatMoney(value),
|
||||
},
|
||||
{
|
||||
title: '比例',
|
||||
dataIndex: 'rate_applied',
|
||||
width: 90,
|
||||
align: 'right',
|
||||
render: value => `${(Number(value) * 100).toFixed(1)}%`,
|
||||
},
|
||||
{
|
||||
title: '返利',
|
||||
dataIndex: 'rebate_amount_cent',
|
||||
width: 130,
|
||||
align: 'right',
|
||||
render: value => <strong>{formatMoney(value)}</strong>,
|
||||
},
|
||||
{
|
||||
title: '结算时间',
|
||||
dataIndex: 'settled_at',
|
||||
width: 170,
|
||||
render: value => formatDateTime(value),
|
||||
},
|
||||
]
|
||||
|
||||
export function AdminInvitationsPage() {
|
||||
const { message } = App.useApp()
|
||||
const queryClient = useQueryClient()
|
||||
const [configForm] = Form.useForm<{
|
||||
first_month_rate: number
|
||||
second_month_rate: number
|
||||
subsequent_rate: number
|
||||
enabled: boolean
|
||||
}>()
|
||||
const [tab, setTab] = useState<'relations' | 'rebates'>('relations')
|
||||
const [relFilters, setRelFilters] = useState({ phone: '', invite_code: '' })
|
||||
const [relApplied, setRelApplied] = useState({ phone: '', invite_code: '' })
|
||||
const [relPage, setRelPage] = useState(1)
|
||||
const [rebateFilters, setRebateFilters] = useState({ order_no: '', inviter_phone: '', invitee_phone: '' })
|
||||
const [rebateApplied, setRebateApplied] = useState(rebateFilters)
|
||||
const [rebatePage, setRebatePage] = useState(1)
|
||||
const [pageSize] = useState(20)
|
||||
const [saving, setSaving] = useState(false)
|
||||
|
||||
const permissions = useAdminSessionStore(state => state.admin?.permissions)
|
||||
const canView = Boolean(
|
||||
permissions?.includes('*') ||
|
||||
permissions?.includes('distribution:site_view') ||
|
||||
permissions?.includes('distribution:settlement_view')
|
||||
)
|
||||
const canManage = Boolean(permissions?.includes('*') || permissions?.includes('distribution:site_manage'))
|
||||
|
||||
const configQuery = useQuery({
|
||||
queryKey: ['admin-invitation-config'],
|
||||
queryFn: () => fetchAdminInvitationConfig(),
|
||||
enabled: canView,
|
||||
})
|
||||
const relationsQuery = useQuery({
|
||||
queryKey: ['admin-invitation-relations', relApplied, relPage],
|
||||
queryFn: () =>
|
||||
fetchAdminInvitationRelations({
|
||||
phone: relApplied.phone.trim() || undefined,
|
||||
invite_code: relApplied.invite_code.trim() || undefined,
|
||||
page: relPage,
|
||||
page_size: pageSize,
|
||||
}),
|
||||
enabled: canView && tab === 'relations',
|
||||
placeholderData: keepPreviousData,
|
||||
})
|
||||
const rebatesQuery = useQuery({
|
||||
queryKey: ['admin-invite-rebates', rebateApplied, rebatePage],
|
||||
queryFn: () =>
|
||||
fetchAdminInviteRebates({
|
||||
order_no: rebateApplied.order_no.trim() || undefined,
|
||||
inviter_phone: rebateApplied.inviter_phone.trim() || undefined,
|
||||
invitee_phone: rebateApplied.invitee_phone.trim() || undefined,
|
||||
page: rebatePage,
|
||||
page_size: pageSize,
|
||||
}),
|
||||
enabled: canView && tab === 'rebates',
|
||||
placeholderData: keepPreviousData,
|
||||
})
|
||||
const summaryQuery = useQuery({
|
||||
queryKey: ['admin-invite-rebate-summary', rebateApplied],
|
||||
queryFn: () =>
|
||||
fetchAdminInviteRebateSummary({
|
||||
order_no: rebateApplied.order_no.trim() || undefined,
|
||||
inviter_phone: rebateApplied.inviter_phone.trim() || undefined,
|
||||
invitee_phone: rebateApplied.invitee_phone.trim() || undefined,
|
||||
}),
|
||||
enabled: canView && tab === 'rebates',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!configQuery.data) return
|
||||
configForm.setFieldsValue({
|
||||
first_month_rate: Number((configQuery.data.first_month_rate * 100).toFixed(2)),
|
||||
second_month_rate: Number((configQuery.data.second_month_rate * 100).toFixed(2)),
|
||||
subsequent_rate: Number((configQuery.data.subsequent_rate * 100).toFixed(2)),
|
||||
enabled: configQuery.data.enabled,
|
||||
})
|
||||
}, [configQuery.data, configForm])
|
||||
|
||||
if (!canView) {
|
||||
return (
|
||||
<div className="admin-page">
|
||||
<AdminPageHeader eyebrow="JIEYOU OPS · 运营" title="邀请管理" />
|
||||
<Alert type="warning" showIcon message="当前账号没有邀请数据查看权限" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const summary = summaryQuery.data
|
||||
|
||||
return (
|
||||
<div className="admin-page">
|
||||
<AdminPageHeader
|
||||
eyebrow="JIEYOU OPS · 运营"
|
||||
title="邀请管理"
|
||||
description="维护邀请返利比例,查看邀请关系与返利流水;比例按百分比录入,提交时转 0–1 小数。"
|
||||
meta={
|
||||
<Space size={8} wrap>
|
||||
<Tag color="geekblue">返利总额 {formatMoney(summary?.total_rebate_cent ?? 0)}</Tag>
|
||||
<Tag>明细 {summary?.ledger_count ?? 0} 条</Tag>
|
||||
</Space>
|
||||
}
|
||||
extra={
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
loading={relationsQuery.isFetching || rebatesQuery.isFetching}
|
||||
onClick={() => {
|
||||
void relationsQuery.refetch()
|
||||
void rebatesQuery.refetch()
|
||||
void summaryQuery.refetch()
|
||||
}}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
<Card title="返利比例配置" className="admin-table-card" style={{ marginBottom: 16 }}>
|
||||
<Form form={configForm} layout="inline" style={{ gap: 12, flexWrap: 'wrap' }}>
|
||||
<Form.Item name="first_month_rate" label="首月(%)">
|
||||
<InputNumber min={0} max={100} precision={2} style={{ width: 120 }} disabled={!canManage} />
|
||||
</Form.Item>
|
||||
<Form.Item name="second_month_rate" label="次月(%)">
|
||||
<InputNumber min={0} max={100} precision={2} style={{ width: 120 }} disabled={!canManage} />
|
||||
</Form.Item>
|
||||
<Form.Item name="subsequent_rate" label="后续(%)">
|
||||
<InputNumber min={0} max={100} precision={2} style={{ width: 120 }} disabled={!canManage} />
|
||||
</Form.Item>
|
||||
<Form.Item name="enabled" label="启用" valuePropName="checked">
|
||||
<Switch disabled={!canManage} />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="primary"
|
||||
loading={saving}
|
||||
disabled={!canManage}
|
||||
onClick={async () => {
|
||||
const values = await configForm.validateFields()
|
||||
setSaving(true)
|
||||
try {
|
||||
await updateAdminInvitationConfig({
|
||||
first_month_rate: values.first_month_rate / 100,
|
||||
second_month_rate: values.second_month_rate / 100,
|
||||
subsequent_rate: values.subsequent_rate / 100,
|
||||
enabled: values.enabled,
|
||||
})
|
||||
message.success('返利比例已更新')
|
||||
await queryClient.invalidateQueries({ queryKey: ['admin-invitation-config'] })
|
||||
} catch (error) {
|
||||
message.error(errorText(error))
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}}
|
||||
>
|
||||
保存配置
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<Typography.Paragraph type="secondary" style={{ margin: '8px 0 0' }}>
|
||||
最近更新:{formatDateTime(configQuery.data?.updated_at)}
|
||||
</Typography.Paragraph>
|
||||
</Card>
|
||||
|
||||
<Space style={{ marginBottom: 12 }}>
|
||||
<Segmented
|
||||
value={tab}
|
||||
options={[
|
||||
{ value: 'relations', label: '邀请关系' },
|
||||
{ value: 'rebates', label: '返利流水' },
|
||||
]}
|
||||
onChange={value => setTab(value as 'relations' | 'rebates')}
|
||||
/>
|
||||
</Space>
|
||||
|
||||
{tab === 'relations' ? (
|
||||
<>
|
||||
<FilterBar
|
||||
loading={relationsQuery.isFetching}
|
||||
onSearch={() => {
|
||||
setRelPage(1)
|
||||
setRelApplied(relFilters)
|
||||
}}
|
||||
onReset={() => {
|
||||
setRelFilters({ phone: '', invite_code: '' })
|
||||
setRelApplied({ phone: '', invite_code: '' })
|
||||
setRelPage(1)
|
||||
}}
|
||||
>
|
||||
<FilterField label="手机号">
|
||||
<Input
|
||||
allowClear
|
||||
value={relFilters.phone}
|
||||
placeholder="邀请人/被邀请人"
|
||||
onChange={event =>
|
||||
setRelFilters(current => ({ ...current, phone: event.target.value }))
|
||||
}
|
||||
/>
|
||||
</FilterField>
|
||||
<FilterField label="邀请码">
|
||||
<Input
|
||||
allowClear
|
||||
value={relFilters.invite_code}
|
||||
onChange={event =>
|
||||
setRelFilters(current => ({ ...current, invite_code: event.target.value }))
|
||||
}
|
||||
/>
|
||||
</FilterField>
|
||||
</FilterBar>
|
||||
|
||||
<DataTable<AdminInvitationRelation>
|
||||
columns={relationColumns}
|
||||
dataSource={relationsQuery.data?.items ?? []}
|
||||
rowKey="id"
|
||||
loading={relationsQuery.isFetching}
|
||||
total={relationsQuery.data?.total ?? 0}
|
||||
page={relationsQuery.data?.page ?? relPage}
|
||||
pageSize={relationsQuery.data?.page_size ?? pageSize}
|
||||
onPageChange={nextPage => setRelPage(nextPage)}
|
||||
emptyDescription="还没有邀请关系"
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="wallet-metrics" style={{ marginBottom: 12 }}>
|
||||
<div className="wallet-metric">
|
||||
<span>返利总额</span>
|
||||
<strong>{formatMoney(summary?.total_rebate_cent ?? 0)}</strong>
|
||||
<small>明细 {summary?.ledger_count ?? 0} 条</small>
|
||||
</div>
|
||||
<div className="wallet-metric">
|
||||
<span>邀请人 / 被邀请人</span>
|
||||
<strong>
|
||||
{summary?.inviter_count ?? 0} / {summary?.invitee_count ?? 0}
|
||||
</strong>
|
||||
<small>去重统计</small>
|
||||
</div>
|
||||
<div className="wallet-metric">
|
||||
<span>订单 / 提号返利</span>
|
||||
<strong>{formatMoney(summary?.order_rebate_cent ?? 0)}</strong>
|
||||
<small>提号 {formatMoney(summary?.pickup_rebate_cent ?? 0)}</small>
|
||||
</div>
|
||||
<div className="wallet-metric">
|
||||
<span>首月 / 次月 / 后续</span>
|
||||
<strong>{formatMoney(summary?.first_month_cent ?? 0)}</strong>
|
||||
<small>
|
||||
次月 {formatMoney(summary?.second_month_cent ?? 0)} · 后续{' '}
|
||||
{formatMoney(summary?.subsequent_cent ?? 0)}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FilterBar
|
||||
loading={rebatesQuery.isFetching}
|
||||
onSearch={() => {
|
||||
setRebatePage(1)
|
||||
setRebateApplied(rebateFilters)
|
||||
}}
|
||||
onReset={() => {
|
||||
const empty = { order_no: '', inviter_phone: '', invitee_phone: '' }
|
||||
setRebateFilters(empty)
|
||||
setRebateApplied(empty)
|
||||
setRebatePage(1)
|
||||
}}
|
||||
>
|
||||
<FilterField label="订单号">
|
||||
<Input
|
||||
allowClear
|
||||
value={rebateFilters.order_no}
|
||||
onChange={event =>
|
||||
setRebateFilters(current => ({ ...current, order_no: event.target.value }))
|
||||
}
|
||||
/>
|
||||
</FilterField>
|
||||
<FilterField label="邀请人手机">
|
||||
<Input
|
||||
allowClear
|
||||
value={rebateFilters.inviter_phone}
|
||||
onChange={event =>
|
||||
setRebateFilters(current => ({ ...current, inviter_phone: event.target.value }))
|
||||
}
|
||||
/>
|
||||
</FilterField>
|
||||
<FilterField label="被邀请人手机">
|
||||
<Input
|
||||
allowClear
|
||||
value={rebateFilters.invitee_phone}
|
||||
onChange={event =>
|
||||
setRebateFilters(current => ({ ...current, invitee_phone: event.target.value }))
|
||||
}
|
||||
/>
|
||||
</FilterField>
|
||||
</FilterBar>
|
||||
|
||||
<DataTable<AdminInviteRebate>
|
||||
columns={rebateColumns}
|
||||
dataSource={rebatesQuery.data?.items ?? []}
|
||||
rowKey="id"
|
||||
loading={rebatesQuery.isFetching}
|
||||
total={rebatesQuery.data?.total ?? 0}
|
||||
page={rebatesQuery.data?.page ?? rebatePage}
|
||||
pageSize={rebatesQuery.data?.page_size ?? pageSize}
|
||||
onPageChange={nextPage => setRebatePage(nextPage)}
|
||||
emptyDescription="还没有返利流水"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Statistic style={{ display: 'none' }} value={0} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -123,6 +123,10 @@ import type {
|
||||
AdminNotificationPage,
|
||||
AdminPushChannel,
|
||||
AdminPushRule,
|
||||
AdminInvitationConfig,
|
||||
AdminInvitationRelationPage,
|
||||
AdminInviteRebatePage,
|
||||
AdminInviteRebateSummary,
|
||||
ManualDisbursementPayload,
|
||||
OperatingExpensePage,
|
||||
OperatingExpensePayload,
|
||||
@@ -1796,3 +1800,81 @@ export async function updatePushRule(
|
||||
|
||||
|
||||
|
||||
|
||||
export async function fetchAdminInvitationConfig(siteId?: number) {
|
||||
const { data } = await adminClient.get<ApiResponse<AdminInvitationConfig>>(
|
||||
'/admin/invitations/configs',
|
||||
{ params: siteId ? { site_id: siteId } : undefined }
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function updateAdminInvitationConfig(payload: {
|
||||
first_month_rate: number
|
||||
second_month_rate: number
|
||||
subsequent_rate: number
|
||||
enabled: boolean
|
||||
site_id?: number
|
||||
}) {
|
||||
const { data } = await adminClient.put<ApiResponse<AdminInvitationConfig>>(
|
||||
'/admin/invitations/configs',
|
||||
payload
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function fetchAdminInvitationRelations(query: {
|
||||
phone?: string
|
||||
invite_code?: string
|
||||
status?: string
|
||||
page?: number
|
||||
page_size?: number
|
||||
}) {
|
||||
const { data } = await adminClient.get<ApiResponse<Partial<AdminInvitationRelationPage>>>(
|
||||
'/admin/invitations',
|
||||
{ params: compactParams(query) }
|
||||
)
|
||||
return {
|
||||
items: data.data.items ?? [],
|
||||
total: data.data.total ?? 0,
|
||||
page: data.data.page ?? query.page ?? 1,
|
||||
page_size: data.data.page_size ?? query.page_size ?? 20,
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchAdminInviteRebates(query: {
|
||||
order_no?: string
|
||||
inviter_phone?: string
|
||||
invitee_phone?: string
|
||||
trade_role?: string
|
||||
start_date?: string
|
||||
end_date?: string
|
||||
page?: number
|
||||
page_size?: number
|
||||
}) {
|
||||
const { data } = await adminClient.get<ApiResponse<Partial<AdminInviteRebatePage>>>(
|
||||
'/admin/invitations/rebates',
|
||||
{ params: compactParams(query) }
|
||||
)
|
||||
return {
|
||||
items: data.data.items ?? [],
|
||||
total: data.data.total ?? 0,
|
||||
page: data.data.page ?? query.page ?? 1,
|
||||
page_size: data.data.page_size ?? query.page_size ?? 20,
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchAdminInviteRebateSummary(query: {
|
||||
order_no?: string
|
||||
inviter_phone?: string
|
||||
invitee_phone?: string
|
||||
trade_role?: string
|
||||
start_date?: string
|
||||
end_date?: string
|
||||
}) {
|
||||
const { data } = await adminClient.get<ApiResponse<AdminInviteRebateSummary>>(
|
||||
'/admin/invitations/rebates/summary',
|
||||
{ params: compactParams(query) }
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
@@ -913,6 +913,77 @@ export interface AdminPushRule {
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface AdminInvitationConfig {
|
||||
distribution_site_id: number
|
||||
first_month_rate: number
|
||||
second_month_rate: number
|
||||
subsequent_rate: number
|
||||
enabled: boolean
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface AdminInvitationRelation {
|
||||
id: number
|
||||
inviter_id: number
|
||||
inviter_phone: string
|
||||
inviter_nickname: string
|
||||
invitee_id: number
|
||||
invitee_phone: string
|
||||
invitee_nickname: string
|
||||
distribution_site_id?: number | null
|
||||
invite_code: string
|
||||
status: string
|
||||
verified_at?: string | null
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface AdminInvitationRelationPage {
|
||||
items: AdminInvitationRelation[]
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
}
|
||||
|
||||
export interface AdminInviteRebate {
|
||||
id: number
|
||||
order_id: number
|
||||
order_no: string
|
||||
invitee_masked_phone: string
|
||||
invitee_nickname: string
|
||||
trade_role: string
|
||||
single_side_basis_cent: number
|
||||
rate_applied: number
|
||||
cycle_stage: string
|
||||
rebate_amount_cent: number
|
||||
settled_at: string
|
||||
}
|
||||
|
||||
export interface AdminInviteRebatePage {
|
||||
items: AdminInviteRebate[]
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
}
|
||||
|
||||
export interface AdminInviteRebateSummary {
|
||||
total_rebate_cent: number
|
||||
ledger_count: number
|
||||
inviter_count: number
|
||||
invitee_count: number
|
||||
avg_rebate_cent: number
|
||||
order_rebate_cent: number
|
||||
order_count: number
|
||||
pickup_rebate_cent: number
|
||||
pickup_count: number
|
||||
buyer_rebate_cent: number
|
||||
buyer_count: number
|
||||
seller_rebate_cent: number
|
||||
seller_count: number
|
||||
first_month_cent: number
|
||||
second_month_cent: number
|
||||
subsequent_cent: number
|
||||
}
|
||||
|
||||
export interface AdminAnnouncementQuery {
|
||||
category?: string
|
||||
status?: string
|
||||
|
||||
Reference in New Issue
Block a user