feat(listings): 补充租期、人脸、封禁与改名展示

This commit is contained in:
2026-11-12 13:01:02 +08:00
parent 3337a3b814
commit f1c0cb3730
7 changed files with 146 additions and 6 deletions
@@ -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)
@@ -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 (
<Link to={`/listings/${item.id}`} className="listing-card-link">
<Card className="listing-card" hoverable cover={<ListingGridCover item={item} />}>
@@ -247,6 +265,10 @@ function ListingGridCard({ item }: { item: Listing }) {
<Tag bordered={false}>{item.login_platform || '登录方式待定'}</Tag>
{item.rank_level ? <Tag bordered={false}>{item.rank_level}</Tag> : null}
{online ? <Tag bordered={false}>在线 {online}</Tag> : null}
{rentalDays ? <Tag bordered={false}>预计租期 {rentalDays} 天</Tag> : null}
{faceOwner ? <Tag bordered={false}>人脸 {faceOwner}</Tag> : null}
{banRecord ? <Tag bordered={false}>封禁 {banRecord}</Tag> : null}
{gameNameChange ? <Tag bordered={false}>改名 {gameNameChange}</Tag> : null}
</div>
<div className="listing-card__meta">
<span>哈夫币 {formatCoinM(item.haf_coin_amount)}</span>
+43
View File
@@ -47,6 +47,10 @@ const assetFieldLabels: Record<string, string> = {
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<string, unknown> | null): string
return `${start} - ${end}`
}
export function faceOwnerText(summary?: Record<string, unknown> | null): string {
const value = summary?.face_owner
if (typeof value !== 'string') return ''
return value.trim()
}
export function banRecordText(summary?: Record<string, unknown> | 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<string, unknown> | 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<string, unknown>
}): 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') {
@@ -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 (
@@ -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() {
<dd>未填写</dd>
</div>
)}
{faceOwner ? (
<div>
<dt>人脸情况</dt>
<dd>{faceOwner}</dd>
</div>
) : null}
{banRecord ? (
<div>
<dt>封禁记录</dt>
<dd>{banRecord}</dd>
</div>
) : null}
{gameNameChange ? (
<div>
<dt>可改游戏名</dt>
<dd>{gameNameChange}</dd>
</div>
) : null}
{rentalDays ? (
<div>
<dt>预计租期</dt>
<dd>{rentalDays} 天</dd>
</div>
) : null}
<div>
<dt>在线时间</dt>
<dd>{assetValue(onlineTime)}</dd>
@@ -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) || '—'],
+3
View File
@@ -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 {