230 lines
7.7 KiB
TypeScript
230 lines
7.7 KiB
TypeScript
import type { AdminListing } from '@/features/admin/types'
|
||
import { formatCoinM, formatMoney } from '@/shared/utils/format'
|
||
|
||
type JsonRecord = Record<string, unknown>
|
||
|
||
function recordOf(value: unknown): JsonRecord {
|
||
return typeof value === 'object' && value !== null ? (value as JsonRecord) : {}
|
||
}
|
||
|
||
function textOf(value: unknown) {
|
||
return value == null || value === '' ? '' : String(value)
|
||
}
|
||
|
||
function numberOf(value: unknown) {
|
||
const parsed = Number(value)
|
||
return Number.isFinite(parsed) ? parsed : 0
|
||
}
|
||
|
||
function compactNumber(value: number) {
|
||
const rounded = Math.round(value * 10) / 10
|
||
return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1)
|
||
}
|
||
|
||
export function listingCodeText(row: AdminListing) {
|
||
return row.listing_no || `SP${String(row.id).padStart(6, '0')}`
|
||
}
|
||
|
||
export function listingCoinText(row: AdminListing) {
|
||
return formatCoinM(row.haf_coin_amount)
|
||
}
|
||
|
||
export function listingRegionText(row: AdminListing) {
|
||
const regions = row.asset_summary?.common_regions
|
||
if (Array.isArray(regions)) {
|
||
const names = regions.filter(
|
||
(region): region is string => typeof region === 'string' && Boolean(region.trim())
|
||
)
|
||
if (names.length > 0) return names.join('、')
|
||
}
|
||
return row.server_region || '—'
|
||
}
|
||
|
||
export function listingAssetText(row: AdminListing, key: string) {
|
||
const value = row.asset_summary?.[key]
|
||
if (value == null || value === '') return '—'
|
||
if (Array.isArray(value)) {
|
||
const names = value.filter(
|
||
(item): item is string => typeof item === 'string' && Boolean(item.trim())
|
||
)
|
||
return names.length > 0 ? names.join('、') : '—'
|
||
}
|
||
if (typeof value === 'object') return '—'
|
||
return String(value)
|
||
}
|
||
|
||
export function listingLevelText(row: AdminListing, key: string) {
|
||
const value = listingAssetText(row, key)
|
||
return value === '—' ? value : value.replace(/级$/, '')
|
||
}
|
||
|
||
export function listingQuantity(row: AdminListing, key: string) {
|
||
const resources = row.asset_summary?.resources
|
||
if (!Array.isArray(resources)) return 0
|
||
const match = resources.find(item => {
|
||
const resource = recordOf(item)
|
||
return textOf(resource.key) === key
|
||
})
|
||
return numberOf(recordOf(match).quantity)
|
||
}
|
||
|
||
export function listingResourceText(row: AdminListing, key: string) {
|
||
const quantity = listingQuantity(row, key)
|
||
return quantity > 0 ? compactNumber(quantity) : '—'
|
||
}
|
||
|
||
export function listingSkinGroupText(row: AdminListing, groupKey: string) {
|
||
const groups = recordOf(row.asset_summary?.skin_groups)
|
||
const names = groups[groupKey]
|
||
if (!Array.isArray(names)) return '—'
|
||
const values = names.filter(
|
||
(name): name is string => typeof name === 'string' && Boolean(name.trim())
|
||
)
|
||
return values.length > 0 ? values.join('、') : '—'
|
||
}
|
||
|
||
export function listingCharacterSkinText(row: AdminListing) {
|
||
const groups = [
|
||
{ label: '红皮', key: 'operatorRed' },
|
||
{ label: '金皮', key: 'operatorGold' },
|
||
{ label: '武器', key: 'weapon' },
|
||
]
|
||
const parts = groups
|
||
.map(group => {
|
||
const names = listingSkinGroupText(row, group.key)
|
||
return names === '—' ? '' : `${group.label}:${names}`
|
||
})
|
||
.filter(Boolean)
|
||
return parts.length > 0 ? parts.join(' / ') : '—'
|
||
}
|
||
|
||
export function listingAllHeroText(row: AdminListing) {
|
||
const value =
|
||
listingAssetText(row, 'all_hero') !== '—'
|
||
? listingAssetText(row, 'all_hero')
|
||
: listingAssetText(row, 'all_heroes')
|
||
return value === '是' || value === '否' ? value : '—'
|
||
}
|
||
|
||
export function listingOwnerRemark(row: AdminListing) {
|
||
return row.description?.trim() || '—'
|
||
}
|
||
|
||
export function listingPureCoinPrice(row: AdminListing) {
|
||
const summary = row.asset_summary ?? {}
|
||
const breakdown = recordOf(summary.price_breakdown)
|
||
const buyerCent = numberOf(breakdown.buyer_coin_base_price_cent)
|
||
if (buyerCent > 0) return Math.round(buyerCent)
|
||
|
||
const buyerYuan = numberOf(breakdown.buyer_coin_base_price)
|
||
if (buyerYuan > 0) return Math.round(buyerYuan * 100)
|
||
|
||
const consumableCent =
|
||
numberOf(breakdown.consumable_price_cent) || numberOf(breakdown.consumable_price) * 100
|
||
return Math.max(0, Math.round(row.price_cent - consumableCent))
|
||
}
|
||
|
||
export function listingPureCoinPriceText(row: AdminListing) {
|
||
const cents = listingPureCoinPrice(row)
|
||
return cents > 0 ? formatMoney(cents) : '—'
|
||
}
|
||
|
||
export function listingRentalDuration(row: AdminListing) {
|
||
const coinM = numberOf(row.haf_coin_amount) / 1_000_000
|
||
const dailyLoss = numberOf(row.asset_summary?.daily_loss_m)
|
||
if (coinM <= 0 || dailyLoss <= 0) {
|
||
return { days: '—', dailyLoss: '' }
|
||
}
|
||
return {
|
||
days: `${Math.max(1, Math.ceil(coinM / dailyLoss))}天`,
|
||
dailyLoss: `日耗 ${compactNumber(dailyLoss)}M`,
|
||
}
|
||
}
|
||
|
||
export function listingRentDepositText(row: AdminListing) {
|
||
return `${formatMoney(row.price_cent)} / ${formatMoney(row.deposit_amount_cent)}`
|
||
}
|
||
|
||
export function listingRatioText(row: AdminListing) {
|
||
const ratio = numberOf(row.asset_summary?.publish_ratio)
|
||
if (ratio <= 0) return '—'
|
||
return `1:${Math.round(ratio)}`
|
||
}
|
||
|
||
export function listingAccountLevel(row: AdminListing) {
|
||
const fireLevel = listingAssetText(row, 'fire_level')
|
||
if (fireLevel !== '—') return fireLevel
|
||
return row.rank_level || '—'
|
||
}
|
||
|
||
function breakdownCent(row: AdminListing, key: string) {
|
||
const breakdown = recordOf(row.asset_summary?.price_breakdown)
|
||
const cent = numberOf(breakdown[`${key}_cent`])
|
||
if (cent > 0) return Math.round(cent)
|
||
const yuan = numberOf(breakdown[key])
|
||
return yuan > 0 ? Math.round(yuan * 100) : 0
|
||
}
|
||
|
||
export function listingConsumablePriceCent(row: AdminListing) {
|
||
return breakdownCent(row, 'consumable_price')
|
||
}
|
||
|
||
export function listingSellerCoinBasePriceCent(row: AdminListing) {
|
||
const stored = breakdownCent(row, 'seller_coin_base_price')
|
||
if (stored > 0) return stored
|
||
const fallback = row.price_cent - listingConsumablePriceCent(row)
|
||
return Math.max(0, Math.round(fallback))
|
||
}
|
||
|
||
export function listingSellerTotalPriceCent(row: AdminListing) {
|
||
const stored = breakdownCent(row, 'seller_total_price')
|
||
if (stored > 0) return stored
|
||
return listingSellerCoinBasePriceCent(row) + listingConsumablePriceCent(row)
|
||
}
|
||
|
||
export function listingBuyerCoinBasePriceCent(row: AdminListing) {
|
||
return listingPureCoinPrice(row)
|
||
}
|
||
|
||
export function listingBuyerTotalPriceCent(row: AdminListing) {
|
||
const stored = breakdownCent(row, 'buyer_total_price')
|
||
if (stored > 0) return stored
|
||
return listingBuyerCoinBasePriceCent(row) + listingConsumablePriceCent(row)
|
||
}
|
||
|
||
export function listingPlatformMarkupCent(row: AdminListing) {
|
||
const stored = breakdownCent(row, 'platform_markup_amount')
|
||
if (stored > 0) return stored
|
||
return Math.max(0, listingBuyerCoinBasePriceCent(row) - listingSellerCoinBasePriceCent(row))
|
||
}
|
||
|
||
export function listingSellerRatio(row: AdminListing) {
|
||
const breakdown = recordOf(row.asset_summary?.price_breakdown)
|
||
const stored = numberOf(breakdown.seller_ratio) || numberOf(breakdown.seller_reference_ratio)
|
||
if (stored > 0) return stored
|
||
const baseYuan = listingSellerCoinBasePriceCent(row) / 100
|
||
const coinWan = numberOf(row.haf_coin_amount) / 10000
|
||
return baseYuan > 0 ? coinWan / baseYuan : 0
|
||
}
|
||
|
||
export function listingBuyerRatio(row: AdminListing) {
|
||
const breakdown = recordOf(row.asset_summary?.price_breakdown)
|
||
const stored = numberOf(breakdown.buyer_ratio)
|
||
if (stored > 0) return stored
|
||
const baseYuan = listingBuyerCoinBasePriceCent(row) / 100
|
||
const coinWan = numberOf(row.haf_coin_amount) / 10000
|
||
return baseYuan > 0 ? coinWan / baseYuan : 0
|
||
}
|
||
|
||
export function listingSkinNames(row: AdminListing): string[] {
|
||
const groups = recordOf(row.asset_summary?.skin_groups)
|
||
const names: string[] = []
|
||
Object.values(groups).forEach(value => {
|
||
if (!Array.isArray(value)) return
|
||
value.forEach(name => {
|
||
if (typeof name === 'string' && name.trim()) names.push(name.trim())
|
||
})
|
||
})
|
||
return Array.from(new Set(names))
|
||
}
|