Files
hfb_sys/frontend/src/features/admin/api/adminDistribution.ts
T
yml2213 4ea08e355b feat(distribution): 分销站点增加用户数量统计与用户信息展示模块
- 数据模型与迁移: 新增 000082 迁移在 users 表追加 distribution_site_id 并建索引,用户首次在分销站点注册/短信登录时自动绑定站点归属
- 分销管理接口: 新增 GET /admin/distribution/users 接口,聚合本站注册用户与下单客户,返回完整11位手机号、累计消费金额、订单数及来源标签
- 统计与大盘扩展: GET /admin/distribution/sites/:id/analytics 补全今日注册、累计用户、成交客户指标,主站分销商列表追加用户体量展示
- 分销后台前端: 新增「客户与用户」页面 (Users.tsx) 支持一键复制手机号与订单穿透,大盘 (Dashboard.tsx) 增加用户规模核心卡片
- 自动化测试与门禁: 新增 TestListDistributionUsersAndAnalytics 单元测试,通过全量 Go 后端集成测试及前后端构建检查
2026-10-10 14:58:33 +08:00

389 lines
9.7 KiB
TypeScript

import { apiClient } from '@/shared/api/client'
import type { ApiResponse } from '@/shared/types/types'
export interface DistributionSite {
id: number
site_code: string
site_name: string
domain: string
status: string
display_account_enabled: boolean
tax_enabled: boolean
tax_rate_bps: number
settlement_hold_days: number
listing_qrcode_url?: string
receive_ratio_delta?: number
receive_cost_delta?: number
admin_user_id?: number
admin_username?: string
admin_nickname?: string
member_type?: string
user_count?: number
order_user_count?: number
created_at: string
}
export interface DistributionRule {
id: number
site_id?: number
mode: string
version: number
status: string
platform_receive_ratio: number
platform_sell_ratio: number
distributor_receive_ratio: number
distributor_sell_ratio: number
distributor_a_ratio: number
distributor_b_ratio: number
service_fee_value: number
service_fee_unit: string
payment_fee_bps: number
tax_enabled: boolean
tax_rate_bps: number
effective_from: string
effective_to?: string
}
export interface DistributionOrder {
order_id: number
order_no: string
site_id: number
status: string
refund_status: string
settlement_status: string
distributor_settlement_status?: string
release_at?: string
distribution_mode: string
gross_paid_amount_cent: number
refund_amount_cent: number
net_paid_amount_cent: number
payment_fee_cent: number
service_fee_cent: number
markup_amount_cent: number
tax_amount_cent: number
split_base_amount_cent: number
rounding_adjustment_cent: number
created_at: string
}
export interface DistributionOrderPage {
items: DistributionOrder[]
total: number
page: number
page_size: number
}
export interface DistributionSettlementBatch {
id: number
site_id: number
batch_no: string
period_start: string
period_end: string
total_amount_cent: number
status: string
}
export interface DistributionSettlementItem {
id: number
order_id: number
party_role: string
amount_cent: number
status: string
}
export interface OnboardDistributionSitePayload {
site_code: string
site_name: string
domain: string
admin_username: string
admin_password: string
admin_nickname?: string
member_type?: string
display_account_enabled?: boolean
tax_enabled?: boolean
tax_rate_bps?: number
settlement_hold_days?: number
}
export interface OnboardDistributionSiteResult {
site: DistributionSite
rule: DistributionRule
member: {
admin_id: number
username: string
nickname: string
is_superadmin: boolean
site_id?: number
site_code?: string
site_name?: string
domain?: string
member_type?: string
}
}
export async function fetchDistributionSites() {
const { data } = await apiClient.get<ApiResponse<{ items: DistributionSite[] }>>(
'/admin/distribution/sites'
)
return data.data.items ?? []
}
export interface DistributionSiteDefaults {
default_settlement_hold_days: number
}
export async function fetchDistributionSiteDefaults() {
const { data } = await apiClient.get<ApiResponse<DistributionSiteDefaults>>(
'/admin/distribution/site-defaults'
)
return data.data
}
export async function onboardDistributionSite(payload: OnboardDistributionSitePayload) {
const { data } = await apiClient.post<ApiResponse<OnboardDistributionSiteResult>>(
'/admin/distribution/sites/onboard',
payload
)
return data.data
}
export async function updateDistributionSite(
id: number,
payload: Partial<
Pick<
DistributionSite,
| 'site_name'
| 'domain'
| 'status'
| 'display_account_enabled'
| 'tax_enabled'
| 'tax_rate_bps'
| 'settlement_hold_days'
| 'listing_qrcode_url'
| 'receive_ratio_delta'
| 'receive_cost_delta'
>
>
) {
const { data } = await apiClient.put<ApiResponse<DistributionSite>>(
`/admin/distribution/sites/${id}`,
payload
)
return data.data
}
export async function deleteDistributionSite(id: number) {
const { data } = await apiClient.delete<ApiResponse<{ deleted: boolean }>>(
`/admin/distribution/sites/${id}`
)
return data.data
}
export async function resetDistributionSiteAdminPassword(id: number, newPassword: string) {
const { data } = await apiClient.put<ApiResponse<{ message: string }>>(
`/admin/distribution/sites/${id}/admin-password`,
{ new_password: newPassword }
)
return data.data
}
export async function fetchDistributionRules(siteId?: number, mode?: string) {
const { data } = await apiClient.get<ApiResponse<{ items: DistributionRule[] }>>(
'/admin/distribution/rules',
{
params: { site_id: siteId || undefined, mode: mode || undefined },
}
)
return data.data.items ?? []
}
export interface CreateRulePayload {
site_id?: number
mode: string
distributor_receive_ratio?: number
platform_sell_ratio?: number
distributor_sell_ratio?: number
platform_receive_ratio?: number
distributor_a_ratio?: number
distributor_b_ratio?: number
service_fee_value: number
service_fee_unit: string
payment_fee_bps: number
tax_enabled: boolean
tax_rate_bps: number
effective_from?: string
}
export async function createDistributionRule(payload: CreateRulePayload) {
const { data } = await apiClient.post<ApiResponse<DistributionRule>>(
'/admin/distribution/rules',
payload
)
return data.data
}
export async function updateDistributionRuleStatus(id: number, status: string) {
const { data } = await apiClient.put<ApiResponse<DistributionRule>>(
`/admin/distribution/rules/${id}/status`,
{ status }
)
return data.data
}
export async function fetchDistributionOrders(params: {
kind?: string
status?: string
mode?: string
financialStatus?: string
siteID?: number
page?: number
pageSize?: number
}) {
const { data } = await apiClient.get<ApiResponse<DistributionOrderPage>>(
'/admin/distribution/orders',
{
params: {
kind: params.kind || undefined,
status: params.status || undefined,
mode: params.mode || undefined,
financial_status: params.financialStatus || undefined,
site_id: params.siteID || undefined,
page: params.page || 1,
page_size: params.pageSize || 20,
},
}
)
return data.data
}
export async function fetchDistributionSettlementBatches(siteId: number) {
const { data } = await apiClient.get<ApiResponse<{ items: DistributionSettlementBatch[] }>>(
'/admin/distribution/settlement-batches',
{
params: { site_id: siteId },
}
)
return data.data.items ?? []
}
export async function createDistributionSettlementBatch(
siteId: number,
periodStart: string,
periodEnd: string
) {
const { data } = await apiClient.post<ApiResponse<DistributionSettlementBatch>>(
'/admin/distribution/settlement-batches',
{
site_id: siteId,
period_start: periodStart,
period_end: periodEnd,
}
)
return data.data
}
export async function settleDistributionBatch(id: number) {
const { data } = await apiClient.post<ApiResponse<DistributionSettlementBatch>>(
`/admin/distribution/settlement-batches/${id}/settle`
)
return data.data
}
export async function cancelDistributionBatch(id: number) {
const { data } = await apiClient.post<ApiResponse<DistributionSettlementBatch>>(
`/admin/distribution/settlement-batches/${id}/cancel`
)
return data.data
}
export async function fetchDistributionSettlementItems(batchId: number) {
const { data } = await apiClient.get<ApiResponse<{ items: DistributionSettlementItem[] }>>(
`/admin/distribution/settlement-batches/${batchId}/items`
)
return data.data.items ?? []
}
export interface SyncSiteAccountsRequest {
listing_ids?: number[]
clean_stale_listings?: boolean
default_shelf_ratio_delta?: number
}
export interface SyncSiteAccountsResult {
synced_count: number
skipped_count: number
updated_count?: number
reenabled_count?: number
disabled_stale_count?: number
total_active: number
}
export async function syncDistributionSiteAccounts(siteId: number, req?: SyncSiteAccountsRequest) {
const { data } = await apiClient.post<ApiResponse<SyncSiteAccountsResult>>(
`/admin/distribution/sites/${siteId}/sync-accounts`,
req ?? {}
)
return data.data
}
export interface DistributionSiteSMSConfig {
enabled: boolean
provider: string
access_key_id: string
masked_secret: string
has_secret: boolean
endpoint: string
sign_name: string
login_template_code: string
}
export interface UpdateDistributionSiteSMSConfigPayload {
enabled?: boolean
provider?: string
access_key_id?: string
access_key_secret?: string
endpoint?: string
sign_name?: string
login_template_code?: string
}
export interface TestDistributionSiteSMSConfigPayload {
phone: string
enabled?: boolean
provider?: string
access_key_id?: string
access_key_secret?: string
endpoint?: string
sign_name?: string
login_template_code?: string
}
export async function fetchDistributionSiteSMSConfig(siteId: number) {
const { data } = await apiClient.get<ApiResponse<DistributionSiteSMSConfig>>(
`/admin/distribution/sites/${siteId}/sms-config`
)
return data.data
}
export async function updateDistributionSiteSMSConfig(
siteId: number,
payload: UpdateDistributionSiteSMSConfigPayload
) {
const { data } = await apiClient.put<ApiResponse<DistributionSiteSMSConfig>>(
`/admin/distribution/sites/${siteId}/sms-config`,
payload
)
return data.data
}
export async function testDistributionSiteSMSConfig(
siteId: number,
payload: TestDistributionSiteSMSConfigPayload
) {
const { data } = await apiClient.post<ApiResponse<{ message: string }>>(
`/admin/distribution/sites/${siteId}/sms-config/test`,
payload
)
return data.data
}