diff --git a/frontend/src/features/listings/ListingDetailPage.tsx b/frontend/src/features/listings/ListingDetailPage.tsx index 81e62f5..93b8633 100644 --- a/frontend/src/features/listings/ListingDetailPage.tsx +++ b/frontend/src/features/listings/ListingDetailPage.tsx @@ -24,6 +24,12 @@ import { Link, useNavigate, useParams } from 'react-router-dom' import { BrandLogo } from '@/components/BrandLogo' import { useListingDetail } from '@/features/listings/api' +import { + banRecordText, + faceOwnerText, + gameNameChangeText, + listingRentalDays, +} from '@/features/listings/labels' import { recordListingView, useListingCollections } from '@/features/listings/collections' import { addRemoteFavorite, @@ -132,6 +138,10 @@ function coreStats(item: Listing) { const summary = item.asset_summary ?? {} const dailyLoss = Number(summary.daily_loss_m ?? 0) const secretKd = Number(summary.secret_kd ?? 0) + const rentalDays = listingRentalDays(item) + const faceOwner = faceOwnerText(summary) + const banRecord = banRecordText(summary) + const gameNameChange = gameNameChangeText(summary) return [ { label: '哈夫币', value: formatCoinM(item.haf_coin_amount) }, { label: '保险格数', value: textOf(summary.season_insurance) }, @@ -140,12 +150,15 @@ function coreStats(item: Listing) { { label: '体力', value: textOf(summary.stamina_level) }, { label: '负重', value: textOf(summary.load_level) }, { label: '日消耗', value: dailyLoss > 0 ? `${dailyLoss} M / 天` : '' }, + { label: '预计租期', value: rentalDays ? `${rentalDays} 天` : '' }, { label: '绝密 KD', value: secretKd > 0 ? String(secretKd) : '' }, { label: '段位', value: item.rank_level }, { label: '登录方式', value: item.login_platform }, { label: '区服', value: item.server_region }, { label: '在线时段', value: onlineTimeText(summary.online_time) }, - { label: '可改游戏名', value: textOf(summary.modifiable_name) }, + { label: '可改游戏名', value: gameNameChange }, + { label: '封禁记录', value: banRecord }, + { label: '人脸情况', value: faceOwner }, { label: '全英雄', value: textOf(summary.all_heroes) }, { label: '凌晨响应', value: textOf(summary.early_morning_response) }, ].filter(stat => stat.value) diff --git a/frontend/src/features/listings/ListingListPage.tsx b/frontend/src/features/listings/ListingListPage.tsx index 8bdf7d3..31cd97c 100644 --- a/frontend/src/features/listings/ListingListPage.tsx +++ b/frontend/src/features/listings/ListingListPage.tsx @@ -27,7 +27,13 @@ import { Link } from 'react-router-dom' import { BrandLogo } from '@/components/BrandLogo' import { HomePortalRow } from '@/features/home/HomePortalRow' import { useListings } from '@/features/listings/api' -import { onlineTimeText } from '@/features/listings/labels' +import { + banRecordText, + faceOwnerText, + gameNameChangeText, + listingRentalDays, + onlineTimeText, +} from '@/features/listings/labels' import { awmRanges, coinRanges, @@ -131,6 +137,10 @@ function ListingCover({ item }: { item: Listing }) { } function ListingRow({ item }: { item: Listing }) { + const rentalDays = listingRentalDays(item) + const faceOwner = faceOwnerText(item.asset_summary) + const banRecord = banRecordText(item.asset_summary) + const gameNameChange = gameNameChangeText(item.asset_summary) const specs = [ { label: '哈夫币', value: formatCoinM(item.haf_coin_amount) }, { label: '保险', value: assetText(item, 'season_insurance') }, @@ -140,6 +150,10 @@ function ListingRow({ item }: { item: Listing }) { { label: '登录', value: item.login_platform }, { label: '区服', value: item.server_region }, { label: '在线', value: onlineTimeText(item.asset_summary) }, + { label: '预计租期', value: rentalDays ? `${rentalDays} 天` : '' }, + { label: '人脸', value: faceOwner }, + { label: '封禁', value: banRecord }, + { label: '改名', value: gameNameChange }, { label: '折算比', value: ratioText(item) ? `1元≈${ratioText(item)}` : '' }, ].filter(spec => spec.value) const skins = skinNames(item) @@ -230,6 +244,10 @@ function ListingGridCover({ item }: { item: Listing }) { function ListingGridCard({ item }: { item: Listing }) { const online = onlineTimeText(item.asset_summary) + const rentalDays = listingRentalDays(item) + const faceOwner = faceOwnerText(item.asset_summary) + const banRecord = banRecordText(item.asset_summary) + const gameNameChange = gameNameChangeText(item.asset_summary) return ( }> @@ -247,6 +265,10 @@ function ListingGridCard({ item }: { item: Listing }) { {item.login_platform || '登录方式待定'} {item.rank_level ? {item.rank_level} : null} {online ? 在线 {online} : null} + {rentalDays ? 预计租期 {rentalDays} 天 : null} + {faceOwner ? 人脸 {faceOwner} : null} + {banRecord ? 封禁 {banRecord} : null} + {gameNameChange ? 改名 {gameNameChange} : null}
哈夫币 {formatCoinM(item.haf_coin_amount)} diff --git a/frontend/src/features/listings/labels.ts b/frontend/src/features/listings/labels.ts index 4ab1764..ba13093 100644 --- a/frontend/src/features/listings/labels.ts +++ b/frontend/src/features/listings/labels.ts @@ -47,6 +47,10 @@ const assetFieldLabels: Record = { skin_names: '皮肤名称', online_time: '在线时间', online_time_text: '在线时间', + face_owner: '人脸情况', + ban_record: '封禁记录', + can_change_game_name: '可改游戏名', + modifiable_name: '可改游戏名', rank_level: '段位', haf_coin_amount: '哈夫币', } @@ -83,6 +87,45 @@ export function onlineTimeText(summary?: Record | null): string return `${start} - ${end}` } +export function faceOwnerText(summary?: Record | null): string { + const value = summary?.face_owner + if (typeof value !== 'string') return '' + return value.trim() +} + +export function banRecordText(summary?: Record | null): string { + const value = summary?.ban_record + if (typeof value !== 'string') return '' + const text = value.trim() + if (!text) return '' + if (text.includes('无') || text === '否') return '无封禁' + if (text.includes('有') || text === '是') return '有封禁' + return text +} + +export function gameNameChangeText(summary?: Record | null): string { + const primary = summary?.can_change_game_name + const fallback = summary?.modifiable_name + const raw = typeof primary === 'string' && primary.trim() ? primary : fallback + if (typeof raw !== 'string') return '' + const text = raw.trim() + if (!text) return '' + if (text === '是') return '可改' + if (text === '否') return '不可改' + return text +} + +export function listingRentalDays(item: { + haf_coin_amount: number + asset_summary?: Record +}): number | null { + const coinM = Number(item.haf_coin_amount) / 1_000_000 + const dailyLoss = Number(item.asset_summary?.daily_loss_m ?? 0) + if (!Number.isFinite(coinM) || coinM <= 0) return null + if (!Number.isFinite(dailyLoss) || dailyLoss <= 0) return null + return Math.max(1, Math.ceil(coinM / dailyLoss)) +} + export function formatAssetValue(key: string, value: unknown): string { if (value == null || value === '') return '—' if (key === 'online_time') { diff --git a/frontend/src/features/mobile/MobileHomePage.tsx b/frontend/src/features/mobile/MobileHomePage.tsx index 2fda0a9..9aa30a0 100644 --- a/frontend/src/features/mobile/MobileHomePage.tsx +++ b/frontend/src/features/mobile/MobileHomePage.tsx @@ -15,7 +15,13 @@ import { Link } from 'react-router-dom' import { BrandLogo } from '@/components/BrandLogo' import { useListings } from '@/features/listings/api' -import { onlineTimeText } from '@/features/listings/labels' +import { + banRecordText, + faceOwnerText, + gameNameChangeText, + listingRentalDays, + onlineTimeText, +} from '@/features/listings/labels' import { coinRanges, emptyListingFilters, @@ -90,11 +96,19 @@ function MobileChipGroup({ function MobileListingCard({ item }: { item: Listing }) { const online = onlineTimeText(item.asset_summary) + const rentalDays = listingRentalDays(item) + const faceOwner = faceOwnerText(item.asset_summary) + const banRecord = banRecordText(item.asset_summary) + const gameNameChange = gameNameChangeText(item.asset_summary) const specs = [ assetText(item, 'season_insurance') ? `保险 ${assetText(item, 'season_insurance')}` : '', assetText(item, 'stamina_level') ? `体力 ${assetText(item, 'stamina_level')}` : '', assetText(item, 'load_level') ? `负重 ${assetText(item, 'load_level')}` : '', online ? `在线 ${online}` : '', + rentalDays ? `预计租期 ${rentalDays} 天` : '', + faceOwner ? `人脸 ${faceOwner}` : '', + banRecord ? `封禁 ${banRecord}` : '', + gameNameChange ? `改名 ${gameNameChange}` : '', ].filter(Boolean) return ( diff --git a/frontend/src/features/mobile/MobileListingDetailPage.tsx b/frontend/src/features/mobile/MobileListingDetailPage.tsx index 52a7756..cca41d8 100644 --- a/frontend/src/features/mobile/MobileListingDetailPage.tsx +++ b/frontend/src/features/mobile/MobileListingDetailPage.tsx @@ -5,6 +5,12 @@ import { useNavigate, useParams } from 'react-router-dom' import { BrandLogo } from '@/components/BrandLogo' import { useListingDetail } from '@/features/listings/api' +import { + banRecordText, + faceOwnerText, + gameNameChangeText, + listingRentalDays, +} from '@/features/listings/labels' import { ensureSupportConversation } from '@/features/messages/api' import { createOrder, useOrderAgreements, usePostRentalNotice } from '@/features/orders/api' import { useSessionStore } from '@/stores/session' @@ -143,8 +149,17 @@ export function MobileListingDetailPage() { ? (data.asset_summary.online_time as unknown) : undefined const assetEntries = Object.entries(data.asset_summary || {}).filter( - ([key]) => key !== 'online_time' + ([key]) => + key !== 'online_time' && + key !== 'face_owner' && + key !== 'ban_record' && + key !== 'can_change_game_name' && + key !== 'modifiable_name' ) + const rentalDays = listingRentalDays(data) + const faceOwner = faceOwnerText(data.asset_summary) + const banRecord = banRecordText(data.asset_summary) + const gameNameChange = gameNameChangeText(data.asset_summary) const isOwner = user?.id === data.owner_id return ( @@ -210,6 +225,30 @@ export function MobileListingDetailPage() {
未填写
)} + {faceOwner ? ( +
+
人脸情况
+
{faceOwner}
+
+ ) : null} + {banRecord ? ( +
+
封禁记录
+
{banRecord}
+
+ ) : null} + {gameNameChange ? ( +
+
可改游戏名
+
{gameNameChange}
+
+ ) : null} + {rentalDays ? ( +
+
预计租期
+
{rentalDays} 天
+
+ ) : null}
在线时间
{assetValue(onlineTime)}
diff --git a/frontend/src/features/seller/SellerListingDetailPage.tsx b/frontend/src/features/seller/SellerListingDetailPage.tsx index 8efff63..46d6a9d 100644 --- a/frontend/src/features/seller/SellerListingDetailPage.tsx +++ b/frontend/src/features/seller/SellerListingDetailPage.tsx @@ -24,7 +24,12 @@ import { } from '@ant-design/icons' import { useNavigate, useParams } from 'react-router-dom' -import { listingReviewStatusMeta, listingStatusMeta } from '@/features/listings/labels' +import { + faceOwnerText, + gameNameChangeText, + listingReviewStatusMeta, + listingStatusMeta, +} from '@/features/listings/labels' import type { StatusMeta } from '@/features/admin/labels' import { useSessionStore } from '@/stores/session' import { confirmModal } from '@/shared/utils/confirm' @@ -328,8 +333,9 @@ export function SellerListingDetailPage({ mobile = false }: { mobile?: boolean } ['哈夫币', coinText(listing)], ['每日损耗', summary.daily_loss_m ? `${numberValue(summary.daily_loss_m)}M/天` : '—'], ['在线时间', onlineTimeText(summary)], + ['人脸情况', faceOwnerText(summary) || '—'], ['凌晨响应', stringValue(summary.early_morning_response) || '—'], - ['可改游戏名', stringValue(summary.can_change_game_name) || '—'], + ['可改游戏名', gameNameChangeText(summary) || '—'], ['全英雄', stringValue(summary.all_hero) || '—'], ['解锁德穆兰', stringValue(summary.unlock_demulan) || '—'], ['下赛季通行证', stringValue(summary.next_season_pass) || '—'], diff --git a/frontend/src/styles/mobile-listing.css b/frontend/src/styles/mobile-listing.css index a58c015..a663c20 100644 --- a/frontend/src/styles/mobile-listing.css +++ b/frontend/src/styles/mobile-listing.css @@ -133,6 +133,7 @@ padding: 10px 0; border-bottom: 1px solid var(--line); font-size: 12px; + min-width: 0; } .mobile-detail__assets dt { @@ -143,6 +144,8 @@ margin: 0; color: var(--ink); text-align: right; + min-width: 0; + overflow-wrap: anywhere; } .mobile-detail__notice {