- 数据模型与迁移: 新增 000082 迁移在 users 表追加 distribution_site_id 并建索引,用户首次在分销站点注册/短信登录时自动绑定站点归属 - 分销管理接口: 新增 GET /admin/distribution/users 接口,聚合本站注册用户与下单客户,返回完整11位手机号、累计消费金额、订单数及来源标签 - 统计与大盘扩展: GET /admin/distribution/sites/:id/analytics 补全今日注册、累计用户、成交客户指标,主站分销商列表追加用户体量展示 - 分销后台前端: 新增「客户与用户」页面 (Users.tsx) 支持一键复制手机号与订单穿透,大盘 (Dashboard.tsx) 增加用户规模核心卡片 - 自动化测试与门禁: 新增 TestListDistributionUsersAndAnalytics 单元测试,通过全量 Go 后端集成测试及前后端构建检查
389 lines
9.7 KiB
TypeScript
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
|
|
}
|