2026-07-14 20:23:03 +08:00

383 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { apiRequest } from "@/shared/api/request";
import { API_ENDPOINTS, API_OPERATIONS, apiEndpointPath } from "@/shared/api/generated/endpoints";
import type {
ApiPage,
CoinAdjustmentCreateDto,
CoinAdjustmentDto,
CoinAdjustmentPayload,
CoinLedgerEntryDto,
CoinLedgerUserDto,
CoinSellerLedgerDto,
PageQuery,
ReportDto,
} from "@/shared/api/types";
export interface GiftDiamondRatioItem {
effectiveRegionId: number;
giftTypeCode: string;
ratioPercent: string;
returnCoinRatioPercent: string;
regionId: number;
status: string;
updatedAtMs?: number;
}
export interface GiftRecordUserDto {
userId: string;
displayUserId?: string;
defaultDisplayUserId?: string;
prettyDisplayUserId?: string;
prettyId?: string;
username?: string;
avatar?: string;
}
export interface GiftRecordDto {
transactionId: string;
scene: "room" | "direct";
roomId?: string;
sender: GiftRecordUserDto;
receiver: GiftRecordUserDto;
gift: {
giftId: string;
name: string;
coverUrl?: string;
};
giftCount: number;
unitValue: number;
giftValue: number;
createdAtMs: number;
}
export interface GiftDiamondRatioResponse {
regionId: number;
items: GiftDiamondRatioItem[];
}
export type WheelRewardType = "coin" | "dress" | "gift" | "prop";
export interface WheelTierDto {
tierId: string;
displayName: string;
rewardType: WheelRewardType;
rewardId: string;
rewardCount: number;
rewardCoins: number;
rtpValueCoins: number;
probabilityPercent: number;
enabled: boolean;
metadataJson: string;
}
export interface WheelConfigDto {
appCode?: string;
wheelId: string;
enabled: boolean;
ruleVersion: number;
drawPriceCoins: number;
targetRTPPercent: number;
poolRatePercent: number;
settlementWindowDraws: number;
initialPoolCoins: number;
poolReserveCoins: number;
maxSingleRTPPayout: number;
effectiveFromMs: number;
createdByAdminId?: number;
createdAtMs?: number;
tiers: WheelTierDto[];
}
export interface WheelConfigPayload {
wheel_id: string;
enabled: boolean;
draw_price_coins: number;
target_rtp_percent: number;
pool_rate_percent: number;
settlement_window_draws: number;
initial_pool_coins: number;
pool_reserve_coins: number;
max_single_rtp_payout: number;
effective_from_ms: number;
tiers: Array<{
tier_id: string;
display_name: string;
reward_type: WheelRewardType;
reward_id: string;
reward_count: number;
reward_coins: number;
rtp_value_coins: number;
probability_percent: number;
enabled: boolean;
metadata_json: string;
}>;
}
export interface WheelDrawDto {
drawId: string;
drawIds: string[];
commandId: string;
wheelId: string;
ruleVersion: number;
selectedTierId: string;
rewardType: string;
rewardId: string;
rewardCount: number;
rewardCoins: number;
rtpValueCoins: number;
rewardStatus: string;
rtpWindowIndex: number;
actualRTPPPM: number;
createdAtMs: number;
walletTransactionId: string;
coinBalanceAfter: number;
metadataJson: string;
}
export interface WheelDrawSummaryDto {
wheelId: string;
totalDraws: number;
uniqueUsers: number;
totalSpentCoins: number;
totalRTPValueCoins: number;
actualRTPPPM: number;
pendingDraws: number;
grantedDraws: number;
failedDraws: number;
}
type RawWheelConfig = WheelConfigDto & Record<string, unknown>;
type RawWheelTier = WheelTierDto & Record<string, unknown>;
type RawWheelDraw = WheelDrawDto & Record<string, unknown>;
type RawWheelSummary = WheelDrawSummaryDto & Record<string, unknown>;
type WheelSummaryQuery = Record<string, number | string | undefined>;
export function listCoinLedger(query: PageQuery = {}): Promise<ApiPage<CoinLedgerEntryDto>> {
const endpoint = API_ENDPOINTS.listCoinLedger;
return apiRequest<ApiPage<CoinLedgerEntryDto>>(apiEndpointPath(API_OPERATIONS.listCoinLedger), {
method: endpoint.method,
query,
});
}
export function listGiftRecords(query: PageQuery = {}): Promise<ApiPage<GiftRecordDto>> {
const endpoint = API_ENDPOINTS.listGiftRecords;
return apiRequest<ApiPage<GiftRecordDto>>(apiEndpointPath(API_OPERATIONS.listGiftRecords), {
method: endpoint.method,
query,
});
}
export function listCoinSellerLedger(query: PageQuery = {}): Promise<ApiPage<CoinSellerLedgerDto>> {
const endpoint = API_ENDPOINTS.listCoinSellerLedger;
return apiRequest<ApiPage<CoinSellerLedgerDto>>(apiEndpointPath(API_OPERATIONS.listCoinSellerLedger), {
method: endpoint.method,
query,
});
}
export function exportCoinSellerLedger(query: PageQuery = {}): Promise<Response> {
const endpoint = API_ENDPOINTS.exportCoinSellerLedger;
return apiRequest(apiEndpointPath(API_OPERATIONS.exportCoinSellerLedger), {
method: endpoint.method,
query,
raw: true,
});
}
export function listCoinAdjustments(query: PageQuery = {}): Promise<ApiPage<CoinAdjustmentDto>> {
const endpoint = API_ENDPOINTS.listCoinAdjustments;
return apiRequest<ApiPage<CoinAdjustmentDto>>(apiEndpointPath(API_OPERATIONS.listCoinAdjustments), {
method: endpoint.method,
query,
});
}
export function listReports(query: PageQuery = {}): Promise<ApiPage<ReportDto>> {
const endpoint = API_ENDPOINTS.listReports;
return apiRequest<ApiPage<ReportDto>>(apiEndpointPath(API_OPERATIONS.listReports), {
method: endpoint.method,
query,
});
}
export function lookupCoinAdjustmentTarget(userId: string): Promise<CoinLedgerUserDto> {
const endpoint = API_ENDPOINTS.lookupCoinAdjustmentTarget;
return apiRequest<CoinLedgerUserDto>(apiEndpointPath(API_OPERATIONS.lookupCoinAdjustmentTarget), {
method: endpoint.method,
query: { user_id: userId },
});
}
export function createCoinAdjustment(payload: CoinAdjustmentPayload): Promise<CoinAdjustmentCreateDto> {
const endpoint = API_ENDPOINTS.createCoinAdjustment;
return apiRequest<CoinAdjustmentCreateDto, CoinAdjustmentPayload>(
apiEndpointPath(API_OPERATIONS.createCoinAdjustment),
{
body: payload,
method: endpoint.method,
},
);
}
export function getGiftDiamondRatios(regionId: string | number): Promise<GiftDiamondRatioResponse> {
return apiRequest<GiftDiamondRatioResponse>("/v1/admin/operations/gift-diamond-ratios", {
method: "GET",
query: { region_id: regionId },
});
}
export function updateGiftDiamondRatios(payload: {
regionId: number;
ratios: Record<string, string>;
returnCoinRatios: Record<string, string>;
}): Promise<GiftDiamondRatioResponse> {
return apiRequest<GiftDiamondRatioResponse, typeof payload>("/v1/admin/operations/gift-diamond-ratios", {
body: payload,
method: "PUT",
});
}
export function getWheelConfig(wheelId = "default"): Promise<WheelConfigDto> {
const endpoint = API_ENDPOINTS.getWheelConfig;
return apiRequest<RawWheelConfig>(apiEndpointPath(API_OPERATIONS.getWheelConfig), {
method: endpoint.method,
query: { wheel_id: wheelId },
}).then(normalizeWheelConfig);
}
export function updateWheelConfig(payload: WheelConfigPayload): Promise<WheelConfigDto> {
const endpoint = API_ENDPOINTS.upsertWheelConfig;
return apiRequest<RawWheelConfig, WheelConfigPayload>(apiEndpointPath(API_OPERATIONS.upsertWheelConfig), {
body: payload,
method: endpoint.method,
query: { wheel_id: payload.wheel_id },
}).then(normalizeWheelConfig);
}
export function listWheelDraws(query: PageQuery = {}): Promise<ApiPage<WheelDrawDto>> {
const endpoint = API_ENDPOINTS.listWheelDraws;
return apiRequest<ApiPage<RawWheelDraw>>(apiEndpointPath(API_OPERATIONS.listWheelDraws), {
method: endpoint.method,
query,
}).then((page) => ({
...page,
items: (page.items || []).map(normalizeWheelDraw),
}));
}
export function getWheelDrawSummary(query: WheelSummaryQuery = {}): Promise<WheelDrawSummaryDto> {
const endpoint = API_ENDPOINTS.getWheelDrawSummary;
return apiRequest<RawWheelSummary>(apiEndpointPath(API_OPERATIONS.getWheelDrawSummary), {
method: endpoint.method,
query,
}).then(normalizeWheelSummary);
}
function normalizeWheelConfig(item: RawWheelConfig): WheelConfigDto {
// 后端为了兼容 Go DTO 使用 snake_case页面统一消费 camelCase
// 避免表单、表格、保存 payload 混用两套字段名导致配置保存缺字段。
return {
appCode: stringValue(item.appCode ?? item.app_code),
createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms),
createdByAdminId: numberValue(item.createdByAdminId ?? item.created_by_admin_id),
drawPriceCoins: numberValue(item.drawPriceCoins ?? item.draw_price_coins),
effectiveFromMs: numberValue(item.effectiveFromMs ?? item.effective_from_ms),
enabled: booleanValue(item.enabled),
initialPoolCoins: numberValue(item.initialPoolCoins ?? item.initial_pool_coins),
maxSingleRTPPayout: numberValue(item.maxSingleRTPPayout ?? item.max_single_rtp_payout),
poolRatePercent: numberValue(item.poolRatePercent ?? item.pool_rate_percent),
poolReserveCoins: numberValue(item.poolReserveCoins ?? item.pool_reserve_coins),
ruleVersion: numberValue(item.ruleVersion ?? item.rule_version),
settlementWindowDraws: numberValue(item.settlementWindowDraws ?? item.settlement_window_draws),
targetRTPPercent: numberValue(item.targetRTPPercent ?? item.target_rtp_percent),
tiers: arrayValue(item.tiers).map(normalizeWheelTier),
wheelId: stringValue(item.wheelId ?? item.wheel_id),
};
}
function normalizeWheelTier(raw: unknown): WheelTierDto {
const item = asRecord(raw) as RawWheelTier;
const rewardType = normalizeRewardType(item.rewardType ?? item.reward_type);
return {
displayName: stringValue(item.displayName ?? item.display_name),
enabled: booleanValue(item.enabled),
metadataJson: stringValue(item.metadataJson ?? item.metadata_json) || "{}",
probabilityPercent: numberValue(item.probabilityPercent ?? item.probability_percent),
rewardCoins: numberValue(item.rewardCoins ?? item.reward_coins),
rewardCount: numberValue(item.rewardCount ?? item.reward_count),
rewardId: stringValue(item.rewardId ?? item.reward_id),
rewardType,
rtpValueCoins:
rewardType === "prop" || rewardType === "dress"
? 0
: numberValue(item.rtpValueCoins ?? item.rtp_value_coins),
tierId: stringValue(item.tierId ?? item.tier_id) || rewardType,
};
}
function normalizeWheelDraw(item: RawWheelDraw): WheelDrawDto {
return {
actualRTPPPM: numberValue(item.actualRTPPPM ?? item.actual_rtp_ppm),
coinBalanceAfter: numberValue(item.coinBalanceAfter ?? item.coin_balance_after),
commandId: stringValue(item.commandId ?? item.command_id),
createdAtMs: numberValue(item.createdAtMs ?? item.created_at_ms),
drawId: stringValue(item.drawId ?? item.draw_id),
drawIds: arrayValue(item.drawIds ?? item.draw_ids).map(stringValue),
metadataJson: stringValue(item.metadataJson ?? item.metadata_json) || "{}",
rewardCoins: numberValue(item.rewardCoins ?? item.reward_coins),
rewardCount: numberValue(item.rewardCount ?? item.reward_count),
rewardId: stringValue(item.rewardId ?? item.reward_id),
rewardStatus: stringValue(item.rewardStatus ?? item.reward_status),
rewardType: stringValue(item.rewardType ?? item.reward_type),
rtpValueCoins: numberValue(item.rtpValueCoins ?? item.rtp_value_coins),
rtpWindowIndex: numberValue(item.rtpWindowIndex ?? item.rtp_window_index),
ruleVersion: numberValue(item.ruleVersion ?? item.rule_version),
selectedTierId: stringValue(item.selectedTierId ?? item.selected_tier_id),
walletTransactionId: stringValue(item.walletTransactionId ?? item.wallet_transaction_id),
wheelId: stringValue(item.wheelId ?? item.wheel_id),
};
}
function normalizeWheelSummary(item: RawWheelSummary): WheelDrawSummaryDto {
return {
actualRTPPPM: numberValue(item.actualRTPPPM ?? item.actual_rtp_ppm),
failedDraws: numberValue(item.failedDraws ?? item.failed_draws),
grantedDraws: numberValue(item.grantedDraws ?? item.granted_draws),
pendingDraws: numberValue(item.pendingDraws ?? item.pending_draws),
totalDraws: numberValue(item.totalDraws ?? item.total_draws),
totalRTPValueCoins: numberValue(item.totalRTPValueCoins ?? item.total_rtp_value_coins),
totalSpentCoins: numberValue(item.totalSpentCoins ?? item.total_spent_coins),
uniqueUsers: numberValue(item.uniqueUsers ?? item.unique_users),
wheelId: stringValue(item.wheelId ?? item.wheel_id),
};
}
function asRecord(value: unknown): Record<string, unknown> {
return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : {};
}
function arrayValue(value: unknown): unknown[] {
return Array.isArray(value) ? value : [];
}
function booleanValue(value: unknown) {
return value === true || value === "true" || value === 1 || value === "1";
}
function normalizeRewardType(value: unknown): WheelRewardType {
const normalized = stringValue(value).trim().toLowerCase();
if (normalized === "dress" || normalized === "gift" || normalized === "prop") {
return normalized;
}
return "coin";
}
function stringValue(value: unknown) {
return typeof value === "string" ? value : value === undefined || value === null ? "" : String(value);
}
function numberValue(value: unknown) {
const number = Number(value || 0);
return Number.isFinite(number) ? number : 0;
}