feat(admin): 优化后台宽屏布局与商品管理列表
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
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:${compactNumber(ratio)}`
|
||||
}
|
||||
|
||||
export function listingAccountLevel(row: AdminListing) {
|
||||
const fireLevel = listingAssetText(row, 'fire_level')
|
||||
if (fireLevel !== '—') return fireLevel
|
||||
return row.rank_level || '—'
|
||||
}
|
||||
Reference in New Issue
Block a user