feat(admin): 优化后台宽屏布局与商品管理列表

This commit is contained in:
yml2213
2026-11-01 18:38:34 +08:00
parent 067c6116c5
commit 7f6ebddd5e
4 changed files with 915 additions and 86 deletions
@@ -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 || '—'
}