feat(listings): 补充租期、人脸、封禁与改名展示
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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) || '—'],
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user