445 lines
14 KiB
TypeScript
445 lines
14 KiB
TypeScript
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<ApiResponse<ListingPublishOptions>>('/listing-publish-options')
|
|
return mergeListingPublishOptions(data.data)
|
|
}
|
|
|
|
export async function fetchListingPublishAgreements() {
|
|
const { data } = await apiClient.get<ApiResponse<ListingPublishAgreements>>('/listing-publish-agreements')
|
|
return mergeListingPublishAgreements(data.data)
|
|
}
|
|
|
|
export async function fetchListingSalePriceConfig() {
|
|
const { data } = await apiClient.get<ApiResponse<PublishSalePriceConfig>>('/listing-sale-price-config')
|
|
return mergeListingSalePriceConfig(data.data)
|
|
}
|
|
|
|
export function mergeListingPublishOptions(options?: Partial<ListingPublishOptions>): 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>): PublishSalePriceConfig {
|
|
return normalizeSalePriceConfig(options)
|
|
}
|
|
|
|
export function mergeListingPublishAgreements(options?: Partial<ListingPublishAgreements>): 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<string, unknown> {
|
|
return typeof value === 'object' && value !== null
|
|
}
|