import { apiClient } from '@/shared/api/client' import type { ApiResponse } from '@/shared/types/types' export type ChargeMode = '赠送' | '收费' export type QuantityKey = string export type ScreenshotKey = string export type SkinCategoryKey = string export interface PublishOptionGroup { key: SkinCategoryKey | string title: string options: string[] } export interface PublishQuantityItem { key: QuantityKey label: string price: string placeholder?: string } export interface PublishScreenshotSlot { key: ScreenshotKey label: string required: boolean hint: string } export interface PublishPriceConfig { deposit_placeholder: string deposit_hint: string price_placeholder: string ratio_description: string } export interface PublishDepositSkinGroupRule { group_key: string label: string amount_per_item: number } export interface PublishDepositRecommendConfig { base_amount: number skin_group_rules: PublishDepositSkinGroupRule[] } export interface PublishInsuranceBaseRatio { insurance: string ratio: number } export interface PublishRatioConfigItem { key: string label: string kind: string group_key?: string missing_penalty: number } export interface PublishCoinCorrection { threshold_m: number correction: number } export interface PublishSaleFixedMarkupRule { min_m: number max_m: number markup_amount: number } export interface PublishSaleRatioAdjustmentRule { min_m: number max_m: number ratio_subtract: number } export interface PublishRatioConfig { insurance_base_ratios: PublishInsuranceBaseRatio[] config_items: PublishRatioConfigItem[] coin_corrections: PublishCoinCorrection[] } export interface PublishSalePriceConfig { fixed_markup_rules: PublishSaleFixedMarkupRule[] ratio_adjustment_rules: PublishSaleRatioAdjustmentRule[] } export interface AgreementContent { title: string content: string } export interface ListingPublishAgreements { virtual_asset_sale: AgreementContent seller_agreement: AgreementContent } export interface ListingPublishOptions { server_options: string[] face_options: string[] rank_options: string[] insurance_options: string[] level_options: string[] login_method_options: string[] region_options: string[] skin_groups: PublishOptionGroup[] quantity_items: PublishQuantityItem[] screenshot_slots: PublishScreenshotSlot[] ban_record_options: string[] ban_evidence_options: string[] fire_level_min: number price_config: PublishPriceConfig deposit_recommend_config: PublishDepositRecommendConfig ratio_config: PublishRatioConfig } export const emptyListingPublishOptions: ListingPublishOptions = { server_options: [], face_options: [], rank_options: [], insurance_options: [], level_options: [], login_method_options: [], region_options: [], skin_groups: [], quantity_items: [], screenshot_slots: [], ban_record_options: [], ban_evidence_options: [], fire_level_min: 38, price_config: { deposit_placeholder: '', deposit_hint: '租客缴纳,封号必赔付', price_placeholder: '', ratio_description: '', }, deposit_recommend_config: { base_amount: 50, skin_group_rules: [ { group_key: 'melee', label: '刀皮', amount_per_item: 5 }, { group_key: 'operatorGold', label: '干员金皮', amount_per_item: 10 }, { group_key: 'operatorRed', label: '干员红皮', amount_per_item: 30 }, ], }, ratio_config: { insurance_base_ratios: [], config_items: [], coin_corrections: [], }, } export const emptyListingSalePriceConfig: PublishSalePriceConfig = { fixed_markup_rules: [ { min_m: 10, max_m: 30, markup_amount: 28 }, { min_m: 30, max_m: 50, markup_amount: 31 }, { min_m: 50, max_m: 70, markup_amount: 34 }, { min_m: 70, max_m: 90, markup_amount: 38 }, ], ratio_adjustment_rules: [ { min_m: 90, max_m: 150, ratio_subtract: 5 }, { min_m: 150, max_m: 230, ratio_subtract: 4 }, { min_m: 230, max_m: 310, ratio_subtract: 3.5 }, { min_m: 310, max_m: 390, ratio_subtract: 3 }, { min_m: 390, max_m: 470, ratio_subtract: 0 }, { min_m: 470, max_m: 550, ratio_subtract: 0 }, { min_m: 550, max_m: 0, ratio_subtract: 0 }, ], } export const emptyListingPublishAgreements: ListingPublishAgreements = { virtual_asset_sale: { title: '虚拟资产出售协议', content: '', }, seller_agreement: { title: '号主协议', content: '', }, } export async function fetchListingPublishOptions() { const { data } = await apiClient.get>('/listing-publish-options') return mergeListingPublishOptions(data.data) } export async function fetchListingPublishAgreements() { const { data } = await apiClient.get>('/listing-publish-agreements') return mergeListingPublishAgreements(data.data) } export async function fetchListingSalePriceConfig() { const { data } = await apiClient.get>('/listing-sale-price-config') return mergeListingSalePriceConfig(data.data) } export function mergeListingPublishOptions(options?: Partial): ListingPublishOptions { return { server_options: normalizeStringList(options?.server_options), face_options: normalizeStringList(options?.face_options), rank_options: normalizeStringList(options?.rank_options), insurance_options: normalizeStringList(options?.insurance_options), level_options: normalizeStringList(options?.level_options), login_method_options: normalizeStringList(options?.login_method_options), region_options: normalizeStringList(options?.region_options), skin_groups: normalizeOptionGroups(options?.skin_groups), quantity_items: normalizeQuantityItems(options?.quantity_items), screenshot_slots: normalizeScreenshotSlots(options?.screenshot_slots), ban_record_options: normalizeStringList(options?.ban_record_options), ban_evidence_options: normalizeStringList(options?.ban_evidence_options), fire_level_min: readPositiveInteger(options?.fire_level_min, 38), price_config: normalizePriceConfig(options?.price_config), deposit_recommend_config: normalizeDepositRecommendConfig(options?.deposit_recommend_config), ratio_config: normalizeRatioConfig(options?.ratio_config), } } export function mergeListingSalePriceConfig(options?: Partial): PublishSalePriceConfig { return normalizeSalePriceConfig(options) } export function mergeListingPublishAgreements(options?: Partial): ListingPublishAgreements { return { virtual_asset_sale: normalizeAgreementContent(options?.virtual_asset_sale, emptyListingPublishAgreements.virtual_asset_sale), seller_agreement: normalizeAgreementContent(options?.seller_agreement, emptyListingPublishAgreements.seller_agreement), } } function normalizeAgreementContent(value: unknown, fallback: AgreementContent): AgreementContent { const row = isRecord(value) ? value : {} const title = typeof row.title === 'string' && row.title.trim() ? row.title.trim() : fallback.title const content = typeof row.content === 'string' && row.content.trim() ? row.content.trim() : fallback.content return { title, content } } function normalizeStringList(values?: unknown[]) { return Array.isArray(values) ? values.map((item) => (typeof item === 'string' ? item.trim() : '')).filter(Boolean) : [] } function normalizeOptionGroups(values?: unknown[]): PublishOptionGroup[] { if (!Array.isArray(values)) return [] return values .map((item) => { const group = isRecord(item) ? item : {} return { key: typeof group.key === 'string' ? group.key.trim() : '', title: typeof group.title === 'string' ? group.title.trim() : '', options: normalizeStringList(Array.isArray(group.options) ? group.options : []), } }) .filter((item) => item.key && item.title) } function normalizeQuantityItems(values?: unknown[]): PublishQuantityItem[] { if (!Array.isArray(values)) return [] return values .map((item) => { const row = isRecord(item) ? item : {} return { key: typeof row.key === 'string' ? row.key.trim() : '', label: typeof row.label === 'string' ? row.label.trim() : '', price: typeof row.price === 'string' ? row.price.trim() : '', placeholder: typeof row.placeholder === 'string' ? row.placeholder.trim() : undefined, } }) .filter((item) => item.key && item.label) } function normalizeScreenshotSlots(values?: unknown[]): PublishScreenshotSlot[] { if (!Array.isArray(values)) return [] return values .map((item) => { const row = isRecord(item) ? item : {} return { key: typeof row.key === 'string' ? row.key.trim() : '', label: typeof row.label === 'string' ? row.label.trim() : '', required: row.required === true, hint: typeof row.hint === 'string' ? row.hint.trim() : '', } }) .filter((item) => item.key && item.label) } function normalizePriceConfig(value?: unknown): PublishPriceConfig { const row = isRecord(value) ? value : {} return { deposit_placeholder: typeof row.deposit_placeholder === 'string' ? row.deposit_placeholder.trim() : '', deposit_hint: typeof row.deposit_hint === 'string' && row.deposit_hint.trim() ? row.deposit_hint.trim() : emptyListingPublishOptions.price_config.deposit_hint, price_placeholder: typeof row.price_placeholder === 'string' ? row.price_placeholder.trim() : '', ratio_description: typeof row.ratio_description === 'string' ? row.ratio_description.trim() : '', } } function normalizeDepositRecommendConfig(value?: unknown): PublishDepositRecommendConfig { const row = isRecord(value) ? value : {} const config = { base_amount: readNumber(row.base_amount), skin_group_rules: normalizeDepositSkinGroupRules( Array.isArray(row.skin_group_rules) ? row.skin_group_rules : [], ), } if (config.base_amount <= 0) { config.base_amount = emptyListingPublishOptions.deposit_recommend_config.base_amount } if (config.skin_group_rules.length === 0) { config.skin_group_rules = [...emptyListingPublishOptions.deposit_recommend_config.skin_group_rules] } return config } function normalizeDepositSkinGroupRules(values: unknown[]): PublishDepositSkinGroupRule[] { return values .map((item) => { const row = isRecord(item) ? item : {} return { group_key: typeof row.group_key === 'string' ? row.group_key.trim() : '', label: typeof row.label === 'string' ? row.label.trim() : '', amount_per_item: readNumber(row.amount_per_item), } }) .filter((item) => item.group_key && item.label && item.amount_per_item >= 0) } function normalizeRatioConfig(value?: unknown): PublishRatioConfig { const row = isRecord(value) ? value : {} return { insurance_base_ratios: normalizeInsuranceBaseRatios( Array.isArray(row.insurance_base_ratios) ? row.insurance_base_ratios : [], ), config_items: normalizeRatioConfigItems( Array.isArray(row.config_items) ? row.config_items : [], ), coin_corrections: normalizeCoinCorrections( Array.isArray(row.coin_corrections) ? row.coin_corrections : [], ), } } function normalizeSalePriceConfig(value?: unknown): PublishSalePriceConfig { const row = isRecord(value) ? value : {} const config = { fixed_markup_rules: normalizeSaleFixedMarkupRules( Array.isArray(row.fixed_markup_rules) ? row.fixed_markup_rules : [], ), ratio_adjustment_rules: normalizeSaleRatioAdjustmentRules( Array.isArray(row.ratio_adjustment_rules) ? row.ratio_adjustment_rules : [], ), } if (config.fixed_markup_rules.length === 0 && config.ratio_adjustment_rules.length === 0) { return JSON.parse(JSON.stringify(emptyListingSalePriceConfig)) as PublishSalePriceConfig } return config } function normalizeInsuranceBaseRatios(values: unknown[]): PublishInsuranceBaseRatio[] { return values .map((item) => { const row = isRecord(item) ? item : {} return { insurance: typeof row.insurance === 'string' ? row.insurance.trim() : '', ratio: readNumber(row.ratio), } }) .filter((item) => item.insurance && item.ratio > 0) } function normalizeRatioConfigItems(values: unknown[]): PublishRatioConfigItem[] { return values .map((item) => { const row = isRecord(item) ? item : {} return { key: typeof row.key === 'string' ? row.key.trim() : '', label: typeof row.label === 'string' ? row.label.trim() : '', kind: typeof row.kind === 'string' ? row.kind.trim() : '', group_key: typeof row.group_key === 'string' ? row.group_key.trim() : '', missing_penalty: readNumber(row.missing_penalty), } }) .filter((item) => item.key && item.label && item.kind) } function normalizeCoinCorrections(values: unknown[]): PublishCoinCorrection[] { return values .map((item) => { const row = isRecord(item) ? item : {} return { threshold_m: readNumber(row.threshold_m), correction: readNumber(row.correction), } }) .filter((item) => item.threshold_m >= 0 && item.correction > 0) } function normalizeSaleFixedMarkupRules(values: unknown[]): PublishSaleFixedMarkupRule[] { return values .map((item) => { const row = isRecord(item) ? item : {} return { min_m: readNumber(row.min_m), max_m: readNumber(row.max_m), markup_amount: readNumber(row.markup_amount), } }) .filter((item) => item.min_m >= 0 && item.max_m >= item.min_m && item.markup_amount >= 0) } function normalizeSaleRatioAdjustmentRules(values: unknown[]): PublishSaleRatioAdjustmentRule[] { return values .map((item) => { const row = isRecord(item) ? item : {} return { min_m: readNumber(row.min_m), max_m: readNumber(row.max_m), ratio_subtract: readNumber(row.ratio_subtract), } }) .filter( (item) => item.min_m >= 0 && (item.max_m === 0 || item.max_m >= item.min_m) && item.ratio_subtract >= 0, ) } function readNumber(value: unknown) { const parsed = Number(value) return Number.isFinite(parsed) ? parsed : 0 } function readPositiveInteger(value: unknown, fallback: number) { const parsed = Math.trunc(readNumber(value)) return parsed > 0 ? parsed : fallback } function isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null }