383 lines
14 KiB
TypeScript
383 lines
14 KiB
TypeScript
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;
|
||
}
|